blob: 0dbef0e40a86df3951f824fd5c17c91ef7be62d5 [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 <utility>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/histogram_tester.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/compositor/test/no_transport_image_transport_factory.h"
#include "content/browser/frame_host/cross_site_transferring_request.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/input_messages.h"
#include "content/common/site_isolation_policy.h"
#include "content/common/view_messages.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host_iterator.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/browser_side_navigation_policy.h"
#include "content/public/common/javascript_message_type.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_notification_tracker.h"
#include "content/public/test/test_utils.h"
#include "content/test/browser_side_navigation_test_utils.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_web_contents.h"
#include "net/base/load_flags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/web/WebFrameOwnerProperties.h"
#include "third_party/WebKit/public/web/WebSandboxFlags.h"
#include "ui/base/page_transition_types.h"
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, base::get<0>(params));
}
// Helper function for strict mixed content checking tests.
void CheckMixedContentIPC(TestRenderFrameHost* rfh,
bool expected_param,
int expected_routing_id) {
const IPC::Message* message =
rfh->GetProcess()->sink().GetUniqueMessageMatching(
FrameMsg_EnforceStrictMixedContentChecking::ID);
ASSERT_TRUE(message);
EXPECT_EQ(expected_routing_id, message->routing_id());
FrameMsg_EnforceStrictMixedContentChecking::Param params;
EXPECT_TRUE(
FrameMsg_EnforceStrictMixedContentChecking::Read(message, &params));
EXPECT_EQ(expected_param, base::get<0>(params));
}
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.
WebUIController* CreateWebUIControllerForURL(WebUI* web_ui,
const GURL& url) const 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 new WebUIController(web_ui);
return nullptr;
}
WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
const GURL& url) const 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) const override {
return HasWebUIScheme(url);
}
bool UseWebUIBindingsForURL(BrowserContext* browser_context,
const GURL& url) const 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:
DISALLOW_COPY_AND_ASSIGN(CloseWebContentsDelegate);
bool close_called_;
};
// This observer keeps track of the last deleted RenderViewHost to avoid
// accessing it and causing use-after-free condition.
class RenderViewHostDeletedObserver : public WebContentsObserver {
public:
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:
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:
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:
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 !defined(OS_ANDROID)
ImageTransportFactory::InitializeForUnitTests(
make_scoped_ptr(new NoTransportImageTransportFactory));
#endif
}
void TearDown() override {
RenderViewHostImplTestHarness::TearDown();
WebUIControllerFactory::UnregisterFactoryForTesting(&factory_);
#if !defined(OS_ANDROID)
// RenderWidgetHostView holds on to a reference to SurfaceManager, so it
// must be shut down before the ImageTransportFactory.
ImageTransportFactory::Terminate();
#endif
}
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); }
void NavigateActiveAndCommit(const GURL& url) {
// Note: we navigate the active RenderFrameHost because previous navigations
// won't have committed yet, so NavigateAndCommit does the wrong thing
// for us.
controller().LoadURL(
url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
int entry_id = controller().GetPendingEntry()->GetUniqueID();
// Simulate the BeforeUnload_ACK that is received from the current renderer
// for a cross-site navigation.
// PlzNavigate: it is necessary to call PrepareForCommit before getting the
// main and the pending frame because when we are trying to navigate to a
// WebUI from a new tab, a RenderFrameHost is created to display it that is
// committed immediately (since it is a new tab). Therefore the main frame
// is replaced without a pending frame being created, and we don't get the
// right values for the RFH to navigate: we try to use the old one that has
// been deleted in the meantime.
contents()->GetMainFrame()->PrepareForCommit();
TestRenderFrameHost* old_rfh = contents()->GetMainFrame();
TestRenderFrameHost* active_rfh = contents()->GetPendingMainFrame()
? contents()->GetPendingMainFrame()
: old_rfh;
EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT, old_rfh->rfh_state());
// Commit the navigation with a new page ID.
int32_t max_page_id =
contents()->GetMaxPageIDForSiteInstance(active_rfh->GetSiteInstance());
// Use an observer to avoid accessing a deleted renderer later on when the
// state is being checked.
RenderFrameDeletedObserver rfh_observer(old_rfh);
RenderViewHostDeletedObserver rvh_observer(old_rfh->GetRenderViewHost());
active_rfh->SendNavigate(max_page_id + 1, entry_id, true, url);
// Make sure that we start to run the unload handler at the time of commit.
if (old_rfh != active_rfh && !rfh_observer.deleted()) {
EXPECT_EQ(RenderFrameHostImpl::STATE_PENDING_SWAP_OUT,
old_rfh->rfh_state());
}
// Simulate the swap out ACK coming from the pending renderer. This should
// either shut down the old RFH or leave it in a swapped out state.
if (old_rfh != active_rfh) {
old_rfh->OnSwappedOut();
EXPECT_TRUE(rfh_observer.deleted());
}
EXPECT_EQ(active_rfh, contents()->GetMainFrame());
EXPECT_EQ(NULL, contents()->GetPendingMainFrame());
}
bool ShouldSwapProcesses(RenderFrameHostManager* manager,
const NavigationEntryImpl* current_entry,
const NavigationEntryImpl* new_entry) const {
CHECK(new_entry);
BrowserContext* browser_context =
manager->delegate_->GetControllerForRenderManager().GetBrowserContext();
const GURL& current_effective_url = current_entry ?
SiteInstanceImpl::GetEffectiveURL(browser_context,
current_entry->GetURL()) :
manager->render_frame_host_->GetSiteInstance()->GetSiteURL();
bool current_is_view_source_mode = current_entry ?
current_entry->IsViewSourceMode() : new_entry->IsViewSourceMode();
return manager->ShouldSwapBrowsingInstancesForNavigation(
current_effective_url,
current_is_view_source_mode,
new_entry->site_instance(),
SiteInstanceImpl::GetEffectiveURL(browser_context, new_entry->GetURL()),
new_entry->IsViewSourceMode());
}
// Creates a test RenderViewHost that's swapped out.
void CreateSwappedOutRenderViewHost() {
const GURL kChromeURL("chrome://foo");
const GURL kDestUrl("http://www.google.com/");
// Navigate our first tab to a chrome url and then to the destination.
NavigateActiveAndCommit(kChromeURL);
TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
// Navigate to a cross-site URL.
contents()->GetController().LoadURL(
kDestUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
int entry_id = contents()->GetController().GetPendingEntry()->GetUniqueID();
contents()->GetMainFrame()->PrepareForCommit();
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);
// BeforeUnload finishes.
ntp_rfh->SendBeforeUnloadACK(true);
dest_rfh->SendNavigate(101, entry_id, true, kDestUrl);
ntp_rfh->OnSwappedOut();
}
// Returns the RenderFrameHost that should be used in the navigation to
// |entry|.
RenderFrameHostImpl* NavigateToEntry(
RenderFrameHostManager* manager,
const NavigationEntryImpl& entry) {
// Tests currently only navigate using main frame FrameNavigationEntries.
FrameNavigationEntry* frame_entry = entry.root_node()->frame_entry.get();
if (IsBrowserSideNavigationEnabled()) {
NavigationControllerImpl* controller =
static_cast<NavigationControllerImpl*>(manager->current_frame_host()
->frame_tree_node()
->navigator()
->GetController());
FrameMsg_Navigate_Type::Value navigate_type =
entry.restore_type() == NavigationEntryImpl::RESTORE_NONE
? FrameMsg_Navigate_Type::NORMAL
: FrameMsg_Navigate_Type::RESTORE;
scoped_ptr<NavigationRequest> navigation_request =
NavigationRequest::CreateBrowserInitiated(
manager->frame_tree_node_, frame_entry->url(),
frame_entry->referrer(), *frame_entry, entry, navigate_type,
LOFI_UNSPECIFIED, false, base::TimeTicks::Now(), controller);
// 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);
frame_host->set_pending_commit(true);
return frame_host;
}
return manager->Navigate(frame_entry->url(), *frame_entry, entry);
}
// Returns the pending RenderFrameHost.
// PlzNavigate: returns the speculative RenderFrameHost.
RenderFrameHostImpl* GetPendingFrameHost(
RenderFrameHostManager* manager) {
if (IsBrowserSideNavigationEnabled())
return manager->speculative_render_frame_host_.get();
return manager->pending_frame_host();
}
// Exposes RenderFrameHostManager::CollectOpenerFrameTrees for testing.
void CollectOpenerFrameTrees(
FrameTreeNode* node,
std::vector<FrameTree*>* opener_frame_trees,
base::hash_set<FrameTreeNode*>* nodes_with_back_links) {
node->render_manager()->CollectOpenerFrameTrees(opener_frame_trees,
nodes_with_back_links);
}
void BaseSimultaneousNavigationWithOneWebUI(
const std::function<void(RenderFrameHostImpl*,
RenderFrameHostImpl*,
WebUIImpl*,
RenderFrameHostManager*)>& commit_lambda);
void BaseSimultaneousNavigationWithTwoWebUIs(
const std::function<void(RenderFrameHostImpl*,
RenderFrameHostImpl*,
WebUIImpl*,
WebUIImpl*,
RenderFrameHostManager*)>& commit_lambda);
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, NewTabPageProcesses) {
set_should_create_webui(true);
const GURL kChromeUrl("chrome://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.
NavigateActiveAndCommit(kChromeUrl);
EXPECT_TRUE(active_rvh()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
NavigateActiveAndCommit(kDestUrl);
EXPECT_FALSE(contents()->GetPendingMainFrame());
// Make a second tab.
scoped_ptr<TestWebContents> contents2(
TestWebContents::Create(browser_context(), NULL));
// 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.
contents2->GetController().LoadURL(
kChromeUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
int entry_id = contents2->GetController().GetPendingEntry()->GetUniqueID();
contents2->GetMainFrame()->PrepareForCommit();
TestRenderFrameHost* ntp_rfh2 = contents2->GetMainFrame();
EXPECT_FALSE(contents2->CrossProcessNavigationPending());
ntp_rfh2->SendNavigate(100, entry_id, true, kChromeUrl);
// The second one is the opposite, creating a cross-site transition and
// requiring a beforeunload ack.
contents2->GetController().LoadURL(
kDestUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
entry_id = contents2->GetController().GetPendingEntry()->GetUniqueID();
contents2->GetMainFrame()->PrepareForCommit();
EXPECT_TRUE(contents2->CrossProcessNavigationPending());
TestRenderFrameHost* dest_rfh2 = contents2->GetPendingMainFrame();
ASSERT_TRUE(dest_rfh2);
dest_rfh2->SendNavigate(101, entry_id, true, kDestUrl);
// 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 the new tab page, and verify that they share a
// RenderProcessHost (not a SiteInstance).
NavigateActiveAndCommit(kChromeUrl);
EXPECT_FALSE(contents()->GetPendingMainFrame());
contents2->GetController().LoadURL(
kChromeUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
entry_id = contents2->GetController().GetPendingEntry()->GetUniqueID();
contents2->GetMainFrame()->PrepareForCommit();
contents2->GetPendingMainFrame()->SendNavigate(102, entry_id, true,
kChromeUrl);
EXPECT_NE(contents()->GetMainFrame()->GetSiteInstance(),
contents2->GetMainFrame()->GetSiteInstance());
EXPECT_EQ(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("chrome://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.
NavigateActiveAndCommit(kChromeURL);
TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
TestRenderViewHost* ntp_rvh = ntp_rfh->GetRenderViewHost();
// Send an update favicon message and make sure it works.
{
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(ntp_rfh->GetRenderViewHost()->GetWidget()->OnMessageReceived(
ViewHostMsg_UpdateFaviconURL(
ntp_rfh->GetRenderViewHost()->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.
NavigateActiveAndCommit(kDestUrl);
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->GetRenderViewHost()->GetWidget()->OnMessageReceived(
ViewHostMsg_UpdateFaviconURL(
dest_rfh->GetRenderViewHost()->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_rvh->GetWidget()->OnMessageReceived(ViewHostMsg_UpdateFaviconURL(
dest_rfh->GetRenderViewHost()->GetRoutingID(), icons)));
EXPECT_FALSE(observer.favicon_received());
}
}
// Test that the ViewHostMsg_UpdateFaviconURL IPC message is ignored if the
// renderer is in the STATE_PENDING_SWAP_OUT_STATE. The favicon code assumes
// that it only gets ViewHostMsg_UpdateFaviconURL messages for the most recently
// committed navigation for each WebContentsImpl.
TEST_F(RenderFrameHostManagerTest, UpdateFaviconURLWhilePendingSwapOut) {
const GURL kChromeURL("chrome://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.
NavigateActiveAndCommit(kChromeURL);
TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
// Send an update favicon message and make sure it works.
{
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(rfh1->GetRenderViewHost()->GetWidget()->OnMessageReceived(
ViewHostMsg_UpdateFaviconURL(rfh1->GetRenderViewHost()->GetRoutingID(),
icons)));
EXPECT_TRUE(observer.favicon_received());
}
// Create one more frame in the same SiteInstance where |rfh1| exists so that
// it doesn't get deleted on navigation to another site.
rfh1->GetSiteInstance()->IncrementActiveFrameCount();
// Navigate to a cross-site URL and commit the new page.
controller().LoadURL(
kDestUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
int entry_id = controller().GetPendingEntry()->GetUniqueID();
contents()->GetMainFrame()->PrepareForCommit();
TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();
contents()->TestDidNavigate(rfh2, 1, entry_id, true, kDestUrl,
ui::PAGE_TRANSITION_TYPED);
EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT, rfh2->rfh_state());
EXPECT_EQ(RenderFrameHostImpl::STATE_PENDING_SWAP_OUT, rfh1->rfh_state());
// The new RVH should be able to update its favicons.
{
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(rfh2->GetRenderViewHost()->GetWidget()->OnMessageReceived(
ViewHostMsg_UpdateFaviconURL(rfh2->GetRenderViewHost()->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(rfh1->GetRenderViewHost()->GetWidget()->OnMessageReceived(
ViewHostMsg_UpdateFaviconURL(rfh1->GetRenderViewHost()->GetRoutingID(),
icons)));
EXPECT_FALSE(observer.favicon_received());
}
}
// Test if RenderViewHost::GetRenderWidgetHosts() only returns active
// widgets.
TEST_F(RenderFrameHostManagerTest, GetRenderWidgetHostsReturnsActiveViews) {
CreateSwappedOutRenderViewHost();
scoped_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();
scoped_ptr<RenderWidgetHostIterator> widgets(
RenderWidgetHost::GetRenderWidgetHosts());
while (RenderWidgetHost* w = widgets->GetNextHost()) {
bool found = false;
scoped_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.
scoped_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), instance1));
contents()->SetOpener(opener1.get());
scoped_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();
// 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,
WebContents* web_contents)
: WebContentsObserver(WebContents::FromRenderViewHost(render_view_host)),
render_view_host_(render_view_host),
web_contents_(web_contents) {}
void RenderViewDeleted(RenderViewHost* render_view_host) override {
if (render_view_host == render_view_host_)
delete web_contents_;
}
private:
RenderViewHost* render_view_host_;
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("chrome://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.
NavigateActiveAndCommit(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.
TestWebContents* web_contents =
TestWebContents::Create(browser_context(), ntp_rfh->GetSiteInstance());
web_contents->NavigateAndCommit(kUrl1);
RenderViewHostDestroyer destroyer(ntp_rfh->GetRenderViewHost(),
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("chrome://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.
NavigateActiveAndCommit(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.
controller().LoadURL(
kViewSourceUrl, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
int entry_id = controller().GetPendingEntry()->GetUniqueID();
// Simulate response from RenderFrame for DispatchBeforeUnload.
contents()->GetMainFrame()->PrepareForCommit();
ASSERT_TRUE(contents()->GetPendingMainFrame())
<< "Expected new pending RenderFrameHost to be created.";
RenderFrameHost* last_rfh = contents()->GetPendingMainFrame();
int32_t new_id =
contents()->GetMaxPageIDForSiteInstance(last_rfh->GetSiteInstance()) + 1;
contents()->GetPendingMainFrame()->SendNavigate(new_id, entry_id, true, kUrl);
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.
controller().LoadURL(
kViewSourceUrl, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
entry_id = controller().GetPendingEntry()->GetUniqueID();
contents()->GetMainFrame()->PrepareForCommit();
// The same RenderViewHost should be reused.
EXPECT_FALSE(contents()->GetPendingMainFrame());
EXPECT_EQ(last_rfh, contents()->GetMainFrame());
// The renderer sends a commit.
contents()->GetMainFrame()->SendNavigateWithTransition(
new_id, entry_id, false, kUrl, ui::PAGE_TRANSITION_TYPED);
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());
scoped_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());
EXPECT_FALSE(manager->pending_render_view_host());
}
// Tests the Navigate function. We navigate three sites consecutively and check
// how the pending/committed RenderViewHost are modified.
TEST_F(RenderFrameHostManagerTest, Navigate) {
scoped_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 = NULL;
// 1) The first navigation. --------------------------
const GURL kUrl1("http://www.google.com/");
NavigationEntryImpl entry1(
NULL /* instance */, -1 /* page_id */, kUrl1, Referrer(),
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
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);
// 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);
// 2) Navigate to next site. -------------------------
const GURL kUrl2("http://www.google.com/foo");
NavigationEntryImpl entry2(
NULL /* instance */, -1 /* page_id */, kUrl2,
Referrer(kUrl1, blink::WebReferrerPolicyDefault),
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
true /* is_renderer_init */);
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);
EXPECT_TRUE(host == manager->current_frame_host());
ASSERT_TRUE(host);
EXPECT_TRUE(host->GetSiteInstance()->HasSite());
// 3) Cross-site navigate to next site. --------------
const GURL kUrl3("http://webkit.org/");
NavigationEntryImpl entry3(
NULL /* instance */, -1 /* page_id */, kUrl3,
Referrer(kUrl2, blink::WebReferrerPolicyDefault),
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */);
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);
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());
}
// Tests WebUI creation.
TEST_F(RenderFrameHostManagerTest, WebUI) {
set_should_create_webui(true);
scoped_refptr<SiteInstance> instance =
SiteInstance::Create(browser_context());
scoped_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("chrome://foo");
NavigationEntryImpl entry(NULL /* instance */, -1 /* page_id */, kUrl,
Referrer(), base::string16() /* title */,
ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
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());
// The Web UI is committed immediately because the RenderViewHost has not been
// used yet. UpdateStateForNavigate() took the short cut path.
if (IsBrowserSideNavigationEnabled()) {
// In PlzNavigate, 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());
} else {
// The WebUI was immediately committed and there should be none navigating.
EXPECT_FALSE(manager->GetNavigatingWebUI());
}
EXPECT_TRUE(manager->current_frame_host()->web_ui());
// Commit.
manager->DidNavigateFrame(host, true);
EXPECT_TRUE(
host->render_view_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.
scoped_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, -1,
FrameReplicationState(), false);
EXPECT_TRUE(manager1->current_host()->IsRenderViewLive());
EXPECT_TRUE(manager1->current_frame_host()->IsRenderFrameLive());
// Navigate to a WebUI page.
const GURL kUrl1("chrome://foo");
NavigationEntryImpl entry1(NULL /* instance */, -1 /* page_id */, kUrl1,
Referrer(), base::string16() /* title */,
ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
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->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
// Commit and ensure we still have bindings.
manager1->DidNavigateFrame(host1, true);
SiteInstance* webui_instance = host1->GetSiteInstance();
EXPECT_EQ(host1, manager1->current_frame_host());
EXPECT_TRUE(
host1->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
// Now simulate clicking a link that opens in a new tab.
scoped_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, -1,
FrameReplicationState(), false);
EXPECT_TRUE(manager2->current_host()->IsRenderViewLive());
const GURL kUrl2("chrome://foo/bar");
NavigationEntryImpl entry2(NULL /* instance */, -1 /* page_id */, kUrl2,
Referrer(), base::string16() /* title */,
ui::PAGE_TRANSITION_LINK,
true /* is_renderer_init */);
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->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
manager2->DidNavigateFrame(host2, true);
}
// 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("chrome://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("chrome://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("chrome://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());
}
// Tests that we don't end up in an inconsistent state if a page does a back and
// then reload. http://crbug.com/51680
// Also tests that only user-gesture navigations can interrupt cross-process
// navigations. http://crbug.com/75195
TEST_F(RenderFrameHostManagerTest, PageDoesBackAndReload) {
if (IsBrowserSideNavigationEnabled()) {
// PlzNavigate uses a significantly different logic for renderer initiated
// navigations and navigation cancellation. Adapting this test would make it
// full of special cases and almost unreadable.
// There are tests that exercise these concerns for PlzNavigate, all from
// NavigatorTestWithBrowserSideNavigation:
// - BrowserInitiatedNavigationCancel
// - RendererUserInitiatedNavigationCancel
// - RendererNonUserInitiatedNavigationDoesntCancelRendererUserInitiated
// - RendererNonUserInitiatedNavigationDoesntCancelBrowserInitiated
// - RendererNonUserInitiatedNavigationCancelSimilarNavigation
SUCCEED() << "Test is not applicable with browser side navigation enabled";
return;
}
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2("http://www.evil-site.com/");
// Navigate to a safe site, then an evil site.
// This will switch RenderFrameHosts. We cannot assert that the first and
// second RFHs are different, though, because the first one may be promptly
// deleted.
contents()->NavigateAndCommit(kUrl1);
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* evil_rfh = contents()->GetMainFrame();
// Now let's simulate the evil page calling history.back().
contents()->OnGoToEntryAtOffset(-1);
contents()->GetMainFrame()->PrepareForCommit();
// We should have a new pending RFH.
// Note that in this case, the navigation has not committed, so evil_rfh will
// not be deleted yet.
EXPECT_NE(evil_rfh, contents()->GetPendingMainFrame());
EXPECT_NE(evil_rfh->GetRenderViewHost(),
contents()->GetPendingMainFrame()->GetRenderViewHost());
// Before that RFH has committed, the evil page reloads itself.
FrameHostMsg_DidCommitProvisionalLoad_Params params;
params.page_id = 0;
params.nav_entry_id = 0;
params.did_create_new_entry = false;
params.url = kUrl2;
params.transition = ui::PAGE_TRANSITION_CLIENT_REDIRECT;
params.should_update_history = false;
params.gesture = NavigationGestureAuto;
params.was_within_same_page = false;
params.is_post = false;
params.page_state = PageState::CreateFromURL(kUrl2);
evil_rfh->SimulateNavigationStart(kUrl2);
evil_rfh->SendNavigateWithParams(&params);
evil_rfh->SimulateNavigationStop();
// That should NOT have cancelled the pending RFH, because the reload did
// not have a user gesture. Thus, the pending back navigation will still
// eventually commit.
EXPECT_TRUE(contents()->GetRenderManagerForTesting()->
pending_render_view_host() != NULL);
EXPECT_TRUE(contents()->GetRenderManagerForTesting()->pending_frame_host() !=
NULL);
EXPECT_EQ(evil_rfh,
contents()->GetRenderManagerForTesting()->current_frame_host());
EXPECT_EQ(evil_rfh->GetRenderViewHost(),
contents()->GetRenderManagerForTesting()->current_host());
// Also we should not have a pending navigation entry.
EXPECT_TRUE(contents()->GetController().GetPendingEntry() == NULL);
NavigationEntry* entry = contents()->GetController().GetVisibleEntry();
ASSERT_TRUE(entry != NULL);
EXPECT_EQ(kUrl2, entry->GetURL());
// Now do the same but as a user gesture.
params.gesture = NavigationGestureUser;
evil_rfh->SimulateNavigationStart(kUrl2);
evil_rfh->SendNavigateWithParams(&params);
evil_rfh->SimulateNavigationStop();
// User navigation should have cancelled the pending RFH.
EXPECT_TRUE(contents()->GetRenderManagerForTesting()->
pending_render_view_host() == NULL);
EXPECT_TRUE(contents()->GetRenderManagerForTesting()->pending_frame_host() ==
NULL);
EXPECT_EQ(evil_rfh,
contents()->GetRenderManagerForTesting()->current_frame_host());
EXPECT_EQ(evil_rfh->GetRenderViewHost(),
contents()->GetRenderManagerForTesting()->current_host());
// Also we should not have a pending navigation entry.
EXPECT_TRUE(contents()->GetController().GetPendingEntry() == NULL);
entry = contents()->GetController().GetVisibleEntry();
ASSERT_TRUE(entry != NULL);
EXPECT_EQ(kUrl2, entry->GetURL());
}
// 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("http://www.chromium.org/");
// 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).
contents()->GetController().GoBack();
EXPECT_TRUE(rfh2->is_waiting_for_beforeunload_ack());
contents()->GetMainFrame()->PrepareForCommit();
EXPECT_FALSE(rfh2->is_waiting_for_beforeunload_ack());
// The back navigation commits.
const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
contents()->GetPendingMainFrame()->SendNavigate(
entry1->GetPageID(), entry1->GetUniqueID(), false, entry1->GetURL());
EXPECT_TRUE(rfh2->IsWaitingForUnloadACK());
EXPECT_EQ(RenderFrameHostImpl::STATE_PENDING_SWAP_OUT, rfh2->rfh_state());
// We should be able to navigate forward.
contents()->GetController().GoForward();
contents()->GetMainFrame()->PrepareForCommit();
const NavigationEntry* entry2 = contents()->GetController().GetPendingEntry();
contents()->GetPendingMainFrame()->SendNavigate(
entry2->GetPageID(), entry2->GetUniqueID(), false, entry2->GetURL());
EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT, main_test_rfh()->rfh_state());
}
// 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("http://www.chromium.org/");
const GURL kChromeUrl("chrome://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();
// Create 2 new tabs and simulate them being the opener chain for the main
// tab. They should be in the same SiteInstance.
scoped_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* opener1_manager =
opener1->GetRenderManagerForTesting();
contents()->SetOpener(opener1.get());
scoped_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 swapped out RFH and RFH is created in the first opener tab.
RenderFrameProxyHost* opener1_proxy =
opener1_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
RenderFrameHostImpl* opener1_rfh = opener1_proxy->render_frame_host();
TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
EXPECT_FALSE(opener1_rfh);
EXPECT_FALSE(opener1_rvh->is_active());
// Ensure a swapped out RFH and RVH is created in the second opener tab.
RenderFrameProxyHost* opener2_proxy =
opener2_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
RenderFrameHostImpl* opener2_rfh = opener2_proxy->render_frame_host();
TestRenderViewHost* opener2_rvh = static_cast<TestRenderViewHost*>(
opener2_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
EXPECT_FALSE(opener2_rfh);
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("http://www.chromium.org/");
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
// Create a new tab and simulate having it be the opener for the main tab.
scoped_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.
scoped_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("http://www.chromium.org/");
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
scoped_refptr<SiteInstanceImpl> site_instance1 =
main_test_rfh()->GetSiteInstance();
// Create a new tab and simulate having it be the opener for the main tab.
scoped_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.
const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
contents()->GetPendingMainFrame()->SendNavigate(
entry1->GetPageID(), entry1->GetUniqueID(), false, entry1->GetURL());
// 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("http://www.chromium.org/");
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
scoped_refptr<SiteInstanceImpl> site_instance1 =
main_test_rfh()->GetSiteInstance();
// Create a new tab and simulate having it be the opener for the main tab.
scoped_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();
const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
contents()->GetPendingMainFrame()->SendNavigate(
entry1->GetPageID(), entry1->GetUniqueID(), false, entry1->GetURL());
// 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("http://www.chromium.org/");
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
// Create a new tab as an opener for the main tab.
scoped_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, -1, 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(true);
contents()->GetMainFrame()->PrepareForCommit();
EXPECT_TRUE(
opener1_manager->GetSwappedOutRenderViewHost(rfh1->GetSiteInstance())
->IsRenderViewLive());
EXPECT_EQ(
opener1_manager->GetRoutingIdForSiteInstance(rfh1->GetSiteInstance()),
contents()->GetMainFrame()->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("chrome://chrome/settings");
const GURL kPluginUrl("chrome://plugins");
// Navigate to an initial WebUI URL.
contents()->NavigateAndCommit(kSettingsUrl);
// Ensure the RVH has WebUI bindings.
TestRenderViewHost* rvh1 = test_rvh();
EXPECT_TRUE(rvh1->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.
scoped_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 swapped out RFH and RVH is created in the first opener tab.
RenderFrameProxyHost* opener1_proxy =
opener1_manager->GetRenderFrameProxyHost(rvh2->GetSiteInstance());
RenderFrameHostImpl* opener1_rfh = opener1_proxy->render_frame_host();
TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
EXPECT_FALSE(opener1_rfh);
EXPECT_FALSE(opener1_rvh->is_active());
// Ensure the new RVH has WebUI bindings.
EXPECT_TRUE(rvh2->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);
scoped_ptr<TestWebContents> web_contents(
TestWebContents::Create(browser_context(), instance));
RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
RenderFrameHostImpl* host = NULL;
// 1) The first navigation. --------------------------
const GURL kUrl1("http://www.google.com/");
NavigationEntryImpl entry1(
NULL /* instance */, -1 /* page_id */, kUrl1, Referrer(),
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
host = NavigateToEntry(manager, entry1);
// The RenderFrameHost created in Init will be reused.
EXPECT_TRUE(host == manager->current_frame_host());
EXPECT_FALSE(manager->pending_frame_host());
EXPECT_EQ(manager->current_frame_host()->GetSiteInstance(), instance);
// Commit.
manager->DidNavigateFrame(host, true);
// 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");
NavigationEntryImpl entry2(
NULL /* instance */, -1 /* page_id */, kUrl2,
Referrer(kUrl1, blink::WebReferrerPolicyDefault),
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
true /* is_renderer_init */);
host = NavigateToEntry(manager, entry2);
// The RenderFrameHost created in Init will be reused.
EXPECT_EQ(host, manager->current_frame_host());
EXPECT_FALSE(manager->pending_frame_host());
// Commit.
manager->DidNavigateFrame(host, true);
EXPECT_EQ(host, manager->current_frame_host());
ASSERT_TRUE(host);
EXPECT_EQ(host->GetSiteInstance(), instance);
}
// Test that we cancel a pending RVH if we close the tab while it's pending.
// http://crbug.com/294697.
TEST_F(RenderFrameHostManagerTest, NavigateWithEarlyClose) {
TestNotificationTracker notifications;
scoped_refptr<SiteInstance> instance =
SiteInstance::Create(browser_context());
BeforeUnloadFiredWebContentsDelegate delegate;
scoped_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(NULL /* instance */, -1 /* page_id */, kUrl1,
Referrer(), base::string16() /* title */,
ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
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);
// 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(
NULL /* instance */, -1 /* page_id */, kUrl2, Referrer(),
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
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. -------------------------
notifications.ListenFor(
NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
Source<RenderWidgetHost>(host2->render_view_host()->GetWidget()));
manager->OnBeforeUnloadACK(false, true, base::TimeTicks());
EXPECT_TRUE(
notifications.Check1AndReset(NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED));
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("http://www.chromium.org/");
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());
if (IsBrowserSideNavigationEnabled())
rfh1->PrepareForCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
// Simulate the unresponsiveness timer. The tab should close.
contents()->RendererUnresponsive(rfh1->render_view_host()->GetWidget());
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_EQ(RenderFrameHostImpl::STATE_DEFAULT, rfh1->rfh_state());
// Navigate to new site, simulating onbeforeunload approval.
controller().LoadURL(
kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
int entry_id = controller().GetPendingEntry()->GetUniqueID();
contents()->GetMainFrame()->PrepareForCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT, rfh1->rfh_state());
TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();
// Simulate the swap out ack, unexpectedly early (before commit). It should
// have no effect.
rfh1->OnSwappedOut();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT, rfh1->rfh_state());
// The new page commits.
contents()->TestDidNavigate(rfh2, 1, entry_id, true, kUrl2,
ui::PAGE_TRANSITION_TYPED);
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(rfh2, contents()->GetMainFrame());
EXPECT_TRUE(contents()->GetPendingMainFrame() == NULL);
EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT, rfh2->rfh_state());
EXPECT_EQ(RenderFrameHostImpl::STATE_PENDING_SWAP_OUT, rfh1->rfh_state());
// Simulate the swap out ack.
rfh1->OnSwappedOut();
// rfh1 should have been deleted.
EXPECT_TRUE(rfh_deleted_observer.deleted());
rfh1 = NULL;
}
// 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_EQ(RenderFrameHostImpl::STATE_DEFAULT, rfh1->rfh_state());
// 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.
controller().LoadURL(
kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
int entry_id = controller().GetPendingEntry()->GetUniqueID();
contents()->GetMainFrame()->PrepareForCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT, rfh1->rfh_state());
TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();
// The new page commits.
contents()->TestDidNavigate(rfh2, 1, entry_id, true, kUrl2,
ui::PAGE_TRANSITION_TYPED);
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(rfh2, contents()->GetMainFrame());
EXPECT_TRUE(contents()->GetPendingMainFrame() == NULL);
EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT, rfh2->rfh_state());
EXPECT_EQ(RenderFrameHostImpl::STATE_PENDING_SWAP_OUT, rfh1->rfh_state());
// 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_EQ(RenderFrameHostImpl::STATE_DEFAULT, rfh1->rfh_state());
// 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.
controller().LoadURL(
kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
int entry_id = controller().GetPendingEntry()->GetUniqueID();
rfh1->PrepareForCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();
// The new page commits.
contents()->TestDidNavigate(rfh2, 1, entry_id, true, kUrl2,
ui::PAGE_TRANSITION_TYPED);
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(rfh2, contents()->GetMainFrame());
EXPECT_TRUE(contents()->GetPendingMainFrame() == NULL);
EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT, rfh2->rfh_state());
EXPECT_EQ(RenderFrameHostImpl::STATE_PENDING_SWAP_OUT, rfh1->rfh_state());
// 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("http://www.chromium.org/");
RenderFrameHostImpl* pending_rfh = NULL;
base::TimeTicks now = base::TimeTicks::Now();
// Navigate to the first page.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
EXPECT_EQ(RenderFrameHostImpl::STATE_DEFAULT, rfh1->rfh_state());
// 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/");
// Create a page with two child frames.
contents()->NavigateAndCommit(kUrlA);
contents()->GetMainFrame()->OnCreateChildFrame(
contents()->GetMainFrame()->GetProcess()->GetNextRoutingID(),
blink::WebTreeScopeType::Document, "frame_name", "uniqueName1",
blink::WebSandboxFlags::None, blink::WebFrameOwnerProperties());
contents()->GetMainFrame()->OnCreateChildFrame(
contents()->GetMainFrame()->GetProcess()->GetNextRoutingID(),
blink::WebTreeScopeType::Document, "frame_name", "uniqueName2",
blink::WebSandboxFlags::None, blink::WebFrameOwnerProperties());
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(NULL /* instance */, -1 /* page_id */, kUrlA,
Referrer(), base::string16() /* title */,
ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
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);
// 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(NULL /* instance */, -1 /* page_id */, kUrlB,
Referrer(kUrlA, blink::WebReferrerPolicyDefault),
base::string16() /* title */,
ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */);
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(RenderFrameHostImpl::IsRFHStateActive(
GetPendingFrameHost(iframe1)->rfh_state()));
EXPECT_TRUE(RenderFrameHostImpl::IsRFHStateActive(
GetPendingFrameHost(iframe2)->rfh_state()));
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 = NULL; // 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 = NULL; // 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();
scoped_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());
rph->SimulateCrash();
EXPECT_FALSE(contents1->GetMainFrame()->IsRenderFrameLive());
EXPECT_FALSE(contents2->GetMainFrame()->IsRenderFrameLive());
EXPECT_EQ(contents1->GetSiteInstance(), contents2->GetSiteInstance());
// Reload |contents1|.
contents1->NavigateAndCommit(kUrl1);
EXPECT_TRUE(contents1->GetMainFrame()->IsRenderFrameLive());
EXPECT_FALSE(contents2->GetMainFrame()->IsRenderFrameLive());
EXPECT_EQ(contents1->GetSiteInstance(), contents2->GetSiteInstance());
// |contents1| creates an out of process iframe.
contents1->GetMainFrame()->OnCreateChildFrame(
contents1->GetMainFrame()->GetProcess()->GetNextRoutingID(),
blink::WebTreeScopeType::Document, "frame_name", "uniqueName1",
blink::WebSandboxFlags::None, blink::WebFrameOwnerProperties());
RenderFrameHostManager* iframe =
contents()->GetFrameTree()->root()->child_at(0)->render_manager();
NavigationEntryImpl entry(NULL /* instance */, -1 /* page_id */, kUrl2,
Referrer(kUrl1, blink::WebReferrerPolicyDefault),
base::string16() /* title */,
ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */);
RenderFrameHostImpl* cross_site = NavigateToEntry(iframe, entry);
iframe->DidNavigateFrame(cross_site, true);
// 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(),
blink::WebTreeScopeType::Document, std::string(),
"uniqueName1", blink::WebSandboxFlags::None,
blink::WebFrameOwnerProperties());
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("chrome://foo");
NavigationEntryImpl webui_entry(
nullptr /* instance */, -1 /* page_id */, kWebUIUrl, Referrer(),
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
RenderFrameHostManager* main_rfhm = contents()->GetRenderManagerForTesting();
RenderFrameHostImpl* webui_rfh = NavigateToEntry(main_rfhm, webui_entry);
EXPECT_EQ(webui_rfh, GetPendingFrameHost(main_rfhm));
EXPECT_TRUE(webui_rfh->render_view_host()->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 */, -1 /* page_id */, kSubframeUrl, Referrer(),
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */);
RenderFrameHostImpl* bar_rfh = NavigateToEntry(subframe_rfhm, subframe_entry);
EXPECT_FALSE(bar_rfh->render_view_host()->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("http://www.chromium.org/");
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
// Create 2 new tabs and construct the opener chain as follows:
//
// tab2 <--- tab1 <---- contents()
// | ^
// +-------+
//
scoped_ptr<TestWebContents> tab1(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* tab1_manager = tab1->GetRenderManagerForTesting();
scoped_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, base::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("http://www.chromium.org/");
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
// Create an opener tab, and simulate that its opener points to itself.
scoped_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, base::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();
tree1->AddFrame(root1, process_id, 12, blink::WebTreeScopeType::Document,
std::string(), "uniqueName0", blink::WebSandboxFlags::None,
blink::WebFrameOwnerProperties());
tree1->AddFrame(root1, process_id, 13, blink::WebTreeScopeType::Document,
std::string(), "uniqueName1", blink::WebSandboxFlags::None,
blink::WebFrameOwnerProperties());
scoped_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, blink::WebTreeScopeType::Document,
std::string(), "uniqueName2", blink::WebSandboxFlags::None,
blink::WebFrameOwnerProperties());
tree2->AddFrame(root2, process_id, 23, blink::WebTreeScopeType::Document,
std::string(), "uniqueName3", blink::WebSandboxFlags::None,
blink::WebFrameOwnerProperties());
scoped_ptr<TestWebContents> tab3(
TestWebContents::Create(browser_context(), nullptr));
FrameTree* tree3 = tab3->GetFrameTree();
FrameTreeNode* root3 = tree3->root();
scoped_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, blink::WebTreeScopeType::Document,
std::string(), "uniqueName4", blink::WebSandboxFlags::None,
blink::WebFrameOwnerProperties());
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;
base::hash_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/");
// 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(),
blink::WebTreeScopeType::Document, "frame1", "uniqueName1",
blink::WebSandboxFlags::None, blink::WebFrameOwnerProperties());
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
blink::WebTreeScopeType::Document, "frame2", "uniqueName2",
blink::WebSandboxFlags::None, blink::WebFrameOwnerProperties());
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
blink::WebTreeScopeType::Document, "frame3", "uniqueName3",
blink::WebSandboxFlags::None, blink::WebFrameOwnerProperties());
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 */, -1 /* page_id */, kUrlB,
Referrer(kUrlA, blink::WebReferrerPolicyDefault),
base::string16() /* title */,
ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */);
TestRenderFrameHost* host1 =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child1, entryB));
TestRenderFrameHost* host2 =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child2, entryB));
child1->DidNavigateFrame(host1, true);
child2->DidNavigateFrame(host2, true);
// Navigate the third subframe to C.
NavigationEntryImpl entryC(nullptr /* instance */, -1 /* page_id */, kUrlC,
Referrer(kUrlA, blink::WebReferrerPolicyDefault),
base::string16() /* title */,
ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */);
TestRenderFrameHost* host3 =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child3, entryC));
child3->DidNavigateFrame(host3, true);
// 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);
// 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();
VerifyPageFocusMessage(main_test_rfh()->GetProcess(), true,
main_test_rfh()->GetRenderViewHost()->GetRoutingID());
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();
VerifyPageFocusMessage(main_test_rfh()->GetProcess(), false,
main_test_rfh()->GetRenderViewHost()->GetRoutingID());
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/");
// Set up a page at a.com with a b.com subframe.
contents()->NavigateAndCommit(kUrlA);
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
blink::WebTreeScopeType::Document, "frame1", "uniqueName1",
blink::WebSandboxFlags::None, blink::WebFrameOwnerProperties());
FrameTreeNode* root = contents()->GetFrameTree()->root();
RenderFrameHostManager* child = root->child_at(0)->render_manager();
// Navigate subframe to B.
NavigationEntryImpl entryB(nullptr /* instance */, -1 /* page_id */, kUrlB,
Referrer(kUrlA, blink::WebReferrerPolicyDefault),
base::string16() /* title */,
ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */);
TestRenderFrameHost* hostB =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child, entryB));
child->DidNavigateFrame(hostB, true);
// 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 */, -1 /* page_id */, kUrlC,
Referrer(kUrlA, blink::WebReferrerPolicyDefault),
base::string16() /* title */,
ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */);
TestRenderFrameHost* hostC =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child, entryC));
child->DidNavigateFrame(hostC, true);
// 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("chrome://foo/");
scoped_refptr<SiteInstanceImpl> initial_instance =
SiteInstanceImpl::Create(browser_context());
initial_instance->SetSite(kInitUrl);
scoped_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<scoped_ptr<NavigationEntry>> entries;
scoped_ptr<NavigationEntry> new_entry =
NavigationControllerImpl::CreateNavigationEntry(
kInitUrl, Referrer(), ui::PAGE_TRANSITION_TYPED, false, std::string(),
browser_context());
new_entry->SetPageID(0);
entries.push_back(std::move(new_entry));
controller.Restore(
0, NavigationController::RESTORE_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 */, 0 /* page_id */, kInitUrl,
Referrer(), base::string16() /* title */,
ui::PAGE_TRANSITION_RELOAD,
false /* is_renderer_init */);
entry.set_restore_type(
NavigationEntryImpl::RESTORE_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);
EXPECT_EQ(current_host, manager->current_frame_host());
EXPECT_EQ(web_ui, current_host->web_ui());
EXPECT_FALSE(current_host->pending_web_ui());
}
// Shared code until before commit for the SimultaneousNavigationWithOneWebUI*
// tests, accepting a lambda to execute the commit step.
void RenderFrameHostManagerTest::BaseSimultaneousNavigationWithOneWebUI(
const std::function<void(RenderFrameHostImpl*,
RenderFrameHostImpl*,
WebUIImpl*,
RenderFrameHostManager*)>& commit_lambda) {
set_should_create_webui(true);
NavigateActiveAndCommit(GURL("chrome://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(true);
main_test_rfh()->PrepareForCommit();
// 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");
NavigationEntryImpl entry(NULL /* instance */, -1 /* page_id */, kUrl,
Referrer(), base::string16() /* title */,
ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
RenderFrameHostImpl* host2 = NavigateToEntry(manager, entry);
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_EQ(host2, GetPendingFrameHost(manager));
EXPECT_FALSE(host2->web_ui());
EXPECT_FALSE(host2->pending_web_ui());
EXPECT_NE(web_ui, host2->web_ui());
commit_lambda(host1, host2, web_ui, manager);
}
// Simulates two simultaneous navigations involving one WebUI where the current
// RenderFrameHost commits.
TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithOneWebUI1) {
auto commit_current_frame_host = [this](
RenderFrameHostImpl* host1, RenderFrameHostImpl* host2, WebUIImpl* web_ui,
RenderFrameHostManager* manager) {
// The current RenderFrameHost commits; its WebUI should still be in place.
manager->DidNavigateFrame(host1, true);
EXPECT_EQ(host1, manager->current_frame_host());
EXPECT_EQ(web_ui, host1->web_ui());
EXPECT_FALSE(host1->pending_web_ui());
EXPECT_FALSE(manager->GetNavigatingWebUI());
EXPECT_FALSE(GetPendingFrameHost(manager));
};
BaseSimultaneousNavigationWithOneWebUI(commit_current_frame_host);
}
// Simulates two simultaneous navigations involving one WebUI where the new,
// cross-site RenderFrameHost commits.
TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithOneWebUI2) {
auto commit_new_frame_host = [this](
RenderFrameHostImpl* host1, RenderFrameHostImpl* host2, WebUIImpl* web_ui,
RenderFrameHostManager* manager) {
// The new RenderFrameHost commits; there should be no active WebUI.
manager->DidNavigateFrame(host2, true);
EXPECT_EQ(host2, manager->current_frame_host());
EXPECT_FALSE(host2->web_ui());
EXPECT_FALSE(host2->pending_web_ui());
EXPECT_FALSE(manager->GetNavigatingWebUI());
EXPECT_FALSE(GetPendingFrameHost(manager));
};
BaseSimultaneousNavigationWithOneWebUI(commit_new_frame_host);
}
// Shared code until before commit for the SimultaneousNavigationWithTwoWebUIs*
// tests, accepting a lambda to execute the commit step.
void RenderFrameHostManagerTest::BaseSimultaneousNavigationWithTwoWebUIs(
const std::function<void(RenderFrameHostImpl*,
RenderFrameHostImpl*,
WebUIImpl*,
WebUIImpl*,
RenderFrameHostManager*)>& commit_lambda) {
set_should_create_webui(true);
set_webui_type(1);
NavigateActiveAndCommit(GURL("chrome://foo/"));
RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
RenderFrameHostImpl* host1 = manager->current_frame_host();
EXPECT_TRUE(host1->IsRenderFrameLive());
WebUIImpl* web_ui1 = host1->web_ui();
EXPECT_TRUE(web_ui1);
// Starts a reload of the WebUI page.
contents()->GetController().Reload(true);
// It should be a same-site navigation reusing the same WebUI.
EXPECT_EQ(web_ui1, manager->GetNavigatingWebUI());
EXPECT_EQ(web_ui1, host1->web_ui());
EXPECT_EQ(web_ui1, host1->pending_web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
// Navigation another WebUI page, with a different type.
set_webui_type(2);
const GURL kUrl("chrome://bar/");
NavigationEntryImpl entry(NULL /* instance */, -1 /* page_id */, kUrl,
Referrer(), base::string16() /* title */,
ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
RenderFrameHostImpl* host2 = NavigateToEntry(manager, entry);
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 and a cross-site both going to WebUIs. So it's
// unclear what GetNavigatingWebUI should return in this case. As it currently
// favors the cross-site navigation it returns the speculative/pending
// RenderFrameHost's WebUI instance.
EXPECT_EQ(web_ui1, host1->web_ui());
EXPECT_EQ(web_ui1, host1->pending_web_ui());
WebUIImpl* web_ui2 = manager->GetNavigatingWebUI();
EXPECT_TRUE(web_ui2);
EXPECT_NE(web_ui2, web_ui1);
EXPECT_NE(host2, host1);
EXPECT_EQ(host2, GetPendingFrameHost(manager));
EXPECT_EQ(web_ui2, host2->web_ui());
EXPECT_FALSE(host2->pending_web_ui());
commit_lambda(host1, host2, web_ui1, web_ui2, manager);
}
// Simulates two simultaneous navigations involving two WebUIs where the current
// RenderFrameHost commits.
TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithTwoWebUIs1) {
auto commit_current_frame_host = [this](
RenderFrameHostImpl* host1, RenderFrameHostImpl* host2,
WebUIImpl* web_ui1, WebUIImpl* web_ui2, RenderFrameHostManager* manager) {
// The current RenderFrameHost commits; its WebUI should still be active.
manager->DidNavigateFrame(host1, true);
EXPECT_EQ(host1, manager->current_frame_host());
EXPECT_EQ(web_ui1, host1->web_ui());
EXPECT_FALSE(host1->pending_web_ui());
EXPECT_FALSE(manager->GetNavigatingWebUI());
EXPECT_FALSE(GetPendingFrameHost(manager));
};
BaseSimultaneousNavigationWithTwoWebUIs(commit_current_frame_host);
}
// Simulates two simultaneous navigations involving two WebUIs where the new,
// cross-site RenderFrameHost commits.
TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithTwoWebUIs2) {
auto commit_new_frame_host = [this](
RenderFrameHostImpl* host1, RenderFrameHostImpl* host2,
WebUIImpl* web_ui1, WebUIImpl* web_ui2, RenderFrameHostManager* manager) {
// The new RenderFrameHost commits; its WebUI should now be active.
manager->DidNavigateFrame(host2, true);
EXPECT_EQ(host2, manager->current_frame_host());
EXPECT_EQ(web_ui2, host2->web_ui());
EXPECT_FALSE(host2->pending_web_ui());
EXPECT_FALSE(manager->GetNavigatingWebUI());
EXPECT_FALSE(GetPendingFrameHost(manager));
};
BaseSimultaneousNavigationWithTwoWebUIs(commit_new_frame_host);
}
TEST_F(RenderFrameHostManagerTest, CanCommitOrigin) {
const GURL kUrl("http://a.com/");
const GURL kUrlBar("http://a.com/bar");
NavigateActiveAndCommit(kUrl);
controller().LoadURL(
kUrlBar, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
main_test_rfh()->PrepareForCommit();
FrameHostMsg_DidCommitProvisionalLoad_Params params;
params.page_id = 0;
params.nav_entry_id = 0;
params.did_create_new_entry = false;
params.transition = ui::PAGE_TRANSITION_LINK;
params.should_update_history = false;
params.gesture = NavigationGestureAuto;
params.was_within_same_page = false;
params.is_post = false;
params.page_state = PageState::CreateFromURL(kUrlBar);
struct TestCase {
const char* const url;
const char* const origin;
bool mismatch;
} cases[] = {
// Positive case where the two match.
{ "http://a.com/foo.html", "http://a.com", false },
// Host mismatches.
{ "http://a.com/", "http://b.com", true },
{ "http://b.com/", "http://a.com", true },
// Scheme mismatches.
{ "file://", "http://a.com", true },
{ "https://a.com/", "http://a.com", true },
// about:blank URLs inherit the origin of the context that navigated them.
{ "about:blank", "http://a.com", false },
// Unique origin.
{ "http://a.com", "null", false },
};
for (const auto& test_case : cases) {
params.url = GURL(test_case.url);
params.origin = url::Origin(GURL(test_case.origin));
int expected_bad_msg_count = process()->bad_msg_count();
if (test_case.mismatch)
expected_bad_msg_count++;
main_test_rfh()->SendNavigateWithParams(&params);
EXPECT_EQ(expected_bad_msg_count, process()->bad_msg_count())
<< " url:" << test_case.url
<< " origin:" << test_case.origin
<< " mismatch:" << test_case.mismatch;
}
}
// RenderFrameHostManagerTest extension for PlzNavigate enabled tests.
class RenderFrameHostManagerTestWithBrowserSideNavigation
: public RenderFrameHostManagerTest {
public:
void SetUp() override {
EnableBrowserSideNavigation();
RenderFrameHostManagerTest::SetUp();
}
};
// PlzNavigate: Tests that the correct intermediary and final navigation states
// are reached when navigating from a renderer that is not live to a WebUI URL.
TEST_F(RenderFrameHostManagerTestWithBrowserSideNavigation,
NavigateFromDeadRendererToWebUI) {
set_should_create_webui(true);
RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
RenderFrameHostImpl* initial_host = manager->current_frame_host();
ASSERT_TRUE(initial_host);
EXPECT_FALSE(initial_host->IsRenderFrameLive());
// Navigation request.
const GURL kUrl("chrome://foo");
NavigationEntryImpl entry(nullptr /* instance */, -1 /* page_id */, kUrl,
Referrer(), base::string16() /* title */,
ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
FrameNavigationEntry* frame_entry = entry.root_node()->frame_entry.get();
scoped_ptr<NavigationRequest> navigation_request =
NavigationRequest::CreateBrowserInitiated(
contents()->GetFrameTree()->root(), frame_entry->url(),
frame_entry->referrer(), *frame_entry, entry,
FrameMsg_Navigate_Type::NORMAL, LOFI_UNSPECIFIED, false,
base::TimeTicks::Now(),
static_cast<NavigationControllerImpl*>(&controller()));
manager->DidCreateNavigationRequest(navigation_request.get());
// As the initial RenderFrame was not live, the new RenderFrameHost should be
// made as active/current immediately along with its WebUI at request time.
RenderFrameHostImpl* host = manager->current_frame_host();
ASSERT_TRUE(host);
EXPECT_NE(host, initial_host);
EXPECT_TRUE(host->IsRenderFrameLive());
WebUIImpl* web_ui = host->web_ui();
EXPECT_TRUE(web_ui);
EXPECT_FALSE(host->pending_web_ui());
EXPECT_FALSE(manager->GetNavigatingWebUI());
EXPECT_FALSE(GetPendingFrameHost(manager));
// Prepare to commit, update the navigating RenderFrameHost.
EXPECT_EQ(host, manager->GetFrameHostForNavigation(*navigation_request));
// There should be a pending WebUI set to reuse the current one.
EXPECT_EQ(web_ui, host->web_ui());
EXPECT_EQ(web_ui, host->pending_web_ui());
EXPECT_EQ(web_ui, manager->GetNavigatingWebUI());
// No pending RenderFrameHost as the current one should be reused.
EXPECT_FALSE(GetPendingFrameHost(manager));
// The RenderFrameHost committed.
manager->DidNavigateFrame(host, true);
EXPECT_EQ(host, manager->current_frame_host());
EXPECT_FALSE(GetPendingFrameHost(manager));
EXPECT_EQ(web_ui, host->web_ui());
EXPECT_FALSE(host->pending_web_ui());
EXPECT_FALSE(manager->GetNavigatingWebUI());
}
// PlzNavigate: Tests that the correct intermediary and final navigation states
// are reached when navigating same-site between two WebUIs of the same type.
TEST_F(RenderFrameHostManagerTestWithBrowserSideNavigation,
NavigateSameSiteBetweenWebUIs) {
set_should_create_webui(true);
NavigateActiveAndCommit(GURL("chrome://foo"));
RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
RenderFrameHostImpl* host = manager->current_frame_host();
EXPECT_TRUE(host->IsRenderFrameLive());
WebUIImpl* web_ui = host->web_ui();
EXPECT_TRUE(web_ui);
// Navigation request. No change in the returned WebUI type.
const GURL kUrl("chrome://foo/bar");
NavigationEntryImpl entry(nullptr /* instance */, -1 /* page_id */, kUrl,
Referrer(), base::string16() /* title */,
ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
FrameNavigationEntry* frame_entry = entry.root_node()->frame_entry.get();
scoped_ptr<NavigationRequest> navigation_request =
NavigationRequest::CreateBrowserInitiated(
contents()->GetFrameTree()->root(), frame_entry->url(),
frame_entry->referrer(), *frame_entry, entry,
FrameMsg_Navigate_Type::NORMAL, LOFI_UNSPECIFIED, false,
base::TimeTicks::Now(),
static_cast<NavigationControllerImpl*>(&controller()));
manager->DidCreateNavigationRequest(navigation_request.get());
// The current WebUI should still be in place and the pending WebUI should be
// set to reuse it.
EXPECT_EQ(web_ui, manager->GetNavigatingWebUI());
EXPECT_EQ(web_ui, host->web_ui());
EXPECT_EQ(web_ui, host->pending_web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
// Prepare to commit, update the navigating RenderFrameHost.
EXPECT_EQ(host, manager->GetFrameHostForNavigation(*navigation_request));
EXPECT_EQ(web_ui, manager->GetNavigatingWebUI());
EXPECT_EQ(web_ui, host->web_ui());
EXPECT_EQ(web_ui, host->pending_web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
// The RenderFrameHost committed.
manager->DidNavigateFrame(host, true);
EXPECT_EQ(web_ui, host->web_ui());
EXPECT_FALSE(manager->GetNavigatingWebUI());
EXPECT_FALSE(host->pending_web_ui());
}
// PlzNavigate: Tests that the correct intermediary and final navigation states
// are reached when navigating cross-site between two different WebUI types.
TEST_F(RenderFrameHostManagerTestWithBrowserSideNavigation,
NavigateCrossSiteBetweenWebUIs) {
// Cross-site navigations will always cause the change of the WebUI instance
// but for consistency sake different types will be set for each navigation.
set_should_create_webui(true);
set_webui_type(1);
NavigateActiveAndCommit(GURL("chrome://foo"));
RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
RenderFrameHostImpl* host = manager->current_frame_host();
EXPECT_TRUE(host->IsRenderFrameLive());
EXPECT_TRUE(host->web_ui());
// Set the WebUI controller to return a different WebUIType value. This will
// cause the next navigation to "chrome://bar" to require a different WebUI
// than the current one, forcing it to be treated as cross-site.
set_webui_type(2);
// Navigation request.
const GURL kUrl("chrome://bar");
NavigationEntryImpl entry(nullptr /* instance */, -1 /* page_id */, kUrl,
Referrer(), base::string16() /* title */,
ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */);
FrameNavigationEntry* frame_entry = entry.root_node()->frame_entry.get();
scoped_ptr<NavigationRequest> navigation_request =
NavigationRequest::CreateBrowserInitiated(
contents()->GetFrameTree()->root(), frame_entry->url(),
frame_entry->referrer(), *frame_entry, entry,
FrameMsg_Navigate_Type::NORMAL, LOFI_UNSPECIFIED, false,
base::TimeTicks::Now(),
static_cast<NavigationControllerImpl*>(&controller()));
manager->DidCreateNavigationRequest(navigation_request.get());
// The current WebUI should still be in place and there should be a new
// active WebUI instance in the speculative RenderFrameHost.
EXPECT_TRUE(manager->current_frame_host()->web_ui());
EXPECT_FALSE(manager->current_frame_host()->pending_web_ui());
RenderFrameHostImpl* speculative_host = GetPendingFrameHost(manager);
EXPECT_TRUE(speculative_host);
WebUIImpl* next_web_ui = manager->GetNavigatingWebUI();
EXPECT_TRUE(next_web_ui);
EXPECT_EQ(next_web_ui, speculative_host->web_ui());
EXPECT_NE(next_web_ui, manager->current_frame_host()->web_ui());
EXPECT_FALSE(speculative_host->pending_web_ui());
// Prepare to commit, update the navigating RenderFrameHost.
EXPECT_EQ(speculative_host,
manager->GetFrameHostForNavigation(*navigation_request));
EXPECT_TRUE(manager->current_frame_host()->web_ui());
EXPECT_FALSE(manager->current_frame_host()->pending_web_ui());
EXPECT_EQ(speculative_host, GetPendingFrameHost(manager));
EXPECT_NE(next_web_ui, manager->current_frame_host()->web_ui());
EXPECT_EQ(next_web_ui, speculative_host->web_ui());
EXPECT_EQ(next_web_ui, manager->GetNavigatingWebUI());
EXPECT_FALSE(speculative_host->pending_web_ui());
// The RenderFrameHost committed.
manager->DidNavigateFrame(speculative_host, true);
EXPECT_EQ(speculative_host, manager->current_frame_host());
EXPECT_EQ(next_web_ui, manager->current_frame_host()->web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
EXPECT_FALSE(speculative_host->pending_web_ui());
EXPECT_FALSE(manager->GetNavigatingWebUI());
}
// Tests that frame proxies receive updates when a frame's enforcement
// of strict mixed content checking changes.
TEST_F(RenderFrameHostManagerTestWithSiteIsolation,
ProxiesReceiveShouldEnforceStrictMixedContentChecking) {
const GURL kUrl1("http://www.google.test");
const GURL kUrl2("http://www.google2.test");
const GURL kUrl3("http://www.google2.test/foo");
contents()->NavigateAndCommit(kUrl1);
// Create a child frame and navigate it cross-site.
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
blink::WebTreeScopeType::Document, "frame1", "uniqueName1",
blink::WebSandboxFlags::None, blink::WebFrameOwnerProperties());
FrameTreeNode* root = contents()->GetFrameTree()->root();
RenderFrameHostManager* child = root->child_at(0)->render_manager();
// Navigate subframe to kUrl2.
NavigationEntryImpl entry1(nullptr /* instance */, -1 /* page_id */, kUrl2,
Referrer(kUrl1, blink::WebReferrerPolicyDefault),
base::string16() /* title */,
ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */);
TestRenderFrameHost* child_host =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child, entry1));
child->DidNavigateFrame(child_host, true);
// Verify that parent and child are in different processes.
EXPECT_NE(child_host->GetProcess(), main_test_rfh()->GetProcess());
// Change the parent's enforcement of strict mixed content checking,
// and check that the correct IPC is sent to the child frame's
// process.
EXPECT_FALSE(root->current_replication_state()
.should_enforce_strict_mixed_content_checking);
main_test_rfh()->DidEnforceStrictMixedContentChecking();
RenderFrameProxyHost* proxy_to_child =
root->render_manager()->GetRenderFrameProxyHost(
child_host->GetSiteInstance());
EXPECT_NO_FATAL_FAILURE(
CheckMixedContentIPC(child_host, true, proxy_to_child->GetRoutingID()));
EXPECT_TRUE(root->current_replication_state()
.should_enforce_strict_mixed_content_checking);
// Do the same for the child's enforcement. In general, the parent
// needs to know the status of the child's flag in case a grandchild
// is created: if A.com embeds B.com, and B.com enforces strict mixed
// content checking, and B.com adds an iframe to A.com, then the
// A.com process needs to know B.com's flag so that the grandchild
// A.com frame can inherit it.
EXPECT_FALSE(root->child_at(0)
->current_replication_state()
.should_enforce_strict_mixed_content_checking);
child_host->DidEnforceStrictMixedContentChecking();
RenderFrameProxyHost* proxy_to_parent =
child->GetRenderFrameProxyHost(main_test_rfh()->GetSiteInstance());
EXPECT_NO_FATAL_FAILURE(CheckMixedContentIPC(
main_test_rfh(), true, proxy_to_parent->GetRoutingID()));
EXPECT_TRUE(root->child_at(0)
->current_replication_state()
.should_enforce_strict_mixed_content_checking);
// Check that the flag for the parent's proxy to the child is reset
// when the child navigates.
main_test_rfh()->GetProcess()->sink().ClearMessages();
FrameHostMsg_DidCommitProvisionalLoad_Params commit_params;
commit_params.page_id = 0;
commit_params.nav_entry_id = 0;
commit_params.did_create_new_entry = false;
commit_params.url = kUrl3;
commit_params.transition = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
commit_params.should_update_history = false;
commit_params.gesture = NavigationGestureAuto;
commit_params.was_within_same_page = false;
commit_params.is_post = false;
commit_params.page_state = PageState::CreateFromURL(kUrl3);
commit_params.should_enforce_strict_mixed_content_checking = false;
child_host->SendNavigateWithParams(&commit_params);
EXPECT_NO_FATAL_FAILURE(CheckMixedContentIPC(
main_test_rfh(), false, proxy_to_parent->GetRoutingID()));
EXPECT_FALSE(root->child_at(0)
->current_replication_state()
.should_enforce_strict_mixed_content_checking);
}
} // namespace content