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