blob: fd5e6dbb17d770ef8983ecae6d609f0193d405e8 [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/renderer_host/render_frame_host_manager.h"
#include <stdint.h>
#include <set>
#include <string>
#include <tuple>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/hash/hash.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_entry_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_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/content_navigation_policy.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/browser/render_widget_host_observer.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_ui_controller.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/common/javascript_dialog_type.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
#include "content/public/test/fake_local_frame.h"
#include "content/public/test/fake_remote_frame.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/scoped_web_ui_controller_factory_registration.h"
#include "content/public/test/test_utils.h"
#include "content/test/mock_widget_input_handler.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/render_document_feature.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 "net/http/http_response_headers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/frame/frame_policy.h"
#include "third_party/blink/public/common/loader/previews_state.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
#include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom.h"
#include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom.h"
#include "ui/base/page_transition_types.h"
#if defined(OS_ANDROID)
#include "content/public/browser/android/compositor.h"
#endif
namespace content {
namespace {
// 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());
}
class RenderFrameHostManagerTestWebUIControllerFactory
: public WebUIControllerFactory {
public:
RenderFrameHostManagerTestWebUIControllerFactory() {}
RenderFrameHostManagerTestWebUIControllerFactory(
const RenderFrameHostManagerTestWebUIControllerFactory&) = delete;
RenderFrameHostManagerTestWebUIControllerFactory& operator=(
const RenderFrameHostManagerTestWebUIControllerFactory&) = delete;
~RenderFrameHostManagerTestWebUIControllerFactory() override {}
// WebUIFactory implementation.
std::unique_ptr<WebUIController> CreateWebUIControllerForURL(
WebUI* web_ui,
const GURL& url) override {
// If WebUI creation is enabled for the test and this is a WebUI URL,
// returns a new instance.
if (HasWebUIScheme(url))
return std::make_unique<WebUIController>(web_ui);
return nullptr;
}
WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
const GURL& url) override {
// If WebUI creation is enabled for the test and this is a WebUI URL,
// returns a mock WebUI type.
if (HasWebUIScheme(url)) {
return reinterpret_cast<WebUI::TypeID>(base::FastHash(url.host()));
}
return WebUI::kNoWebUI;
}
bool UseWebUIForURL(BrowserContext* browser_context,
const GURL& url) override {
return HasWebUIScheme(url);
}
};
class BeforeUnloadFiredWebContentsDelegate : public WebContentsDelegate {
public:
BeforeUnloadFiredWebContentsDelegate() {}
BeforeUnloadFiredWebContentsDelegate(
const BeforeUnloadFiredWebContentsDelegate&) = delete;
BeforeUnloadFiredWebContentsDelegate& operator=(
const BeforeUnloadFiredWebContentsDelegate&) = delete;
~BeforeUnloadFiredWebContentsDelegate() override {}
void BeforeUnloadFired(WebContents* web_contents,
bool proceed,
bool* proceed_to_fire_unload) override {
*proceed_to_fire_unload = proceed;
}
};
class CloseWebContentsDelegate : public WebContentsDelegate {
public:
CloseWebContentsDelegate() : close_called_(false) {}
CloseWebContentsDelegate(const CloseWebContentsDelegate&) = delete;
CloseWebContentsDelegate& operator=(const CloseWebContentsDelegate&) = delete;
~CloseWebContentsDelegate() override {}
void CloseContents(WebContents* web_contents) override {
close_called_ = true;
}
bool is_closed() { return close_called_; }
private:
bool close_called_;
};
// This observer keeps track of the last deleted RenderViewHost to avoid
// accessing it and causing use-after-free condition.
class RenderViewHostDeletedObserver : public WebContentsObserver {
public:
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(
RenderFrameHost* render_frame_host,
const std::vector<blink::mojom::FaviconURLPtr>& 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);
};
// A shorter version for RenderFrameHostManager::DidNavigateFrame(rfh, ...).
// This provides all the arguments that aren't tested in this file.
void DidNavigateFrame(RenderFrameHostManager* rfh_manager,
RenderFrameHostImpl* rfh) {
rfh_manager->DidNavigateFrame(rfh, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */,
false /* clear_proxies_on_commit */,
blink::FramePolicy());
}
} // namespace
// Test that the "level" feature param has the expected effect.
class RenderDocumentFeatureTest : public testing::Test {
protected:
void SetLevel(const RenderDocumentLevel level) {
InitAndEnableRenderDocumentFeature(&feature_list_,
GetRenderDocumentLevelName(level));
}
void DisableRenderDocument() {
feature_list_.InitAndDisableFeature(features::kRenderDocument);
}
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(RenderDocumentFeatureTest, FeatureDisabled) {
DisableRenderDocument();
EXPECT_FALSE(ShouldCreateNewHostForSameSiteSubframe());
}
TEST_F(RenderDocumentFeatureTest, LevelCrashed) {
SetLevel(RenderDocumentLevel::kCrashedFrame);
EXPECT_FALSE(ShouldCreateNewHostForSameSiteSubframe());
}
TEST_F(RenderDocumentFeatureTest, LevelSub) {
SetLevel(RenderDocumentLevel::kSubframe);
EXPECT_TRUE(ShouldCreateNewHostForSameSiteSubframe());
}
class RenderFrameHostManagerTest
: public RenderViewHostImplTestHarness,
public ::testing::WithParamInterface<std::string> {
public:
RenderFrameHostManagerTest() {
InitAndEnableRenderDocumentFeature(&feature_list_, GetParam());
}
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
// Isolate |isolated_cross_site_url()|so it cannot share a process
// with another site.
ChildProcessSecurityPolicyImpl::GetInstance()->AddFutureIsolatedOrigins(
{url::Origin::Create(isolated_cross_site_url())},
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST,
browser_context());
// Reset the WebContents so the isolated origin will be honored by
// all BrowsingInstances used in the test.
SetContents(CreateTestWebContents());
}
}
GURL isolated_cross_site_url() const {
return GURL("http://isolated-cross-site.com");
}
// Creates an inactive test RenderViewHost.
void CreateInactiveRenderViewHost() {
const GURL kChromeURL(GetWebUIURL("foo"));
const GURL kDestUrl("http://www.google.com/");
// Navigate our first tab to a chrome url and then to the destination.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeURL);
TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
// Navigate to a cross-site URL.
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kDestUrl, contents());
navigation->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
// Manually increase the number of active frames in the
// SiteInstance that ntp_rfh belongs to, to prevent the SiteInstance from
// being destroyed when ntp_rfh goes away.
ntp_rfh->GetSiteInstance()->IncrementActiveFrameCount();
TestRenderFrameHost* dest_rfh =
contents()->GetSpeculativePrimaryMainFrame();
CHECK(dest_rfh);
EXPECT_NE(ntp_rfh, dest_rfh);
// Commit. This replaces ntp_rfh with a proxy and leaves its RVH inactive.
navigation->Commit();
}
// Returns the RenderFrameHost that should be used in the navigation to
// |entry|.
RenderFrameHostImpl* NavigateToEntry(RenderFrameHostManager* manager,
NavigationEntryImpl* entry) {
// Tests currently only navigate using main frame FrameNavigationEntries.
FrameNavigationEntry* frame_entry = entry->root_node()->frame_entry.get();
FrameTreeNode* frame_tree_node =
manager->current_frame_host()->frame_tree_node();
NavigationControllerImpl& controller = manager->current_frame_host()
->frame_tree_node()
->navigator()
.controller();
blink::mojom::NavigationType navigate_type =
entry->IsRestored() ? blink::mojom::NavigationType::RESTORE
: blink::mojom::NavigationType::DIFFERENT_DOCUMENT;
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 = std::string(
base::TrimWhitespaceASCII(post_content_type, base::TRIM_ALL));
}
auto& referrer = frame_entry->referrer();
blink::mojom::CommonNavigationParamsPtr common_params =
entry->ConstructCommonNavigationParams(
*frame_entry, request_body, frame_entry->url(),
blink::mojom::Referrer::New(referrer.url, referrer.policy),
navigate_type, blink::PreviewsTypes::PREVIEWS_UNSPECIFIED,
base::TimeTicks::Now(), base::TimeTicks::Now());
blink::mojom::CommitNavigationParamsPtr commit_params =
entry->ConstructCommitNavigationParams(
*frame_entry, common_params->url, frame_entry->committed_origin(),
common_params->method,
entry->GetSubframeUniqueNames(frame_tree_node),
controller.GetPendingEntryIndex() == -1 /* intended_as_new_entry */,
controller.GetIndexOfEntry(entry),
controller.GetLastCommittedEntryIndex(), controller.GetEntryCount(),
frame_tree_node->current_replication_state().frame_policy);
commit_params->post_content_type = post_content_type;
std::unique_ptr<NavigationRequest> navigation_request =
NavigationRequest::CreateBrowserInitiated(
frame_tree_node, std::move(common_params), std::move(commit_params),
!entry->is_renderer_initiated(), false /* was_opener_suppressed */,
nullptr /* initiator_frame_token */,
ChildProcessHost::kInvalidUniqueID /* initiator_process_id */,
entry->extra_headers(), frame_entry, entry, request_body,
nullptr /* navigation_ui_data */, absl::nullopt /* impression */,
false /* is_pdf */);
// 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.get()));
CHECK(frame_host);
frame_host->SetPolicyContainerHost(
base::MakeRefCounted<PolicyContainerHost>());
return frame_host;
}
// Returns the speculative RenderFrameHost.
RenderFrameHostImpl* GetPendingFrameHost(RenderFrameHostManager* manager) {
return manager->speculative_render_frame_host_.get();
}
// Exposes RenderFrameHostManager::CollectOpenerFrameTrees for testing.
void CollectOpenerFrameTrees(
FrameTreeNode* node,
std::vector<FrameTree*>* opener_frame_trees,
std::unordered_set<FrameTreeNode*>* nodes_with_back_links) {
node->render_manager()->CollectOpenerFrameTrees(opener_frame_trees,
nodes_with_back_links);
}
private:
RenderFrameHostManagerTestWebUIControllerFactory factory_;
ScopedWebUIControllerFactoryRegistration factory_registration_{&factory_};
base::test::ScopedFeatureList feature_list_;
};
// 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_P(RenderFrameHostManagerTest, ChromeSchemeProcesses) {
const GURL kChromeUrl(GetWebUIURL("foo"));
const GURL kDestUrl("http://www.google.com/");
// Navigate our first tab to the chrome url and then to the destination,
// ensuring we grant bindings to the chrome URL.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeUrl);
EXPECT_TRUE(main_rfh()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kDestUrl);
EXPECT_FALSE(contents()->GetSpeculativePrimaryMainFrame());
// Make a second tab.
std::unique_ptr<TestWebContents> contents2(
TestWebContents::Create(browser_context(), nullptr));
// Load the two URLs in the second tab. Note that the first navigation creates
// a RFH that's not pending (since there is no cross-site transition), so
// we use the committed one.
auto navigation1 =
NavigationSimulator::CreateBrowserInitiated(kChromeUrl, contents2.get());
navigation1->Start();
EXPECT_FALSE(contents2->CrossProcessNavigationPending());
navigation1->Commit();
// The second one is the opposite, creating a cross-site transition.
auto navigation2 =
NavigationSimulator::CreateBrowserInitiated(kDestUrl, contents2.get());
navigation2->Start();
EXPECT_TRUE(contents2->CrossProcessNavigationPending());
TestRenderFrameHost* dest_rfh2 = contents2->GetSpeculativePrimaryMainFrame();
ASSERT_TRUE(dest_rfh2);
navigation2->Commit();
// The two RFH's should be different in every way.
EXPECT_NE(contents()->GetMainFrame()->GetProcess(), dest_rfh2->GetProcess());
EXPECT_NE(contents()->GetMainFrame()->GetSiteInstance(),
dest_rfh2->GetSiteInstance());
EXPECT_FALSE(dest_rfh2->GetSiteInstance()->IsRelatedSiteInstance(
contents()->GetMainFrame()->GetSiteInstance()));
// Navigate both to a chrome://... URL, and verify that they have a separate
// RenderProcessHost and a separate SiteInstance.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeUrl);
EXPECT_FALSE(contents()->GetSpeculativePrimaryMainFrame());
NavigationSimulator::NavigateAndCommitFromBrowser(contents2.get(),
kChromeUrl);
EXPECT_NE(contents()->GetMainFrame()->GetSiteInstance(),
contents2->GetMainFrame()->GetSiteInstance());
EXPECT_NE(contents()->GetMainFrame()->GetSiteInstance()->GetProcess(),
contents2->GetMainFrame()->GetSiteInstance()->GetProcess());
}
// Ensure that the browser ignores most IPC messages that arrive from a
// RenderViewHost that has been swapped out. We do not want to take
// action on requests from a non-active renderer. The main exception is
// for synchronous messages, which cannot be ignored without leaving the
// renderer in a stuck state. See http://crbug.com/93427.
TEST_P(RenderFrameHostManagerTest, FilterMessagesWhileSwappedOut) {
const GURL kChromeURL(GetWebUIURL("foo"));
const GURL kDestUrl("http://www.google.com/");
std::vector<blink::mojom::FaviconURLPtr> icons;
// Navigate our first tab to a chrome url and then to the destination.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeURL);
TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
// Send an update favicon message and make sure it works.
{
PluginFaviconMessageObserver observer(contents());
ntp_rfh->UpdateFaviconURL(std::move(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 unload to keep |ntp_rfh| alive).
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kDestUrl, contents());
navigation->set_drop_unload_ack(true);
navigation->Commit();
TestRenderFrameHost* dest_rfh = contents()->GetMainFrame();
ASSERT_TRUE(dest_rfh);
EXPECT_NE(ntp_rfh, dest_rfh);
// The new RVH should be able to update its favicon.
{
PluginFaviconMessageObserver observer(contents());
dest_rfh->UpdateFaviconURL(std::move(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());
ntp_rfh->UpdateFaviconURL(std::move(icons));
EXPECT_FALSE(observer.favicon_received());
}
}
// Test that the UpdateFaviconURL function is ignored if the
// renderer is in the pending deletion state. The favicon code assumes
// that it only gets UpdateFaviconURL function for the most
// recently committed navigation for each WebContentsImpl.
TEST_P(RenderFrameHostManagerTest, UpdateFaviconURLWhilePendingUnload) {
const GURL kChromeURL(GetWebUIURL("foo"));
const GURL kDestUrl("http://www.google.com/");
std::vector<blink::mojom::FaviconURLPtr> icons;
// Navigate our first tab to a chrome url and then to the destination.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeURL);
TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
// Send an update favicon message and make sure it works.
{
PluginFaviconMessageObserver observer(contents());
ntp_rfh->UpdateFaviconURL(std::move(icons));
EXPECT_TRUE(observer.favicon_received());
}
// Create one more frame in the same SiteInstance where |ntp_rfh| exists so
// that it doesn't get deleted on navigation to another site.
ntp_rfh->GetSiteInstance()->IncrementActiveFrameCount();
// Navigate to a cross-site URL and commit the new page.
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kDestUrl, contents());
navigation->set_drop_unload_ack(true);
navigation->Commit();
TestRenderFrameHost* dest_rfh = contents()->GetMainFrame();
EXPECT_TRUE(ntp_rfh->IsPendingDeletion());
EXPECT_TRUE(dest_rfh->IsActive());
// The new RFH should be able to update its favicons.
{
PluginFaviconMessageObserver observer(contents());
dest_rfh->UpdateFaviconURL(std::move(icons));
EXPECT_TRUE(observer.favicon_received());
}
// The old renderer, being slow, now updates its favicons. The message should
// be ignored.
{
PluginFaviconMessageObserver observer(contents());
ntp_rfh->UpdateFaviconURL(std::move(icons));
EXPECT_FALSE(observer.favicon_received());
}
}
// Test if RenderViewHost::GetRenderWidgetHosts() only returns active
// widgets.
TEST_P(RenderFrameHostManagerTest, GetRenderWidgetHostsReturnsActiveViews) {
CreateInactiveRenderViewHost();
std::unique_ptr<RenderWidgetHostIterator> widgets(
RenderWidgetHost::GetRenderWidgetHosts());
// We know that there is the only one active widget. Another view is
// now inactive, so the inactive 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 inactive ones.
TEST_P(RenderFrameHostManagerTest,
GetRenderWidgetHostsWithinGetAllRenderWidgetHosts) {
CreateInactiveRenderViewHost();
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 replaced.
TEST_P(RenderFrameHostManagerTest, ActiveFrameCountWhileSwappingInAndOut) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2("http://www.chromium.org/");
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
SiteInstanceImpl* instance1 = rfh1->GetSiteInstance();
EXPECT_EQ(instance1->active_frame_count(), 1U);
// Create 2 new tabs and simulate them being the opener chain for the main
// tab. They should be in the same SiteInstance.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), instance1));
contents()->SetOpener(opener1.get());
std::unique_ptr<TestWebContents> opener2(
TestWebContents::Create(browser_context(), instance1));
opener1->SetOpener(opener2.get());
EXPECT_EQ(instance1->active_frame_count(), 3U);
// Navigate to a cross-site URL (different SiteInstance but same
// BrowsingInstance).
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
SiteInstanceImpl* instance2 = rfh2->GetSiteInstance();
if (AreDefaultSiteInstancesEnabled()) {
EXPECT_TRUE(instance1->IsDefaultSiteInstance());
EXPECT_EQ(instance1->active_frame_count(), 3U);
EXPECT_EQ(instance1, instance2);
} else {
// rvh2 is on chromium.org which is different from google.com on
// which other tabs are.
EXPECT_EQ(instance2->active_frame_count(), 1U);
// There are two active views on google.com now.
EXPECT_EQ(instance1->active_frame_count(), 2U);
}
// Navigate to the original origin (google.com).
contents()->NavigateAndCommit(kUrl1);
EXPECT_EQ(instance1->active_frame_count(), 3U);
}
// This deletes a WebContents when the given RVH is deleted. This is
// only for testing whether deleting an RVH does not cause any UaF in
// other parts of the system. For now, this class is only used for the
// next test cases to detect the bug mentioned at
// http://crbug.com/259859.
class RenderViewHostDestroyer : public WebContentsObserver {
public:
RenderViewHostDestroyer(RenderViewHost* render_view_host,
std::unique_ptr<WebContents> web_contents)
: WebContentsObserver(WebContents::FromRenderViewHost(render_view_host)),
render_view_host_(render_view_host),
web_contents_(std::move(web_contents)) {}
void RenderViewDeleted(RenderViewHost* render_view_host) override {
if (render_view_host == render_view_host_)
web_contents_.reset();
}
private:
RenderViewHost* render_view_host_;
std::unique_ptr<WebContents> web_contents_;
DISALLOW_COPY_AND_ASSIGN(RenderViewHostDestroyer);
};
// Test if ShutdownRenderViewHostsInSiteInstance() does not touch any
// RenderWidget that has been freed while deleting a RenderViewHost in
// a previous iteration. This is a regression test for
// http://crbug.com/259859.
TEST_P(RenderFrameHostManagerTest,
DetectUseAfterFreeInShutdownRenderViewHostsInSiteInstance) {
const GURL kChromeURL(GetWebUIURL("newtab"));
const GURL kUrl1("http://www.google.com");
const GURL kUrl2("http://www.chromium.org");
// Navigate our first tab to a chrome url and then to the destination.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeURL);
TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
// Create one more tab and navigate to kUrl1. web_contents is not
// wrapped as scoped_ptr since it intentionally deleted by destroyer
// below as part of this test.
std::unique_ptr<TestWebContents> web_contents =
TestWebContents::Create(browser_context(), ntp_rfh->GetSiteInstance());
web_contents->NavigateAndCommit(kUrl1);
RenderViewHostDestroyer destroyer(ntp_rfh->GetRenderViewHost(),
std::move(web_contents));
// This causes the first tab to navigate to kUrl2, which destroys
// the ntp_rfh in ShutdownRenderViewHostsInSiteInstance(). When
// ntp_rfh is destroyed, it also destroys the RVHs in web_contents
// too. This can test whether
// SiteInstanceImpl::ShutdownRenderViewHostsInSiteInstance() can
// touch any object freed in this way or not while iterating through
// all widgets.
contents()->NavigateAndCommit(kUrl2);
}
// Stub out local frame mojo binding. Intercepts calls to EnableViewSourceMode
// and marks the message as received. This class attaches to the first
// RenderFrameHostImpl created.
class EnableViewSourceLocalFrame : public content::FakeLocalFrame,
public WebContentsObserver {
public:
explicit EnableViewSourceLocalFrame(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override {
if (!initialized_) {
initialized_ = true;
Init(navigation_handle->GetRenderFrameHost()
->GetRemoteAssociatedInterfaces());
}
}
void EnableViewSourceMode() final { enabled_view_source_ = true; }
bool IsViewSourceModeEnabled() const { return enabled_view_source_; }
void ResetState() { enabled_view_source_ = false; }
private:
bool enabled_view_source_ = false;
bool initialized_ = false;
};
// 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_P(RenderFrameHostManagerTest, AlwaysSendEnableViewSourceMode) {
const GURL kChromeUrl(GetWebUIURL("foo"));
const GURL kUrl("http://foo/");
const GURL kViewSourceUrl("view-source:http://foo/");
// We have to navigate to some page at first since without this, the first
// navigation will reuse the SiteInstance created by Init(), and the second
// one will create a new SiteInstance. Because current_instance and
// new_instance will be different, a new RenderViewHost will be created for
// the second navigation. We have to avoid this in order to exercise the
// target code path.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeUrl);
EnableViewSourceLocalFrame local_frame(contents());
// Navigate. Note that "view source" URLs are implemented by putting the RFH
// into a view-source mode and then navigating to the inner URL, so that's why
// the bare URL is what's committed and returned by the last committed entry's
// GetURL() call.
auto navigation = NavigationSimulatorImpl::CreateBrowserInitiated(
kViewSourceUrl, contents());
navigation->Start();
NavigationRequest* request =
main_test_rfh()->frame_tree_node()->navigation_request();
CHECK(request);
ASSERT_TRUE(contents()->GetSpeculativePrimaryMainFrame())
<< "Expected new pending RenderFrameHost to be created.";
RenderFrameHost* last_rfh = contents()->GetSpeculativePrimaryMainFrame();
navigation->Commit();
EXPECT_EQ(1, controller().GetLastCommittedEntryIndex());
NavigationEntry* last_committed = controller().GetLastCommittedEntry();
ASSERT_NE(nullptr, last_committed);
EXPECT_EQ(kUrl, last_committed->GetURL());
EXPECT_EQ(kViewSourceUrl, last_committed->GetVirtualURL());
EXPECT_FALSE(controller().GetPendingEntry());
// The RFH should have been put in view-source mode.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(local_frame.IsViewSourceModeEnabled());
local_frame.ResetState();
// Navigate, again.
navigation = NavigationSimulatorImpl::CreateBrowserInitiated(kViewSourceUrl,
contents());
navigation->Start();
request = main_test_rfh()->frame_tree_node()->navigation_request();
CHECK(request);
// The same RenderViewHost should be reused.
navigation->ReadyToCommit();
EXPECT_FALSE(contents()->GetSpeculativePrimaryMainFrame());
EXPECT_EQ(last_rfh, contents()->GetMainFrame());
navigation->Commit();
EXPECT_EQ(1, controller().GetLastCommittedEntryIndex());
EXPECT_FALSE(controller().GetPendingEntry());
// New message should be sent out to make sure to enter view-source mode.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(local_frame.IsViewSourceModeEnabled());
}
// Tests the Init function by checking the initial RenderViewHost.
TEST_P(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();
RenderFrameHostImpl* rfh = manager->current_frame_host();
RenderViewHostImpl* rvh = rfh->render_view_host();
ASSERT_TRUE(rfh);
ASSERT_TRUE(rvh);
EXPECT_EQ(instance, rfh->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_P(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(), absl::nullopt,
std::u16string() /* 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.
DidNavigateFrame(manager, host);
// 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(UrlInfo::CreateForTesting(kUrl1));
manager->GetRenderWidgetHostView()->SetBackgroundColor(SK_ColorRED);
// 2) Navigate to next site. -------------------------
const GURL kUrl2("http://www.google.com/foo");
const url::Origin kInitiatorOrigin =
url::Origin::Create(GURL("https://initiator.example.com"));
NavigationEntryImpl entry2(
nullptr /* instance */, kUrl2,
Referrer(kUrl1, network::mojom::ReferrerPolicy::kDefault),
kInitiatorOrigin, std::u16string() /* 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.
DidNavigateFrame(manager, host);
EXPECT_TRUE(host == manager->current_frame_host());
ASSERT_TRUE(host);
EXPECT_TRUE(host->GetSiteInstance()->HasSite());
ASSERT_TRUE(manager->GetRenderWidgetHostView()->GetBackgroundColor());
EXPECT_EQ(SK_ColorRED,
*manager->GetRenderWidgetHostView()->GetBackgroundColor());
// 3) Cross-site navigate to next site. --------------
const GURL kUrl3("http://webkit.org/");
NavigationEntryImpl entry3(
nullptr /* instance */, kUrl3,
Referrer(kUrl2, network::mojom::ReferrerPolicy::kDefault), absl::nullopt,
std::u16string() /* 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.
DidNavigateFrame(manager, GetPendingFrameHost(manager));
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_P(RenderFrameHostManagerTest, WebUI) {
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(initial_rfh->render_view_host()->IsRenderViewLive());
EXPECT_FALSE(manager->current_frame_host()->web_ui());
EXPECT_TRUE(initial_rfh);
const GURL kUrl(GetWebUIURL("foo"));
NavigationEntryImpl entry(
nullptr /* instance */, kUrl, Referrer(), absl::nullopt,
std::u16string() /* 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 WebUI because GetFrameHostForNavigation was already called
// twice.
EXPECT_TRUE(host->web_ui());
EXPECT_TRUE(manager->current_frame_host()->web_ui());
// Commit.
DidNavigateFrame(manager, host);
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_P(RenderFrameHostManagerTest, WebUIInNewTab) {
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.
RenderViewHostImpl* rvh1 = manager1->current_frame_host()->render_view_host();
rvh1->CreateRenderView(absl::nullopt, MSG_ROUTING_NONE, false);
EXPECT_TRUE(rvh1->IsRenderViewLive());
EXPECT_TRUE(manager1->current_frame_host()->IsRenderFrameLive());
// Navigate to a WebUI page.
const GURL kUrl1(GetWebUIURL("foo"));
NavigationEntryImpl entry1(
nullptr /* instance */, kUrl1, Referrer(), absl::nullopt,
std::u16string() /* 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.
DidNavigateFrame(manager1, host1);
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.
RenderViewHostImpl* rvh2 = manager2->current_frame_host()->render_view_host();
rvh2->CreateRenderView(absl::nullopt, MSG_ROUTING_NONE, false);
EXPECT_TRUE(rvh2->IsRenderViewLive());
const GURL kUrl2(GetWebUIURL("foo/bar"));
const url::Origin kInitiatorOrigin =
url::Origin::Create(GURL("https://initiator.example.com"));
NavigationEntryImpl entry2(
nullptr /* instance */, kUrl2, Referrer(), kInitiatorOrigin,
std::u16string() /* 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(host2->web_ui());
EXPECT_TRUE(host2->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
DidNavigateFrame(manager2, host2);
}
// Tests that a WebUI is correctly reused between chrome:// pages.
TEST_P(RenderFrameHostManagerTest, WebUIWasReused) {
// Navigate to a WebUI page.
const GURL kUrl1(GetWebUIURL("foo"));
contents()->NavigateAndCommit(kUrl1);
WebUIImpl* web_ui = main_test_rfh()->web_ui();
EXPECT_TRUE(web_ui);
// Navigate to another WebUI page which should be same-site and keep the
// current WebUI.
const GURL kUrl2(GetWebUIURL("foo/bar"));
contents()->NavigateAndCommit(kUrl2);
EXPECT_EQ(web_ui, main_test_rfh()->web_ui());
}
// Tests that a WebUI is correctly cleaned up when navigating from a chrome://
// page to a non-chrome:// page.
TEST_P(RenderFrameHostManagerTest, WebUIWasCleared) {
// Navigate to a WebUI page.
const GURL kUrl1(GetWebUIURL("foo"));
contents()->NavigateAndCommit(kUrl1);
EXPECT_TRUE(main_test_rfh()->web_ui());
// Navigate to a non-WebUI page.
const GURL kUrl2("http://www.google.com");
contents()->NavigateAndCommit(kUrl2);
EXPECT_FALSE(main_test_rfh()->web_ui());
}
// Ensure that we can go back and forward even if a unload ACK isn't received.
// See http://crbug.com/93427.
TEST_P(RenderFrameHostManagerTest, NavigateAfterMissingUnloadACK) {
// When a page enters the BackForwardCache, the RenderFrameHost is not
// deleted. Similarly, no
// mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame message is sent.
contents()->GetController().GetBackForwardCache().DisableForTesting(
BackForwardCache::TEST_ASSUMES_NO_CACHING);
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to two pages.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
// Keep active_frame_count nonzero so that no unloaded 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
// mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame isn't received. This
// shouldn't happen, but we have seen it when going back quickly across many
// entries (http://crbug.com/93427).
auto back_navigation1 = NavigationSimulatorImpl::CreateHistoryNavigation(
-1, contents(), false /* is_renderer_initiated */);
back_navigation1->ReadyToCommit();
EXPECT_FALSE(rfh2->is_waiting_for_beforeunload_completion());
// The back navigation commits.
back_navigation1->set_drop_unload_ack(true);
back_navigation1->Commit();
EXPECT_TRUE(rfh2->IsWaitingForUnloadACK());
EXPECT_TRUE(rfh2->IsPendingDeletion());
// We should be able to navigate forward.
NavigationSimulator::GoForward(contents());
EXPECT_TRUE(main_test_rfh()->IsActive());
}
// Test that we create RenderFrameProxy objects 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_P(RenderFrameHostManagerTest, CreateProxiesForOpeners) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
const GURL kChromeUrl(GetWebUIURL("foo"));
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
TestRenderFrameHost* rfh1 = main_test_rfh();
scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
RenderFrameDeletedObserver rfh1_deleted_observer(rfh1);
TestRenderViewHost* rvh1 = test_rvh();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create 2 new tabs and simulate them being the opener chain for the main
// tab. They should be in the same SiteInstance.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* opener1_manager =
opener1->GetRenderManagerForTesting();
contents()->SetOpener(opener1.get());
std::unique_ptr<TestWebContents> opener2(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* opener2_manager =
opener2->GetRenderManagerForTesting();
opener1->SetOpener(opener2.get());
// Navigate to a cross-site URL (different SiteInstance but same
// BrowsingInstance).
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
EXPECT_TRUE(site_instance1->IsRelatedSiteInstance(rfh2->GetSiteInstance()));
// Ensure rvh1 is kept with the proxy of the current tab.
EXPECT_TRUE(rfh1_deleted_observer.deleted());
EXPECT_EQ(rvh1, manager->GetRenderFrameProxyHost(site_instance1.get())
->GetRenderViewHost());
// Ensure a proxy and inactive RVH are created in the first opener tab.
RenderFrameProxyHost* rfph1 =
opener1_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
TestRenderViewHost* opener1_rvh =
static_cast<TestRenderViewHost*>(rfph1->GetRenderViewHost());
EXPECT_FALSE(opener1_rvh->is_active());
// Ensure a proxy and inactive RVH are created in the second opener tab.
RenderFrameProxyHost* rfph2 =
opener2_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
TestRenderViewHost* opener2_rvh =
static_cast<TestRenderViewHost*>(rfph2->GetRenderViewHost());
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
// proxies for the opener chain in this case.
EXPECT_FALSE(
opener1_manager->GetRenderFrameProxyHost(rfh3->GetSiteInstance()));
EXPECT_FALSE(
opener2_manager->GetRenderFrameProxyHost(rfh3->GetSiteInstance()));
}
// Test that a page can disown the opener of the WebContents.
TEST_P(RenderFrameHostManagerTest, DisownOpener) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create a new tab and simulate having it be the opener for the main tab.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
contents()->SetOpener(opener1.get());
EXPECT_TRUE(contents()->HasOpener());
// Navigate to a cross-site URL (different SiteInstance but same
// BrowsingInstance).
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
// Disown the opener from rfh2.
rfh2->SimulateDidChangeOpener(absl::nullopt);
// Ensure the opener is cleared.
EXPECT_FALSE(contents()->HasOpener());
}
// Test that a page can disown a same-site opener of the WebContents.
TEST_P(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->SimulateDidChangeOpener(absl::nullopt);
// 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_P(RenderFrameHostManagerTest, DisownOpenerDuringNavigation) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
scoped_refptr<SiteInstanceImpl> site_instance1 =
main_test_rfh()->GetSiteInstance();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create a new tab and simulate having it be the opener for the main tab.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), site_instance1.get()));
contents()->SetOpener(opener1.get());
EXPECT_TRUE(contents()->HasOpener());
// Navigate to a cross-site URL (different SiteInstance but same
// BrowsingInstance).
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
// Start a back navigation.
contents()->GetController().GoBack();
contents()->GetMainFrame()->PrepareForCommit();
// Disown the opener from rfh2.
rfh2->SimulateDidChangeOpener(absl::nullopt);
// Ensure the opener is cleared.
EXPECT_FALSE(contents()->HasOpener());
// The back navigation commits.
NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
contents()->GetSpeculativePrimaryMainFrame()->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_P(RenderFrameHostManagerTest, DisownOpenerAfterNavigation) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
scoped_refptr<SiteInstanceImpl> site_instance1 =
main_test_rfh()->GetSiteInstance();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create a new tab and simulate having it be the opener for the main tab.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), site_instance1.get()));
contents()->SetOpener(opener1.get());
EXPECT_TRUE(contents()->HasOpener());
// Navigate to a cross-site URL (different SiteInstance but same
// BrowsingInstance).
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
// Commit a back navigation before the DidChangeOpener message arrives.
contents()->GetController().GoBack();
contents()->GetMainFrame()->PrepareForCommit();
NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
contents()->GetSpeculativePrimaryMainFrame()->SendNavigateWithTransition(
entry1->GetUniqueID(), false, entry1->GetURL(),
entry1->GetTransitionType());
// Disown the opener from rfh2.
rfh2->SimulateDidChangeOpener(absl::nullopt);
EXPECT_FALSE(contents()->HasOpener());
}
// Test that we clean up RenderFrameProxyHosts when a process hosting the
// associated frames crashes. http://crbug.com/258993
TEST_P(RenderFrameHostManagerTest, CleanUpProxiesOnProcessCrash) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
// Create a new tab as an opener for the main tab.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
RenderFrameHostManager* opener1_manager =
opener1->GetRenderManagerForTesting();
contents()->SetOpener(opener1.get());
// Make sure the new opener RVH is considered live.
RenderViewHostImpl* opener_rvh =
opener1_manager->current_frame_host()->render_view_host();
opener_rvh->CreateRenderView(absl::nullopt, MSG_ROUTING_NONE, false);
EXPECT_TRUE(opener_rvh->IsRenderViewLive());
EXPECT_TRUE(opener1_manager->current_frame_host()->IsRenderFrameLive());
// Use a cross-process navigation in the opener to make the old RVH inactive.
EXPECT_FALSE(
opener1_manager->GetRenderFrameProxyHost(rfh1->GetSiteInstance()));
opener1->NavigateAndCommit(kUrl2);
RenderFrameProxyHost* rfph1 =
opener1_manager->GetRenderFrameProxyHost(rfh1->GetSiteInstance());
RenderViewHostImpl* rvh1 = rfph1->GetRenderViewHost();
EXPECT_TRUE(rvh1);
EXPECT_FALSE(rvh1->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_EQ(rfph1, render_frame_proxy_host);
EXPECT_FALSE(render_frame_proxy_host->is_render_frame_proxy_live());
// Expect the RVH to exist but not be live.
EXPECT_TRUE(rfph1->GetRenderViewHost());
EXPECT_FALSE(rfph1->GetRenderViewHost()->IsRenderViewLive());
// Reload the initial tab. This should recreate the opener's RVH in the
// original SiteInstance.
contents()->GetController().Reload(ReloadType::NORMAL, true);
contents()->GetMainFrame()->PrepareForCommit();
TestRenderFrameHost* rfh2 = contents()->GetMainFrame();
EXPECT_TRUE(opener1_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance())
->GetRenderViewHost()
->IsRenderViewLive());
EXPECT_EQ(
opener1_manager->GetFrameTokenForSiteInstance(rfh2->GetSiteInstance()),
rfh2->GetRenderViewHost()->opener_frame_token());
}
// Test that we reuse the same guest SiteInstance if we navigate across sites.
TEST_P(RenderFrameHostManagerTest, NoSwapOnGuestNavigations) {
// Create a custom site URL for the SiteInstance. There is nothing special
// about this URL other than we expect the resulting SiteInstance to return
// this exact URL from its GetSiteURL() method.
const GURL kGuestSiteUrl("my-guest-scheme://someapp/somepath");
scoped_refptr<SiteInstance> instance =
SiteInstance::CreateForGuest(browser_context(), kGuestSiteUrl);
std::unique_ptr<TestWebContents> web_contents(
TestWebContents::Create(browser_context(), instance));
EXPECT_TRUE(instance->IsGuest());
EXPECT_EQ(kGuestSiteUrl, instance->GetSiteURL());
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(), absl::nullopt,
std::u16string() /* 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.
DidNavigateFrame(manager, host);
// Commit to SiteInstance should be delayed until RenderFrame commit.
EXPECT_EQ(host, manager->current_frame_host());
ASSERT_TRUE(host);
EXPECT_TRUE(host->GetSiteInstance()->HasSite());
// 2) Navigate to a different domain. -------------------------
// Guests stay in the same process on navigation.
const GURL kUrl2("http://www.chromium.org");
const url::Origin kInitiatorOrigin =
url::Origin::Create(GURL("https://initiator.example.com"));
NavigationEntryImpl entry2(
nullptr /* instance */, kUrl2,
Referrer(kUrl1, network::mojom::ReferrerPolicy::kDefault),
kInitiatorOrigin, std::u16string() /* 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.
DidNavigateFrame(manager, host);
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_P(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(), absl::nullopt,
std::u16string() /* 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.
DidNavigateFrame(manager, host);
// Commit to SiteInstance should be delayed until RenderFrame commits.
EXPECT_EQ(host, manager->current_frame_host());
EXPECT_FALSE(host->GetSiteInstance()->HasSite());
host->GetSiteInstance()->SetSite(UrlInfo::CreateForTesting(kUrl1));
// 2) Cross-site navigate to next site. -------------------------
const GURL kUrl2("http://www.example.com");
NavigationEntryImpl entry2(
nullptr /* instance */, kUrl2, Referrer(), absl::nullopt,
std::u16string() /* 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->BeforeUnloadCompleted(true, base::TimeTicks());
run_loop.Run();
EXPECT_FALSE(GetPendingFrameHost(manager));
EXPECT_EQ(host, manager->current_frame_host());
}
TEST_P(RenderFrameHostManagerTest, CloseWithPendingWhileUnresponsive) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
CloseWebContentsDelegate close_delegate;
contents()->SetDelegate(&close_delegate);
// Navigate to the first page.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
// Start to close the tab, but assume it's unresponsive.
rfh1->render_view_host()->ClosePage();
EXPECT_TRUE(rfh1->render_view_host()->is_waiting_for_page_close_completion());
// 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
// mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame is received.
// (mojo::FrameNavigationControl::Unload and the corresponding
// mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame always occur after
// commit.) Also tests that an early
// mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame is properly ignored.
TEST_P(RenderFrameHostManagerTest, DeleteFrameAfterUnloadACK) {
// When a page enters the BackForwardCache, the RenderFrameHost is not
// deleted. Similarly, no
// mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame message is sent.
contents()->GetController().GetBackForwardCache().DisableForTesting(
BackForwardCache::TEST_ASSUMES_NO_CACHING);
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->IsActive());
// Navigate to new site, simulating onbeforeunload approval.
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl2, contents());
navigation->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(rfh1->IsActive());
TestRenderFrameHost* rfh2 = contents()->GetSpeculativePrimaryMainFrame();
// Simulate the unload ack, unexpectedly early (before commit). It should
// have no effect.
rfh1->SimulateUnloadACK();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(rfh1->IsActive());
// The new page commits.
navigation->set_drop_unload_ack(true);
navigation->Commit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(rfh2, contents()->GetMainFrame());
EXPECT_TRUE(contents()->GetSpeculativePrimaryMainFrame() == nullptr);
EXPECT_TRUE(rfh2->IsActive());
EXPECT_TRUE(rfh1->IsPendingDeletion());
// Simulate the unload ack.
rfh1->SimulateUnloadACK();
// rfh1 should have been deleted.
EXPECT_TRUE(rfh_deleted_observer.deleted());
rfh1 = nullptr;
}
// Tests that the RenderFrameHost is properly unloaded when the
// mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame is received.
// (mojo::FrameNavigationControl::Unload and the corresponding
// mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame always occur after
// commit.)
TEST_P(RenderFrameHostManagerTest, UnloadFrameAfterUnloadACK) {
// When a page enters the BackForwardCache, the RenderFrameHost is not
// deleted. Similarly, no
// mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame message is sent.
contents()->GetController().GetBackForwardCache().DisableForTesting(
BackForwardCache::TEST_ASSUMES_NO_CACHING);
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->IsActive());
// Increment the number of active frames in SiteInstanceImpl so that rfh1 is
// not deleted on unload.
rfh1->GetSiteInstance()->IncrementActiveFrameCount();
// Navigate to new site, simulating onbeforeunload approval.
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl2, contents());
navigation->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(rfh1->IsActive());
TestRenderFrameHost* rfh2 = contents()->GetSpeculativePrimaryMainFrame();
// The new page commits.
navigation->set_drop_unload_ack(true);
navigation->Commit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(rfh2, contents()->GetMainFrame());
EXPECT_TRUE(contents()->GetSpeculativePrimaryMainFrame() == nullptr);
EXPECT_TRUE(rfh1->IsPendingDeletion());
EXPECT_TRUE(rfh2->IsActive());
// Simulate the unload ack.
rfh1->OnUnloaded();
// rfh1 should be deleted.
EXPECT_TRUE(rfh_deleted_observer.deleted());
}
// Test that a RenderFrameHost is properly deleted if a navigation in the new
// renderer commits before sending the mojo::FrameNavigationControl::Unload
// 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_P(RenderFrameHostManagerTest, CommitNewNavigationBeforeSendingUnload) {
// When a page enters the BackForwardCache, the RenderFrameHost is not
// deleted. Similarly, no
// mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame message is sent.
contents()->GetController().GetBackForwardCache().DisableForTesting(
BackForwardCache::TEST_ASSUMES_NO_CACHING);
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->IsActive());
// Increment the number of active frames in rfh1's SiteInstance so that the
// SiteInstance is not deleted on unload.
scoped_refptr<SiteInstanceImpl> site_instance = rfh1->GetSiteInstance();
site_instance->IncrementActiveFrameCount();
// Navigate to new site, simulating onbeforeunload approval.
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl2, contents());
navigation->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(rfh1->IsActive());
TestRenderFrameHost* rfh2 = contents()->GetSpeculativePrimaryMainFrame();
// The new page commits.
navigation->set_drop_unload_ack(true);
navigation->Commit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(rfh2, contents()->GetMainFrame());
EXPECT_TRUE(contents()->GetSpeculativePrimaryMainFrame() == nullptr);
EXPECT_TRUE(rfh1->IsPendingDeletion());
EXPECT_TRUE(rfh2->IsActive());
// Simulate the unload ack.
rfh1->OnUnloaded();
// 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_P(RenderFrameHostManagerTest, CancelPendingProperlyDeletesOrSwaps) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
RenderFrameHostImpl* pending_rfh = nullptr;
// Navigate to the first page.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
EXPECT_TRUE(rfh1->IsActive());
// Navigate to a new site, starting a cross-site navigation.
controller().LoadURL(kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK,
std::string());
{
pending_rfh = contents()->GetSpeculativePrimaryMainFrame();
RenderFrameDeletedObserver rfh_deleted_observer(pending_rfh);
// Cancel the navigation by simulating a declined beforeunload dialog.
contents()->GetMainFrame()->SimulateBeforeUnloadCompleted(false);
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()->GetSpeculativePrimaryMainFrame();
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()->SimulateBeforeUnloadCompleted(false);
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_P(RenderFrameHostManagerTestWithSiteIsolation, DetachPendingChild) {
const GURL kUrlA("http://www.google.com/");
const GURL kUrlB("http://webkit.org/");
constexpr auto kOwnerType = blink::FrameOwnerElementType::kIframe;
// Create a page with two child frames.
contents()->NavigateAndCommit(kUrlA);
contents()->GetMainFrame()->OnCreateChildFrame(
contents()->GetMainFrame()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, "frame_name", "uniqueName1",
false, blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), kOwnerType);
contents()->GetMainFrame()->OnCreateChildFrame(
contents()->GetMainFrame()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, "frame_name", "uniqueName2",
false, blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), kOwnerType);
RenderFrameHostManager* root_manager =
contents()->GetFrameTree()->root()->render_manager();
RenderFrameHostManager* iframe1 =
contents()->GetFrameTree()->root()->child_at(0)->render_manager();
RenderFrameHostManager* iframe2 =
contents()->GetFrameTree()->root()->child_at(1)->render_manager();
// 1) The first navigation.
NavigationEntryImpl entryA(
nullptr /* instance */, kUrlA, Referrer(), absl::nullopt,
std::u16string() /* 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.
DidNavigateFrame(iframe1, host1);
// Commit to SiteInstance should be delayed until RenderFrame commit.
EXPECT_TRUE(host1 == iframe1->current_frame_host());
ASSERT_TRUE(host1);
EXPECT_TRUE(host1->GetSiteInstance()->HasSite());
// 2) Cross-site navigate both frames to next site.
NavigationEntryImpl entryB(
nullptr /* instance */, kUrlB,
Referrer(kUrlA, network::mojom::ReferrerPolicy::kDefault), absl::nullopt,
std::u16string() /* 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_EQ(GetPendingFrameHost(iframe1)->lifecycle_state(),
RenderFrameHostImpl::LifecycleStateImpl::kSpeculative);
EXPECT_EQ(GetPendingFrameHost(iframe2)->lifecycle_state(),
RenderFrameHostImpl::LifecycleStateImpl::kSpeculative);
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()->Detach();
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()->Detach();
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.";
}
#if defined(OS_ANDROID)
// TODO(lukasza): https://crbug.com/1067432: Calling Compositor::Initialize()
// DCHECKs flakily and without such call the test below consistently fails on
// Android (DCHECKing about parent_view->GetFrameSinkId().is_valid() in
// RenderWidgetHostViewChildFrame::SetFrameConnectorDelegate).
#define MAYBE_TwoTabsCrashOneReloadsOneLeaves \
DISABLED_TwoTabsCrashOneReloadsOneLeaves
#else
#define MAYBE_TwoTabsCrashOneReloadsOneLeaves TwoTabsCrashOneReloadsOneLeaves
#endif
// 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_P(RenderFrameHostManagerTestWithSiteIsolation,
MAYBE_TwoTabsCrashOneReloadsOneLeaves) {
#if defined(OS_ANDROID)
// TODO(lukasza): https://crbug.com/1067432: This call might DCHECK flakily
// about !CompositorImpl::IsInitialized()..
Compositor::Initialize();
#endif
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2("http://webkit.org/");
const GURL kUrl3("http://whatwg.org/");
// |contents1| and |contents2| navigate to the same page and then crash.
TestWebContents* contents1 = contents();
std::unique_ptr<TestWebContents> contents2(
TestWebContents::Create(browser_context(), contents1->GetSiteInstance()));
contents1->NavigateAndCommit(kUrl1);
contents2->NavigateAndCommit(kUrl1);
MockRenderProcessHost* rph = contents1->GetMainFrame()->GetProcess();
EXPECT_EQ(rph, contents2->GetMainFrame()->GetProcess());
EXPECT_TRUE(contents1->GetMainFrame()->GetView());
EXPECT_TRUE(contents2->GetMainFrame()->GetView());
rph->SimulateCrash();
EXPECT_FALSE(contents1->GetMainFrame()->IsRenderFrameLive());
EXPECT_FALSE(contents2->GetMainFrame()->IsRenderFrameLive());
EXPECT_EQ(contents1->GetSiteInstance(), contents2->GetSiteInstance());
EXPECT_FALSE(contents1->GetMainFrame()->GetView());
EXPECT_FALSE(contents2->GetMainFrame()->GetView());
// Reload |contents1|.
contents1->NavigateAndCommit(kUrl1);
EXPECT_TRUE(contents1->GetMainFrame()->IsRenderFrameLive());
EXPECT_FALSE(contents2->GetMainFrame()->IsRenderFrameLive());
EXPECT_EQ(contents1->GetSiteInstance(), contents2->GetSiteInstance());
EXPECT_TRUE(contents1->GetMainFrame()->GetView());
EXPECT_FALSE(contents2->GetMainFrame()->GetView());
// |contents1| creates an out of process iframe.
contents1->GetMainFrame()->OnCreateChildFrame(
contents1->GetMainFrame()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, "frame_name", "uniqueName1",
false, blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(),
blink::FrameOwnerElementType::kIframe);
RenderFrameHostManager* iframe =
contents()->GetFrameTree()->root()->child_at(0)->render_manager();
NavigationEntryImpl entry(
nullptr /* instance */, kUrl2,
Referrer(kUrl1, network::mojom::ReferrerPolicy::kDefault), absl::nullopt,
std::u16string() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
RenderFrameHostImpl* cross_site = NavigateToEntry(iframe, &entry);
DidNavigateFrame(iframe, cross_site);
// 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_P(RenderFrameHostManagerTestWithSiteIsolation,
DontGrantPendingWebUIToSubframe) {
// 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::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, std::string(), "uniqueName1",
false, blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(),
blink::FrameOwnerElementType::kIframe);
RenderFrameHostManager* subframe_rfhm =
contents()->GetFrameTree()->root()->child_at(0)->render_manager();
// Start a pending WebUI navigation in the main frame and verify that the
// pending RVH has bindings.
const GURL kWebUIUrl(GetWebUIURL("foo"));
NavigationEntryImpl webui_entry(
nullptr /* instance */, kWebUIUrl, Referrer(), absl::nullopt,
std::u16string() /* 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(), absl::nullopt,
std::u16string() /* 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);
}
// This class intercepts RenderFrameProxyHost creations, and overrides their
// respective blink::mojom::RemoteFrame instances, so that it can watch the
// updates of opener frames.
class UpdateOpenerProxyObserver : public RenderFrameProxyHost::TestObserver {
public:
UpdateOpenerProxyObserver() {
RenderFrameProxyHost::SetObserverForTesting(this);
}
~UpdateOpenerProxyObserver() override {
RenderFrameProxyHost::SetObserverForTesting(nullptr);
}
absl::optional<blink::FrameToken> OpenerFrameToken(
RenderFrameProxyHost* proxy) {
return remote_frames_[proxy]->opener_frame_token();
}
private:
class Remote : public content::FakeRemoteFrame {
public:
explicit Remote(RenderFrameProxyHost* proxy) {
Init(proxy->GetRemoteAssociatedInterfacesTesting());
}
void UpdateOpener(
const absl::optional<blink::FrameToken>& frame_token) override {
frame_token_ = frame_token;
}
absl::optional<blink::FrameToken> opener_frame_token() {
return frame_token_;
}
private:
absl::optional<blink::FrameToken> frame_token_;
};
void OnCreated(RenderFrameProxyHost* proxy_host) override {
remote_frames_[proxy_host] = std::make_unique<Remote>(proxy_host);
}
std::map<RenderFrameProxyHost*, std::unique_ptr<Remote>> remote_frames_;
};
// Test that opener proxies are created properly with a cycle on the opener
// chain.
TEST_P(RenderFrameHostManagerTest, CreateOpenerProxiesWithCycleOnOpenerChain) {
UpdateOpenerProxyObserver proxy_observers;
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create 2 new tabs and construct the opener chain as follows:
//
// tab2 <--- tab1 <---- contents()
// | ^
// +-------+
//
std::unique_ptr<TestWebContents> tab1(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* tab1_manager = tab1->GetRenderManagerForTesting();
std::unique_ptr<TestWebContents> tab2(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* tab2_manager = tab2->GetRenderManagerForTesting();
contents()->SetOpener(tab1.get());
tab1->SetOpener(tab2.get());
tab2->SetOpener(tab1.get());
// Navigate main window to a cross-site URL. This will call
// CreateOpenerProxies() to create proxies for the two opener tabs in the new
// SiteInstance.
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
// Check that each tab now has a proxy in the new SiteInstance.
RenderFrameProxyHost* tab1_proxy =
tab1_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
EXPECT_TRUE(tab1_proxy);
RenderFrameProxyHost* tab2_proxy =
tab2_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
EXPECT_TRUE(tab2_proxy);
// Verify that the proxies' openers point to each other.
auto tab1_opener_frame_token =
tab1_manager->GetOpenerFrameToken(rfh2->GetSiteInstance());
auto tab2_opener_frame_token =
tab2_manager->GetOpenerFrameToken(rfh2->GetSiteInstance());
EXPECT_EQ(*tab1_opener_frame_token, tab2_proxy->GetFrameToken());
EXPECT_EQ(*tab2_opener_frame_token, tab1_proxy->GetFrameToken());
// Setting tab2_proxy's opener required an extra IPC message to be set, since
// the opener's frame token wasn't available when tab2_proxy was created.
// Verify that this IPC was sent and that it passed correct frame token.
base::RunLoop().RunUntilIdle();
DCHECK(proxy_observers.OpenerFrameToken(tab2_proxy) ==
tab2_manager->GetOpenerFrameToken(rfh2->GetSiteInstance()));
}
// Test that opener proxies are created properly when the opener points
// to itself.
TEST_P(RenderFrameHostManagerTest, CreateOpenerProxiesWhenOpenerPointsToSelf) {
UpdateOpenerProxyObserver proxy_observers;
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create an opener tab, and simulate that its opener points to itself.
std::unique_ptr<TestWebContents> opener(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* opener_manager = opener->GetRenderManagerForTesting();
contents()->SetOpener(opener.get());
opener->SetOpener(opener.get());
// Navigate main window to a cross-site URL. This will call
// CreateOpenerProxies() to create proxies for the opener tab in the new
// SiteInstance.
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
// Check that the opener now has a proxy in the new SiteInstance.
RenderFrameProxyHost* opener_proxy =
opener_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
EXPECT_TRUE(opener_proxy);
// Verify that the proxy's opener points to itself.
auto opener_frame_token =
opener_manager->GetOpenerFrameToken(rfh2->GetSiteInstance());
EXPECT_EQ(*opener_frame_token, opener_proxy->GetFrameToken());
// Setting the opener in opener_proxy required an extra IPC message, since
// the opener's frame_token wasn't available when opener_proxy was created.
// Verify that this IPC was sent and that it passed correct frame token.
base::RunLoop().RunUntilIdle();
DCHECK(proxy_observers.OpenerFrameToken(opener_proxy) ==
opener_manager->GetOpenerFrameToken(rfh2->GetSiteInstance()));
}
// 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_P(RenderFrameHostManagerTest, TraverseComplexOpenerChain) {
contents()->NavigateAndCommit(GURL("http://tab1.com"));
FrameTree* tree1 = contents()->GetFrameTree();
FrameTreeNode* root1 = tree1->root();
int process_id = root1->current_frame_host()->GetProcess()->GetID();
constexpr auto kOwnerType = blink::FrameOwnerElementType::kIframe;
const bool is_dummy_frame_for_inner_tree = false;
tree1->AddFrame(
root1->current_frame_host(), process_id, 12,
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, std::string(), "uniqueName0",
false, blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), false,
kOwnerType, is_dummy_frame_for_inner_tree);
tree1->AddFrame(
root1->current_frame_host(), process_id, 13,
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, std::string(), "uniqueName1",
false, blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), false,
kOwnerType, is_dummy_frame_for_inner_tree);
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->current_frame_host(), process_id, 22,
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, std::string(), "uniqueName2",
false, blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), false,
kOwnerType, is_dummy_frame_for_inner_tree);
tree2->AddFrame(
root2->current_frame_host(), process_id, 23,
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, std::string(), "uniqueName3",
false, blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), false,
kOwnerType, is_dummy_frame_for_inner_tree);
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->current_frame_host(), process_id, 42,
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, std::string(), "uniqueName4",
false, blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), false,
kOwnerType, is_dummy_frame_for_inner_tree);
root1->child_at(1)->SetOpener(root1->child_at(1));
root1->SetOpener(root2->child_at(1));
root2->SetOpener(root3);
root2->child_at(0)->SetOpener(root4);
root2->child_at(1)->SetOpener(root4);
root4->child_at(0)->SetOpener(root3);
std::vector<FrameTree*> opener_frame_trees;
std::unordered_set<FrameTreeNode*> nodes_with_back_links;
CollectOpenerFrameTrees(root1, &opener_frame_trees, &nodes_with_back_links);
EXPECT_EQ(4U, opener_frame_trees.size());
EXPECT_EQ(tree1, opener_frame_trees[0]);
EXPECT_EQ(tree2, opener_frame_trees[1]);
EXPECT_EQ(tree3, opener_frame_trees[2]);
EXPECT_EQ(tree4, opener_frame_trees[3]);
EXPECT_EQ(2U, nodes_with_back_links.size());
EXPECT_TRUE(nodes_with_back_links.find(root1->child_at(1)) !=
nodes_with_back_links.end());
EXPECT_TRUE(nodes_with_back_links.find(root4->child_at(0)) !=
nodes_with_back_links.end());
}
// This class intercepts RenderFrameProxyHost creations, and overrides their
// respective blink::mojom::RemoteFrame instances, so that it can watch the
// start and stop loading states.
class PageFocusProxyObserver : public RenderFrameProxyHost::TestObserver {
public:
PageFocusProxyObserver() {
RenderFrameProxyHost::SetObserverForTesting(this);
}
~PageFocusProxyObserver() override {
RenderFrameProxyHost::SetObserverForTesting(nullptr);
}
bool IsPageFocused(RenderFrameProxyHost* proxy) {
return remote_frames_[proxy]->set_page_focus();
}
private:
class Remote : public content::FakeRemoteFrame {
public:
explicit Remote(RenderFrameProxyHost* proxy) {
Init(proxy->GetRemoteAssociatedInterfacesTesting());
}
void SetPageFocus(bool is_focused) override {
set_page_focus_ = is_focused;
}
bool set_page_focus() { return set_page_focus_; }
private:
bool set_page_focus_ = false;
};
void OnCreated(RenderFrameProxyHost* proxy_host) override {
remote_frames_[proxy_host] = std::make_unique<Remote>(proxy_host);
}
std::map<RenderFrameProxyHost*, std::unique_ptr<Remote>> remote_frames_;
};
// 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_P(RenderFrameHostManagerTest, PageFocusPropagatesToSubframeProcesses) {
// This test only makes sense when cross-site subframes use separate
// processes.
if (!AreAllSitesIsolatedForTesting())
return;
// Start monitoring RenderFrameProxyHost.
PageFocusProxyObserver proxy_observer;
const GURL kUrlA("http://a.com/");
const GURL kUrlB("http://b.com/");
const GURL kUrlC("http://c.com/");
constexpr auto kOwnerType = blink::FrameOwnerElementType::kIframe;
// Set up a page at a.com with three subframes: two for b.com and one for
// c.com.
contents()->NavigateAndCommit(kUrlA);
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, "frame1", "uniqueName1", false,
blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), kOwnerType);
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, "frame2", "uniqueName2", false,
blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), kOwnerType);
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, "frame3", "uniqueName3", false,
blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), kOwnerType);
FrameTreeNode* root = contents()->GetFrameTree()->root();
RenderFrameHostManager* child1 = root->child_at(0)->render_manager();
RenderFrameHostManager* child2 = root->child_at(1)->render_manager();
RenderFrameHostManager* child3 = root->child_at(2)->render_manager();
// Navigate first two subframes to B.
NavigationEntryImpl entryB(
nullptr /* instance */, kUrlB,
Referrer(kUrlA, network::mojom::ReferrerPolicy::kDefault), absl::nullopt,
std::u16string() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
TestRenderFrameHost* host1 =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child1, &entryB));
// The main frame should have proxies for B.
RenderFrameProxyHost* proxyB =
root->render_manager()->GetRenderFrameProxyHost(host1->GetSiteInstance());
EXPECT_TRUE(proxyB);
TestRenderFrameHost* host2 =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child2, &entryB));
DidNavigateFrame(child1, host1);
DidNavigateFrame(child2, host2);
// Navigate the third subframe to C.
NavigationEntryImpl entryC(
nullptr /* instance */, kUrlC,
Referrer(kUrlA, network::mojom::ReferrerPolicy::kDefault), absl::nullopt,
std::u16string() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
TestRenderFrameHost* host3 =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child3, &entryC));
// The main frame should have proxies for C.
RenderFrameProxyHost* proxyC =
root->render_manager()->GetRenderFrameProxyHost(host3->GetSiteInstance());
EXPECT_TRUE(proxyC);
DidNavigateFrame(child3, host3);
// 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());
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);
EXPECT_TRUE(proxy_observer.IsPageFocused(proxyB));
EXPECT_TRUE(proxy_observer.IsPageFocused(proxyC));
// 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);
EXPECT_FALSE(proxy_observer.IsPageFocused(proxyB));
EXPECT_FALSE(proxy_observer.IsPageFocused(proxyC));
}
// 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_P(RenderFrameHostManagerTest,
PageFocusIsPreservedAcrossSubframeNavigations) {
// This test only makes sense when cross-site subframes use separate
// processes.
if (!AreAllSitesIsolatedForTesting())
return;
// Start monitoring RenderFrameProxyHost.
PageFocusProxyObserver proxy_observer;
const GURL kUrlA("http://a.com/");
const GURL kUrlB("http://b.com/");
const GURL kUrlC("http://c.com/");
constexpr auto kOwnerType = blink::FrameOwnerElementType::kIframe;
// Set up a page at a.com with a b.com subframe.
contents()->NavigateAndCommit(kUrlA);
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, "frame1", "uniqueName1", false,
blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), kOwnerType);
FrameTreeNode* root = contents()->GetFrameTree()->root();
RenderFrameHostManager* child = root->child_at(0)->render_manager();
// Navigate subframe to B.
NavigationEntryImpl entryB(
nullptr /* instance */, kUrlB,
Referrer(kUrlA, network::mojom::ReferrerPolicy::kDefault), absl::nullopt,
std::u16string() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
TestRenderFrameHost* hostB =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child, &entryB));
DidNavigateFrame(child, hostB);
// Ensure that the main page is focused.
main_test_rfh()->GetView()->Focus();
EXPECT_TRUE(main_test_rfh()->GetView()->HasFocus());
main_test_rfh()->GetRenderWidgetHost()->SetPageFocus(true);
EXPECT_TRUE(main_test_rfh()->GetRenderWidgetHost()->is_focused());
// Navigate the subframe to C.
NavigationEntryImpl entryC(
nullptr /* instance */, kUrlC,
Referrer(kUrlA, network::mojom::ReferrerPolicy::kDefault), absl::nullopt,
std::u16string() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
TestRenderFrameHost* hostC =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child, &entryC));
// The main frame should now have a proxy for C.
RenderFrameProxyHost* proxyC =
root->render_manager()->GetRenderFrameProxyHost(hostC->GetSiteInstance());
EXPECT_TRUE(proxyC);
DidNavigateFrame(child, hostC);
base::RunLoop().RunUntilIdle();
// 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.
EXPECT_TRUE(proxy_observer.IsPageFocused(proxyC));
}
// Checks that a restore navigation to a WebUI works.
TEST_P(RenderFrameHostManagerTest, RestoreNavigationToWebUI) {
const GURL kInitUrl(GetWebUIURL("foo"));
scoped_refptr<SiteInstanceImpl> initial_instance =
SiteInstanceImpl::Create(browser_context());
initial_instance->SetSite(UrlInfo::CreateForTesting(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(), absl::nullopt, ui::PAGE_TRANSITION_TYPED, false,
std::string(), browser_context(),
nullptr /* blob_url_loader_factory */);
entries.push_back(std::move(new_entry));
controller.Restore(0, RestoreType::kRestored, &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(), absl::nullopt,
std::u16string() /* title */, ui::PAGE_TRANSITION_RELOAD,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
entry.set_restore_type(RestoreType::kRestored);
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());
EXPECT_TRUE(current_host->web_ui());
// The RenderFrameHost committed.
DidNavigateFrame(manager, current_host);
EXPECT_EQ(current_host, manager->current_frame_host());
EXPECT_TRUE(current_host->web_ui());
}
// Simulates two simultaneous navigations involving one WebUI where the current
// RenderFrameHost commits.
TEST_P(RenderFrameHostManagerTest, SimultaneousNavigationWithOneWebUI1) {
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GetWebUIURL("foo/"));
RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
RenderFrameHostImpl* host1 = manager->current_frame_host();
EXPECT_TRUE(host1->IsRenderFrameLive());
WebUIImpl* web_ui = host1->web_ui();
EXPECT_TRUE(web_ui);
// Starts a reload of the WebUI page.
contents()->GetController().Reload(ReloadType::NORMAL, true);
auto reload =
NavigationSimulator::CreateFromPending(contents()->GetController());
reload->ReadyToCommit();
// It should be a same-site navigation reusing the same WebUI.
EXPECT_EQ(web_ui, host1->web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
// Navigation request to a non-WebUI page.
const GURL kUrl("http://google.com");
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
navigation->ReadyToCommit();
RenderFrameHostImpl* host2 = GetPendingFrameHost(manager);
ASSERT_TRUE(host2);
// The previous navigation should still be ongoing along with the new,
// cross-site one.
EXPECT_FALSE(host2->web_ui());
EXPECT_EQ(web_ui, host1->web_ui());
EXPECT_NE(host2, host1);
EXPECT_NE(web_ui, host2->web_ui());
// The current RenderFrameHost commits; its WebUI should still be in place.
reload->Commit();
EXPECT_EQ(host1, manager->current_frame_host());
EXPECT_EQ(web_ui, host1->web_ui());
// Because the Navigation that committed was browser-initiated, it will not
// have the user gesture bit set to true. This has the side-effect of not
// deleting the speculative RenderFrameHost at commit time.
// TODO(clamy): The speculative RenderFrameHost should be deleted at commit
// time if a browser-initiated navigation commits.
EXPECT_TRUE(GetPendingFrameHost(manager));
}
// Simulates two simultaneous navigations involving one WebUI where the new,
// cross-site RenderFrameHost commits.
TEST_P(RenderFrameHostManagerTest, SimultaneousNavigationWithOneWebUI2) {
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GetWebUIURL("foo/"));
RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
RenderFrameHostImpl* host1 = manager->current_frame_host();
EXPECT_TRUE(host1->IsRenderFrameLive());
WebUIImpl* web_ui = host1->web_ui();
EXPECT_TRUE(web_ui);
// Starts a reload of the WebUI page.
contents()->GetController().Reload(ReloadType::NORMAL, true);
auto reload =
NavigationSimulator::CreateFromPending(contents()->GetController());
reload->ReadyToCommit();
// It should be a same-site navigation reusing the same WebUI.
EXPECT_FALSE(GetPendingFrameHost(manager));
EXPECT_EQ(web_ui, host1->web_ui());
// Navigation request to a non-WebUI page.
const GURL kUrl("http://google.com");
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
navigation->ReadyToCommit();
RenderFrameHostImpl* host2 = GetPendingFrameHost(manager);
ASSERT_TRUE(host2);
// The previous navigation should still be ongoing along with the new,
// cross-site one.
EXPECT_FALSE(host2->web_ui());
EXPECT_EQ(web_ui, host1->web_ui());
EXPECT_NE(host2, host1);
EXPECT_NE(web_ui, host2->web_ui());
// The new RenderFrameHost commits; there should be no active WebUI.
navigation->Commit();
EXPECT_EQ(host2, manager->current_frame_host());
EXPECT_FALSE(host2->web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
}
// Shared code until before commit for the SimultaneousNavigationWithTwoWebUIs*
// tests, accepting a lambda to execute the commit step.
// Simulates two simultaneous navigations involving two WebUIs where the current
// RenderFrameHost commits.
TEST_P(RenderFrameHostManagerTest, SimultaneousNavigationWithTwoWebUIs1) {
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GetWebUIURL("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);
auto reload =
NavigationSimulator::CreateFromPending(contents()->GetController());
reload->ReadyToCommit();
// It should be a same-site navigation reusing the same WebUI.
EXPECT_EQ(web_ui1, host1->web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
// Navigate to another WebUI page.
const GURL kUrl(GetWebUIURL("bar"));
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
navigation->ReadyToCommit();
RenderFrameHostImpl* host2 = GetPendingFrameHost(manager);
ASSERT_TRUE(host2);
// The previous navigation should still be ongoing along with the new,
// cross-site one.
EXPECT_EQ(web_ui1, host1->web_ui());
EXPECT_TRUE(manager->speculative_frame_host());
WebUIImpl* web_ui2 = manager->speculative_frame_host()->web_ui();
EXPECT_TRUE(web_ui2);
EXPECT_NE(web_ui2, web_ui1);
EXPECT_NE(host2, host1);
EXPECT_EQ(web_ui2, host2->web_ui());
// The current RenderFrameHost commits; its WebUI should still be active.
reload->Commit();
EXPECT_EQ(host1, manager->current_frame_host());
EXPECT_EQ(web_ui1, host1->web_ui());
// Because the Navigation that committed was browser-initiated, it will not
// have the user gesture bit set to true. This has the side-effect of not
// deleting the speculative RenderFrameHost at commit time.
// TODO(clamy): The speculative RenderFrameHost should be deleted at commit
// time if a browser-initiated navigation commits.
EXPECT_TRUE(manager->speculative_frame_host()->web_ui());
EXPECT_TRUE(GetPendingFrameHost(manager));
}
// Simulates two simultaneous navigations involving two WebUIs where the new,
// cross-site RenderFrameHost commits.
TEST_P(RenderFrameHostManagerTest, SimultaneousNavigationWithTwoWebUIs2) {
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GetWebUIURL("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);
auto reload =
NavigationSimulator::CreateFromPending(contents()->GetController());
reload->ReadyToCommit();
// It should be a same-site navigation reusing the same WebUI.
EXPECT_EQ(web_ui1, host1->web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
// Navigate to another WebUI page.
const GURL kUrl(GetWebUIURL("bar/"));
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
navigation->ReadyToCommit();
RenderFrameHostImpl* host2 = GetPendingFrameHost(manager);
ASSERT_TRUE(host2);
// The previous navigation should still be ongoing along with the new,
// cross-site one.
EXPECT_EQ(web_ui1, host1->web_ui());
WebUIImpl* web_ui2 = manager->speculative_frame_host()->web_ui();
EXPECT_TRUE(web_ui2);
EXPECT_NE(web_ui2, web_ui1);
EXPECT_NE(host2, host1);
EXPECT_EQ(web_ui2, host2->web_ui());
navigation->Commit();
EXPECT_EQ(host2, manager->current_frame_host());
EXPECT_EQ(web_ui2, host2->web_ui());
EXPECT_FALSE(manager->speculative_frame_host());
EXPECT_FALSE(GetPendingFrameHost(manager));
}
TEST_P(RenderFrameHostManagerTest, CanCommitOrigin) {
const GURL kUrl("http://a.com/");
const GURL kUrlBar("http://a.com/bar");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kUrl);
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) {
auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
GURL(test_case.url), main_test_rfh());
url::Origin origin = url::Origin::Create(GURL(test_case.origin));
// Manually control what origin will be committed on the "renderer-side" in
// cases where we want to force the "renderer-side" to mismatch the correct
// origin.
if (test_case.mismatch)
navigation->set_origin(origin);
if (origin.opaque()) {
auto response_headers =
base::MakeRefCounted<net::HttpResponseHeaders>(std::string());
response_headers->SetHeader("Content-Security-Policy", "sandbox");
navigation->SetResponseHeaders(response_headers);
}
navigation->ReadyToCommit();
auto* process = static_cast<MockRenderProcessHost*>(
navigation->GetNavigationHandle()->GetRenderFrameHost()->GetProcess());
int expected_bad_msg_count = process->bad_msg_count();
if (test_case.mismatch)
expected_bad_msg_count++;
navigation->Commit();
EXPECT_EQ(expected_bad_msg_count, process->bad_msg_count())
<< " url:" << test_case.url << " origin:" << test_case.origin
<< " mismatch:" << test_case.mismatch;
}
}
// 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_P(RenderFrameHostManagerTest, NavigateFromDeadRendererToWebUI) {
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(GetWebUIURL("foo"));
NavigationEntryImpl entry(
nullptr /* instance */, kUrl, Referrer(), absl::nullopt,
std::u16string() /* 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();
auto& referrer = frame_entry->referrer();
blink::mojom::CommonNavigationParamsPtr common_params =
entry.ConstructCommonNavigationParams(
*frame_entry, nullptr, frame_entry->url(),
blink::mojom::Referrer::New(referrer.url, referrer.policy),
blink::mojom::NavigationType::DIFFERENT_DOCUMENT,
blink::PreviewsTypes::PREVIEWS_UNSPECIFIED, base::TimeTicks::Now(),
base::TimeTicks::Now());
blink::mojom::CommitNavigationParamsPtr commit_params =
entry.ConstructCommitNavigationParams(
*frame_entry, common_params->url, frame_entry->committed_origin(),
common_params->method, entry.GetSubframeUniqueNames(frame_tree_node),
controller().GetPendingEntryIndex() == -1 /* intended_as_new_entry */,
static_cast<NavigationControllerImpl&>(controller())
.GetIndexOfEntry(&entry),
controller().GetLastCommittedEntryIndex(),
controller().GetEntryCount(),
frame_tree_node->current_replication_state().frame_policy);
std::unique_ptr<NavigationRequest> navigation_request =
NavigationRequest::CreateBrowserInitiated(
frame_tree_node, std::move(common_params), std::move(commit_params),
!entry.is_renderer_initiated(), false /* was_opener_suppressed */,
nullptr /* initiator_frame_token */,
ChildProcessHost::kInvalidUniqueID /* initiator_process_id */,
entry.extra_headers(), frame_entry, &entry,
nullptr /* request_body */, nullptr /* navigation_ui_data */,
absl::nullopt /* impression */, false /* is_pdf */);
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(GetPendingFrameHost(manager));
// Prepare to commit, update the navigating RenderFrameHost.
EXPECT_EQ(host, manager->GetFrameHostForNavigation(navigation_request.get()));
// No pending RenderFrameHost as the current one should be reused.
EXPECT_FALSE(GetPendingFrameHost(manager));
EXPECT_EQ(web_ui, host->web_ui());
// The RenderFrameHost committed.
DidNavigateFrame(manager, host);
EXPECT_EQ(host, manager->current_frame_host());
EXPECT_FALSE(GetPendingFrameHost(manager));
EXPECT_EQ(web_ui, host->web_ui());
}
// Tests that the correct intermediary and final navigation states are reached
// when navigating same-site between two WebUIs of the same type.
TEST_P(RenderFrameHostManagerTest, NavigateSameSiteBetweenWebUIs) {
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GetWebUIURL("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(GetWebUIURL("foo/bar"));
auto web_ui_navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
web_ui_navigation->Start();
// The current WebUI should still be in place.
EXPECT_EQ(web_ui, host->web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
// Prepare to commit, update the navigating RenderFrameHost.
web_ui_navigation->ReadyToCommit();
EXPECT_EQ(web_ui, host->web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
// The RenderFrameHost committed and used the same WebUI object.
web_ui_navigation->Commit();
EXPECT_EQ(web_ui, host->web_ui());
}
// Tests that the correct intermediary and final navigation states are reached
// when navigating cross-site between two different WebUI types.
TEST_P(RenderFrameHostManagerTest, 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.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GetWebUIURL("foo"));
RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
RenderFrameHostImpl* host = manager->current_frame_host();
EXPECT_TRUE(host->IsRenderFrameLive());
EXPECT_TRUE(host->web_ui());
// Navigate to different WebUI. 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.
const GURL kUrl(GetWebUIURL("bar"));
auto web_ui_navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
web_ui_navigation->Start();
// 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());
RenderFrameHostImpl* speculative_host = GetPendingFrameHost(manager);
EXPECT_TRUE(speculative_host);
WebUIImpl* next_web_ui = speculative_host->web_ui();
EXPECT_TRUE(next_web_ui);
EXPECT_NE(next_web_ui, manager->current_frame_host()->web_ui());
// The RenderFrameHost committed.
web_ui_navigation->Commit();
EXPECT_EQ(speculative_host, manager->current_frame_host());
EXPECT_EQ(next_web_ui, manager->current_frame_host()->web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
}
// This class intercepts RenderFrameProxyHost creations, and overrides their
// respective blink::mojom::RemoteFrame instances.
class InsecureRequestPolicyProxyObserver
: public RenderFrameProxyHost::TestObserver {
public:
InsecureRequestPolicyProxyObserver() {
RenderFrameProxyHost::SetObserverForTesting(this);
}
~InsecureRequestPolicyProxyObserver() override {
RenderFrameProxyHost::SetObserverForTesting(nullptr);
}
blink::mojom::InsecureRequestPolicy GetRequestPolicy(
RenderFrameProxyHost* proxy_host) {
return remote_frames_[proxy_host]->enforce_insecure_request_policy();
}
private:
// Stub out remote frame mojo binding. Intercepts calls to
// EnforceInsecureRequestPolicy and marks the message as received.
class RemoteFrame : public content::FakeRemoteFrame {
public:
explicit RemoteFrame(RenderFrameProxyHost* render_frame_proxy_host) {
Init(render_frame_proxy_host->GetRemoteAssociatedInterfacesTesting());
}
void EnforceInsecureRequestPolicy(
blink::mojom::InsecureRequestPolicy policy) override {
enforce_insecure_request_policy_ = policy;
}
blink::mojom::InsecureRequestPolicy enforce_insecure_request_policy() {
return enforce_insecure_request_policy_;
}
private:
blink::mojom::InsecureRequestPolicy enforce_insecure_request_policy_;
};
void OnCreated(RenderFrameProxyHost* proxy_host) override {
remote_frames_[proxy_host] = std::make_unique<RemoteFrame>(proxy_host);
}
std::map<RenderFrameProxyHost*, std::unique_ptr<RemoteFrame>> remote_frames_;
};
// Tests that frame proxies receive updates when a frame's enforcement
// of insecure request policy changes.
TEST_P(RenderFrameHostManagerTestWithSiteIsolation,
ProxiesReceiveInsecureRequestPolicy) {
const GURL kUrl1("http://www.google.test");
const GURL kUrl2("http://www.google2.test");
const GURL kUrl3("http://www.google2.test/foo");
InsecureRequestPolicyProxyObserver observer;
contents()->NavigateAndCommit(kUrl1);
// Create a child frame and navigate it cross-site.
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubFrameRemote(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
blink::mojom::TreeScopeType::kDocument, "frame1", "uniqueName1", false,
blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(),
blink::FrameOwnerElementType::kIframe);
FrameTreeNode* root = contents()->GetFrameTree()->root();
RenderFrameHostManager* child = root->child_at(0)->render_manager();
// Navigate subframe to kUrl2.
NavigationSimulator::NavigateAndCommitFromDocument(
kUrl2, child->current_frame_host());
// Verify that parent and child are in different processes.
TestRenderFrameHost* child_host =
static_cast<TestRenderFrameHost*>(child->current_frame_host());
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::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone,
root->current_replication_state().insecure_request_policy);
main_test_rfh()->DidEnforceInsecureRequestPolicy(
blink::mojom::InsecureRequestPolicy::kBlockAllMixedContent);
RenderFrameProxyHost* proxy_to_child =
root->render_manager()->GetRenderFrameProxyHost(
child_host->GetSiteInstance());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::mojom::InsecureRequestPolicy::kBlockAllMixedContent,
observer.GetRequestPolicy(proxy_to_child));
EXPECT_EQ(blink::mojom::InsecureRequestPolicy::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::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone,
root->child_at(0)->current_replication_state().insecure_request_policy);
child_host->DidEnforceInsecureRequestPolicy(
blink::mojom::InsecureRequestPolicy::kBlockAllMixedContent);
RenderFrameProxyHost* proxy_to_parent =
child->GetRenderFrameProxyHost(main_test_rfh()->GetSiteInstance());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::mojom::InsecureRequestPolicy::kBlockAllMixedContent,
observer.GetRequestPolicy(proxy_to_parent));
EXPECT_EQ(
blink::mojom::InsecureRequestPolicy::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();
NavigationSimulator::NavigateAndCommitFromDocument(kUrl3, child_host);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone,
observer.GetRequestPolicy(proxy_to_parent));
EXPECT_EQ(
blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone,
root->child_at(0)->current_replication_state().insecure_request_policy);
}
// This class intercepts RenderFrameProxyHost creations, and overrides their
// respective blink::mojom::RemoteFrame instances, so that it can watch the
// start and stop loading states.
class StartStopLoadingProxyObserver
: public RenderFrameProxyHost::TestObserver {
public:
StartStopLoadingProxyObserver() {
RenderFrameProxyHost::SetObserverForTesting(this);
}
~StartStopLoadingProxyObserver() override {
RenderFrameProxyHost::SetObserverForTesting(nullptr);
}
bool IsLoading(RenderFrameProxyHost* proxy) {
return remote_frames_[proxy]->is_loading();
}
private:
class Remote : public content::FakeRemoteFrame {
public:
explicit Remote(RenderFrameProxyHost* proxy) {
Init(proxy->GetRemoteAssociatedInterfacesTesting());
}
void DidStartLoading() override { is_loading_ = true; }
void DidStopLoading() override { is_loading_ = false; }
bool is_loading() { return is_loading_; }
private:
bool is_loading_ = false;
};
void OnCreated(RenderFrameProxyHost* proxy_host) override {
remote_frames_[proxy_host] = std::make_unique<Remote>(proxy_host);
}
std::map<RenderFrameProxyHost*, std::unique_ptr<Remote>> remote_frames_;
};
// Tests that new frame proxies receive an IPC to update their loading state,
// if they are created for a frame that's currently loading. See
// https://crbug.com/916137.
TEST_P(RenderFrameHostManagerTestWithSiteIsolation,
NewProxyReceivesLoadingState) {
StartStopLoadingProxyObserver proxy_observer;
const GURL kUrl1("http://www.chromium.org");
const GURL kUrl2("http://www.google.com");
const GURL kUrl3("http://foo.com");
// Navigate main frame to |kUrl1| and commit, but don't simulate
// DidStopLoading. The main frame should still be considered loading at this
// point.
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl1, contents());
navigation->SetKeepLoading(true);
navigation->Commit();
FrameTreeNode* root = contents()->GetFrameTree()->root();
EXPECT_TRUE(root->IsLoading());
// Create a child frame.
TestRenderFrameHost* child_host = main_test_rfh()->AppendChild("subframe");
// Navigate the child cross-site. Main frame should still be loading after
// this point.
child_host = static_cast<TestRenderFrameHost*>(
NavigationSimulator::NavigateAndCommitFromDocument(kUrl2, child_host));
EXPECT_TRUE(root->IsLoading());
// Verify that parent and child are in different processes, and that there's
// a proxy for the main frame in the child frame's process.
ASSERT_NE(child_host->GetProcess(), main_test_rfh()->GetProcess());
RenderFrameProxyHost* proxy_to_child =
root->render_manager()->GetRenderFrameProxyHost(
child_host->GetSiteInstance());
ASSERT_TRUE(proxy_to_child);
ASSERT_EQ(proxy_to_child->GetProcess(), child_host->GetProcess());
base::RunLoop().RunUntilIdle();
// Since the main frame was loading at the time the main frame proxy was
// created in child frame's process, verify that we sent a separate IPC to
// update the proxy's loading state. Note that we'll create two proxies for
// the main frame and subframe, and we're interested in the message for
// the main frame proxy.
EXPECT_TRUE(proxy_observer.IsLoading(proxy_to_child));
// Simulate load stop in the main frame.
navigation->StopLoading();
// Navigate the child to a third site.
child_host = static_cast<TestRenderFrameHost*>(
NavigationSimulator::NavigateAndCommitFromDocument(kUrl3, child_host));
proxy_to_child = root->render_manager()->GetRenderFrameProxyHost(
child_host->GetSiteInstance());
ASSERT_TRUE(proxy_to_child);
ASSERT_EQ(proxy_to_child->GetProcess(), child_host->GetProcess());
base::RunLoop().RunUntilIdle();
// Since this time the main frame wasn't loading at the time |proxy_to_child|
// was created in the process for |kUrl3|, verify that we didn't send any
// extra IPCs to update that proxy's loading state.
EXPECT_FALSE(proxy_observer.IsLoading(proxy_to_child));
}
// Tests that a BeginNavigation IPC from a no longer active RFH in pending
// deletion state is ignored.
TEST_P(RenderFrameHostManagerTest,
BeginNavigationIgnoredWhenInPendingDeletion) {
// When a page enters the BackForwardCache, the RenderFrameHost is not
// deleted and is in BackForwardCache instead of being in pending deletion.
// Disabling to consider this scenario.
contents()->GetController().GetBackForwardCache().DisableForTesting(
BackForwardCache::TEST_ASSUMES_NO_CACHING);
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.
auto navigation_to_kUrl2 =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl2, contents());
navigation_to_kUrl2->set_drop_unload_ack(true);
navigation_to_kUrl2->Commit();
EXPECT_NE(initial_rfh, main_test_rfh());
ASSERT_FALSE(delete_observer.deleted());
EXPECT_NE(initial_rfh->lifecycle_state(),
RenderFrameHostImpl::LifecycleStateImpl::kActive);
EXPECT_TRUE(initial_rfh->IsPendingDeletion());
// 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());
}
// Run tests with BackForwardCache.
class RenderFrameHostManagerTestWithBackForwardCache
: public RenderFrameHostManagerTest,
public WebContentsDelegate {
public:
RenderFrameHostManagerTestWithBackForwardCache() {
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kBackForwardCache,
{
{"TimeToLiveInBackForwardCacheInSeconds", "3600"},
}}},
// Allow BackForwardCache for all devices regardless of their memory.
/*disabled_features=*/{features::kBackForwardCacheMemoryControls});
}
bool IsBackForwardCacheSupported() override { return true; }
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that a BeginNavigation IPC from a no longer active RFH in
// BackForwardCache is ignored. This test is a copy of
// "RenderFrameHostManagerTest.BeginNavigationIgnoredWhenInPendingDeletion" with
// BackForwardCache consideration.
TEST_P(RenderFrameHostManagerTestWithBackForwardCache,
BeginNavigationIgnoredWhenInBackForwardCache) {
const GURL kUrl1("http://www.google.com");
const GURL kUrl2("http://www.chromium.org");
const GURL kUrl3("http://foo.com");
contents()->SetDelegate(this);
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 in BackForwardCache.
auto navigation_to_kUrl2 =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl2, contents());
navigation_to_kUrl2->set_drop_unload_ack(true);
navigation_to_kUrl2->Commit();
EXPECT_NE(initial_rfh, main_test_rfh());
ASSERT_FALSE(delete_observer.deleted());
EXPECT_NE(initial_rfh->lifecycle_state(),
RenderFrameHostImpl::LifecycleStateImpl::kActive);
EXPECT_TRUE(initial_rfh->IsInBackForwardCache());
// The initial RFH receives a BeginNavigation IPC. The navigation should not
// start as initial RFH is not active.
auto navigation_to_kUrl3 =
NavigationSimulator::CreateRendererInitiated(kUrl3, initial_rfh);
navigation_to_kUrl3->Start();
EXPECT_FALSE(main_test_rfh()->frame_tree_node()->navigation_request());
}
// Check that after a navigation, the final SiteInstance has the correct
// original URL that was used to determine its site URL.
TEST_P(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, /* requires_dedicated_process */ true);
ContentBrowserClient* regular_client =
SetBrowserClientForTesting(&modified_client);
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kFooUrl);
scoped_refptr<SiteInstanceImpl> initial_instance =
main_test_rfh()->GetSiteInstance();
SiteInfo foo_site_info = SiteInfo::CreateForTesting(
initial_instance->GetIsolationContext(), kFooUrl);
if (AreDefaultSiteInstancesEnabled()) {
EXPECT_TRUE(initial_instance->IsDefaultSiteInstance());
} else {
EXPECT_FALSE(initial_instance->IsDefaultSiteInstance());
EXPECT_EQ(kFooUrl, initial_instance->original_url());
EXPECT_EQ(foo_site_info, initial_instance->GetSiteInfo());
}
// 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 a SiteInfo
// that's based on it.
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());
SiteInfo expected_site_info = SiteInfo::CreateForTesting(
main_test_rfh()->GetSiteInstance()->GetIsolationContext(), kOriginalUrl);
EXPECT_EQ(expected_site_info,
main_test_rfh()->GetSiteInstance()->GetSiteInfo());
EXPECT_NE(foo_site_info, main_test_rfh()->GetSiteInstance()->GetSiteInfo());
SetBrowserClientForTesting(regular_client);
}
class AdTaggingSimulator : public WebContentsObserver {
public:
explicit AdTaggingSimulator(const std::set<GURL>& ad_urls,
WebContents* web_contents)
: WebContentsObserver(web_contents), ad_urls_(ad_urls) {}
void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override {
auto it = ad_urls_.find(navigation_handle->GetURL());
navigation_handle->GetRenderFrameHost()->UpdateIsAdSubframe(it !=
ad_urls_.end());
}
void SimulateOnFrameIsAdSubframe(RenderFrameHost* rfh) {
rfh->UpdateIsAdSubframe(true);
}
private:
std::set<GURL> ad_urls_;
};
class AdStatusInterceptingRemoteFrame : public content::FakeRemoteFrame {
public:
void SetReplicatedIsAdSubframe(bool is_ad_subframe) override {
is_ad_subframe_ = is_ad_subframe;
}
// These methods reset state back to default when they are called.
bool LastAdSubframe() {
bool is_ad_subframe = is_ad_subframe_;
is_ad_subframe_ = false;
return is_ad_subframe;
}
private:
bool is_ad_subframe_ = false;
};
class RenderFrameHostManagerAdTaggingSignalTest
: public RenderFrameHostManagerTest,
public RenderFrameProxyHost::TestObserver {
public:
RenderFrameHostManagerAdTaggingSignalTest() {
RenderFrameProxyHost::SetObserverForTesting(this);
}
~RenderFrameHostManagerAdTaggingSignalTest() override {
RenderFrameProxyHost::SetObserverForTesting(nullptr);
}
void OnCreated(RenderFrameProxyHost* proxy_host) override {
auto fake_remote_frame =
std::make_unique<AdStatusInterceptingRemoteFrame>();
fake_remote_frame->Init(proxy_host->GetRemoteAssociatedInterfacesTesting());
// TODO(yaoxia): when a proxy host is deleted, remove the corresponding map
// entry.
proxy_map_[proxy_host] = std::move(fake_remote_frame);
if (proxy_host->frame_tree_node()
->current_replication_state()
.is_ad_subframe) {
ad_frames_on_proxy_created_.insert(proxy_host);
}
}
void ExpectAdSubframeSignalForFrameProxy(RenderFrameProxyHost* proxy_host,
bool expect_is_ad_subframe) {
base::RunLoop().RunUntilIdle();
auto it = proxy_map_.find(proxy_host);
EXPECT_TRUE(it != proxy_map_.end());
AdStatusInterceptingRemoteFrame* remote_frame = it->second.get();
EXPECT_EQ(expect_is_ad_subframe, remote_frame->LastAdSubframe());
}
void ExpectAdStatusOnFrameProxyCreated(RenderFrameProxyHost* proxy_host) {
EXPECT_TRUE(ad_frames_on_proxy_created_.find(proxy_host) !=
ad_frames_on_proxy_created_.end());
}
void AppendChildToFrame(const std::string& frame_name,
const GURL& url,
RenderFrameHost* rfh) {
RenderFrameHost* subframe_host =
RenderFrameHostTester::For(rfh)->AppendChild(frame_name);
if (url.is_valid())
NavigationSimulator::NavigateAndCommitFromDocument(url, subframe_host);
}
RenderFrameProxyHost* GetProxyHost(FrameTreeNode* proxy_node,
FrameTreeNode* proxy_to_node) {
return proxy_node->render_manager()->GetRenderFrameProxyHost(
proxy_to_node->current_frame_host()->GetSiteInstance());
}
private:
// The set of proxies that when created, the replication state of that frame
// indicates it's an ad.
std::set<RenderFrameProxyHost*> ad_frames_on_proxy_created_;
std::map<RenderFrameProxyHost*,
std::unique_ptr<AdStatusInterceptingRemoteFrame>>
proxy_map_;
};
// Test that when the proxy host is created for the local child frame to be
// swapped out (which occurs before UnfreezableFrameMsg_SwapOut IPC was sent),
// the frame replication state should already have the ad status set.
TEST_P(RenderFrameHostManagerAdTaggingSignalTest,
AdStatusForLocalChildFrameToBeSwappedOut) {
if (!AreAllSitesIsolatedForTesting())
return;
const GURL kUrlA("http://a.com/");
const GURL kUrlB("http://b.com/");
std::set<GURL> ad_urls = {kUrlB};
AdTaggingSimulator ad_tagging_simulator(ad_urls, contents());
contents()->NavigateAndCommit(kUrlA);
AppendChildToFrame("name", kUrlB, web_contents()->GetMainFrame());
FrameTreeNode* subframe_node =
contents()->GetFrameTree()->root()->child_at(0);
ExpectAdStatusOnFrameProxyCreated(
subframe_node->render_manager()->GetProxyToParent());
EXPECT_TRUE(subframe_node->current_replication_state().is_ad_subframe);
}
// A page with top frame A that has subframes B and A1. A1 is an ad iframe that
// does not commit. We expect that the proxy of A1 in B's process will receive
// an ad tagging signal.
TEST_P(RenderFrameHostManagerAdTaggingSignalTest,
AdTagSignalForFrameProxyOfFrameThatDoesNotCommit) {
if (!AreAllSitesIsolatedForTesting())
return;
const GURL kUrlA("http://a.com/");
const GURL kUrlB("http://b.com/");
AdTaggingSimulator ad_tagging_simulator({}, contents());
contents()->NavigateAndCommit(kUrlA);
EXPECT_FALSE(contents()
->GetFrameTree()
->root()
->current_replication_state()
.is_ad_subframe);
AppendChildToFrame("subframe_b", kUrlB, web_contents()->GetMainFrame());
AppendChildToFrame("subframe_a1", GURL(), web_contents()->GetMainFrame());
FrameTreeNode* top_frame_node_a = contents()->GetFrameTree()->root();
FrameTreeNode* subframe_node_b = top_frame_node_a->child_at(0);
FrameTreeNode* subframe_node_a1 = top_frame_node_a->child_at(1);
ad_tagging_simulator.SimulateOnFrameIsAdSubframe(
subframe_node_a1->current_frame_host());
RenderFrameProxyHost* proxy_a1_to_b =
GetProxyHost(subframe_node_a1, subframe_node_b);
EXPECT_TRUE(subframe_node_a1->current_replication_state().is_ad_subframe);
ExpectAdSubframeSignalForFrameProxy(proxy_a1_to_b, true);
}
// A page with top frame A that has subframes B and C. C is then navigated to an
// ad frame D. We expect that both the proxy of D in A's process and the proxy
// of D in B's process will receive an ad tagging signal.
TEST_P(RenderFrameHostManagerAdTaggingSignalTest,
AdTagSignalForFrameProxyOfNewFrame) {
if (!AreAllSitesIsolatedForTesting())
return;
const GURL kUrlA("http://a.com/");
const GURL kUrlB("http://b.com/");
const GURL kUrlC("http://c.com/");
const GURL kUrlD("http://d.com/");
std::set<GURL> ad_urls = {kUrlD};
AdTaggingSimulator ad_tagging_simulator(ad_urls, contents());
contents()->NavigateAndCommit(kUrlA);
EXPECT_FALSE(contents()
->GetFrameTree()
->root()
->current_replication_state()
.is_ad_subframe);
AppendChildToFrame("subframe_b", kUrlB, web_contents()->GetMainFrame());
AppendChildToFrame("subframe_c", kUrlC, web_contents()->GetMainFrame());
FrameTreeNode* top_frame_node_a = contents()->GetFrameTree()->root();
FrameTreeNode* subframe_node_b = top_frame_node_a->child_at(0);
FrameTreeNode* subframe_node_c = top_frame_node_a->child_at(1);
EXPECT_FALSE(subframe_node_b->current_replication_state().is_ad_subframe);
EXPECT_FALSE(subframe_node_c->current_replication_state().is_ad_subframe);
RenderFrameProxyHost* proxy_c_to_a =
GetProxyHost(subframe_node_c, top_frame_node_a);
RenderFrameProxyHost* proxy_c_to_b =
GetProxyHost(subframe_node_c, subframe_node_b);
RenderFrameProxyHost* proxy_b_to_a =
GetProxyHost(subframe_node_b, top_frame_node_a);
RenderFrameProxyHost* proxy_b_to_c =
GetProxyHost(subframe_node_b, subframe_node_c);
RenderFrameProxyHost* proxy_a_to_b =
GetProxyHost(top_frame_node_a, subframe_node_b);
RenderFrameProxyHost* proxy_a_to_c =
GetProxyHost(top_frame_node_a, subframe_node_c);
NavigationSimulator::NavigateAndCommitFromDocument(
kUrlD, subframe_node_c->current_frame_host());
EXPECT_TRUE(subframe_node_c->current_replication_state().is_ad_subframe);
ExpectAdSubframeSignalForFrameProxy(proxy_c_to_a, true);
ExpectAdSubframeSignalForFrameProxy(proxy_c_to_b, true);
ExpectAdSubframeSignalForFrameProxy(proxy_b_to_a, false);
ExpectAdSubframeSignalForFrameProxy(proxy_b_to_c, false);
ExpectAdSubframeSignalForFrameProxy(proxy_a_to_b, false);
ExpectAdSubframeSignalForFrameProxy(proxy_a_to_c, false);
}
// A page with top frame A that has an ad subframe B. Frame C is then created in
// A. We expect that when the proxy host of B in C's process is created, the
// frame's replication status already has the ad bit set, which will be
// propagated to the renderer side later.
TEST_P(RenderFrameHostManagerAdTaggingSignalTest,
AdStatusForFrameProxyOfExistingFrameToNewFrame) {
if (!AreAllSitesIsolatedForTesting())
return;
const GURL kUrlA("http://a.com/");
const GURL kUrlB("http://b.com/");
const GURL kUrlC("http://c.com/");
std::set<GURL> ad_urls = {kUrlB};
AdTaggingSimulator ad_tagging_simulator(ad_urls, contents());
contents()->NavigateAndCommit(kUrlA);
AppendChildToFrame("subframe_b", kUrlB, web_contents()->GetMainFrame());
AppendChildToFrame("subframe_c", kUrlC, web_contents()->GetMainFrame());
FrameTreeNode* subframe_node_b =
contents()->GetFrameTree()->root()->child_at(0);
FrameTreeNode* subframe_node_c =
contents()->GetFrameTree()->root()->child_at(1);
RenderFrameProxyHost* proxy_b_to_c =
GetProxyHost(subframe_node_b, subframe_node_c);
ExpectAdStatusOnFrameProxyCreated(proxy_b_to_c);
}
// Test a A(B(C)) setup where B and C are ads. The creation of C will trigger
// an ad tagging signal for the proxy of C in the process of A.
TEST_P(RenderFrameHostManagerAdTaggingSignalTest, RemoteGrandchildAdTagSignal) {
if (!AreAllSitesIsolatedForTesting())
return;
const GURL kUrlA("http://a.com/");
const GURL kUrlB("http://b.com/");
const GURL kUrlC("http://c.com/");
std::set<GURL> ad_urls = {kUrlB, kUrlC};
AdTaggingSimulator ad_tagging_simulator(ad_urls, contents());
contents()->NavigateAndCommit(kUrlA);
RenderFrameHost* subframe_host =
RenderFrameHostTester::For(web_contents()->GetMainFrame())
->AppendChild("subframe_name");
auto navigation_simulator =
NavigationSimulator::CreateRendererInitiated(kUrlB, subframe_host);
navigation_simulator->Start();
navigation_simulator->Commit();
RenderFrameHost* grandchild_host =
RenderFrameHostTester::For(
navigation_simulator->GetFinalRenderFrameHost())
->AppendChild("subframe_name");
FrameTreeNode* top_frame_node = contents()->GetFrameTree()->root();
FrameTreeNode* subframe_node = top_frame_node->child_at(0);
FrameTreeNode* grandchild_node = subframe_node->child_at(0);
RenderFrameProxyHost* proxy_to_main_frame =
GetProxyHost(grandchild_node, top_frame_node);
NavigationSimulator::NavigateAndCommitFromDocument(kUrlC, grandchild_host);
EXPECT_TRUE(subframe_node->current_replication_state().is_ad_subframe);
EXPECT_TRUE(grandchild_node->current_replication_state().is_ad_subframe);
ExpectAdSubframeSignalForFrameProxy(proxy_to_main_frame, true);
}
INSTANTIATE_TEST_SUITE_P(All,
RenderFrameHostManagerTest,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
RenderFrameHostManagerTestWithSiteIsolation,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
RenderFrameHostManagerAdTaggingSignalTest,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
RenderFrameHostManagerTestWithBackForwardCache,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
} // namespace content