blob: 4722af1bcfe73c79ce2f599d8e53ee3d150420fb [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/string_split.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/test_timeouts.h"
#include "base/threading/hang_watcher.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_process_host_internal_observer.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_launcher_utils.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_observer.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.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/no_renderer_crashes_assertion.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_service.mojom.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_browser_context.h"
#include "content/shell/browser/shell_browser_main_parts.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/storage_partition_test_helpers.h"
#include "content/test/test_content_browser_client.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/media_switches.h"
#include "media/base/test_data_util.h"
#include "media/mojo/buildflags.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#if BUILDFLAG(IS_WIN)
#include "base/test/metrics/histogram_tester.h"
#include "sandbox/policy/switches.h"
#endif
namespace content {
namespace {
// Similar to net::test_server::DelayedHttpResponse, but the delay is resolved
// through Resolver.
class DelayedHttpResponseWithResolver final
: public net::test_server::BasicHttpResponse {
public:
class Resolver final : public base::RefCountedThreadSafe<Resolver> {
public:
void Resolve() {
base::AutoLock auto_lock(lock_);
DCHECK(!resolved_);
resolved_ = true;
if (!task_runner_) {
return;
}
for (auto& response : response_closures_)
task_runner_->PostTask(FROM_HERE, std::move(response));
response_closures_.clear();
}
void Add(base::OnceClosure response) {
base::AutoLock auto_lock(lock_);
if (resolved_) {
std::move(response).Run();
return;
}
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
base::SingleThreadTaskRunner::GetCurrentDefault();
if (task_runner_) {
DCHECK_EQ(task_runner_, task_runner);
} else {
task_runner_ = std::move(task_runner);
}
response_closures_.push_back(std::move(response));
}
private:
friend class base::RefCountedThreadSafe<Resolver>;
~Resolver() = default;
base::Lock lock_;
std::vector<base::OnceClosure> response_closures_;
bool resolved_ GUARDED_BY(lock_) = false;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_ GUARDED_BY(lock_);
};
explicit DelayedHttpResponseWithResolver(scoped_refptr<Resolver> resolver)
: resolver_(std::move(resolver)) {}
DelayedHttpResponseWithResolver(const DelayedHttpResponseWithResolver&) =
delete;
DelayedHttpResponseWithResolver& operator=(
const DelayedHttpResponseWithResolver&) = delete;
void SendResponse(
base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) override {
resolver_->Add(base::BindOnce(
&net::test_server::HttpResponseDelegate::SendHeadersContentAndFinish,
delegate, code(), GetHttpReasonPhrase(code()), BuildHeaders(),
content()));
}
private:
const scoped_refptr<Resolver> resolver_;
};
std::unique_ptr<net::test_server::HttpResponse> HandleBeacon(
const net::test_server::HttpRequest& request) {
if (request.relative_url != "/beacon")
return nullptr;
return std::make_unique<net::test_server::BasicHttpResponse>();
}
std::unique_ptr<net::test_server::HttpResponse> HandleHungBeacon(
const base::RepeatingClosure& on_called,
const net::test_server::HttpRequest& request) {
if (request.relative_url != "/beacon")
return nullptr;
if (on_called) {
on_called.Run();
}
return std::make_unique<net::test_server::HungResponse>();
}
std::unique_ptr<net::test_server::HttpResponse> HandleHungBeaconWithResolver(
scoped_refptr<DelayedHttpResponseWithResolver::Resolver> resolver,
const net::test_server::HttpRequest& request) {
if (request.relative_url != "/beacon")
return nullptr;
return std::make_unique<DelayedHttpResponseWithResolver>(std::move(resolver));
}
} // namespace
class RenderProcessHostTest : public ContentBrowserTest,
public RenderProcessHostObserver {
public:
RenderProcessHostTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(
switches::kAutoplayPolicy,
switches::autoplay::kNoUserGestureRequiredPolicy);
}
void SetUpOnMainThread() override {
// Support multiple sites on the test server.
host_resolver()->AddRule("*", "127.0.0.1");
}
void SetVisibleClients(RenderProcessHost* process, int32_t visible_clients) {
RenderProcessHostImpl* impl = static_cast<RenderProcessHostImpl*>(process);
impl->visible_clients_ = visible_clients;
}
protected:
void SetProcessExitCallback(RenderProcessHost* rph,
base::OnceClosure callback) {
Observe(rph);
process_exit_callback_ = std::move(callback);
}
void Observe(RenderProcessHost* rph) {
DCHECK(!observation_.IsObserving());
observation_.Observe(rph);
}
// RenderProcessHostObserver:
void RenderProcessExited(RenderProcessHost* host,
const ChildProcessTerminationInfo& info) override {
++process_exits_;
if (process_exit_callback_)
std::move(process_exit_callback_).Run();
}
void RenderProcessHostDestroyed(RenderProcessHost* host) override {
++host_destructions_;
observation_.Reset();
}
void WaitUntilProcessExits(int target) {
while (process_exits_ < target)
base::RunLoop().RunUntilIdle();
}
base::ScopedObservation<RenderProcessHost, RenderProcessHostObserver>
observation_{this};
int process_exits_ = 0;
int host_destructions_ = 0;
base::OnceClosure process_exit_callback_;
};
// A mock ContentBrowserClient that only considers a spare renderer to be a
// suitable host.
class SpareRendererContentBrowserClient : public TestContentBrowserClient {
public:
bool IsSuitableHost(RenderProcessHost* process_host,
const GURL& site_url) override {
if (RenderProcessHostImpl::GetSpareRenderProcessHostForTesting()) {
return process_host ==
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting();
}
return true;
}
};
// A mock ContentBrowserClient that only considers a non-spare renderer to be a
// suitable host, but otherwise tries to reuse processes.
class NonSpareRendererContentBrowserClient : public TestContentBrowserClient {
public:
NonSpareRendererContentBrowserClient() = default;
NonSpareRendererContentBrowserClient(
const NonSpareRendererContentBrowserClient&) = delete;
NonSpareRendererContentBrowserClient& operator=(
const NonSpareRendererContentBrowserClient&) = delete;
bool IsSuitableHost(RenderProcessHost* process_host,
const GURL& site_url) override {
return RenderProcessHostImpl::GetSpareRenderProcessHostForTesting() !=
process_host;
}
bool ShouldTryToUseExistingProcessHost(BrowserContext* context,
const GURL& url) override {
return true;
}
bool ShouldUseSpareRenderProcessHost(BrowserContext* browser_context,
const GURL& site_url) override {
return false;
}
};
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, GuestsAreNotSuitableHosts) {
// Set max renderers to 1 to force running out of processes.
RenderProcessHost::SetMaxRendererProcessCount(1);
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
EXPECT_TRUE(NavigateToURL(shell(), test_url));
RenderProcessHost* rph =
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess();
// Make it believe it's a guest.
static_cast<RenderProcessHostImpl*>(rph)->SetForGuestsOnlyForTesting();
EXPECT_EQ(1, RenderProcessHost::GetCurrentRenderProcessCountForTesting());
// Navigate to a different page.
GURL::Replacements replace_host;
replace_host.SetHostStr("localhost");
GURL another_url = embedded_test_server()->GetURL("/simple_page.html");
another_url = another_url.ReplaceComponents(replace_host);
EXPECT_TRUE(NavigateToURL(CreateBrowser(), another_url));
// Expect that we got another process (the guest renderer was not reused).
EXPECT_EQ(2, RenderProcessHost::GetCurrentRenderProcessCountForTesting());
}
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, SpareRenderProcessHostTaken) {
ASSERT_TRUE(embedded_test_server()->Start());
RenderProcessHost::WarmupSpareRenderProcessHost(
ShellContentBrowserClient::Get()->browser_context());
RenderProcessHost* spare_renderer =
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting();
EXPECT_NE(nullptr, spare_renderer);
GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
Shell* window = CreateBrowser();
EXPECT_TRUE(NavigateToURL(window, test_url));
EXPECT_EQ(spare_renderer,
window->web_contents()->GetPrimaryMainFrame()->GetProcess());
// The old spare render process host should no longer be available.
EXPECT_NE(spare_renderer,
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting());
// Check if a fresh spare is available (depending on the operating mode).
if (RenderProcessHostImpl::IsSpareProcessKeptAtAllTimes()) {
EXPECT_NE(nullptr,
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting());
} else {
EXPECT_EQ(nullptr,
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting());
}
}
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, SpareRenderProcessHostNotTaken) {
ASSERT_TRUE(embedded_test_server()->Start());
RenderProcessHost::WarmupSpareRenderProcessHost(
ShellContentBrowserClient::Get()->off_the_record_browser_context());
RenderProcessHost* spare_renderer =
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting();
GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
Shell* window = CreateBrowser();
EXPECT_TRUE(NavigateToURL(window, test_url));
// There should have been another process created for the navigation.
EXPECT_NE(spare_renderer,
window->web_contents()->GetPrimaryMainFrame()->GetProcess());
// Check if a fresh spare is available (depending on the operating mode).
// Note this behavior is identical to what would have happened if the
// RenderProcessHost were taken.
if (RenderProcessHostImpl::IsSpareProcessKeptAtAllTimes()) {
EXPECT_NE(nullptr,
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting());
} else {
EXPECT_EQ(nullptr,
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting());
}
}
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, SpareRenderProcessHostKilled) {
RenderProcessHost::WarmupSpareRenderProcessHost(
ShellContentBrowserClient::Get()->browser_context());
RenderProcessHost* spare_renderer =
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting();
mojo::Remote<mojom::TestService> service;
ASSERT_NE(nullptr, spare_renderer);
spare_renderer->BindReceiver(service.BindNewPipeAndPassReceiver());
base::RunLoop run_loop;
SetProcessExitCallback(spare_renderer, run_loop.QuitClosure());
// Should reply with a bad message and cause process death.
{
ScopedAllowRendererCrashes scoped_allow_renderer_crashes(spare_renderer);
service->DoSomething(base::DoNothing());
run_loop.Run();
}
// The spare RenderProcessHost should disappear when its process dies.
EXPECT_EQ(nullptr,
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting());
}
// Test that the spare renderer works correctly when the limit on the maximum
// number of processes is small.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest,
SpareRendererSurpressedMaxProcesses) {
ASSERT_TRUE(embedded_test_server()->Start());
SpareRendererContentBrowserClient browser_client;
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&browser_client);
RenderProcessHost::SetMaxRendererProcessCount(1);
// A process is created with shell startup, so with a maximum of one renderer
// process the spare RPH should not be created.
RenderProcessHost::WarmupSpareRenderProcessHost(
ShellContentBrowserClient::Get()->browser_context());
EXPECT_EQ(nullptr,
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting());
// A spare RPH should be created with a max of 2 renderer processes.
RenderProcessHost::SetMaxRendererProcessCount(2);
RenderProcessHost::WarmupSpareRenderProcessHost(
ShellContentBrowserClient::Get()->browser_context());
RenderProcessHost* spare_renderer =
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting();
EXPECT_NE(nullptr, spare_renderer);
// Thanks to the injected SpareRendererContentBrowserClient and the limit on
// processes, the spare RPH will always be used via GetExistingProcessHost()
// rather than picked up via MaybeTakeSpareRenderProcessHost().
GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
Shell* new_window = CreateBrowser();
EXPECT_TRUE(NavigateToURL(new_window, test_url));
// Outside of RenderProcessHostImpl::IsSpareProcessKeptAtAllTimes mode, the
// spare RPH should have been dropped during CreateBrowser() and given to the
// new window. OTOH, even in the IsSpareProcessKeptAtAllTimes mode, the spare
// shouldn't be created because of the low process limit.
EXPECT_EQ(nullptr,
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting());
EXPECT_EQ(spare_renderer,
new_window->web_contents()->GetPrimaryMainFrame()->GetProcess());
// Revert to the default process limit and original ContentBrowserClient.
RenderProcessHost::SetMaxRendererProcessCount(0);
SetBrowserClientForTesting(old_client);
}
// Check that the spare renderer is dropped if an existing process is reused.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, SpareRendererOnProcessReuse) {
ASSERT_TRUE(embedded_test_server()->Start());
NonSpareRendererContentBrowserClient browser_client;
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&browser_client);
RenderProcessHost::WarmupSpareRenderProcessHost(
ShellContentBrowserClient::Get()->browser_context());
RenderProcessHost* spare_renderer =
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting();
EXPECT_NE(nullptr, spare_renderer);
// This should reuse the existing process.
Shell* new_browser = CreateBrowser();
EXPECT_EQ(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(),
new_browser->web_contents()->GetPrimaryMainFrame()->GetProcess());
EXPECT_NE(spare_renderer,
new_browser->web_contents()->GetPrimaryMainFrame()->GetProcess());
if (RenderProcessHostImpl::IsSpareProcessKeptAtAllTimes()) {
EXPECT_NE(nullptr,
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting());
} else {
EXPECT_EQ(nullptr,
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting());
}
// The launcher thread reads state from browser_client, need to wait for it to
// be done before resetting the browser client. crbug.com/742533.
base::WaitableEvent launcher_thread_done(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
GetProcessLauncherTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce([](base::WaitableEvent* done) { done->Signal(); },
base::Unretained(&launcher_thread_done)));
ASSERT_TRUE(launcher_thread_done.TimedWait(TestTimeouts::action_timeout()));
SetBrowserClientForTesting(old_client);
}
// Verifies that the spare renderer maintained by SpareRenderProcessHostManager
// is correctly destroyed during browser shutdown. This test is an analogue
// to the //chrome-layer FastShutdown.SpareRenderProcessHost test.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest,
SpareRenderProcessHostDuringShutdown) {
content::RenderProcessHost::WarmupSpareRenderProcessHost(
shell()->web_contents()->GetBrowserContext());
// The verification is that there are no DCHECKs anywhere during test tear
// down.
}
// Verifies that the spare renderer maintained by SpareRenderProcessHostManager
// is correctly destroyed when closing the last content shell.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, SpareRendererDuringClosing) {
content::RenderProcessHost::WarmupSpareRenderProcessHost(
shell()->web_contents()->GetBrowserContext());
shell()->web_contents()->Close();
// The verification is that there are no DCHECKs or UaF anywhere during test
// tear down.
}
// This test verifies that SpareRenderProcessHostManager correctly accounts
// for StoragePartition differences when handing out the spare process.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest,
SpareProcessVsCustomStoragePartition) {
ASSERT_TRUE(embedded_test_server()->Start());
// Provide custom storage partition for test sites.
GURL test_url = embedded_test_server()->GetURL("a.com", "/simple_page.html");
CustomStoragePartitionForSomeSites modified_client(GURL("http://a.com/"));
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&modified_client);
BrowserContext* browser_context =
ShellContentBrowserClient::Get()->browser_context();
scoped_refptr<SiteInstance> test_site_instance =
SiteInstance::CreateForURL(browser_context, test_url);
StoragePartition* default_storage =
browser_context->GetDefaultStoragePartition();
StoragePartition* custom_storage =
browser_context->GetStoragePartition(test_site_instance.get());
EXPECT_NE(default_storage, custom_storage);
// Open a test window - it should be associated with the default storage
// partition.
Shell* window = CreateBrowser();
RenderProcessHost* old_process =
window->web_contents()->GetPrimaryMainFrame()->GetProcess();
EXPECT_EQ(default_storage, old_process->GetStoragePartition());
// Warm up the spare process - it should be associated with the default
// storage partition.
RenderProcessHost::WarmupSpareRenderProcessHost(browser_context);
RenderProcessHost* spare_renderer =
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting();
ASSERT_TRUE(spare_renderer);
EXPECT_EQ(default_storage, spare_renderer->GetStoragePartition());
// Navigate to a URL that requires a custom storage partition.
EXPECT_TRUE(NavigateToURL(window, test_url));
RenderProcessHost* new_process =
window->web_contents()->GetPrimaryMainFrame()->GetProcess();
// Requirement to use a custom storage partition should force a process swap.
EXPECT_NE(new_process, old_process);
// The new process should be associated with the custom storage partition.
EXPECT_EQ(custom_storage, new_process->GetStoragePartition());
// And consequently, the spare shouldn't have been used.
EXPECT_NE(spare_renderer, new_process);
// Restore the original ContentBrowserClient.
SetBrowserClientForTesting(old_client);
}
class RenderProcessHostObserverCounter : public RenderProcessHostObserver {
public:
explicit RenderProcessHostObserverCounter(RenderProcessHost* host) {
host->AddObserver(this);
observing_ = true;
observed_host_ = host;
}
RenderProcessHostObserverCounter(const RenderProcessHostObserverCounter&) =
delete;
RenderProcessHostObserverCounter& operator=(
const RenderProcessHostObserverCounter&) = delete;
~RenderProcessHostObserverCounter() override {
if (observing_)
observed_host_->RemoveObserver(this);
}
void RenderProcessExited(RenderProcessHost* host,
const ChildProcessTerminationInfo& info) override {
DCHECK(observing_);
DCHECK_EQ(host, observed_host_);
exited_count_++;
}
void RenderProcessHostDestroyed(RenderProcessHost* host) override {
DCHECK(observing_);
DCHECK_EQ(host, observed_host_);
destroyed_count_++;
host->RemoveObserver(this);
observing_ = false;
observed_host_ = nullptr;
}
int exited_count() const { return exited_count_; }
int destroyed_count() const { return destroyed_count_; }
private:
int exited_count_ = 0;
int destroyed_count_ = 0;
bool observing_ = false;
raw_ptr<RenderProcessHost> observed_host_ = nullptr;
};
// Check that the spare renderer is properly destroyed via DisableRefCounts().
// Note: DisableRefCounts() used to be called DisableKeepAliveRefCount();
// the name if this test is left unchanged to avoid disrupt any tracking
// tools (e.g. flakiness) that might reference the old name.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, SpareVsDisableKeepAliveRefCount) {
RenderProcessHost::WarmupSpareRenderProcessHost(
ShellContentBrowserClient::Get()->browser_context());
base::RunLoop().RunUntilIdle();
RenderProcessHost* spare_renderer =
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting();
RenderProcessHostObserverCounter counter(spare_renderer);
RenderProcessHostWatcher process_watcher(
spare_renderer, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
spare_renderer->DisableRefCounts();
process_watcher.Wait();
EXPECT_TRUE(process_watcher.did_exit_normally());
// An important part of test verification is that UaF doesn't happen in the
// next revolution of the message pump - without extra care in the
// SpareRenderProcessHostManager RenderProcessHost::Cleanup could be called
// twice leading to a crash caused by double-free flavour of UaF in
// base::DeleteHelper<...>::DoDelete.
base::RunLoop().RunUntilIdle();
DCHECK_EQ(1, counter.exited_count());
DCHECK_EQ(1, counter.destroyed_count());
}
// Check that the spare renderer is properly destroyed via DisableRefCounts().
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, SpareVsFastShutdown) {
RenderProcessHost::WarmupSpareRenderProcessHost(
ShellContentBrowserClient::Get()->browser_context());
base::RunLoop().RunUntilIdle();
RenderProcessHost* spare_renderer =
RenderProcessHostImpl::GetSpareRenderProcessHostForTesting();
RenderProcessHostObserverCounter counter(spare_renderer);
RenderProcessHostWatcher process_watcher(
spare_renderer, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
spare_renderer->FastShutdownIfPossible();
process_watcher.Wait();
EXPECT_FALSE(process_watcher.did_exit_normally());
// An important part of test verification is that UaF doesn't happen in the
// next revolution of the message pump - without extra care in the
// SpareRenderProcessHostManager RenderProcessHost::Cleanup could be called
// twice leading to a crash caused by double-free flavour of UaF in
// base::DeleteHelper<...>::DoDelete.
base::RunLoop().RunUntilIdle();
DCHECK_EQ(1, counter.exited_count());
DCHECK_EQ(1, counter.destroyed_count());
}
class ShellCloser : public RenderProcessHostObserver {
public:
ShellCloser(Shell* shell, std::string* logging_string)
: shell_(shell), logging_string_(logging_string) {}
protected:
// RenderProcessHostObserver:
void RenderProcessExited(RenderProcessHost* host,
const ChildProcessTerminationInfo& info) override {
logging_string_->append("ShellCloser::RenderProcessExited ");
shell_->Close();
}
void RenderProcessHostDestroyed(RenderProcessHost* host) override {
logging_string_->append("ShellCloser::RenderProcessHostDestroyed ");
}
raw_ptr<Shell, DanglingUntriaged> shell_;
raw_ptr<std::string> logging_string_;
};
class ObserverLogger : public RenderProcessHostObserver {
public:
explicit ObserverLogger(std::string* logging_string)
: logging_string_(logging_string), host_destroyed_(false) {}
bool host_destroyed() { return host_destroyed_; }
protected:
// RenderProcessHostObserver:
void RenderProcessExited(RenderProcessHost* host,
const ChildProcessTerminationInfo& info) override {
logging_string_->append("ObserverLogger::RenderProcessExited ");
}
void RenderProcessHostDestroyed(RenderProcessHost* host) override {
logging_string_->append("ObserverLogger::RenderProcessHostDestroyed ");
host_destroyed_ = true;
}
raw_ptr<std::string> logging_string_;
bool host_destroyed_;
};
// Flaky on Android. http://crbug.com/759514.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_AllProcessExitedCallsBeforeAnyHostDestroyedCalls \
DISABLED_AllProcessExitedCallsBeforeAnyHostDestroyedCalls
#else
#define MAYBE_AllProcessExitedCallsBeforeAnyHostDestroyedCalls \
AllProcessExitedCallsBeforeAnyHostDestroyedCalls
#endif
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest,
MAYBE_AllProcessExitedCallsBeforeAnyHostDestroyedCalls) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
EXPECT_TRUE(NavigateToURL(shell(), test_url));
std::string logging_string;
ShellCloser shell_closer(shell(), &logging_string);
ObserverLogger observer_logger(&logging_string);
RenderProcessHost* rph =
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess();
// Ensure that the ShellCloser observer is first, so that it will have first
// dibs on the ProcessExited callback.
base::ScopedObservation<RenderProcessHost, RenderProcessHostObserver>
observation_1(&shell_closer);
base::ScopedObservation<RenderProcessHost, RenderProcessHostObserver>
observation_2(&observer_logger);
observation_1.Observe(rph);
observation_2.Observe(rph);
// This will crash the render process, and start all the callbacks.
// We can't use NavigateToURL here since it accesses the shell() after
// navigating, which the shell_closer deletes.
ScopedAllowRendererCrashes scoped_allow_renderer_crashes(shell());
NavigateToURLBlockUntilNavigationsComplete(shell(),
GURL(blink::kChromeUICrashURL), 1);
// The key here is that all the RenderProcessExited callbacks precede all the
// RenderProcessHostDestroyed callbacks.
EXPECT_EQ(
"ShellCloser::RenderProcessExited "
"ObserverLogger::RenderProcessExited "
"ShellCloser::RenderProcessHostDestroyed "
"ObserverLogger::RenderProcessHostDestroyed ",
logging_string);
}
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, KillProcessOnBadMojoMessage) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
EXPECT_TRUE(NavigateToURL(shell(), test_url));
RenderProcessHost* rph =
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess();
host_destructions_ = 0;
process_exits_ = 0;
mojo::Remote<mojom::TestService> service;
rph->BindReceiver(service.BindNewPipeAndPassReceiver());
base::RunLoop run_loop;
SetProcessExitCallback(rph, run_loop.QuitClosure());
// Should reply with a bad message and cause process death.
{
ScopedAllowRendererCrashes scoped_allow_renderer_crashes(rph);
service->DoSomething(base::DoNothing());
run_loop.Run();
}
EXPECT_EQ(1, process_exits_);
EXPECT_EQ(0, host_destructions_);
}
// Observes a WebContents and a specific frame within it, and waits until they
// both indicate that they are audible.
class AudioStartObserver : public WebContentsObserver {
public:
AudioStartObserver(WebContents* web_contents,
RenderFrameHost* render_frame_host,
base::OnceClosure audible_closure)
: WebContentsObserver(web_contents),
render_frame_host_(
static_cast<RenderFrameHostImpl*>(render_frame_host)),
contents_audible_(web_contents->IsCurrentlyAudible()),
frame_audible_(render_frame_host_->is_audible()),
audible_closure_(std::move(audible_closure)) {
MaybeFireClosure();
}
~AudioStartObserver() override = default;
// WebContentsObserver:
void OnAudioStateChanged(bool audible) override {
DCHECK_NE(audible, contents_audible_);
contents_audible_ = audible;
MaybeFireClosure();
}
void OnFrameAudioStateChanged(RenderFrameHost* render_frame_host,
bool audible) override {
if (render_frame_host_ != render_frame_host)
return;
DCHECK_NE(frame_audible_, audible);
frame_audible_ = audible;
MaybeFireClosure();
}
private:
void MaybeFireClosure() {
if (contents_audible_ && frame_audible_)
std::move(audible_closure_).Run();
}
raw_ptr<RenderFrameHostImpl> render_frame_host_ = nullptr;
bool contents_audible_ = false;
bool frame_audible_ = false;
base::OnceClosure audible_closure_;
};
// Tests that audio stream counts (used for process priority calculations) are
// properly set and cleared during media playback and renderer terminations.
//
// Note: This test can't run when the Mojo Renderer is used since it does not
// create audio streams through the normal audio pathways; at present this is
// only used by Chromecast.
//
// crbug.com/864476: flaky on Android for unclear reasons.
#if BUILDFLAG(ENABLE_MOJO_RENDERER) || BUILDFLAG(IS_ANDROID)
#define KillProcessZerosAudioStreams DISABLED_KillProcessZerosAudioStreams
#endif
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, KillProcessZerosAudioStreams) {
// TODO(maxmorin): This test only uses an output stream. There should be a
// similar test for input streams.
embedded_test_server()->ServeFilesFromSourceDirectory(
media::GetTestDataPath());
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/webaudio_oscillator.html")));
RenderProcessHostImpl* rph = static_cast<RenderProcessHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
{
// Start audio and wait for it to become audible, in both the frame *and*
// the page.
base::RunLoop run_loop;
AudioStartObserver observer(shell()->web_contents(),
shell()->web_contents()->GetPrimaryMainFrame(),
run_loop.QuitClosure());
std::string result;
EXPECT_EQ("OK", EvalJs(shell(), "StartOscillator();",
EXECUTE_SCRIPT_USE_MANUAL_REPLY))
<< "Failed to execute javascript.";
run_loop.Run();
// No point in running the rest of the test if this is wrong.
ASSERT_EQ(1, rph->get_media_stream_count_for_testing());
}
host_destructions_ = 0;
process_exits_ = 0;
mojo::Remote<mojom::TestService> service;
rph->BindReceiver(service.BindNewPipeAndPassReceiver());
{
// Force a bad message event to occur which will terminate the renderer.
// Note: We post task the QuitClosure since RenderProcessExited() is called
// before destroying BrowserMessageFilters; and the next portion of the test
// must run after these notifications have been delivered.
ScopedAllowRendererCrashes scoped_allow_renderer_crashes(rph);
base::RunLoop run_loop;
SetProcessExitCallback(rph, run_loop.QuitClosure());
service->DoSomething(base::DoNothing());
run_loop.Run();
}
{
// Cycle UI and IO loop once to ensure OnChannelClosing() has been delivered
// to audio stream owners and they get a chance to notify of stream closure.
base::RunLoop run_loop;
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, media::BindToCurrentLoop(run_loop.QuitClosure()));
run_loop.Run();
}
// Verify shutdown went as expected.
EXPECT_EQ(0, rph->get_media_stream_count_for_testing());
EXPECT_EQ(1, process_exits_);
EXPECT_EQ(0, host_destructions_);
}
// Test class instance to run specific setup steps for capture streams.
class CaptureStreamRenderProcessHostTest : public RenderProcessHostTest {
public:
void SetUp() override {
// Pixel output is needed when digging pixel values out of video tags for
// verification in VideoCaptureStream tests.
EnablePixelOutput();
RenderProcessHostTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// These flags are necessary to emulate camera input for getUserMedia()
// tests.
command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
RenderProcessHostTest::SetUpCommandLine(command_line);
}
};
// Tests that video capture stream count increments when getUserMedia() is
// called.
IN_PROC_BROWSER_TEST_F(CaptureStreamRenderProcessHostTest,
GetUserMediaIncrementsVideoCaptureStreams) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/media/getusermedia.html")));
RenderProcessHostImpl* rph = static_cast<RenderProcessHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
std::string result;
EXPECT_EQ("OK",
EvalJs(shell(), "getUserMediaAndExpectSuccess({video: true});",
EXECUTE_SCRIPT_USE_MANUAL_REPLY))
<< "Failed to execute javascript.";
EXPECT_EQ(1, rph->get_media_stream_count_for_testing());
}
// Tests that video capture stream count resets when getUserMedia() is called
// and stopped.
IN_PROC_BROWSER_TEST_F(CaptureStreamRenderProcessHostTest,
StopResetsVideoCaptureStreams) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/media/getusermedia.html")));
RenderProcessHostImpl* rph = static_cast<RenderProcessHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
std::string result;
EXPECT_EQ("OK", EvalJs(shell(), "getUserMediaAndStop({video: true});",
EXECUTE_SCRIPT_USE_MANUAL_REPLY))
<< "Failed to execute javascript.";
EXPECT_EQ(0, rph->get_media_stream_count_for_testing());
}
// Tests that video capture stream counts (used for process priority
// calculations) are properly set and cleared during media playback and renderer
// terminations.
IN_PROC_BROWSER_TEST_F(CaptureStreamRenderProcessHostTest,
KillProcessZerosVideoCaptureStreams) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/media/getusermedia.html")));
RenderProcessHostImpl* rph = static_cast<RenderProcessHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
std::string result;
EXPECT_EQ("OK",
EvalJs(shell(), "getUserMediaAndExpectSuccess({video: true});",
EXECUTE_SCRIPT_USE_MANUAL_REPLY))
<< "Failed to execute javascript.";
EXPECT_EQ(1, rph->get_media_stream_count_for_testing());
host_destructions_ = 0;
process_exits_ = 0;
mojo::Remote<mojom::TestService> service;
rph->BindReceiver(service.BindNewPipeAndPassReceiver());
{
// Force a bad message event to occur which will terminate the renderer.
ScopedAllowRendererCrashes scoped_allow_renderer_crashes(rph);
base::RunLoop run_loop;
SetProcessExitCallback(rph, run_loop.QuitClosure());
service->DoSomething(base::DoNothing());
run_loop.Run();
}
{
// Cycle UI and IO loop once to ensure OnChannelClosing() has been delivered
// to audio stream owners and they get a chance to notify of stream closure.
base::RunLoop run_loop;
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, media::BindToCurrentLoop(run_loop.QuitClosure()));
run_loop.Run();
}
EXPECT_EQ(0, rph->get_media_stream_count_for_testing());
EXPECT_EQ(1, process_exits_);
EXPECT_EQ(0, host_destructions_);
}
// Tests that media stream count increments when getUserMedia() is
// called with audio only.
IN_PROC_BROWSER_TEST_F(CaptureStreamRenderProcessHostTest,
GetUserMediaAudioOnlyIncrementsMediaStreams) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/media/getusermedia.html")));
RenderProcessHostImpl* rph = static_cast<RenderProcessHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
std::string result;
EXPECT_EQ("OK",
EvalJs(shell(),
"getUserMediaAndExpectSuccess({video: false, audio: true});",
EXECUTE_SCRIPT_USE_MANUAL_REPLY))
<< "Failed to execute javascript.";
EXPECT_EQ(1, rph->get_media_stream_count_for_testing());
}
// Tests that media stream counts (used for process priority
// calculations) are properly set and cleared during media playback and renderer
// terminations for audio only streams.
IN_PROC_BROWSER_TEST_F(CaptureStreamRenderProcessHostTest,
KillProcessZerosAudioCaptureStreams) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/media/getusermedia.html")));
RenderProcessHostImpl* rph = static_cast<RenderProcessHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
std::string result;
EXPECT_EQ("OK",
EvalJs(shell(),
"getUserMediaAndExpectSuccess({video: false, audio: true});",
EXECUTE_SCRIPT_USE_MANUAL_REPLY))
<< "Failed to execute javascript.";
EXPECT_EQ(1, rph->get_media_stream_count_for_testing());
host_destructions_ = 0;
process_exits_ = 0;
mojo::Remote<mojom::TestService> service;
rph->BindReceiver(service.BindNewPipeAndPassReceiver());
{
// Force a bad message event to occur which will terminate the renderer.
ScopedAllowRendererCrashes scoped_allow_renderer_crashes(rph);
base::RunLoop run_loop;
SetProcessExitCallback(rph, run_loop.QuitClosure());
service->DoSomething(base::DoNothing());
run_loop.Run();
}
{
// Cycle UI and IO loop once to ensure OnChannelClosing() has been delivered
// to audio stream owners and they get a chance to notify of stream closure.
base::RunLoop run_loop;
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, media::BindToCurrentLoop(run_loop.QuitClosure()));
run_loop.Run();
}
EXPECT_EQ(0, rph->get_media_stream_count_for_testing());
EXPECT_EQ(1, process_exits_);
EXPECT_EQ(0, host_destructions_);
}
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, KeepAliveRendererProcess) {
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(HandleBeacon));
ASSERT_TRUE(embedded_test_server()->Start());
if (AreDefaultSiteInstancesEnabled()) {
// Isolate "foo.com" so we are guaranteed that navigations to this site
// will be in a different process.
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{"foo.com"});
}
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/send-beacon.html")));
RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
RenderProcessHostImpl* rph =
static_cast<RenderProcessHostImpl*>(rfh->GetProcess());
// Disable the BackForwardCache to ensure the old process is going to be
// released.
DisableBackForwardCacheForTesting(shell()->web_contents(),
BackForwardCache::TEST_REQUIRES_NO_CACHING);
host_destructions_ = 0;
process_exits_ = 0;
Observe(rph);
rfh->SetKeepAliveTimeoutForTesting(base::Seconds(30));
// Navigate to a site that will be in a different process.
base::TimeTicks start = base::TimeTicks::Now();
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("foo.com", "/title1.html")));
WaitUntilProcessExits(1);
EXPECT_LT(base::TimeTicks::Now() - start, base::Seconds(30));
}
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest,
KeepAliveRendererProcessWithServiceWorker) {
base::RunLoop run_loop;
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(HandleHungBeacon, run_loop.QuitClosure()));
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(),
embedded_test_server()->GetURL("/workers/service_worker_setup.html")));
EXPECT_EQ("ok", EvalJs(shell(), "setup();"));
RenderProcessHostImpl* rph = static_cast<RenderProcessHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
// 1 for the service worker.
EXPECT_EQ(rph->worker_ref_count(), 1u);
EXPECT_EQ(rph->keep_alive_ref_count(), 0u);
// We use /workers/send-beacon.html, not send-beacon.html, due to the
// service worker scope rule.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/workers/send-beacon.html")));
run_loop.Run();
// We are still using the same process.
ASSERT_EQ(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(), rph);
// 1 for the service worker, 1 for the keepalive fetch.
EXPECT_EQ(rph->keep_alive_ref_count(), 1u);
EXPECT_EQ(rph->worker_ref_count(), 1u);
}
// Test is flaky on Android builders: https://crbug.com/875179
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN)
#define MAYBE_KeepAliveRendererProcess_Hung \
DISABLED_KeepAliveRendererProcess_Hung
#else
#define MAYBE_KeepAliveRendererProcess_Hung KeepAliveRendererProcess_Hung
#endif
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest,
MAYBE_KeepAliveRendererProcess_Hung) {
// Disable HangWatcher so it doesn't interfere with this test when hangs take
// place.
base::HangWatcher::StopMonitoringForTesting();
// The test assumes that the render process exits after 1 second. But this
// will be prevented if the process still hosts a bfcached page. So disable
// BFCache for this test.
content::DisableBackForwardCacheForTesting(
shell()->web_contents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(HandleHungBeacon, base::RepeatingClosure()));
ASSERT_TRUE(embedded_test_server()->Start());
const auto kTestUrl = embedded_test_server()->GetURL("/send-beacon.html");
if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
// Isolate host so that the first and second navigation are guaranteed to
// be in different processes.
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{kTestUrl.host()});
}
EXPECT_TRUE(NavigateToURL(shell(), kTestUrl));
RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
RenderProcessHostImpl* rph =
static_cast<RenderProcessHostImpl*>(rfh->GetProcess());
// Disable the BackForwardCache to ensure the old process is going to be
// released.
DisableBackForwardCacheForTesting(shell()->web_contents(),
BackForwardCache::TEST_REQUIRES_NO_CACHING);
host_destructions_ = 0;
process_exits_ = 0;
Observe(rph);
rfh->SetKeepAliveTimeoutForTesting(base::Seconds(1));
base::TimeTicks start = base::TimeTicks::Now();
EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,<p>hello</p>")));
WaitUntilProcessExits(1);
EXPECT_GE(base::TimeTicks::Now() - start, base::Seconds(1));
}
// Test is flaky on Android builders: https://crbug.com/875179
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN)
#define MAYBE_FetchKeepAliveRendererProcess_Hung \
DISABLED_FetchKeepAliveRendererProcess_Hung
#else
#define MAYBE_FetchKeepAliveRendererProcess_Hung \
FetchKeepAliveRendererProcess_Hung
#endif
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest,
MAYBE_FetchKeepAliveRendererProcess_Hung) {
// Disable HangWatcher so it doesn't interfere with this test when hangs take
// place.
base::HangWatcher::StopMonitoringForTesting();
// The test assumes that the render process exits after 1 second. But this
// will be prevented if the process still hosts a bfcached page. So disable
// BFCache for this test.
content::DisableBackForwardCacheForTesting(
shell()->web_contents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(HandleHungBeacon, base::RepeatingClosure()));
ASSERT_TRUE(embedded_test_server()->Start());
const auto kTestUrl = embedded_test_server()->GetURL("/fetch-keepalive.html");
if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
// Isolate host so that the first and second navigation are guaranteed to
// be in different processes.
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{kTestUrl.host()});
}
EXPECT_TRUE(NavigateToURL(shell(), kTestUrl));
RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
RenderProcessHostImpl* rph =
static_cast<RenderProcessHostImpl*>(rfh->GetProcess());
// Disable the BackForwardCache to ensure the old process is going to be
// released.
DisableBackForwardCacheForTesting(shell()->web_contents(),
BackForwardCache::TEST_REQUIRES_NO_CACHING);
host_destructions_ = 0;
process_exits_ = 0;
Observe(rph);
rfh->SetKeepAliveTimeoutForTesting(base::Seconds(1));
base::TimeTicks start = base::TimeTicks::Now();
EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,<p>hello</p>")));
WaitUntilProcessExits(1);
EXPECT_GE(base::TimeTicks::Now() - start, base::Seconds(1));
}
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, ManyKeepaliveRequests) {
auto resolver =
base::MakeRefCounted<DelayedHttpResponseWithResolver::Resolver>();
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(HandleHungBeaconWithResolver, resolver));
const std::u16string title = u"Resolved";
const std::u16string waiting = u"Waiting";
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(),
embedded_test_server()->GetURL("/fetch-keepalive.html?requests=256")));
{
// Wait for the page to make all the keepalive requests.
TitleWatcher watcher(shell()->web_contents(), waiting);
EXPECT_EQ(waiting, watcher.WaitAndGetTitle());
}
resolver->Resolve();
{
TitleWatcher watcher(shell()->web_contents(), title);
EXPECT_EQ(title, watcher.WaitAndGetTitle());
}
}
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, TooManyKeepaliveRequests) {
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(HandleHungBeacon, base::RepeatingClosure()));
ASSERT_TRUE(embedded_test_server()->Start());
const std::u16string title = u"Rejected";
TitleWatcher watcher(shell()->web_contents(), title);
EXPECT_TRUE(NavigateToURL(
shell(),
embedded_test_server()->GetURL("/fetch-keepalive.html?requests=257")));
EXPECT_EQ(title, watcher.WaitAndGetTitle());
}
// Records the value of |host->IsProcessBackgrounded()| when it changes.
// |host| must remain a valid reference for the lifetime of this object.
class IsProcessBackgroundedObserver : public RenderProcessHostInternalObserver {
public:
explicit IsProcessBackgroundedObserver(RenderProcessHostImpl* host) {
host_observation_.Observe(host);
}
void RenderProcessBackgroundedChanged(RenderProcessHostImpl* host) override {
backgrounded_ = host->IsProcessBackgrounded();
}
// Returns the latest recorded value if there was one and resets the recorded
// value to |nullopt|.
absl::optional<bool> TakeValue() {
auto value = backgrounded_;
backgrounded_ = absl::nullopt;
return value;
}
private:
// Stores the last observed value of IsProcessBackgrounded for a host.
absl::optional<bool> backgrounded_;
base::ScopedObservation<RenderProcessHostImpl,
RenderProcessHostInternalObserver>
host_observation_{this};
};
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, PriorityOverride) {
// Start up a real renderer process.
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
EXPECT_TRUE(NavigateToURL(shell(), test_url));
RenderProcessHost* rph =
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess();
RenderProcessHostImpl* process = static_cast<RenderProcessHostImpl*>(rph);
IsProcessBackgroundedObserver observer(process);
// It starts off as normal priority with no override.
EXPECT_FALSE(process->HasPriorityOverride());
EXPECT_FALSE(process->IsProcessBackgrounded());
EXPECT_FALSE(observer.TakeValue().has_value());
process->SetPriorityOverride(false /* foreground */);
EXPECT_TRUE(process->HasPriorityOverride());
EXPECT_TRUE(process->IsProcessBackgrounded());
EXPECT_EQ(observer.TakeValue().value(), process->IsProcessBackgrounded());
process->SetPriorityOverride(true /* foreground */);
EXPECT_TRUE(process->HasPriorityOverride());
EXPECT_FALSE(process->IsProcessBackgrounded());
EXPECT_EQ(observer.TakeValue().value(), process->IsProcessBackgrounded());
process->SetPriorityOverride(false /* foreground */);
EXPECT_TRUE(process->HasPriorityOverride());
EXPECT_TRUE(process->IsProcessBackgrounded());
EXPECT_EQ(observer.TakeValue().value(), process->IsProcessBackgrounded());
// Add a pending view, and expect the process to *stay* backgrounded.
process->AddPendingView();
EXPECT_TRUE(process->HasPriorityOverride());
EXPECT_TRUE(process->IsProcessBackgrounded());
EXPECT_FALSE(observer.TakeValue().has_value());
// Clear the override. The pending view should cause the process to go back to
// being foregrounded.
process->ClearPriorityOverride();
EXPECT_FALSE(process->HasPriorityOverride());
EXPECT_FALSE(process->IsProcessBackgrounded());
EXPECT_EQ(observer.TakeValue().value(), process->IsProcessBackgrounded());
// Clear the pending view so the test doesn't explode.
process->RemovePendingView();
}
// This test verifies properties of RenderProcessHostImpl *before* Init method
// is called.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, ConstructedButNotInitializedYet) {
RenderProcessHost* process = RenderProcessHostImpl::CreateRenderProcessHost(
ShellContentBrowserClient::Get()->browser_context(), nullptr);
// Just verifying that the arguments of CreateRenderProcessHost got processed
// correctly.
EXPECT_EQ(ShellContentBrowserClient::Get()->browser_context(),
process->GetBrowserContext());
EXPECT_FALSE(process->IsForGuestsOnly());
// There should be no OS process before Init() method is called.
EXPECT_FALSE(process->IsInitializedAndNotDead());
EXPECT_FALSE(process->IsReady());
EXPECT_FALSE(process->GetProcess().IsValid());
EXPECT_EQ(base::kNullProcessHandle, process->GetProcess().Handle());
// TODO(lukasza): https://crbug.com/813045: RenderProcessHost shouldn't have
// an associated IPC channel (and shouldn't accumulate IPC messages) unless
// the Init() method was called and the RPH either has connection to an actual
// OS process or is currently attempting to spawn the OS process. After this
// bug is fixed the 1st test assertion below should be reversed (unsure about
// the 2nd one).
EXPECT_TRUE(process->GetChannel());
EXPECT_TRUE(process->GetRendererInterface());
// Cleanup the resources acquired by the test.
process->Cleanup();
}
// This test verifies that a fast shutdown is possible for a starting process.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, FastShutdownForStartingProcess) {
RenderProcessHost* process = RenderProcessHostImpl::CreateRenderProcessHost(
ShellContentBrowserClient::Get()->browser_context(), nullptr);
process->Init();
EXPECT_TRUE(process->FastShutdownIfPossible());
process->Cleanup();
}
// Tests that all RenderFrameHosts that lives in the process are accessible via
// RenderProcessHost::ForEachRenderFrameHost(), except those RenderFrameHosts
// whose lifecycle states are kSpeculative.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, ForEachRenderFrameHost) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a(a,a))");
GURL url_b = embedded_test_server()->GetURL("b.com", "/title1.html");
GURL url_new_window = embedded_test_server()->GetURL(
"c.com", "/cross_site_iframe_factory.html?c(a)");
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
// 1. Navigate to `url_a`; it creates 4 RenderFrameHosts that live in the
// same process.
ASSERT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHost* rfh_a = shell()->web_contents()->GetPrimaryMainFrame();
std::vector<RenderFrameHost*> process_a_frames =
CollectAllRenderFrameHosts(rfh_a);
// 2. Open a new window, and navigate to a page with an a.com subframe.
// The new subframe should reuse the same a.com process.
Shell* new_window = CreateBrowser();
ASSERT_TRUE(NavigateToURL(new_window, url_new_window));
auto* new_window_main_frame = static_cast<RenderFrameHostImpl*>(
new_window->web_contents()->GetPrimaryMainFrame());
ASSERT_EQ(new_window_main_frame->child_count(), 1u);
RenderFrameHost* new_window_sub_frame =
new_window_main_frame->child_at(0)->current_frame_host();
ASSERT_EQ(rfh_a->GetProcess(), new_window_sub_frame->GetProcess());
process_a_frames.push_back(new_window_sub_frame);
// 3. Start to navigate to a cross-origin site, and hold the navigation
// request. This behavior will create a speculative RenderFrameHost.
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
TestNavigationManager manager(web_contents, url_b);
shell()->LoadURL(url_b);
ASSERT_TRUE(manager.WaitForRequestStart());
// 4. Get the speculative RenderFrameHost.
FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
RenderFrameHostImpl* rfh_b = root->render_manager()->speculative_frame_host();
ASSERT_TRUE(rfh_b);
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kSpeculative,
rfh_b->lifecycle_state());
auto non_speculative_rfh_collector =
[](std::vector<RenderFrameHost*>& results, RenderFrameHost* rfh) {
auto* rfhi = static_cast<RenderFrameHostImpl*>(rfh);
EXPECT_NE(RenderFrameHostImpl::LifecycleStateImpl::kSpeculative,
rfhi->lifecycle_state());
results.push_back(rfh);
};
// 5. Check all of the a.com RenderFrameHosts are tracked by `rph_a`.
RenderProcessHostImpl* rph_a =
static_cast<RenderProcessHostImpl*>(rfh_a->GetProcess());
std::vector<RenderFrameHost*> same_process_rfhs;
rph_a->ForEachRenderFrameHost(base::BindRepeating(
non_speculative_rfh_collector, std::ref(same_process_rfhs)));
EXPECT_EQ(5, rph_a->GetRenderFrameHostCount());
EXPECT_EQ(5u, same_process_rfhs.size());
EXPECT_THAT(same_process_rfhs,
testing::UnorderedElementsAreArray(process_a_frames.data(),
process_a_frames.size()));
// 6. Check the speculative RenderFrameHost is ignored.
same_process_rfhs.clear();
RenderProcessHostImpl* rph_b =
static_cast<RenderProcessHostImpl*>(rfh_b->GetProcess());
ASSERT_NE(rph_a, rph_b);
rph_b->ForEachRenderFrameHost(base::BindRepeating(
non_speculative_rfh_collector, std::ref(same_process_rfhs)));
EXPECT_EQ(1, rph_b->GetRenderFrameHostCount());
// The speculative RenderFrameHost should be filtered out.
EXPECT_EQ(same_process_rfhs.size(), 0u);
// 7. Resume the blocked navigation.
manager.WaitForNavigationFinished();
// 8. Check that `RenderProcessHost::ForEachRenderFrameHost` does not filter
// `rfh_b` out, because its lifecycle has changed to kActive.
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
rfh_b->lifecycle_state());
EXPECT_EQ(1, rph_b->GetRenderFrameHostCount());
same_process_rfhs.clear();
rph_b->ForEachRenderFrameHost(base::BindRepeating(
non_speculative_rfh_collector, std::ref(same_process_rfhs)));
EXPECT_EQ(1, rph_b->GetRenderFrameHostCount());
EXPECT_EQ(1u, same_process_rfhs.size());
EXPECT_THAT(same_process_rfhs, testing::ElementsAre(rfh_b));
}
namespace {
// Observer that listens for process leak cleanup events. Note that this only
// hears about cases where the affected RenderViewHost is for the primary main
// frame.
class LeakCleanupObserver : public WebContentsObserver {
public:
explicit LeakCleanupObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
~LeakCleanupObserver() override = default;
// WebContentsObserver:
void PrimaryMainFrameRenderProcessGone(
base::TerminationStatus status) override {
termination_count_++;
CHECK_EQ(status,
base::TerminationStatus::TERMINATION_STATUS_NORMAL_TERMINATION);
}
int termination_count() { return termination_count_; }
void reset_termination_count() { termination_count_ = 0; }
private:
int termination_count_ = 0;
};
// Observer that listens for process exits and counts when they are treated as
// fast shutdown cases.
class FastShutdownExitObserver : public RenderProcessHostObserver {
public:
explicit FastShutdownExitObserver(RenderProcessHost* process) {
process->AddObserver(this);
}
~FastShutdownExitObserver() override = default;
// RenderProcessHostObserver:
void RenderProcessExited(RenderProcessHost* host,
const ChildProcessTerminationInfo& info) override {
if (host->FastShutdownStarted())
fast_shutdown_exit_count_++;
}
void RenderProcessHostDestroyed(RenderProcessHost* host) override {
host->RemoveObserver(this);
}
int fast_shutdown_exit_count() { return fast_shutdown_exit_count_; }
void reset_exit_count() { fast_shutdown_exit_count_ = 0; }
private:
int fast_shutdown_exit_count_ = 0;
};
} // namespace
// Ensure that we don't leak a renderer process if there are only non-live
// RenderFrameHosts assigned to its RenderProcessHost (e.g., when the last live
// frame goes away). See https://crbug.com/1226834.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, AllowUnusedProcessToExit) {
ASSERT_TRUE(embedded_test_server()->Start());
// Ensure all sites get dedicated processes during the test.
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
// Set max renderers to 1 to force reusing a renderer process between two
// unrelated tabs.
RenderProcessHost::SetMaxRendererProcessCount(1);
// The test assumes that the render process exits after navigation, but this
// will be prevented if the process still hosts a bfcached page. Disable
// BFCache for this test.
content::DisableBackForwardCacheForTesting(
shell()->web_contents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
// Ensure the initial tab has not loaded yet.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
RenderFrameHostImpl* original_rfh = root->current_frame_host();
RenderProcessHost* original_process = original_rfh->GetProcess();
int original_process_id = original_process->GetID();
EXPECT_FALSE(original_process->IsInitializedAndNotDead());
EXPECT_FALSE(original_rfh->IsRenderFrameLive());
// Reset the process exit related counts and listen to process exit events.
process_exits_ = 0;
host_destructions_ = 0;
Observe(original_process);
FastShutdownExitObserver fast_shutdown_observer(original_process);
// Create a second tab to a real URL. This will share the first
// RenderProcessHost due to the low limit, and because it was not used or
// locked.
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
Shell* shell2 =
Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(),
url_a, nullptr, gfx::Size());
FrameTreeNode* root2 = static_cast<WebContentsImpl*>(shell2->web_contents())
->GetPrimaryFrameTree()
.root();
RenderFrameHostImpl* rfh2 = root2->current_frame_host();
EXPECT_EQ(original_process, rfh2->GetProcess());
EXPECT_TRUE(original_process->IsInitializedAndNotDead());
EXPECT_TRUE(rfh2->IsRenderFrameLive());
// The original RFH is still in an unloaded state.
EXPECT_FALSE(original_rfh->IsRenderFrameLive());
// Close shell2. This used to leave the process running, since original_rfh
// was still counted as an active frame in the RenderProcessHost even though
// it wasn't live.
LeakCleanupObserver leak_cleanup_observer(shell()->web_contents());
RenderProcessHostWatcher exit_observer(
original_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
RenderFrameDeletedObserver rfh2_deleted_observer(rfh2);
shell2->Close();
rfh2_deleted_observer.WaitUntilDeleted();
exit_observer.Wait();
EXPECT_TRUE(exit_observer.did_exit_normally());
EXPECT_EQ(1, process_exits_);
EXPECT_EQ(0, host_destructions_);
EXPECT_EQ(1, leak_cleanup_observer.termination_count());
// This cleanup should be considered similar to fast shutdown, for observers
// that treat that case as expected behavior.
EXPECT_EQ(1, fast_shutdown_observer.fast_shutdown_exit_count());
EXPECT_EQ(original_rfh, root->current_frame_host());
EXPECT_EQ(original_process, original_rfh->GetProcess());
EXPECT_FALSE(original_process->IsInitializedAndNotDead());
EXPECT_FALSE(original_rfh->IsRenderFrameLive());
// There shouldn't be live RenderViewHosts or proxies either.
EXPECT_FALSE(original_rfh->render_view_host()->IsRenderViewLive());
EXPECT_FALSE(
root->current_frame_host()
->browsing_context_state()
->GetRenderFrameProxyHost(original_rfh->GetSiteInstance()->group()));
// Reset the process exit related counts.
process_exits_ = 0;
host_destructions_ = 0;
leak_cleanup_observer.reset_termination_count();
fast_shutdown_observer.reset_exit_count();
// After the leak cleanup, navigate the original frame to the same site to
// make sure it still works in the original RenderProcessHost, even though it
// swaps to a new RenderFrameHost.
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* replaced_rfh = root->current_frame_host();
ASSERT_EQ(original_process, replaced_rfh->GetProcess());
EXPECT_EQ(original_process_id, replaced_rfh->GetProcess()->GetID());
EXPECT_TRUE(replaced_rfh->GetProcess()->IsInitializedAndNotDead());
EXPECT_TRUE(replaced_rfh->IsRenderFrameLive());
EXPECT_FALSE(original_process->FastShutdownStarted());
EXPECT_EQ(0, process_exits_);
EXPECT_EQ(0, host_destructions_);
EXPECT_EQ(0, fast_shutdown_observer.fast_shutdown_exit_count());
EXPECT_EQ(0, leak_cleanup_observer.termination_count());
// Ensure that the leak cleanup does not occur after a normal cross-process
// navigation, since normal process cleanup generates different events,
// including the full destruction of the RenderProcessHost.
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
RenderProcessHostWatcher cleanup_observer(
original_process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
cleanup_observer.Wait();
RenderFrameHostImpl* rfh_b = root->current_frame_host();
EXPECT_NE(original_process_id, rfh_b->GetProcess()->GetID());
EXPECT_EQ(1, process_exits_);
EXPECT_EQ(1, host_destructions_);
// During the cross-process navigation, we should not have used the leak
// cleanup approach. The leak cleanup would set FastShutdownStarted.
EXPECT_EQ(0, fast_shutdown_observer.fast_shutdown_exit_count());
EXPECT_EQ(0, leak_cleanup_observer.termination_count());
}
// Similar to AllowUnusedProcessToExit, for the case that a sad frame from a
// previous renderer crash is the only remaining RenderFrameHost in a process.
// See https://crbug.com/1226834.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest,
AllowUnusedProcessToExitAfterCrash) {
ASSERT_TRUE(embedded_test_server()->Start());
// Ensure all sites get dedicated processes during the test.
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
GURL initial_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b,b)"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
RenderFrameHostImpl* child_rfh0 = root->child_at(0)->current_frame_host();
RenderFrameHostImpl* child_rfh1 = root->child_at(1)->current_frame_host();
RenderViewHostImpl* rvh_b = child_rfh0->render_view_host();
int process_b_id = child_rfh0->GetProcess()->GetID();
EXPECT_EQ(child_rfh0->GetProcess(), child_rfh1->GetProcess());
EXPECT_TRUE(child_rfh0->GetProcess()->IsInitializedAndNotDead());
EXPECT_TRUE(child_rfh0->IsRenderFrameLive());
EXPECT_TRUE(child_rfh1->IsRenderFrameLive());
EXPECT_TRUE(rvh_b->IsRenderViewLive());
// Reset the process exit related counts.
process_exits_ = 0;
host_destructions_ = 0;
Observe(child_rfh0->GetProcess());
// Terminate the subframe process.
{
RenderProcessHostWatcher termination_observer(
child_rfh0->GetProcess(),
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_rfh0->GetProcess()->Shutdown(0);
termination_observer.Wait();
}
EXPECT_FALSE(child_rfh0->GetProcess()->IsInitializedAndNotDead());
EXPECT_FALSE(child_rfh0->IsRenderFrameLive());
EXPECT_FALSE(child_rfh1->IsRenderFrameLive());
EXPECT_FALSE(rvh_b->IsRenderViewLive());
EXPECT_EQ(1, process_exits_);
EXPECT_EQ(0, host_destructions_);
// Reset the process exit related counts.
process_exits_ = 0;
host_destructions_ = 0;
// Reload the first frame but not the second. This will replace child_rfh0
// with a new RFH in the same SiteInstance and process as before.
{
TestFrameNavigationObserver reload_observer(root->child_at(0));
std::string reload_script(
"var f = document.getElementById('child-0');"
"f.src = f.src;");
EXPECT_TRUE(ExecuteScript(root, reload_script));
reload_observer.Wait();
}
RenderFrameHostImpl* new_child_rfh0 = root->child_at(0)->current_frame_host();
EXPECT_EQ(child_rfh1->GetProcess(), new_child_rfh0->GetProcess());
EXPECT_TRUE(new_child_rfh0->GetProcess()->IsInitializedAndNotDead());
EXPECT_TRUE(new_child_rfh0->IsRenderFrameLive());
EXPECT_FALSE(child_rfh1->IsRenderFrameLive());
// Navigate the first frame to a different process. This again replaces
// new_child_rfh0 with a new RFH, now in a different process.
GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
RenderFrameDeletedObserver rfh_deleted_observer(new_child_rfh0);
TestFrameNavigationObserver navigation_observer(root->child_at(0));
EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), url_c));
navigation_observer.Wait();
rfh_deleted_observer.WaitUntilDeleted();
EXPECT_NE(child_rfh1->GetProcess(),
root->child_at(0)->current_frame_host()->GetProcess());
// This used to leave the b.com process running, since child_rfh1 was still
// counted as an active frame in the RenderProcessHost even though it wasn't
// live.
EXPECT_FALSE(child_rfh1->GetProcess()->IsInitializedAndNotDead());
EXPECT_FALSE(child_rfh1->IsRenderFrameLive());
EXPECT_FALSE(rvh_b->IsRenderViewLive());
EXPECT_EQ(1, process_exits_);
EXPECT_EQ(0, host_destructions_);
// There shouldn't be live proxies either.
RenderFrameProxyHost* proxy =
root->current_frame_host()
->browsing_context_state()
->GetRenderFrameProxyHost(child_rfh1->GetSiteInstance()->group());
EXPECT_FALSE(proxy->is_render_frame_proxy_live());
// Reset the process exit related counts.
process_exits_ = 0;
host_destructions_ = 0;
// After the leak cleanup, reload the second frame to make sure it still works
// in the original RenderProcessHost, even though it swaps to a new
// RenderFrameHost.
{
TestFrameNavigationObserver reload_observer(root->child_at(1));
std::string reload_script(
"var f = document.getElementById('child-1');"
"f.src = f.src;");
EXPECT_TRUE(ExecuteScript(root, reload_script));
reload_observer.Wait();
}
RenderFrameHostImpl* new_child_rfh1 = root->child_at(1)->current_frame_host();
EXPECT_EQ(process_b_id, new_child_rfh1->GetProcess()->GetID());
EXPECT_TRUE(new_child_rfh1->GetProcess()->IsInitializedAndNotDead());
EXPECT_TRUE(new_child_rfh1->IsRenderFrameLive());
EXPECT_EQ(0, process_exits_);
EXPECT_EQ(0, host_destructions_);
}
// Test that RenderProcessHostImpl::Cleanup can handle nested deletions of
// RenderFrameHost objects, when we might encounter a parent RFH that is tracked
// among the IPC listeners but is no longer discoverable via FromID, while
// handling the deletion of a subframe. One way this can occur is during bfcache
// eviction.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, HandleNestedFrameDeletion) {
ASSERT_TRUE(embedded_test_server()->Start());
// Ensure all sites get dedicated processes during the test.
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
// Navigate to a page with a same-process subframe.
GURL url_a(embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
RenderFrameHostImpl* rfh_a = root->current_frame_host();
RenderProcessHost* process_a = rfh_a->GetProcess();
int process_a_id = process_a->GetID();
Observe(process_a);
// Navigate cross-process and evict process A from the back-forward cache.
// This should not cause a crash when looking for non-live RenderFrameHosts.
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
RenderProcessHostWatcher cleanup_observer(
process_a, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
shell()->web_contents()->GetController().GetBackForwardCache().Flush();
cleanup_observer.Wait();
RenderFrameHostImpl* rfh_b = root->current_frame_host();
EXPECT_NE(process_a_id, rfh_b->GetProcess()->GetID());
EXPECT_EQ(1, process_exits_);
EXPECT_EQ(1, host_destructions_);
}
namespace {
// Observer that listens for RenderFrameDeleted and iterates over the remaining
// RenderFrameHosts in the process at the time. This catches a case where a
// parent RenderFrameHost might not be found via RenderFrameHost::FromID because
// of nested frame deletion, which used to cause a CHECK failure.
class RenderFrameDeletionObserver : public WebContentsObserver {
public:
explicit RenderFrameDeletionObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
~RenderFrameDeletionObserver() override = default;
// WebContentsObserver:
void RenderFrameDeleted(RenderFrameHost* render_frame_host) override {
render_frame_deleted_count_++;
// Find all other RenderFrameHosts in the process, which should exclude the
// one being deleted.
auto rfh_collector = [](std::vector<RenderFrameHost*>& results,
RenderFrameHost* rfh) { results.push_back(rfh); };
RenderProcessHostImpl* process =
static_cast<RenderProcessHostImpl*>(render_frame_host->GetProcess());
std::vector<RenderFrameHost*> all_rfhs;
process->ForEachRenderFrameHost(
base::BindRepeating(rfh_collector, std::ref(all_rfhs)));
// Update the cumulative count of other RenderFrameHosts in the process.
render_frame_host_iterator_count_ += all_rfhs.size();
}
// Returns how many time RenderFrameDeleted was called.
int render_frame_deleted_count() { return render_frame_deleted_count_; }
// Returns a cumulative count of how many remaining RenderFrameHosts were
// found in the process at the time of any RenderFrameDeleted calls.
int render_frame_host_iterator_count() {
return render_frame_host_iterator_count_;
}
private:
int render_frame_deleted_count_ = 0;
int render_frame_host_iterator_count_ = 0;
};
} // namespace
// Test that RenderProcessHost::ForEachRenderFrameHost can handle nested
// deletions of RenderFrameHost objects, when we might encounter a parent RFH
// that is no longer discoverable via FromID, while handling the deletion of a
// subframe. One way this can occur is during bfcache eviction.
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, ForEachFrameNestedFrameDeletion) {
// This test specifically wants to test with BackForwardCache eviction, so
// skip it if BackForwardCache is disabled.
if (!IsBackForwardCacheEnabled())
return;
ASSERT_TRUE(embedded_test_server()->Start());
// Ensure all sites get dedicated processes during the test.
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
// Navigate to a page with a same-process subframe.
GURL url_a(embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
RenderFrameHostImpl* rfh_a = root->current_frame_host();
RenderProcessHost* process_a = rfh_a->GetProcess();
int process_a_id = process_a->GetID();
// Listen for RenderFrameDeleted and count the other RenderFrameHosts in the
// process at the time.
RenderFrameDeletionObserver rfh_deletion_observer(shell()->web_contents());
// Navigate cross-process and evict process A from the back-forward cache.
// This should not cause a crash when iterating over RenderFrameHosts.
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
RenderProcessHostWatcher cleanup_observer(
process_a, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
shell()->web_contents()->GetController().GetBackForwardCache().Flush();
cleanup_observer.Wait();
RenderFrameHostImpl* rfh_b = root->current_frame_host();
EXPECT_NE(process_a_id, rfh_b->GetProcess()->GetID());
// RenderFrameDeleted should have been called for both the main frame and
// subframe in process A.
EXPECT_EQ(2, rfh_deletion_observer.render_frame_deleted_count());
// The subframe's RenderFrameDeleted happens after both the main frame and
// subframe have become undiscoverable by FromID (i.e., removed from
// g_routing_id_frame_map). The subframe isn't expected to be found during its
// own RenderFrameDeleted (since it has been removed from
// render_frame_host_id_set_), but the partially destructed main frame also
// isn't found at that time when iterating over other frames in the process.
EXPECT_EQ(0, rfh_deletion_observer.render_frame_host_iterator_count());
}
#if BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_F(RenderProcessHostTest, ZeroExecutionTimes) {
// This test only works if the renderer process is sandboxed.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
sandbox::policy::switches::kNoSandbox)) {
return;
}
base::HistogramTester histogram_tester;
RenderProcessHost* process = RenderProcessHostImpl::CreateRenderProcessHost(
ShellContentBrowserClient::Get()->browser_context(), nullptr);
RenderProcessHostWatcher process_watcher(
process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_READY);
process->Init();
process_watcher.Wait();
EXPECT_TRUE(process->IsReady());
histogram_tester.ExpectUniqueSample(
"BrowserRenderProcessHost.SuspendedChild.UserExecutionRecorded", false,
1);
histogram_tester.ExpectUniqueSample(
"BrowserRenderProcessHost.SuspendedChild.KernelExecutionRecorded", false,
1);
process->Cleanup();
}
#endif // BUILDFLAG(IS_WIN)
} // namespace content