blob: cd03646689b25d829efd0400a6ed5f4b685b12c4 [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 <stddef.h>
#include <stdint.h>
#include <functional>
#include <memory>
#include <set>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/thread_annotations.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/frame_host/navigation_handle_impl.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/frame_host/render_frame_proxy_host.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/webui/web_ui_controller_factory_registry.h"
#include "content/browser/webui/web_ui_impl.h"
#include "content/common/content_constants_internal.h"
#include "content/public/browser/browser_child_process_host.h"
#include "content/public/browser/child_process_launcher_utils.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/navigation_policy.h"
#include "content/public/common/page_state.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/web_preferences.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_navigation_throttle.h"
#include "content/public/test/test_navigation_throttle_inserter.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/did_commit_navigation_interceptor.h"
#include "content/test/test_content_browser_client.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "net/test/url_request/url_request_failed_job.h"
#include "services/network/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
using base::ASCIIToUTF16;
namespace content {
namespace {
const char kOpenUrlViaClickTargetFunc[] =
"(function(url) {\n"
" var lnk = document.createElement(\"a\");\n"
" lnk.href = url;\n"
" lnk.target = \"_blank\";\n"
" document.body.appendChild(lnk);\n"
" lnk.click();\n"
"})";
// Adds a link with given url and target=_blank, and clicks on it.
void OpenUrlViaClickTarget(const ToRenderFrameHost& adapter, const GURL& url) {
EXPECT_TRUE(ExecuteScript(adapter,
std::string(kOpenUrlViaClickTargetFunc) + "(\"" + url.spec() + "\");"));
}
class TestWebUIMessageHandler : public WebUIMessageHandler {
public:
using WebUIMessageHandler::AllowJavascript;
using WebUIMessageHandler::IsJavascriptAllowed;
protected:
void RegisterMessages() override {}
};
// This class implements waiting for RenderFrameHost destruction. It relies on
// the fact that RenderFrameDeleted event is fired when RenderFrameHost is
// destroyed.
// Note: RenderFrameDeleted is also fired when the process associated with the
// RenderFrameHost crashes, so this cannot be used in cases where process dying
// is expected.
class RenderFrameHostDestructionObserver : public WebContentsObserver {
public:
explicit RenderFrameHostDestructionObserver(RenderFrameHost* rfh)
: WebContentsObserver(WebContents::FromRenderFrameHost(rfh)),
message_loop_runner_(new MessageLoopRunner),
deleted_(false),
render_frame_host_(rfh) {}
~RenderFrameHostDestructionObserver() override {}
bool deleted() const { return deleted_; }
void Wait() {
if (deleted_)
return;
message_loop_runner_->Run();
}
// WebContentsObserver implementation:
void RenderFrameDeleted(RenderFrameHost* rfh) override {
if (rfh == render_frame_host_) {
CHECK(!deleted_);
deleted_ = true;
}
if (deleted_ && message_loop_runner_->loop_running()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, message_loop_runner_->QuitClosure());
}
}
private:
scoped_refptr<MessageLoopRunner> message_loop_runner_;
bool deleted_;
RenderFrameHost* render_frame_host_;
};
// A NavigationThrottle implementation that blocks all outgoing navigation
// requests for a specific WebContents. It is used to block navigations to
// WebUI URLs in the following test.
class RequestBlockingNavigationThrottle : public NavigationThrottle {
public:
explicit RequestBlockingNavigationThrottle(NavigationHandle* handle)
: NavigationThrottle(handle) {}
static std::unique_ptr<NavigationThrottle> Create(NavigationHandle* handle) {
return std::make_unique<RequestBlockingNavigationThrottle>(handle);
}
private:
ThrottleCheckResult WillStartRequest() override {
return NavigationThrottle::BLOCK_REQUEST;
}
const char* GetNameForLogging() override {
return "RequestBlockingNavigationThrottle";
}
DISALLOW_COPY_AND_ASSIGN(RequestBlockingNavigationThrottle);
};
// Helper function for error page navigations that makes sure that the last
// committed origin on |node| is an opaque origin with a precursor that matches
// |url|'s origin.
// Returns true if the frame has an opaque origin with the expected precursor
// information. Otherwise returns false.
bool IsOriginOpaqueAndCompatibleWithURL(FrameTreeNode* node, const GURL& url) {
url::Origin frame_origin =
node->current_frame_host()->GetLastCommittedOrigin();
if (!frame_origin.opaque()) {
LOG(ERROR) << "Frame origin was not opaque. " << frame_origin;
return false;
}
const GURL url_origin = url.GetOrigin();
const GURL precursor_origin =
frame_origin.GetTupleOrPrecursorTupleIfOpaque().GetURL();
if (url_origin != precursor_origin) {
LOG(ERROR) << "url_origin '" << url_origin << "' != precursor_origin '"
<< precursor_origin << "'";
return false;
}
return true;
}
bool IsMainFrameOriginOpaqueAndCompatibleWithURL(Shell* shell,
const GURL& url) {
return IsOriginOpaqueAndCompatibleWithURL(
static_cast<WebContentsImpl*>(shell->web_contents())
->GetFrameTree()
->root(),
url);
}
} // anonymous namespace
class RenderFrameHostManagerTest : public ContentBrowserTest {
public:
RenderFrameHostManagerTest() : foo_com_("foo.com") {
replace_host_.SetHostStr(foo_com_);
}
void SetUpCommandLine(base::CommandLine* command_line) override {
const char kBlinkPageLifecycleFeature[] = "PageLifecycle";
command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
kBlinkPageLifecycleFeature);
}
void SetUpOnMainThread() override {
// Support multiple sites on the test server.
host_resolver()->AddRule("*", "127.0.0.1");
}
void DisableBackForwardCache(
BackForwardCache::DisableForTestingReason reason) const {
return static_cast<WebContentsImpl*>(shell()->web_contents())
->GetController()
.back_forward_cache()
.DisableForTesting(reason);
}
void StartServer() {
ASSERT_TRUE(embedded_test_server()->Start());
foo_host_port_ = embedded_test_server()->host_port_pair();
foo_host_port_.set_host(foo_com_);
}
void StartEmbeddedServer() {
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
}
std::unique_ptr<content::URLLoaderInterceptor> SetupRequestFailForURL(
const GURL& url) {
return URLLoaderInterceptor::SetupRequestFailForURL(url,
net::ERR_DNS_TIMED_OUT);
}
// Returns a URL on foo.com with the given path.
GURL GetCrossSiteURL(const std::string& path) {
GURL cross_site_url(embedded_test_server()->GetURL(path));
return cross_site_url.ReplaceComponents(replace_host_);
}
void NavigateToPageWithLinks(Shell* shell) {
EXPECT_TRUE(NavigateToURL(
shell, embedded_test_server()->GetURL("/click-noreferrer-links.html")));
// Rewrite selected links on the page to be actual cross-site (bar.com)
// URLs. This does not use the /cross-site/ redirector, since that creates
// links that initially look same-site.
std::string script = "setOriginForLinks('http://bar.com:" +
embedded_test_server()->base_url().port() + "/');";
EXPECT_TRUE(ExecuteScript(shell, script));
}
protected:
std::string foo_com_;
GURL::Replacements replace_host_;
net::HostPortPair foo_host_port_;
};
// Web pages should not have script access to the swapped out page.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest, NoScriptAccessAfterSwapOut) {
StartEmbeddedServer();
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Open a same-site link in a new window.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickSameSiteTargetedLink());",
&success));
EXPECT_TRUE(success);
Shell* new_shell = new_shell_observer.GetShell();
// Wait for the navigation in the new window to finish, if it hasn't.
WaitForLoadStop(new_shell->web_contents());
EXPECT_EQ("/navigate_opener.html",
new_shell->web_contents()->GetLastCommittedURL().path());
// Should have the same SiteInstance.
EXPECT_EQ(orig_site_instance, new_shell->web_contents()->GetSiteInstance());
// We should have access to the opened window's location.
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(testScriptAccessToWindow());",
&success));
EXPECT_TRUE(success);
// Now navigate the new window to a different site.
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(
new_shell, embedded_test_server()->GetURL("foo.com", "/title1.html")));
scoped_refptr<SiteInstance> new_site_instance(
new_shell->web_contents()->GetSiteInstance());
if (AreDefaultSiteInstancesEnabled()) {
EXPECT_EQ(orig_site_instance, new_site_instance);
} else {
EXPECT_NE(orig_site_instance, new_site_instance);
}
// We should no longer have script access to the opened window's location.
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(testScriptAccessToWindow());",
&success));
EXPECT_FALSE(success);
// We now navigate the window to an about:blank page.
TestNavigationObserver navigation_observer(new_shell->web_contents());
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(), "window.domAutomationController.send(clickBlankTargetedLink());",
&success));
EXPECT_TRUE(success);
// Wait for the navigation in the new window to finish.
navigation_observer.Wait();
GURL blank_url(url::kAboutBlankURL);
EXPECT_EQ(blank_url,
new_shell->web_contents()->GetLastCommittedURL());
EXPECT_EQ(orig_site_instance, new_shell->web_contents()->GetSiteInstance());
// We should have access to the opened window's location.
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(testScriptAccessToWindow());",
&success));
EXPECT_TRUE(success);
}
// Test for crbug.com/24447. Following a cross-site link with rel=noreferrer
// and target=_blank should create a new SiteInstance.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
SwapProcessWithRelNoreferrerAndTargetBlank) {
StartEmbeddedServer();
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Test clicking a rel=noreferrer + target=blank link.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickNoRefTargetBlankLink());",
&success));
EXPECT_TRUE(success);
// Wait for the window to open.
Shell* new_shell = new_shell_observer.GetShell();
EXPECT_EQ("/title2.html", new_shell->web_contents()->GetVisibleURL().path());
// Check that `window.opener` is not set.
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell, "window.domAutomationController.send(window.opener == null);",
&success));
EXPECT_TRUE(success);
// Wait for the cross-site transition in the new tab to finish.
WaitForLoadStop(new_shell->web_contents());
// Should have a new SiteInstance.
scoped_refptr<SiteInstance> noref_blank_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance, noref_blank_site_instance);
}
// Same as above, but for 'noopener'
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
SwapProcessWithRelNoopenerAndTargetBlank) {
StartEmbeddedServer();
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Test clicking a rel=noreferrer + target=blank link.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickNoOpenerTargetBlankLink());",
&success));
EXPECT_TRUE(success);
// Wait for the window to open.
Shell* new_shell = new_shell_observer.GetShell();
EXPECT_EQ("/title2.html", new_shell->web_contents()->GetVisibleURL().path());
// Check that `window.opener` is not set.
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell, "window.domAutomationController.send(window.opener == null);",
&success));
EXPECT_TRUE(success);
// Wait for the cross-site transition in the new tab to finish.
WaitForLoadStop(new_shell->web_contents());
// Check that the referrer is set correctly.
std::string expected_referrer =
embedded_test_server()->GetURL("/click-noreferrer-links.html").spec();
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell, "window.domAutomationController.send(document.referrer == '" +
expected_referrer + "');",
&success));
EXPECT_TRUE(success);
// Should have a new SiteInstance.
scoped_refptr<SiteInstance> noopener_blank_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance, noopener_blank_site_instance);
}
// 'noopener' also works from 'window.open'
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
SwapProcessWithWindowOpenAndNoopener) {
StartEmbeddedServer();
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get());
// Test opening a window with the 'noopener' feature.
ShellAddedObserver new_shell_observer;
bool hasWindowReference = true;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send("
" openWindowWithTargetAndFeatures('/title2.html', '', 'noopener')"
");",
&hasWindowReference));
// We should not get a reference to the opened window.
EXPECT_FALSE(hasWindowReference);
// Wait for the window to open.
Shell* new_shell = new_shell_observer.GetShell();
// Wait for the cross-site transition in the new tab to finish.
WaitForLoadStop(new_shell->web_contents());
EXPECT_EQ("/title2.html",
new_shell->web_contents()->GetLastCommittedURL().path());
// Check that `window.opener` is not set.
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell, "window.domAutomationController.send(window.opener == null);",
&success));
EXPECT_TRUE(success);
// Check that the referrer is set correctly.
std::string expected_referrer =
embedded_test_server()->GetURL("/click-noreferrer-links.html").spec();
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell, "window.domAutomationController.send(document.referrer == '" +
expected_referrer + "');",
&success));
EXPECT_TRUE(success);
// Should have a new SiteInstance.
scoped_refptr<SiteInstance> noopener_blank_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance, noopener_blank_site_instance);
}
// As of crbug.com/69267, we create a new BrowsingInstance (and SiteInstance)
// for rel=noreferrer links in new windows, even to same site pages and named
// targets.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
SwapProcessWithSameSiteRelNoreferrer) {
StartEmbeddedServer();
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Test clicking a same-site rel=noreferrer + target=foo link.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickSameSiteNoRefTargetedLink());",
&success));
EXPECT_TRUE(success);
// Wait for the window to open.
Shell* new_shell = new_shell_observer.GetShell();
// Opens in new window.
EXPECT_EQ("/title2.html", new_shell->web_contents()->GetVisibleURL().path());
// Check that `window.opener` is not set.
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell, "window.domAutomationController.send(window.opener == null);",
&success));
EXPECT_TRUE(success);
// Wait for the cross-site transition in the new tab to finish.
WaitForLoadStop(new_shell->web_contents());
// Should have a new SiteInstance (in a new BrowsingInstance).
scoped_refptr<SiteInstance> noref_blank_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance, noref_blank_site_instance);
}
// Same as above, but for 'noopener'
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
SwapProcessWithSameSiteRelNoopener) {
StartEmbeddedServer();
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Test clicking a same-site rel=noopener + target=foo link.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(shell(),
"window.domAutomationController.send("
"clickSameSiteNoOpenerTargetedLink())"
";",
&success));
EXPECT_TRUE(success);
// Wait for the window to open.
Shell* new_shell = new_shell_observer.GetShell();
// Opens in new window.
EXPECT_EQ("/title2.html", new_shell->web_contents()->GetVisibleURL().path());
// Check that `window.opener` is not set.
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell, "window.domAutomationController.send(window.opener == null);",
&success));
EXPECT_TRUE(success);
// Wait for the cross-site transition in the new tab to finish.
WaitForLoadStop(new_shell->web_contents());
// Should have a new SiteInstance (in a new BrowsingInstance).
scoped_refptr<SiteInstance> noref_blank_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance, noref_blank_site_instance);
}
// Test for crbug.com/24447. Following a cross-site link with just
// target=_blank should not create a new SiteInstance, unless we
// are running in --site-per-process mode.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
DontSwapProcessWithOnlyTargetBlank) {
StartEmbeddedServer();
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Test clicking a target=blank link.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(), "window.domAutomationController.send(clickTargetBlankLink());",
&success));
EXPECT_TRUE(success);
// Wait for the window to open.
Shell* new_shell = new_shell_observer.GetShell();
// Wait for the cross-site transition in the new tab to finish.
EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
EXPECT_EQ("/title2.html",
new_shell->web_contents()->GetLastCommittedURL().path());
// Should have the same SiteInstance unless we're in site-per-process mode.
scoped_refptr<SiteInstance> blank_site_instance(
new_shell->web_contents()->GetSiteInstance());
if (AreAllSitesIsolatedForTesting())
EXPECT_NE(orig_site_instance, blank_site_instance);
else
EXPECT_EQ(orig_site_instance, blank_site_instance);
}
// Test for crbug.com/24447. Following a cross-site link with rel=noreferrer
// and no target=_blank should not create a new SiteInstance.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
DontSwapProcessWithOnlyRelNoreferrer) {
StartEmbeddedServer();
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Test clicking a rel=noreferrer link.
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(), "window.domAutomationController.send(clickNoRefLink());",
&success));
EXPECT_TRUE(success);
// Wait for the cross-site transition in the current tab to finish.
WaitForLoadStop(shell()->web_contents());
// Opens in same window.
EXPECT_EQ(1u, Shell::windows().size());
EXPECT_EQ("/title2.html",
shell()->web_contents()->GetLastCommittedURL().path());
// Should have the same SiteInstance unless we're in site-per-process mode.
scoped_refptr<SiteInstance> noref_site_instance(
shell()->web_contents()->GetSiteInstance());
if (AreAllSitesIsolatedForTesting() ||
IsProactivelySwapBrowsingInstanceEnabled()) {
EXPECT_NE(orig_site_instance, noref_site_instance);
} else {
EXPECT_EQ(orig_site_instance, noref_site_instance);
}
}
// Same as above, but for 'noopener'
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
DontSwapProcessWithOnlyRelNoOpener) {
StartEmbeddedServer();
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Test clicking a rel=noreferrer link.
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(), "window.domAutomationController.send(clickNoRefLink());",
&success));
EXPECT_TRUE(success);
// Wait for the cross-site transition in the current tab to finish.
WaitForLoadStop(shell()->web_contents());
// Opens in same window.
EXPECT_EQ(1u, Shell::windows().size());
EXPECT_EQ("/title2.html",
shell()->web_contents()->GetLastCommittedURL().path());
// Should have the same SiteInstance unless we're in site-per-process mode.
scoped_refptr<SiteInstance> noref_site_instance(
shell()->web_contents()->GetSiteInstance());
if (AreAllSitesIsolatedForTesting() ||
IsProactivelySwapBrowsingInstanceEnabled()) {
EXPECT_NE(orig_site_instance, noref_site_instance);
} else {
EXPECT_EQ(orig_site_instance, noref_site_instance);
}
}
// Test for crbug.com/116192. Targeted links should still work after the
// named target window has swapped processes.
// Disabled Flaky test - crbug.com/859487
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
DISABLED_AllowTargetedNavigationsAfterSwap) {
StartEmbeddedServer();
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Test clicking a target=foo link.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickSameSiteTargetedLink());",
&success));
EXPECT_TRUE(success);
Shell* new_shell = new_shell_observer.GetShell();
// Wait for the navigation in the new tab to finish, if it hasn't.
WaitForLoadStop(new_shell->web_contents());
EXPECT_EQ("/navigate_opener.html",
new_shell->web_contents()->GetLastCommittedURL().path());
// Should have the same SiteInstance.
scoped_refptr<SiteInstance> blank_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_EQ(orig_site_instance, blank_site_instance);
// Now navigate the new tab to a different site.
GURL cross_site_url(
embedded_test_server()->GetURL("foo.com", "/title1.html"));
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, cross_site_url));
scoped_refptr<SiteInstance> new_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance, new_site_instance);
// Clicking the original link in the first tab should cause us to swap back.
TestNavigationObserver navigation_observer(new_shell->web_contents());
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickSameSiteTargetedLink());",
&success));
EXPECT_TRUE(success);
navigation_observer.Wait();
// Should have swapped back and shown the new window again.
scoped_refptr<SiteInstance> revisit_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_EQ(orig_site_instance, revisit_site_instance);
// If it navigates away to another process, the original window should
// still be able to close it (using a cross-process close message).
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, cross_site_url));
EXPECT_EQ(new_site_instance.get(),
new_shell->web_contents()->GetSiteInstance());
WebContentsDestroyedWatcher close_watcher(new_shell->web_contents());
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(), "window.domAutomationController.send(testCloseWindow());",
&success));
EXPECT_TRUE(success);
close_watcher.Wait();
}
// Test that setting the opener to null in a window affects cross-process
// navigations, including those to existing entries. http://crbug.com/156669.
// This test crashes under ThreadSanitizer, http://crbug.com/356758.
#if defined(THREAD_SANITIZER)
#define MAYBE_DisownOpener DISABLED_DisownOpener
#else
#define MAYBE_DisownOpener DisownOpener
#endif
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest, MAYBE_DisownOpener) {
StartEmbeddedServer();
if (AreDefaultSiteInstancesEnabled()) {
// Isolate "foo.com" so we are guaranteed to get a non-default
// SiteInstance for navigations to this origin.
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{"foo.com"});
}
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Test clicking a target=_blank link.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickSameSiteTargetBlankLink());",
&success));
EXPECT_TRUE(success);
Shell* new_shell = new_shell_observer.GetShell();
EXPECT_TRUE(new_shell->web_contents()->HasOpener());
// Wait for the navigation in the new tab to finish, if it hasn't.
WaitForLoadStop(new_shell->web_contents());
EXPECT_EQ("/title2.html",
new_shell->web_contents()->GetLastCommittedURL().path());
// Should have the same SiteInstance.
scoped_refptr<SiteInstance> blank_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_EQ(orig_site_instance, blank_site_instance);
// Now navigate the new tab to a different site.
GURL cross_site_url(
embedded_test_server()->GetURL("foo.com", "/title1.html"));
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, cross_site_url));
scoped_refptr<SiteInstance> new_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance, new_site_instance);
EXPECT_TRUE(new_shell->web_contents()->HasOpener());
// Now disown the opener.
EXPECT_TRUE(ExecuteScript(new_shell, "window.opener = null;"));
EXPECT_FALSE(new_shell->web_contents()->HasOpener());
// Go back and ensure the opener is still null.
{
TestNavigationObserver back_nav_load_observer(new_shell->web_contents());
new_shell->web_contents()->GetController().GoBack();
back_nav_load_observer.Wait();
}
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell, "window.domAutomationController.send(window.opener == null);",
&success));
EXPECT_TRUE(success);
EXPECT_FALSE(new_shell->web_contents()->HasOpener());
// Now navigate forward again (creating a new process) and check opener.
EXPECT_TRUE(NavigateToURL(new_shell, cross_site_url));
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell, "window.domAutomationController.send(window.opener == null);",
&success));
EXPECT_TRUE(success);
EXPECT_FALSE(new_shell->web_contents()->HasOpener());
}
// Test that subframes can disown their openers. http://crbug.com/225528.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest, DisownSubframeOpener) {
const GURL frame_url("data:text/html,<iframe name=\"foo\"></iframe>");
EXPECT_TRUE(NavigateToURL(shell(), frame_url));
// Give the frame an opener using window.open.
EXPECT_TRUE(ExecuteScript(shell(), "window.open('about:blank','foo');"));
// Check that the browser process updates the subframe's opener.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(root, root->child_at(0)->opener());
EXPECT_EQ(nullptr, root->child_at(0)->original_opener());
// Now disown the frame's opener. Shouldn't crash.
EXPECT_TRUE(ExecuteScript(shell(), "window.frames[0].opener = null;"));
// Check that the subframe's opener in the browser process is disowned.
EXPECT_EQ(nullptr, root->child_at(0)->opener());
EXPECT_EQ(nullptr, root->child_at(0)->original_opener());
}
// Check that window.name is preserved for top frames when they navigate
// cross-process. See https://crbug.com/504164.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
PreserveTopFrameWindowNameOnCrossProcessNavigations) {
StartEmbeddedServer();
if (AreDefaultSiteInstancesEnabled()) {
// Isolate "foo.com" so we are guaranteed it is placed in a different
// process.
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{"foo.com"});
}
GURL main_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Open a popup using window.open with a 'foo' window.name.
Shell* new_shell = OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo");
EXPECT_TRUE(new_shell);
// The window.name for the new popup should be "foo".
std::string name;
EXPECT_TRUE(ExecuteScriptAndExtractString(
new_shell, "window.domAutomationController.send(window.name);", &name));
EXPECT_EQ("foo", name);
// Now navigate the new tab to a different site.
GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title2.html"));
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, foo_url));
scoped_refptr<SiteInstance> new_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance->GetProcess(), new_site_instance->GetProcess());
// window.name should still be "foo".
name = "";
EXPECT_TRUE(ExecuteScriptAndExtractString(
new_shell, "window.domAutomationController.send(window.name);", &name));
EXPECT_EQ("foo", name);
// Open another popup from the 'foo' popup and navigate it cross-site.
Shell* new_shell2 = OpenPopup(new_shell, GURL(url::kAboutBlankURL), "bar");
EXPECT_TRUE(new_shell2);
GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title3.html"));
EXPECT_TRUE(NavigateToURLFromRenderer(new_shell2, bar_url));
// Check that the new popup's window.opener has name "foo", which verifies
// that new swapped-out RenderViews also propagate window.name. This has to
// be done via window.open, since window.name isn't readable cross-origin.
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell2,
"window.domAutomationController.send("
" window.opener === window.open('','foo'));",
&success));
EXPECT_TRUE(success);
}
// Test for crbug.com/99202. PostMessage calls should still work after
// navigating the source and target windows to different sites.
// Specifically:
// 1) Create 3 windows (opener, "foo", and _blank) and send "foo" cross-process.
// 2) Fail to post a message from "foo" to opener with the wrong target origin.
// 3) Post a message from "foo" to opener, which replies back to "foo".
// 4) Post a message from _blank to "foo".
// 5) Post a message from "foo" to a subframe of opener, which replies back.
// 6) Post a message from _blank to a subframe of "foo".
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
SupportCrossProcessPostMessage) {
StartEmbeddedServer();
if (AreDefaultSiteInstancesEnabled()) {
// Isolate "foo.com" so we are guaranteed it is placed in a different
// process.
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{"foo.com"});
}
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original SiteInstance and RVHM for later comparison.
WebContents* opener_contents = shell()->web_contents();
scoped_refptr<SiteInstance> orig_site_instance(
opener_contents->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
RenderFrameHostManager* opener_manager = static_cast<WebContentsImpl*>(
opener_contents)->GetRenderManagerForTesting();
// 1) Open two more windows, one named. These initially have openers but no
// reference to each other. We will later post a message between them.
// First, a named target=foo window.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
opener_contents,
"window.domAutomationController.send(clickSameSiteTargetedLink());",
&success));
EXPECT_TRUE(success);
Shell* new_shell = new_shell_observer.GetShell();
// Wait for the navigation in the new window to finish, if it hasn't, then
// send it to post_message.html on a different site.
WebContents* foo_contents = new_shell->web_contents();
WaitForLoadStop(foo_contents);
EXPECT_EQ("/navigate_opener.html",
foo_contents->GetLastCommittedURL().path());
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(
new_shell,
embedded_test_server()->GetURL("foo.com", "/post_message.html")));
scoped_refptr<SiteInstance> foo_site_instance(
foo_contents->GetSiteInstance());
EXPECT_NE(orig_site_instance, foo_site_instance);
// Second, a target=_blank window.
ShellAddedObserver new_shell_observer2;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickSameSiteTargetBlankLink());",
&success));
EXPECT_TRUE(success);
// Wait for the navigation in the new window to finish, if it hasn't, then
// send it to post_message.html on the original site.
Shell* new_shell2 = new_shell_observer2.GetShell();
WebContents* new_contents = new_shell2->web_contents();
WaitForLoadStop(new_contents);
EXPECT_EQ("/title2.html", new_contents->GetLastCommittedURL().path());
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(
new_shell2, embedded_test_server()->GetURL("/post_message.html")));
EXPECT_EQ(orig_site_instance.get(), new_contents->GetSiteInstance());
RenderFrameHostManager* new_manager =
static_cast<WebContentsImpl*>(new_contents)->GetRenderManagerForTesting();
// We now have three windows. The opener should have a swapped out RVH
// for the new SiteInstance, but the _blank window should not.
EXPECT_EQ(3u, Shell::windows().size());
EXPECT_TRUE(
opener_manager->GetSwappedOutRenderViewHost(foo_site_instance.get()));
EXPECT_FALSE(
new_manager->GetSwappedOutRenderViewHost(foo_site_instance.get()));
// 2) Fail to post a message from the foo window to the opener if the target
// origin is wrong. We won't see an error, but we can check for the right
// number of received messages below.
EXPECT_TRUE(ExecuteScriptAndExtractBool(
foo_contents,
"window.domAutomationController.send(postToOpener('msg',"
" 'http://google.com'));",
&success));
EXPECT_TRUE(success);
ASSERT_FALSE(
opener_manager->GetSwappedOutRenderViewHost(orig_site_instance.get()));
// 3) Post a message from the foo window to the opener. The opener will
// reply, causing the foo window to update its own title.
base::string16 expected_title = ASCIIToUTF16("msg");
TitleWatcher title_watcher(foo_contents, expected_title);
EXPECT_TRUE(ExecuteScriptAndExtractBool(
foo_contents,
"window.domAutomationController.send(postToOpener('msg','*'));",
&success));
EXPECT_TRUE(success);
ASSERT_FALSE(
opener_manager->GetSwappedOutRenderViewHost(orig_site_instance.get()));
ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
// We should have received only 1 message in the opener and "foo" tabs,
// and updated the title.
int opener_received_messages = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
opener_contents,
"window.domAutomationController.send(window.receivedMessages);",
&opener_received_messages));
int foo_received_messages = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
foo_contents,
"window.domAutomationController.send(window.receivedMessages);",
&foo_received_messages));
EXPECT_EQ(1, foo_received_messages);
EXPECT_EQ(1, opener_received_messages);
EXPECT_EQ(ASCIIToUTF16("msg"), foo_contents->GetTitle());
// 4) Now post a message from the _blank window to the foo window. The
// foo window will update its title and will not reply.
expected_title = ASCIIToUTF16("msg2");
TitleWatcher title_watcher2(foo_contents, expected_title);
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_contents,
"window.domAutomationController.send(postToFoo('msg2'));",
&success));
EXPECT_TRUE(success);
ASSERT_EQ(expected_title, title_watcher2.WaitAndGetTitle());
// This postMessage should have created a swapped out RVH for the new
// SiteInstance in the target=_blank window.
EXPECT_TRUE(
new_manager->GetSwappedOutRenderViewHost(foo_site_instance.get()));
// TODO(nasko): Test subframe targeting of postMessage once
// http://crbug.com/153701 is fixed.
}
// Test for crbug.com/278336. MessagePorts should work cross-process. Messages
// which contain Transferables that need to be forwarded between processes via
// RenderFrameProxy::willCheckAndDispatchMessageEvent should work.
// Specifically:
// 1) Create 2 windows (opener and "foo") and send "foo" cross-process.
// 2) Post a message containing a message port from opener to "foo".
// 3) Post a message from "foo" back to opener via the passed message port.
// The test will be enabled when the feature implementation lands.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
SupportCrossProcessPostMessageWithMessagePort) {
StartEmbeddedServer();
if (AreDefaultSiteInstancesEnabled()) {
// Isolate "foo.com" so we are guaranteed it is placed in a different
// process.
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{"foo.com"});
}
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original SiteInstance and RVHM for later comparison.
WebContents* opener_contents = shell()->web_contents();
scoped_refptr<SiteInstance> orig_site_instance(
opener_contents->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
RenderFrameHostManager* opener_manager = static_cast<WebContentsImpl*>(
opener_contents)->GetRenderManagerForTesting();
// 1) Open a named target=foo window. We will later post a message between the
// opener and the new window.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
opener_contents,
"window.domAutomationController.send(clickSameSiteTargetedLink());",
&success));
EXPECT_TRUE(success);
Shell* new_shell = new_shell_observer.GetShell();
// Wait for the navigation in the new window to finish, if it hasn't, then
// send it to post_message.html on a different site.
WebContents* foo_contents = new_shell->web_contents();
WaitForLoadStop(foo_contents);
EXPECT_EQ("/navigate_opener.html",
foo_contents->GetLastCommittedURL().path());
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(
new_shell,
embedded_test_server()->GetURL("foo.com", "/post_message.html")));
scoped_refptr<SiteInstance> foo_site_instance(
foo_contents->GetSiteInstance());
EXPECT_NE(orig_site_instance, foo_site_instance);
// We now have two windows. The opener should have a swapped out RVH
// for the new SiteInstance.
EXPECT_EQ(2u, Shell::windows().size());
EXPECT_TRUE(
opener_manager->GetSwappedOutRenderViewHost(foo_site_instance.get()));
// 2) Post a message containing a MessagePort from opener to the the foo
// window. The foo window will reply via the passed port, causing the opener
// to update its own title.
base::string16 expected_title = ASCIIToUTF16("msg-back-via-port");
TitleWatcher title_observer(opener_contents, expected_title);
EXPECT_TRUE(ExecuteScriptAndExtractBool(
opener_contents,
"window.domAutomationController.send(postWithPortToFoo());",
&success));
EXPECT_TRUE(success);
ASSERT_FALSE(
opener_manager->GetSwappedOutRenderViewHost(orig_site_instance.get()));
ASSERT_EQ(expected_title, title_observer.WaitAndGetTitle());
// Check message counts.
int opener_received_messages_via_port = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
opener_contents,
"window.domAutomationController.send(window.receivedMessagesViaPort);",
&opener_received_messages_via_port));
int foo_received_messages = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
foo_contents,
"window.domAutomationController.send(window.receivedMessages);",
&foo_received_messages));
int foo_received_messages_with_port = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
foo_contents,
"window.domAutomationController.send(window.receivedMessagesWithPort);",
&foo_received_messages_with_port));
EXPECT_EQ(1, foo_received_messages);
EXPECT_EQ(1, foo_received_messages_with_port);
EXPECT_EQ(1, opener_received_messages_via_port);
EXPECT_EQ(ASCIIToUTF16("msg-with-port"), foo_contents->GetTitle());
EXPECT_EQ(ASCIIToUTF16("msg-back-via-port"), opener_contents->GetTitle());
}
// Test for crbug.com/116192. Navigations to a window's opener should
// still work after a process swap.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
AllowTargetedNavigationsInOpenerAfterSwap) {
StartEmbeddedServer();
if (AreDefaultSiteInstancesEnabled()) {
// Isolate "foo.com" so we are guaranteed to get a non-default
// SiteInstance for navigations to this origin.
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{"foo.com"});
}
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original tab and SiteInstance for later comparison.
WebContents* orig_contents = shell()->web_contents();
scoped_refptr<SiteInstance> orig_site_instance(
orig_contents->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Test clicking a target=foo link.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
orig_contents,
"window.domAutomationController.send(clickSameSiteTargetedLink());",
&success));
EXPECT_TRUE(success);
Shell* new_shell = new_shell_observer.GetShell();
// Wait for the navigation in the new window to finish, if it hasn't.
WaitForLoadStop(new_shell->web_contents());
EXPECT_EQ("/navigate_opener.html",
new_shell->web_contents()->GetLastCommittedURL().path());
// Should have the same SiteInstance.
scoped_refptr<SiteInstance> blank_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_EQ(orig_site_instance, blank_site_instance);
// Now navigate the original (opener) tab to a different site.
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(
shell(), embedded_test_server()->GetURL("foo.com", "/title1.html")));
scoped_refptr<SiteInstance> new_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance, new_site_instance);
// The opened tab should be able to navigate the opener back to its process.
TestNavigationObserver navigation_observer(orig_contents);
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell, "window.domAutomationController.send(navigateOpener());",
&success));
EXPECT_TRUE(success);
navigation_observer.Wait();
// Should have swapped back into this process.
scoped_refptr<SiteInstance> revisit_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_EQ(orig_site_instance, revisit_site_instance);
}
// Test that subframes do not crash when sending a postMessage to the top frame
// from an unload handler while the top frame is being swapped out as part of
// navigating cross-process. https://crbug.com/475651.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
PostMessageFromSubframeUnloadHandler) {
StartEmbeddedServer();
GURL frame_url(embedded_test_server()->GetURL("/post_message.html"));
GURL main_url("data:text/html,<iframe name='foo' src='" + frame_url.spec() +
"'></iframe>");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_NE(nullptr, orig_site_instance.get());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
EXPECT_EQ(frame_url, root->child_at(0)->current_url());
// Register an unload handler that sends a postMessage to the top frame.
EXPECT_TRUE(ExecuteScript(root->child_at(0), "registerUnload();"));
// Navigate the top frame cross-site. This will cause the top frame to be
// swapped out and run unload handlers, and the original renderer process
// should then terminate since it's not rendering any other frames.
RenderProcessHostWatcher exit_observer(
root->current_frame_host()->GetProcess(),
RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("foo.com", "/title1.html")));
scoped_refptr<SiteInstance> new_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance, new_site_instance);
// Ensure that the original renderer process exited cleanly without crashing.
exit_observer.Wait();
EXPECT_TRUE(exit_observer.did_exit_normally());
}
// Test that opening a new window in the same SiteInstance and then navigating
// both windows to a different SiteInstance allows the first process to exit.
// See http://crbug.com/126333.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
ProcessExitWithSwappedOutViews) {
StartEmbeddedServer();
if (AreDefaultSiteInstancesEnabled()) {
// Isolate "foo.com" so we are guaranteed to get a non-default
// SiteInstance for navigations to this origin.
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{"foo.com"});
}
// Load a page with links that open in a new window.
NavigateToPageWithLinks(shell());
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Test clicking a target=foo link.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickSameSiteTargetedLink());",
&success));
EXPECT_TRUE(success);
Shell* new_shell = new_shell_observer.GetShell();
// Wait for the navigation in the new window to finish, if it hasn't.
WaitForLoadStop(new_shell->web_contents());
EXPECT_EQ("/navigate_opener.html",
new_shell->web_contents()->GetLastCommittedURL().path());
// Should have the same SiteInstance.
scoped_refptr<SiteInstance> opened_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_EQ(orig_site_instance, opened_site_instance);
// Now navigate the opened window to a different site.
GURL cross_site_url(
embedded_test_server()->GetURL("foo.com", "/title1.html"));
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, cross_site_url));
scoped_refptr<SiteInstance> new_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance, new_site_instance);
// The original process should still be alive, since it is still used in the
// first window.
RenderProcessHost* orig_process = orig_site_instance->GetProcess();
EXPECT_TRUE(orig_process->IsInitializedAndNotDead());
// Navigate the first window to a different site as well. The original
// process should exit, since all of its views are now swapped out.
RenderProcessHostWatcher exit_observer(
orig_process,
RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(shell(), cross_site_url));
exit_observer.Wait();
scoped_refptr<SiteInstance> new_site_instance2(
shell()->web_contents()->GetSiteInstance());
EXPECT_EQ(new_site_instance, new_site_instance2);
}
// Test for crbug.com/76666. A cross-site navigation that fails with a 204
// error should not make us ignore future renderer-initiated navigations.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest, ClickLinkAfter204Error) {
StartServer();
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(orig_site_instance.get() != nullptr);
// Load a cross-site page that fails with a 204 error.
EXPECT_TRUE(
NavigateToURLAndExpectNoCommit(shell(), GetCrossSiteURL("/nocontent")));
// We should still be looking at the normal page. Because we started from a
// blank new tab, the typed URL will still be visible until the user clears it
// manually. The last committed URL will be the previous page.
scoped_refptr<SiteInstance> post_nav_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_EQ(orig_site_instance, post_nav_site_instance);
EXPECT_EQ("/nocontent",
shell()->web_contents()->GetVisibleURL().path());
EXPECT_FALSE(
shell()->web_contents()->GetController().GetLastCommittedEntry());
// Renderer-initiated navigations should work.
base::string16 expected_title = ASCIIToUTF16("Title Of Awesomeness");
TitleWatcher title_watcher(shell()->web_contents(), expected_title);
GURL url = embedded_test_server()->GetURL("/title2.html");
EXPECT_TRUE(ExecuteScript(
shell(), base::StringPrintf("location.href = '%s'", url.spec().c_str())));
ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
// Opens in same tab.
EXPECT_EQ(1u, Shell::windows().size());
EXPECT_EQ("/title2.html",
shell()->web_contents()->GetLastCommittedURL().path());
// Should have the same SiteInstance.
scoped_refptr<SiteInstance> new_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_EQ(orig_site_instance, new_site_instance);
}
// A collection of tests to prevent URL spoofs when showing pending URLs above
// initial empty documents, ensuring that the URL reverts to about:blank if the
// document is accessed. See https://crbug.com/9682.
class RenderFrameHostManagerSpoofingTest : public RenderFrameHostManagerTest {
public:
void SetUpInProcessBrowserTestFixture() override {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
base::CommandLine new_command_line(command_line->GetProgram());
base::CommandLine::SwitchMap switches = command_line->GetSwitches();
// Injecting the DOM automation controller causes false positives, since it
// triggers the DidAccessInitialDocument() callback by mutating the global
// object.
switches.erase(switches::kDomAutomationController);
for (const auto& it : switches)
new_command_line.AppendSwitchNative(it.first, it.second);
*command_line = new_command_line;
}
protected:
// Custom ExecuteScript() helper that doesn't depend on DOM automation
// controller. This is used to guarantee the script has completed execution,
// but the spoofing tests synchronize execution using window title changes.
void ExecuteScript(const ToRenderFrameHost& adapter, const char* script) {
adapter.render_frame_host()->ExecuteJavaScriptForTests(
base::UTF8ToUTF16(script), base::NullCallback());
}
};
// Helper to wait until a WebContent's NavigationController has a visible entry.
class VisibleEntryWaiter : public WebContentsObserver {
public:
explicit VisibleEntryWaiter(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
void Wait() {
if (web_contents()->GetController().GetVisibleEntry())
return;
run_loop_.Run();
}
// WebContentsObserver overrides:
void DidStartNavigation(NavigationHandle* navigation_handle) override {
run_loop_.Quit();
}
private:
base::RunLoop run_loop_;
};
// Sanity test that a newly opened window shows the pending URL if the initial
// empty document is not modified. This is intentionally structured as similarly
// as possible to the subsequent ShowLoadingURLUntil*Spoof tests: it performs
// the same operations as the subsequent tests except DOM modification. This
// should help catch instances where the subsequent tests incorrectly pass due
// to a side effect of the test infrastructure.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerSpoofingTest,
ShowLoadingURLIfNotModified) {
ASSERT_TRUE(embedded_test_server()->Start());
// Load a page that can open a URL that won't commit in a new window.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/click-nocontent-link.html")));
WebContents* orig_contents = shell()->web_contents();
// Click a /nocontent link that opens in a new window but never commits.
ShellAddedObserver new_shell_observer;
ExecuteScript(orig_contents, "clickNoContentTargetedLink();");
// Wait for the window to open.
Shell* new_shell = new_shell_observer.GetShell();
WebContents* contents = new_shell->web_contents();
// Make sure the new window has started the provisional load, so the
// associated navigation controller will have a visible entry.
{
VisibleEntryWaiter waiter(contents);
waiter.Wait();
}
// Ensure the destination URL is visible, because it is considered the
// initial navigation.
EXPECT_TRUE(contents->GetController().IsInitialNavigation());
EXPECT_EQ("/nocontent",
contents->GetController().GetVisibleEntry()->GetURL().path());
// Now get another reference to the window object, but don't otherwise access
// it. This is to ensure that DidAccessInitialDocument() notifications are not
// incorrectly generated when nothing is modified.
base::string16 expected_title = ASCIIToUTF16("Modified Title");
TitleWatcher title_watcher(orig_contents, expected_title);
ExecuteScript(orig_contents, "getNewWindowReference();");
ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
// The destination URL should still be visible, since nothing was modified.
EXPECT_TRUE(contents->GetController().IsInitialNavigation());
EXPECT_EQ("/nocontent",
contents->GetController().GetVisibleEntry()->GetURL().path());
}
// Test for crbug.com/9682. We should show the URL for a pending renderer-
// initiated navigation in a new tab, until the content of the initial
// about:blank page is modified by another window. At that point, we should
// revert to showing about:blank to prevent a URL spoof.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerSpoofingTest,
ShowLoadingURLUntilSpoof) {
ASSERT_TRUE(embedded_test_server()->Start());
// Load a page that can open a URL that won't commit in a new window.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/click-nocontent-link.html")));
WebContents* orig_contents = shell()->web_contents();
// Change the link to be cross-site.
GURL target_url = embedded_test_server()->GetURL("foo.com", "/nocontent");
ExecuteScript(
orig_contents,
base::StringPrintf(
"document.getElementById('nocontent_targeted_link').href = '%s';",
target_url.spec().c_str())
.c_str());
// Click a /nocontent link that opens in a new window but never commits.
ShellAddedObserver new_shell_observer;
ExecuteScript(orig_contents, "clickNoContentTargetedLink();");
// Wait for the window to open.
Shell* new_shell = new_shell_observer.GetShell();
WebContents* contents = new_shell->web_contents();
// Make sure the new window has started the navigation, so the associated
// navigation controller will have a visible entry.
{
VisibleEntryWaiter waiter(contents);
waiter.Wait();
}
// Ensure the destination URL is visible, because it is considered the
// initial navigation.
EXPECT_TRUE(contents->GetController().IsInitialNavigation());
EXPECT_EQ(target_url, contents->GetController().GetVisibleEntry()->GetURL());
// Now modify the contents of the new window from the opener. This will also
// modify the title of the document to give us something to listen for.
base::string16 expected_title = ASCIIToUTF16("Modified Title");
TitleWatcher title_watcher(orig_contents, expected_title);
ExecuteScript(orig_contents, "modifyNewWindow();");
ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
// At this point, we should no longer be showing the destination URL.
// The visible entry should be null, resulting in about:blank in the address
// bar.
EXPECT_FALSE(contents->GetController().GetVisibleEntry());
}
// Same as ShowLoadingURLUntilSpoof, but reloads the new popup before modifying
// it, to test https://crbug.com/847718. The reload should not cause the
// visible entry to stick around after the modification, even though it is
// triggered in the browser process.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerSpoofingTest,
ShowLoadingURLUntilSpoofAfterReload) {
ASSERT_TRUE(embedded_test_server()->Start());
// Load a page that can open a URL that won't commit in a new window.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/click-nocontent-link.html")));
WebContents* orig_contents = shell()->web_contents();
// Change the link to be cross-site.
GURL target_url = embedded_test_server()->GetURL("foo.com", "/nocontent");
ExecuteScript(
orig_contents,
base::StringPrintf(
"document.getElementById('nocontent_targeted_link').href = '%s';",
target_url.spec().c_str())
.c_str());
// Click a /nocontent link that opens in a new window but never commits.
ShellAddedObserver new_shell_observer;
ExecuteScript(orig_contents, "clickNoContentTargetedLink();");
// Wait for the window to open.
Shell* new_shell = new_shell_observer.GetShell();
WebContents* contents = new_shell->web_contents();
// Make sure the new window has started the navigation, so the associated
// navigation controller will have a visible entry.
{
VisibleEntryWaiter waiter(contents);
waiter.Wait();
}
// Ensure the destination URL is visible, because it is considered the
// initial navigation.
EXPECT_TRUE(contents->GetController().IsInitialNavigation());
EXPECT_EQ(target_url, contents->GetController().GetVisibleEntry()->GetURL());
// Reload the popup before modifying it. See https://crbug.com/847718.
contents->GetController().Reload(ReloadType::NORMAL, false);
// Now modify the contents of the new window from the opener. This will also
// modify the title of the document to give us something to listen for.
base::string16 expected_title = ASCIIToUTF16("Modified Title");
TitleWatcher title_watcher(orig_contents, expected_title);
ExecuteScript(orig_contents, "modifyNewWindow();");
ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
// At this point, we should no longer be showing the destination URL.
// The visible entry should be null, resulting in about:blank in the address
// bar.
EXPECT_FALSE(contents->GetController().GetVisibleEntry());
}
// Similar but using document.open(): once a Document is opened, subsequent
// document.write() calls can insert arbitrary content into the target Document.
// Since this could result in URL spoofing, the pending URL should no longer be
// shown in the omnibox.
//
// Note: document.write() implicitly invokes document.open() if the Document has
// not already been opened, so there's no need to test document.write()
// separately.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerSpoofingTest,
ShowLoadingURLUntilDocumentOpenSpoof) {
ASSERT_TRUE(embedded_test_server()->Start());
// Load a page that can open a URL that won't commit in a new window.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/click-nocontent-link.html")));
WebContents* orig_contents = shell()->web_contents();
// Click a /nocontent link that opens in a new window but never commits.
ShellAddedObserver new_shell_observer;
ExecuteScript(orig_contents, "clickNoContentTargetedLink();");
// Wait for the window to open.
Shell* new_shell = new_shell_observer.GetShell();
WebContents* contents = new_shell->web_contents();
// Make sure the new window has started the provisional load, so the
// associated navigation controller will have a visible entry.
{
VisibleEntryWaiter waiter(contents);
waiter.Wait();
}
// Ensure the destination URL is visible, because it is considered the
// initial navigation.
EXPECT_TRUE(contents->GetController().IsInitialNavigation());
EXPECT_EQ("/nocontent",
contents->GetController().GetVisibleEntry()->GetURL().path());
// Now modify the contents of the new window from the opener. This will also
// modify the title of the document to give us something to listen for.
base::string16 expected_title = ASCIIToUTF16("Modified Title");
TitleWatcher title_watcher(orig_contents, expected_title);
ExecuteScript(orig_contents, "modifyNewWindowWithDocumentOpen();");
ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
// At this point, we should no longer be showing the destination URL.
// The visible entry should be null, resulting in about:blank in the address
// bar.
EXPECT_FALSE(contents->GetController().GetVisibleEntry());
}
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
WasDiscardedWhenNavigationInterruptsReload) {
EXPECT_TRUE(embedded_test_server()->Start());
GURL discarded_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), discarded_url));
// Discard the page.
shell()->web_contents()->SetWasDiscarded(true);
// Reload the discarded page, but pretend that it's slow to commit.
TestNavigationManager first_reload(shell()->web_contents(), discarded_url);
shell()->web_contents()->GetController().Reload(
ReloadType::ORIGINAL_REQUEST_URL, false);
EXPECT_TRUE(first_reload.WaitForRequestStart());
// Before the response is received, simulate user navigating to another URL.
GURL second_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
TestNavigationManager second_navigation(shell()->web_contents(), second_url);
shell()->LoadURL(second_url);
second_navigation.WaitForNavigationFinished();
const char kDiscardedStateJS[] =
"window.domAutomationController.send(window.document.wasDiscarded);";
bool discarded_result;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(shell(), kDiscardedStateJS,
&discarded_result));
EXPECT_FALSE(discarded_result);
}
// Ensures that a pending navigation's URL is no longer visible after the
// speculative RFH is discarded due to a concurrent renderer-initiated
// navigation. See https://crbug.com/760342.
// TODO(https://crbug.com/945194): Disabled due to flaky timeouts.
IN_PROC_BROWSER_TEST_F(
RenderFrameHostManagerTest,
DISABLED_ResetVisibleURLOnCrossProcessNavigationInterrupted) {
const std::string kVictimPath = "/victim.html";
const std::string kAttackPath = "/attack.html";
net::test_server::ControllableHttpResponse victim_response(
embedded_test_server(), kVictimPath);
net::test_server::ControllableHttpResponse attack_response(
embedded_test_server(), kAttackPath);
EXPECT_TRUE(embedded_test_server()->Start());
const GURL kVictimURL = embedded_test_server()->GetURL("a.com", kVictimPath);
const GURL kAttackURL = embedded_test_server()->GetURL("b.com", kAttackPath);
// First navigate to the attacker page. This page will be cross-site compared
// to the next navigations we will attempt.
const GURL kAttackInitialURL =
embedded_test_server()->GetURL("b.com", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), kAttackInitialURL));
EXPECT_EQ(kAttackInitialURL, shell()->web_contents()->GetVisibleURL());
// Now, start a browser-initiated cross-site navigation to a new page that
// will hang at ready to commit stage.
TestNavigationManager victim_navigation(shell()->web_contents(), kVictimURL);
shell()->LoadURL(kVictimURL);
EXPECT_TRUE(victim_navigation.WaitForRequestStart());
victim_navigation.ResumeNavigation();
victim_response.WaitForRequest();
victim_response.Send(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n");
EXPECT_TRUE(victim_navigation.WaitForResponse());
victim_navigation.ResumeNavigation();
// The navigation is ready to commit: it has been handed to the speculative
// RenderFrameHost for commit.
RenderFrameHostImpl* speculative_rfh =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root()
->render_manager()
->speculative_frame_host();
CHECK(speculative_rfh);
EXPECT_TRUE(speculative_rfh->is_loading());
// Since we have a browser-initiated pending navigation, the navigation URL is
// showing in the address bar.
EXPECT_EQ(kVictimURL, shell()->web_contents()->GetVisibleURL());
// The attacker page requests a navigation to a new document while the
// browser-initiated navigation hasn't committed yet.
TestNavigationManager attack_navigation(shell()->web_contents(), kAttackURL);
EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
shell()->web_contents(),
"location.href = \"" + kAttackURL.spec() + "\";"));
EXPECT_TRUE(attack_navigation.WaitForRequestStart());
// This deletes the speculative RenderFrameHost that was supposed to commit
// the browser-initiated navigation.
speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root()
->render_manager()
->speculative_frame_host();
EXPECT_FALSE(speculative_rfh);
// The URL of the browser-initiated navigation should no longer be shown in
// the address bar since the RenderFrameHost that was supposed to commit it
// has been discarded. Instead, we should be showing the URL of the current
// page as we do not show the URL of pending navigations when they are
// renderer-initiated.
EXPECT_NE(kVictimURL, shell()->web_contents()->GetVisibleURL());
EXPECT_EQ(kAttackInitialURL, shell()->web_contents()->GetVisibleURL());
// The attacker navigation results in a 204.
attack_navigation.ResumeNavigation();
attack_response.WaitForRequest();
attack_response.Send(
"HTTP/1.1 204 OK\r\n"
"Connection: close\r\n"
"\r\n");
attack_navigation.WaitForNavigationFinished();
speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root()
->render_manager()
->speculative_frame_host();
EXPECT_FALSE(speculative_rfh);
// We are still showing the URL of the current page.
EXPECT_EQ(kAttackInitialURL, shell()->web_contents()->GetVisibleURL());
}
// Ensures that deleting a speculative RenderFrameHost trying to commit a
// navigation to the pending NavigationEntry will not crash if it happens
// because a new navigation to the same pending NavigationEntry started. This is
// a regression test for crbug.com/796135.
IN_PROC_BROWSER_TEST_F(
RenderFrameHostManagerTest,
DeleteSpeculativeRFHPendingCommitOfPendingEntryOnInterrupted1) {
const std::string kOriginalPath = "/original.html";
const std::string kFirstRedirectPath = "/redirect1.html";
const std::string kSecondRedirectPath = "/reidrect2.html";
net::test_server::ControllableHttpResponse original_response1(
embedded_test_server(), kOriginalPath);
net::test_server::ControllableHttpResponse original_response2(
embedded_test_server(), kOriginalPath);
net::test_server::ControllableHttpResponse original_response3(
embedded_test_server(), kOriginalPath);
net::test_server::ControllableHttpResponse first_redirect_response(
embedded_test_server(), kFirstRedirectPath);
net::test_server::ControllableHttpResponse second_redirect_response(
embedded_test_server(), kSecondRedirectPath);
EXPECT_TRUE(embedded_test_server()->Start());
const GURL kOriginalURL =
embedded_test_server()->GetURL("a.com", kOriginalPath);
const GURL kFirstRedirectURL =
embedded_test_server()->GetURL("b.com", kFirstRedirectPath);
const GURL kSecondRedirectURL =
embedded_test_server()->GetURL("c.com", kSecondRedirectPath);
// First navigate to the initial URL. This page will have a cross-site
// redirect.
shell()->LoadURL(kOriginalURL);
original_response1.WaitForRequest();
original_response1.Send(
"HTTP/1.1 302 FOUND\r\n"
"Location: " +
kFirstRedirectURL.spec() +
"\r\n"
"\r\n");
original_response1.Done();
first_redirect_response.WaitForRequest();
first_redirect_response.Send(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n");
first_redirect_response.Send(
"<html>"
"<body></body>"
"</html>");
first_redirect_response.Done();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(kFirstRedirectURL, shell()->web_contents()->GetLastCommittedURL());
// Now reload the original request, but redirect to yet another site.
TestNavigationManager first_reload(shell()->web_contents(), kOriginalURL);
shell()->web_contents()->GetController().Reload(
ReloadType::ORIGINAL_REQUEST_URL, false);
EXPECT_TRUE(first_reload.WaitForRequestStart());
first_reload.ResumeNavigation();
original_response2.WaitForRequest();
original_response2.Send(
"HTTP/1.1 302 FOUND\r\n"
"Location: " +
kSecondRedirectURL.spec() +
"\r\n"
"\r\n");
original_response2.Done();
second_redirect_response.WaitForRequest();
second_redirect_response.Send(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n");
EXPECT_TRUE(first_reload.WaitForResponse());
first_reload.ResumeNavigation();
// The navigation is ready to commit: it has been handed to the speculative
// RenderFrameHost for commit if Site Isolation is enabled, otherwise it
// commits in the same RenderFrameHost.
RenderFrameHostImpl* speculative_rfh =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root()
->render_manager()
->speculative_frame_host();
if (AreAllSitesIsolatedForTesting()) {
CHECK(speculative_rfh);
} else {
CHECK(!speculative_rfh);
}
// The user requests a new reload while the previous reload hasn't committed
// yet. The navigation start deletes the speculative RenderFrameHost that was
// supposed to commit the browser-initiated navigation, unless Site Isolation
// is enabled. This should not crash.
TestNavigationManager second_reload(shell()->web_contents(), kOriginalURL);
shell()->web_contents()->GetController().Reload(
ReloadType::ORIGINAL_REQUEST_URL, false);
EXPECT_TRUE(second_reload.WaitForRequestStart());
speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root()
->render_manager()
->speculative_frame_host();
if (AreAllSitesIsolatedForTesting()) {
EXPECT_TRUE(speculative_rfh);
} else {
EXPECT_FALSE(speculative_rfh);
}
// The second reload results in a 204.
second_reload.ResumeNavigation();
original_response3.WaitForRequest();
original_response3.Send(
"HTTP/1.1 204 OK\r\n"
"Connection: close\r\n"
"\r\n");
second_reload.WaitForNavigationFinished();
speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root()
->render_manager()
->speculative_frame_host();
EXPECT_FALSE(speculative_rfh);
}
// Ensures that deleting a speculative RenderFrameHost trying to commit a
// navigation to the pending NavigationEntry will not crash if it happens
// because a new navigation to the same pending NavigationEntry started. This
// is a variant of the previous test, where we destroy the speculative
// RenderFrameHost to create another speculative RenderFrameHost. This is a
// regression test for crbug.com/796135.
IN_PROC_BROWSER_TEST_F(
RenderFrameHostManagerTest,
DeleteSpeculativeRFHPendingCommitOfPendingEntryOnInterrupted2) {
const std::string kOriginalPath = "/original.html";
const std::string kRedirectPath = "/redirect.html";
net::test_server::ControllableHttpResponse original_response1(
embedded_test_server(), kOriginalPath);
net::test_server::ControllableHttpResponse original_response2(
embedded_test_server(), kOriginalPath);
net::test_server::ControllableHttpResponse redirect_response(
embedded_test_server(), kRedirectPath);
EXPECT_TRUE(embedded_test_server()->Start());
const GURL kOriginalURL =
embedded_test_server()->GetURL("a.com", kOriginalPath);
const GURL kRedirectURL =
embedded_test_server()->GetURL("b.com", kRedirectPath);
const GURL kCrossSiteURL =
embedded_test_server()->GetURL("c.com", "/title1.html");
const GURL kOriginalSiteURL = SiteInstance::GetSiteForURL(
shell()->web_contents()->GetBrowserContext(), kOriginalURL);
const GURL kRedirectSiteURL = SiteInstance::GetSiteForURL(
shell()->web_contents()->GetBrowserContext(), kRedirectURL);
// First navigate to the initial URL.
shell()->LoadURL(kOriginalURL);
original_response1.WaitForRequest();
original_response1.Send(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Cache-Control: no-cache, no-store, must-revalidate\r\n"
"Pragma: no-cache\r\n"
"\r\n");
original_response1.Send(
"<html>"
"<body></body>"
"</html>");
original_response1.Done();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(kOriginalURL, shell()->web_contents()->GetLastCommittedURL());
// Navigate cross-site.
EXPECT_TRUE(NavigateToURL(shell(), kCrossSiteURL));
// Now go back to the original request, which will do a cross-site redirect.
TestNavigationManager first_back(shell()->web_contents(), kOriginalURL);
shell()->GoBackOrForward(-1);
EXPECT_TRUE(first_back.WaitForRequestStart());
first_back.ResumeNavigation();
original_response2.WaitForRequest();
original_response2.Send(
"HTTP/1.1 302 FOUND\r\n"
"Location: " +
kRedirectURL.spec() +
"\r\n"
"\r\n");
original_response2.Done();
redirect_response.WaitForRequest();
redirect_response.Send(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n");
EXPECT_TRUE(first_back.WaitForResponse());
first_back.ResumeNavigation();
// The navigation is ready to commit: it has been handed to the speculative
// RenderFrameHost for commit.
RenderFrameHostImpl* speculative_rfh =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root()
->render_manager()
->speculative_frame_host();
CHECK(speculative_rfh);
EXPECT_TRUE(speculative_rfh->is_loading());
if (AreAllSitesIsolatedForTesting()) {
EXPECT_EQ(kRedirectSiteURL,
speculative_rfh->GetSiteInstance()->GetSiteURL());
} else if (AreDefaultSiteInstancesEnabled()) {
EXPECT_TRUE(speculative_rfh->GetSiteInstance()->IsDefaultSiteInstance());
} else {
EXPECT_EQ(kOriginalSiteURL,
speculative_rfh->GetSiteInstance()->GetSiteURL());
}
int site_instance_id = speculative_rfh->GetSiteInstance()->GetId();
// The user starts a navigation towards the redirected URL, for which we have
// a speculative RenderFrameHost. This shouldn't delete the speculative
// RenderFrameHost.
TestNavigationManager navigation_to_redirect(shell()->web_contents(),
kRedirectURL);
shell()->LoadURL(kRedirectURL);
EXPECT_TRUE(navigation_to_redirect.WaitForRequestStart());
speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root()
->render_manager()
->speculative_frame_host();
CHECK(speculative_rfh);
if (AreDefaultSiteInstancesEnabled()) {
EXPECT_TRUE(speculative_rfh->GetSiteInstance()->IsDefaultSiteInstance());
} else {
EXPECT_EQ(kRedirectSiteURL,
speculative_rfh->GetSiteInstance()->GetSiteURL());
if (AreAllSitesIsolatedForTesting())
EXPECT_EQ(site_instance_id, speculative_rfh->GetSiteInstance()->GetId());
}
// The user requests to go back again while the previous back hasn't committed
// yet. This should delete the speculative RenderFrameHost trying to commit
// the back, and re-create a new speculative RenderFrameHost. This shouldn't
// crash.
TestNavigationManager second_back(shell()->web_contents(), kOriginalURL);
shell()->GoBackOrForward(-1);
EXPECT_TRUE(second_back.WaitForRequestStart());
speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root()
->render_manager()
->speculative_frame_host();
CHECK(speculative_rfh);
if (AreDefaultSiteInstancesEnabled()) {
EXPECT_TRUE(speculative_rfh->GetSiteInstance()->IsDefaultSiteInstance());
} else {
EXPECT_EQ(kOriginalSiteURL,
speculative_rfh->GetSiteInstance()->GetSiteURL());
}
if (AreAllSitesIsolatedForTesting())
EXPECT_NE(site_instance_id, speculative_rfh->GetSiteInstance()->GetId());
}
// Test for crbug.com/9682. We should not show the URL for a pending renderer-
// initiated navigation in a new tab if it is not the initial navigation. In
// this case, the renderer will not notify us of a modification, so we cannot
// show the pending URL without allowing a spoof.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
DontShowLoadingURLIfNotInitialNav) {
ASSERT_TRUE(embedded_test_server()->Start());
// Load a page that can open a URL that won't commit in a new window.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/click-nocontent-link.html")));
WebContents* orig_contents = shell()->web_contents();
// Click a /nocontent link that opens in a new window but never commits.
// By using an onclick handler that first creates the window, the slow
// navigation is not considered an initial navigation.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
orig_contents,
"window.domAutomationController.send("
"clickNoContentScriptedTargetedLink());",
&success));
EXPECT_TRUE(success);
// Wait for the window to open.
Shell* new_shell = new_shell_observer.GetShell();
// Ensure the destination URL is not visible, because it is not the initial
// navigation.
WebContents* contents = new_shell->web_contents();
EXPECT_FALSE(contents->GetController().IsInitialNavigation());
EXPECT_FALSE(contents->GetController().GetVisibleEntry());
}
// Crashes under ThreadSanitizer, http://crbug.com/356758.
#if defined(THREAD_SANITIZER)
#define MAYBE_BackForwardNotStale DISABLED_BackForwardNotStale
#else
#define MAYBE_BackForwardNotStale BackForwardNotStale
#endif
// Test for http://crbug.com/93427. Ensure that cross-site navigations
// do not cause back/forward navigations to be considered stale by the
// renderer.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest, MAYBE_BackForwardNotStale) {
StartEmbeddedServer();
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
// Visit a page on first site.
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
// Visit three pages on second site.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("foo.com", "/title1.html")));
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("foo.com", "/title2.html")));
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("foo.com", "/title3.html")));
// History is now [blank, A1, B1, B2, *B3].
WebContents* contents = shell()->web_contents();
EXPECT_EQ(5, contents->GetController().GetEntryCount());
// Open another window in same process to keep this process alive.
Shell* new_shell = CreateBrowser();
EXPECT_TRUE(NavigateToURL(
new_shell, embedded_test_server()->GetURL("foo.com", "/title1.html")));
// Go back three times to first site.
{
TestNavigationObserver back_nav_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_nav_load_observer.Wait();
}
{
TestNavigationObserver back_nav_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_nav_load_observer.Wait();
}
{
TestNavigationObserver back_nav_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_nav_load_observer.Wait();
}
// Now go forward twice to B2. Shouldn't be left spinning.
{
TestNavigationObserver forward_nav_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoForward();
forward_nav_load_observer.Wait();
}
{
TestNavigationObserver forward_nav_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoForward();
forward_nav_load_observer.Wait();
}
// Go back twice to first site.
{
TestNavigationObserver back_nav_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_nav_load_observer.Wait();
}
{
TestNavigationObserver back_nav_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_nav_load_observer.Wait();
}
// Now go forward directly to B3. Shouldn't be left spinning.
{
TestNavigationObserver forward_nav_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoToIndex(4);
forward_nav_load_observer.Wait();
}
}
// This class ensures that all the given RenderViewHosts have properly been
// shutdown.
class RenderViewHostDestructionObserver : public WebContentsObserver {
public:
explicit RenderViewHostDestructionObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
~RenderViewHostDestructionObserver() override {}
void EnsureRVHGetsDestructed(RenderViewHost* rvh) {
watched_render_view_hosts_.insert(rvh);
}
size_t GetNumberOfWatchedRenderViewHosts() const {
return watched_render_view_hosts_.size();
}
private:
// WebContentsObserver implementation:
void RenderViewDeleted(RenderViewHost* rvh) override {
watched_render_view_hosts_.erase(rvh);
}
std::set<RenderViewHost*> watched_render_view_hosts_;
};
// Crashes under ThreadSanitizer, http://crbug.com/356758.
#if defined(THREAD_SANITIZER)
#define MAYBE_LeakingRenderViewHosts DISABLED_LeakingRenderViewHosts
#else
#define MAYBE_LeakingRenderViewHosts LeakingRenderViewHosts
#endif
// Test for crbug.com/90867. Make sure we don't leak render view hosts since
// they may cause crashes or memory corruptions when trying to call dead
// delegate_. This test also verifies crbug.com/117420 and crbug.com/143255 to
// ensure that a separate SiteInstance is created when navigating to view-source
// URLs, regardless of current URL.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
MAYBE_LeakingRenderViewHosts) {
StartEmbeddedServer();
// Observe the created render_view_host's to make sure they will not leak.
RenderViewHostDestructionObserver rvh_observers(shell()->web_contents());
GURL navigated_url(embedded_test_server()->GetURL("/title2.html"));
GURL view_source_url(kViewSourceScheme + std::string(":") +
navigated_url.spec());
// Let's ensure that when we start with a blank window, navigating away to a
// view-source URL, we create a new SiteInstance.
RenderViewHost* blank_rvh = shell()->web_contents()->GetRenderViewHost();
SiteInstance* blank_site_instance = blank_rvh->GetSiteInstance();
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), GURL::EmptyGURL());
EXPECT_EQ(blank_site_instance->GetSiteURL(), GURL::EmptyGURL());
rvh_observers.EnsureRVHGetsDestructed(blank_rvh);
// Now navigate to the view-source URL and ensure we got a different
// SiteInstance and RenderViewHost.
EXPECT_TRUE(NavigateToURL(shell(), view_source_url));
EXPECT_NE(blank_rvh, shell()->web_contents()->GetRenderViewHost());
EXPECT_NE(blank_site_instance, shell()->web_contents()->
GetRenderViewHost()->GetSiteInstance());
rvh_observers.EnsureRVHGetsDestructed(
shell()->web_contents()->GetRenderViewHost());
// Load a random page and then navigate to view-source: of it.
// This used to cause two RVH instances for the same SiteInstance, which
// was a problem. This is no longer the case.
EXPECT_TRUE(NavigateToURL(shell(), navigated_url));
SiteInstance* site_instance1 = shell()->web_contents()->
GetRenderViewHost()->GetSiteInstance();
rvh_observers.EnsureRVHGetsDestructed(
shell()->web_contents()->GetRenderViewHost());
EXPECT_TRUE(NavigateToURL(shell(), view_source_url));
rvh_observers.EnsureRVHGetsDestructed(
shell()->web_contents()->GetRenderViewHost());
SiteInstance* site_instance2 = shell()->web_contents()->
GetRenderViewHost()->GetSiteInstance();
// Ensure that view-source navigations force a new SiteInstance.
EXPECT_NE(site_instance1, site_instance2);
// Now navigate to a different instance so that we swap out again.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("foo.com", "/title2.html")));
rvh_observers.EnsureRVHGetsDestructed(
shell()->web_contents()->GetRenderViewHost());
// This used to leak a render view host.
shell()->Close();
RunAllPendingInMessageLoop(); // Needed on ChromeOS.
EXPECT_EQ(0U, rvh_observers.GetNumberOfWatchedRenderViewHosts());
}
// Test for crbug.com/143155. Frame tree updates during unload should not
// interrupt the intended navigation.
// Specifically:
// 1) Open 2 tabs in an HTTP SiteInstance, with a subframe in the opener.
// 2) Send the second tab to a different foo.com SiteInstance.
// This created a swapped out opener for the first tab in the foo process.
// 3) Navigate the first tab to the foo.com SiteInstance, and have the first
// tab's unload handler remove its frame.
// In older versions of Chrome, this caused an update to the frame tree that
// resulted in showing an internal page rather than the real page.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
DontPreemptNavigationWithFrameTreeUpdate) {
StartEmbeddedServer();
if (AreDefaultSiteInstancesEnabled()) {
// Isolate "foo.com" so we are guaranteed to get a non-default
// SiteInstance for navigations to this origin.
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{"foo.com"});
}
// 1. Load a page that deletes its iframe during unload.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/remove_frame_on_unload.html")));
// Get the original SiteInstance for later comparison.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
// Open a same-site page in a new window.
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(), "window.domAutomationController.send(openWindow());", &success));
EXPECT_TRUE(success);
Shell* new_shell = new_shell_observer.GetShell();
// Wait for the navigation in the new window to finish, if it hasn't.
WaitForLoadStop(new_shell->web_contents());
EXPECT_EQ("/title1.html",
new_shell->web_contents()->GetLastCommittedURL().path());
// Should have the same SiteInstance.
EXPECT_EQ(orig_site_instance.get(),
new_shell->web_contents()->GetSiteInstance());
// 2. Send the second tab to a different process.
GURL cross_site_url(
embedded_test_server()->GetURL("foo.com", "/title1.html"));
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, cross_site_url));
scoped_refptr<SiteInstance> new_site_instance(
new_shell->web_contents()->GetSiteInstance());
EXPECT_NE(orig_site_instance, new_site_instance);
// 3. Send the first tab to the second tab's process.
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(shell(), cross_site_url));
// Make sure it ends up at the right page.
WaitForLoadStop(shell()->web_contents());
EXPECT_EQ(cross_site_url, shell()->web_contents()->GetLastCommittedURL());
EXPECT_EQ(new_site_instance, shell()->web_contents()->GetSiteInstance());
}
// Ensure that renderer-side debug URLs do not cause a process swap, since they
// are meant to run in the current page. We had a bug where we expected a
// BrowsingInstance swap to occur on pages like view-source and extensions,
// which broke chrome://crash and javascript: URLs.
// See http://crbug.com/335503.
// The test fails on Mac OSX with ASAN.
// See http://crbug.com/699062.
#if defined(OS_MACOSX) && defined(THREAD_SANITIZER)
#define MAYBE_RendererDebugURLsDontSwap DISABLED_RendererDebugURLsDontSwap
#else
#define MAYBE_RendererDebugURLsDontSwap RendererDebugURLsDontSwap
#endif
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
MAYBE_RendererDebugURLsDontSwap) {
StartEmbeddedServer();
GURL original_url(embedded_test_server()->GetURL("/title2.html"));
GURL view_source_url(kViewSourceScheme + std::string(":") +
original_url.spec());
EXPECT_TRUE(NavigateToURL(shell(), view_source_url));
// Check that javascript: URLs work.
base::string16 expected_title = ASCIIToUTF16("msg");
TitleWatcher title_watcher(shell()->web_contents(), expected_title);
shell()->LoadURL(GURL("javascript:document.title='msg'"));
ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
// Crash the renderer of the view-source page.
RenderProcessHostWatcher crash_observer(
shell()->web_contents(),
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
EXPECT_TRUE(
NavigateToURLAndExpectNoCommit(shell(), GURL(kChromeUICrashURL)));
crash_observer.Wait();
}
// Ensure that renderer-side debug URLs don't take effect on crashed renderers.
// Otherwise, we might try to load an unprivileged about:blank page into a
// WebUI-enabled RenderProcessHost, failing a safety check in InitRenderView.
// See http://crbug.com/334214.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
IgnoreRendererDebugURLsWhenCrashed) {
// Visit a WebUI page with bindings.
GURL webui_url = GURL(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost));
EXPECT_TRUE(NavigateToURL(shell(), webui_url));
EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
// Crash the renderer of the WebUI page.
RenderProcessHostWatcher crash_observer(
shell()->web_contents(),
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
EXPECT_TRUE(
NavigateToURLAndExpectNoCommit(shell(), GURL(kChromeUICrashURL)));
crash_observer.Wait();
// Load the crash URL again but don't wait for any action. If it is not
// ignored this time, we will fail the WebUI CHECK in InitRenderView.
shell()->LoadURL(GURL(kChromeUICrashURL));
// Ensure that such URLs can still work as the initial navigation of a tab.
// We postpone the initial navigation of the tab using an empty GURL, so that
// we can add a watcher for crashes.
Shell* shell2 =
Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(),
GURL(), nullptr, gfx::Size());
RenderProcessHostWatcher crash_observer2(
shell2->web_contents(),
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
EXPECT_TRUE(
NavigateToURLAndExpectNoCommit(shell2, GURL(kChromeUIKillURL)));
crash_observer2.Wait();
}
// Ensure that pending_and_current_web_ui_ is cleared when a URL commits.
// Otherwise it might get picked up by InitRenderView when granting bindings
// to other RenderViewHosts. See http://crbug.com/330811.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest, ClearPendingWebUIOnCommit) {
// Visit a WebUI page with bindings.
GURL webui_url(GURL(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost)));
EXPECT_TRUE(NavigateToURL(shell(), webui_url));
EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
WebContentsImpl* web_contents = static_cast<WebContentsImpl*>(
shell()->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
WebUIImpl* webui = root->current_frame_host()->web_ui();
EXPECT_TRUE(webui);
EXPECT_FALSE(
web_contents->GetRenderManagerForTesting()->GetNavigatingWebUI());
// Navigate to another WebUI URL that reuses the WebUI object. Make sure we
// clear GetNavigatingWebUI() when it commits.
GURL webui_url2(webui_url.spec() + "#foo");
EXPECT_TRUE(NavigateToURL(shell(), webui_url2));
EXPECT_EQ(webui, root->current_frame_host()->web_ui());
EXPECT_FALSE(
web_contents->GetRenderManagerForTesting()->GetNavigatingWebUI());
}
class RFHMProcessPerTabTest : public RenderFrameHostManagerTest {
public:
RFHMProcessPerTabTest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kProcessPerTab);
}
};
// Test that we still swap processes for BrowsingInstance changes even in
// --process-per-tab mode. See http://crbug.com/343017.
// Disabled on Android: http://crbug.com/345873.
// Crashes under ThreadSanitizer, http://crbug.com/356758.
#if defined(OS_ANDROID) || defined(THREAD_SANITIZER)
#define MAYBE_BackFromWebUI DISABLED_BackFromWebUI
#else
#define MAYBE_BackFromWebUI BackFromWebUI
#endif
IN_PROC_BROWSER_TEST_F(RFHMProcessPerTabTest, MAYBE_BackFromWebUI) {
StartEmbeddedServer();
GURL original_url(embedded_test_server()->GetURL("/title2.html"));
EXPECT_TRUE(NavigateToURL(shell(), original_url));
// Visit a WebUI page with bindings.
GURL webui_url(GURL(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost)));
EXPECT_TRUE(NavigateToURL(shell(), webui_url));
EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
// Go back and ensure we have no WebUI bindings.
TestNavigationObserver back_nav_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_nav_load_observer.Wait();
EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
}
// crbug.com/372360
// The test loads url1, opens a link pointing to url2 in a new tab, and
// navigates the new tab to url1.
// The following is needed for the bug to happen:
// - url1 must require webui bindings;
// - navigating to url2 in the site instance of url1 should not swap
// browsing instances, but should require a new site instance.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest, WebUIGetsBindings) {
GURL url1(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost));
GURL url2(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIHistogramHost));
// Visit a WebUI page with bindings.
EXPECT_TRUE(NavigateToURL(shell(), url1));
EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
SiteInstance* site_instance1 = shell()->web_contents()->GetSiteInstance();
int process1_id = site_instance1->GetProcess()->GetID();
// Open a new tab. Initially it gets a render view in the original tab's
// current site instance.
TestNavigationObserver nav_observer(nullptr);
nav_observer.StartWatchingNewWebContents();
ShellAddedObserver shao;
OpenUrlViaClickTarget(shell(), url2);
nav_observer.Wait();
Shell* new_shell = shao.GetShell();
WebContentsImpl* new_web_contents = static_cast<WebContentsImpl*>(
new_shell->web_contents());
SiteInstance* site_instance2 = new_web_contents->GetSiteInstance();
int process2_id = site_instance2->GetProcess()->GetID();
// The 2nd WebUI page should swap to a different process (and SiteInstance),
// but should stay in the same BrowsingInstance as the 1st WebUI page.
EXPECT_NE(process1_id, process2_id);
EXPECT_NE(site_instance2, site_instance1);
EXPECT_TRUE(site_instance2->IsRelatedSiteInstance(site_instance1));
RenderViewHost* initial_rvh = new_web_contents->
GetRenderManagerForTesting()->GetSwappedOutRenderViewHost(site_instance1);
ASSERT_TRUE(initial_rvh);
// Navigate to url1 and check bindings.
EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, url1));
// The navigation should have used the first SiteInstance, otherwise
// |initial_rvh| did not have a chance to be used.
EXPECT_EQ(new_web_contents->GetSiteInstance(), site_instance1);
EXPECT_EQ(BINDINGS_POLICY_WEB_UI,
new_web_contents->GetMainFrame()->GetEnabledBindings());
}
// crbug.com/424526
// The test loads a WebUI page in process-per-tab mode, then navigates to a
// blank page and then to a regular page. The bug reproduces if blank page is
// visited in between WebUI and regular page.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
ForceSwapAfterWebUIBindings) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kProcessPerTab);
StartEmbeddedServer();
const GURL web_ui_url(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost));
EXPECT_TRUE(NavigateToURL(shell(), web_ui_url));
EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
// Capture the SiteInstance before navigating to about:blank to ensure
// it doesn't change.
scoped_refptr<SiteInstance> orig_site_instance(
shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
EXPECT_NE(orig_site_instance, shell()->web_contents()->GetSiteInstance());
GURL regular_page_url(embedded_test_server()->GetURL("/title2.html"));
EXPECT_TRUE(NavigateToURL(shell(), regular_page_url));
EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
}
// crbug.com/615274
// This test ensures that after an RFH is swapped out, the associated WebUI
// instance is no longer allowed to send JavaScript messages. This is necessary
// because WebUI currently (and unusually) always sends JavaScript messages to
// the current main frame, rather than the RFH that owns the WebUI.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
WebUIJavascriptDisallowedAfterSwapOut) {
StartEmbeddedServer();
const GURL web_ui_url(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost));
EXPECT_TRUE(NavigateToURL(shell(), web_ui_url));
RenderFrameHostImpl* rfh =
static_cast<WebContentsImpl*>(shell()->web_contents())->GetMainFrame();
// Set up a slow unload handler to force the RFH to linger in the swapped
// out but not-yet-deleted state.
EXPECT_TRUE(
ExecuteScript(rfh, "window.onunload=function(e){ while(1); };\n"));
WebUIImpl* web_ui = rfh->web_ui();
EXPECT_TRUE(web_ui->CanCallJavascript());
auto handler_owner = std::make_unique<TestWebUIMessageHandler>();
TestWebUIMessageHandler* handler = handler_owner.get();
web_ui->AddMessageHandler(std::move(handler_owner));
EXPECT_FALSE(handler->IsJavascriptAllowed());
handler->AllowJavascript();
EXPECT_TRUE(handler->IsJavascriptAllowed());
rfh->DisableSwapOutTimerForTesting();
RenderFrameHostDestructionObserver rfh_observer(rfh);
// Navigate, but wait for commit, not the actual load to finish.
SiteInstanceImpl* web_ui_site_instance = rfh->GetSiteInstance();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
TestFrameNavigationObserver commit_observer(root);
shell()->LoadURL(GURL(url::kAboutBlankURL));
commit_observer.WaitForCommit();
EXPECT_NE(web_ui_site_instance, shell()->web_contents()->GetSiteInstance());
EXPECT_TRUE(
root->render_manager()->GetRenderFrameProxyHost(web_ui_site_instance));
// The previous RFH should still be pending deletion, as we wait for either
// the SwapOut ACK or a timeout.
ASSERT_TRUE(rfh->IsRenderFrameLive());
ASSERT_FALSE(rfh->is_active());
// We specifically want verify behavior between swap-out and RFH destruction.
ASSERT_FALSE(rfh_observer.deleted());
EXPECT_FALSE(handler->IsJavascriptAllowed());
}
// Test for http://crbug.com/703303. Ensures that the renderer process does not
// try to select files whose paths cannot be converted to WebStrings. This
// check is done in the renderer because it is hard to predict which paths will
// turn into empty WebStrings, and the behavior varies by platform.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest, DontSelectInvalidFiles) {
StartServer();
base::RunLoop run_loop;
// Use a file path with an invalid encoding, such that it can't be converted
// to a WebString (on all platforms but Windows).
base::FilePath file;
EXPECT_TRUE(base::PathService::Get(base::DIR_TEMP, &file));
file = file.Append(FILE_PATH_LITERAL("foo\337bar"));
// Navigate and try to get page to reference this file in its PageState.
GURL url1(embedded_test_server()->GetURL("/file_input.html"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
int process_id =
shell()->web_contents()->GetMainFrame()->GetProcess()->GetID();
std::unique_ptr<FileChooserDelegate> delegate(
new FileChooserDelegate(file, run_loop.QuitClosure()));
shell()->web_contents()->SetDelegate(delegate.get());
EXPECT_TRUE(
ExecuteScript(shell(), "document.getElementById('fileinput').click();"));
run_loop.Run();
// The browser process grants access to the file whether or not the renderer
// process realizes that it can't use it. This is ok, since the user actually
// did select the file from the chooser.
EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
process_id, file));
// Disable the swap out timer so we wait for the UpdateState message.
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetMainFrame()
->DisableSwapOutTimerForTesting();
// Navigate to a different process and wait for the old process to exit.
RenderProcessHostWatcher exit_observer(
shell()->web_contents()->GetMainFrame()->GetProcess(),
RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteURL("/title1.html")));
exit_observer.Wait();
EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
shell()->web_contents()->GetMainFrame()->GetProcess()->GetID(), file));
// The renderer process should not have been killed. This is the important
// part of the test. If this fails, then we didn't get a PageState to check
// below, so use an assert (since the test can't meaningfully proceed).
ASSERT_TRUE(exit_observer.did_exit_normally());
// Ensure that the file did not end up in the PageState of the previous entry,
// except on Windows where the path is valid and WebString can handle it.
NavigationEntry* prev_entry =
shell()->web_contents()->GetController().GetEntryAtIndex(0);
EXPECT_EQ(url1, prev_entry->GetURL());
const std::vector<base::FilePath>& files =
prev_entry->GetPageState().GetReferencedFiles();
#if defined(OS_WIN)
EXPECT_EQ(1U, files.size());
#else
EXPECT_EQ(0U, files.size());
#endif
}
// Test for http://crbug.com/262948.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
RestoreFileAccessForHistoryNavigation) {
StartServer();
base::RunLoop run_loop;
base::FilePath file;
EXPECT_TRUE(base::PathService::Get(base::DIR_TEMP, &file));
file = file.AppendASCII("bar");
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
// Navigate to url and get it to reference a file in its PageState.
GURL url1(embedded_test_server()->GetURL("/file_input.html"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
int process_id = wc->GetMainFrame()->GetProcess()->GetID();
std::unique_ptr<FileChooserDelegate> delegate(
new FileChooserDelegate(file, run_loop.QuitClosure()));
wc->SetDelegate(delegate.get());
EXPECT_TRUE(
ExecuteScript(shell(), "document.getElementById('fileinput').click();"));
run_loop.Run();
EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
process_id, file));
// Disable the swap out timer so we wait for the UpdateState message.
wc->GetMainFrame()->DisableSwapOutTimerForTesting();
// Navigate to a different process without access to the file, and wait for
// the old process to exit.
RenderProcessHostWatcher exit_observer(
wc->GetMainFrame()->GetProcess(),
RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteURL("/title1.html")));
exit_observer.Wait();
EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
wc->GetMainFrame()->GetProcess()->GetID(), file));
// Ensure that the file ended up in the PageState of the previous entry.
NavigationEntry* prev_entry = wc->GetController().GetEntryAtIndex(0);
EXPECT_EQ(url1, prev_entry->GetURL());
const std::vector<base::FilePath>& files =
prev_entry->GetPageState().GetReferencedFiles();
ASSERT_EQ(1U, files.size());
EXPECT_EQ(file, files.at(0));
// Go back, ending up in a different RenderProcessHost than before.
TestNavigationObserver back_nav_load_observer(wc);
wc->GetController().GoBack();
back_nav_load_observer.Wait();
EXPECT_NE(process_id, wc->GetMainFrame()->GetProcess()->GetID());
// Ensure that the file access still exists in the new process ID.
EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
wc->GetMainFrame()->GetProcess()->GetID(), file));
// Navigate to a same site page to trigger a PageState update and ensure the
// renderer is not killed.
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
}
// Same as RenderFrameHostManagerTest.RestoreFileAccessForHistoryNavigation, but
// replace the cross-origin navigation by a crash, followed by a reload.
IN_PROC_BROWSER_TEST_F(RenderFrameHostManagerTest,
RestoreFileAccessForHistoryNavigationAfterCrash) {
StartServer();
base::RunLoop run_loop;
base::FilePath file;
EXPECT_TRUE(base::PathService::Get(base::DIR_TEMP, &file));
file = file.AppendASCII("bar");
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
// Navigate to url and get it to reference a file in its PageState.
GURL url1(embedded_test_server()->GetURL("/file_input.html"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
int process_id = wc->GetMainFrame()->GetProcess()->GetID();
std::unique_ptr<FileChooserDelegate> delegate(
new FileChooserDelegate(file, run_loop.QuitClosure()));
wc->SetDelegate(delegate.get());
EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
process_id, file));
EXPECT_TRUE(
ExecuteScript(shell(), "document.getElementById('fileinput').click();"));
run_loop.Run();
EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
process_id, file));
// The PageState hasn't been updated yet. It requires a navigation.
{
NavigationEntry* prev_entry = wc->GetController().GetEntryAtIndex(0);
EXPECT_EQ(url1, prev_entry->GetURL());
const std::vector<base::FilePath>& files =
prev_entry->GetPageState().GetReferencedFiles();
ASSERT_EQ(0U, files.size());
}
// Same-document navigation
EXPECT_TRUE(ExecJs(shell(), "history.pushState({},'title','#foo')"));
// The PageState has been updated, it now contains the file.
{
NavigationEntry* prev_entry = wc->GetController().GetEntryAtIndex(0);
EXPECT_EQ(url1, prev_entry->GetURL());
const std::vector<base::FilePath>& files =
prev_entry->GetPageState().GetReferencedFiles();
ASSERT_EQ(1U, files.size());
EXPECT_EQ(file, files.at(0));
}
// Crash.