| // 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 <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.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/task/bind_post_task.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/threading/hang_watcher.h" |
| #include "base/timer/elapsed_timer.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_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/browsing_data_remover.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_creation_observer.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/pseudonymization_util.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/browsing_data_remover_test_util.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_content_browser_client.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/test/content_browser_test_utils_internal.h" |
| #include "content/test/storage_partition_test_helpers.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/features.h" |
| #include "base/files/file.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "mojo/public/cpp/platform/platform_handle_security_util_win.h" |
| #include "sandbox/policy/switches.h" |
| #endif |
| |
| using testing::_; |
| using testing::InSequence; |
| using testing::Mock; |
| using testing::StrictMock; |
| using testing::Test; |
| |
| 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 RenderProcessHostTestBase : public ContentBrowserTest, |
| public RenderProcessHostObserver { |
| public: |
| RenderProcessHostTestBase() = 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 ContentBrowserTestContentBrowserClient { |
| 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 ContentBrowserTestContentBrowserClient { |
| 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; |
| } |
| }; |
| |
| // A ContentBrowserClient that can wait for calls to |
| // `blink::mojom::KeepAliveHandle`. |
| class KeepAliveHandleContentBrowserClient |
| : public ContentBrowserTestContentBrowserClient { |
| public: |
| explicit KeepAliveHandleContentBrowserClient(base::OnceClosure callback) { |
| started_callback_ = std::move(callback); |
| } |
| void OnKeepaliveRequestStarted(BrowserContext* browser_context) override { |
| ContentBrowserTestContentBrowserClient::OnKeepaliveRequestStarted( |
| browser_context); |
| CHECK(started_callback_); |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, |
| std::move(started_callback_)); |
| } |
| |
| private: |
| base::OnceClosure started_callback_; |
| }; |
| |
| class RenderProcessHostTest : public RenderProcessHostTestBase, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| RenderProcessHostTest() = default; |
| |
| void SetUp() override { |
| if (IsKeepAliveInBrowserMigrationEnabled()) { |
| feature_list_.InitAndEnableFeature( |
| blink::features::kKeepAliveInBrowserMigration); |
| } else { |
| feature_list_.InitAndDisableFeature( |
| blink::features::kKeepAliveInBrowserMigration); |
| } |
| RenderProcessHostTestBase::SetUp(); |
| } |
| |
| protected: |
| bool IsKeepAliveInBrowserMigrationEnabled() { return GetParam(); } |
| |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| RenderProcessHostTest, |
| testing::Values(false, true), |
| [](const testing::TestParamInfo<RenderProcessHostTest::ParamType>& info) { |
| return info.param ? "KeepAliveInBrowserMigration" : "Default"; |
| }); |
| |
| IN_PROC_BROWSER_TEST_P(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_P(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_P(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_P(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_P(RenderProcessHostTest, |
| SpareRendererSurpressedMaxProcesses) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| SpareRendererContentBrowserClient 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); |
| } |
| |
| // Check that the spare renderer is dropped if an existing process is reused. |
| IN_PROC_BROWSER_TEST_P(RenderProcessHostTest, SpareRendererOnProcessReuse) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| NonSpareRendererContentBrowserClient 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())); |
| } |
| |
| // 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_P(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_P(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_P(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"); |
| CustomStoragePartitionBrowserClient modified_client(GURL("http://a.com/")); |
| |
| 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); |
| } |
| |
| 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_P(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_P(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, AcrossTasksDanglingUntriaged> 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_P(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_P(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_P(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();")) |
| << "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, base::BindPostTaskToCurrentDefault(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 RenderProcessHostTestBase { |
| public: |
| void SetUp() override { |
| // Pixel output is needed when digging pixel values out of video tags for |
| // verification in VideoCaptureStream tests. |
| EnablePixelOutput(); |
| RenderProcessHostTestBase::SetUp(); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // These flags are necessary to emulate camera input for getUserMedia() |
| // tests. |
| command_line->AppendSwitch(switches::kUseFakeUIForMediaStream); |
| RenderProcessHostTestBase::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_TRUE(ExecJs(shell(), "getUserMediaAndExpectSuccess({video: true});")) |
| << "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_TRUE(ExecJs(shell(), "getUserMediaAndStop({video: true});")) |
| << "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_TRUE(ExecJs(shell(), "getUserMediaAndExpectSuccess({video: true});")) |
| << "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, base::BindPostTaskToCurrentDefault(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_TRUE(ExecJs( |
| shell(), "getUserMediaAndExpectSuccess({video: false, audio: true});")) |
| << "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_TRUE(ExecJs( |
| shell(), "getUserMediaAndExpectSuccess({video: false, audio: true});")) |
| << "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, base::BindPostTaskToCurrentDefault(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_P(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)); |
| |
| if (IsKeepAliveInBrowserMigrationEnabled()) { |
| // When fetch keepalive in browser migration is enabled, the process will be |
| // able to exit immediately. Instead of verifying the time it takes until it |
| // exits, verify that the `keep_alive_ref_count()` is as expected. |
| EXPECT_EQ(rph->keep_alive_ref_count(), 0); |
| } |
| |
| // 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)); |
| } |
| |
| // TODO(crbug.com/1462719): Fix and re-enable. |
| IN_PROC_BROWSER_TEST_P(RenderProcessHostTest, |
| DISABLED_KeepAliveRendererProcessWithServiceWorker) { |
| if (IsKeepAliveInBrowserMigrationEnabled()) { |
| // TODO(crbug.com/1356128): Add keepalive in-browser support for workers. |
| return; |
| } |
| |
| 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(), 1); |
| EXPECT_EQ(rph->keep_alive_ref_count(), 0); |
| |
| // 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. |
| if (!IsKeepAliveInBrowserMigrationEnabled()) { |
| EXPECT_EQ(rph->keep_alive_ref_count(), 1); |
| } else { |
| // When fetch keepalive in browser migration is enabled, the process will be |
| // able to exit immediately. |
| EXPECT_EQ(rph->keep_alive_ref_count(), 0); |
| } |
| EXPECT_EQ(rph->worker_ref_count(), 1); |
| } |
| |
| // 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_P(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)); |
| |
| if (IsKeepAliveInBrowserMigrationEnabled()) { |
| // When fetch keepalive in browser migration is enabled, the process will be |
| // able to exit immediately. Instead of verifying the time it takes until it |
| // exits, verify that the `keep_alive_ref_count()` is as expected. |
| EXPECT_EQ(rph->keep_alive_ref_count(), 0); |
| } |
| |
| base::TimeTicks start = base::TimeTicks::Now(); |
| EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,<p>hello</p>"))); |
| |
| WaitUntilProcessExits(1); |
| |
| if (!IsKeepAliveInBrowserMigrationEnabled()) { |
| 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_P(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)); |
| |
| if (IsKeepAliveInBrowserMigrationEnabled()) { |
| // Wait for the page to make the keepalive request. |
| const std::u16string waiting = u"Waiting"; |
| TitleWatcher watcher(shell()->web_contents(), waiting); |
| ASSERT_EQ(waiting, watcher.WaitAndGetTitle()); |
| // When fetch keepalive in browser migration is enabled, the process will be |
| // able to exit immediately. Instead of verifying the time it takes until it |
| // exits, verify that the `keep_alive_ref_count()` is as expected. |
| EXPECT_EQ(rph->keep_alive_ref_count(), 0); |
| } |
| |
| base::TimeTicks start = base::TimeTicks::Now(); |
| EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,<p>hello</p>"))); |
| |
| WaitUntilProcessExits(1); |
| |
| if (!IsKeepAliveInBrowserMigrationEnabled()) { |
| EXPECT_GE(base::TimeTicks::Now() - start, base::Seconds(1)); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P(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_P(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_P(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_P(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_P(RenderProcessHostTest, FastShutdownForStartingProcess) { |
| RenderProcessHost* process = RenderProcessHostImpl::CreateRenderProcessHost( |
| ShellContentBrowserClient::Get()->browser_context(), nullptr); |
| process->Init(); |
| EXPECT_TRUE(process->FastShutdownIfPossible()); |
| process->Cleanup(); |
| } |
| |
| // Verifies that a fast shutdown is possible with pending keepalive request. |
| IN_PROC_BROWSER_TEST_P(RenderProcessHostTest, |
| FastShutdownWithKeepAliveRequest) { |
| base::RunLoop request_sent_loop, request_handled_loop; |
| KeepAliveHandleContentBrowserClient browser_client( |
| request_handled_loop.QuitClosure()); |
| embedded_test_server()->RegisterRequestHandler( |
| base::BindRepeating(HandleHungBeacon, request_sent_loop.QuitClosure())); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| const auto kTestUrl = embedded_test_server()->GetURL("/send-beacon.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kTestUrl)); |
| RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| RenderProcessHostImpl* rph = |
| static_cast<RenderProcessHostImpl*>(rfh->GetProcess()); |
| // Ensure keepalive request is sent. |
| request_sent_loop.Run(); |
| |
| if (IsKeepAliveInBrowserMigrationEnabled()) { |
| // When fetch keepalive in browser migration is enabled, the process will be |
| // able to exit immediately. Verify that the `keep_alive_ref_count()` is as |
| // expected. |
| EXPECT_EQ(rph->keep_alive_ref_count(), 0); |
| EXPECT_TRUE(rph->FastShutdownIfPossible()); |
| } else { |
| request_handled_loop.Run(); |
| EXPECT_EQ(rph->keep_alive_ref_count(), 1); |
| EXPECT_FALSE(rph->FastShutdownIfPossible()); |
| } |
| } |
| |
| // 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_P(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()); |
| |
| std::vector<RenderFrameHost*> same_process_rfhs; |
| auto non_speculative_rfh_collector = |
| [&same_process_rfhs](RenderFrameHost* rfh) { |
| auto* rfhi = static_cast<RenderFrameHostImpl*>(rfh); |
| EXPECT_NE(RenderFrameHostImpl::LifecycleStateImpl::kSpeculative, |
| rfhi->lifecycle_state()); |
| same_process_rfhs.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()); |
| rph_a->ForEachRenderFrameHost(non_speculative_rfh_collector); |
| 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(non_speculative_rfh_collector); |
| 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. |
| ASSERT_TRUE(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(non_speculative_rfh_collector); |
| 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_P(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_P(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(ExecJs(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(ExecJs(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_P(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. |
| std::vector<RenderFrameHost*> all_rfhs; |
| RenderProcessHost* process = render_frame_host->GetProcess(); |
| process->ForEachRenderFrameHost( |
| [&all_rfhs](RenderFrameHost* rfh) { all_rfhs.push_back(rfh); }); |
| |
| // 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_P(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_P(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(); |
| } |
| |
| class RenderProcessHostWriteableFileTest |
| : public RenderProcessHostTestBase, |
| public ::testing::WithParamInterface< |
| std::tuple</*enforcement_enabled=*/bool, |
| /*add_no_execute_flags=*/bool>> { |
| public: |
| void SetUp() override { |
| enforcement_feature_.InitWithFeatureState( |
| base::features::kEnforceNoExecutableFileHandles, |
| IsEnforcementEnabled()); |
| RenderProcessHostTestBase::SetUp(); |
| } |
| |
| protected: |
| bool IsEnforcementEnabled() { return std::get<0>(GetParam()); } |
| bool ShouldMarkNoExecute() { return std::get<1>(GetParam()); } |
| |
| private: |
| base::test::ScopedFeatureList enforcement_feature_; |
| }; |
| |
| // This test verifies that the renderer process is wired up correctly with the |
| // mojo invitation flag that indicates that it's untrusted. The other half of |
| // this test that verifies that a security violation actually causes a DCHECK |
| // lives in mojo/core, and can't live here as death tests are not supported for |
| // browser tests. |
| IN_PROC_BROWSER_TEST_P(RenderProcessHostWriteableFileTest, |
| PassUnsafeWriteableExecutableFile) { |
| // This test only works if DCHECKs are enabled. |
| #if !DCHECK_IS_ON() |
| GTEST_SKIP(); |
| #else |
| // This test only works if the renderer process is sandboxed. |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| sandbox::policy::switches::kNoSandbox)) { |
| GTEST_SKIP(); |
| } |
| |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| |
| 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(); |
| |
| mojo::Remote<mojom::TestService> test_service; |
| rph->BindReceiver(test_service.BindNewPipeAndPassReceiver()); |
| |
| uint32_t flags = base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ | |
| base::File::FLAG_WRITE; |
| if (ShouldMarkNoExecute()) { |
| flags = base::File::AddFlagsForPassingToUntrustedProcess(flags); |
| } |
| |
| base::FilePath file_path; |
| base::CreateTemporaryFile(&file_path); |
| base::File temp_file_writeable(file_path, flags); |
| ASSERT_TRUE(temp_file_writeable.IsValid()); |
| |
| bool error_was_called = false; |
| mojo::SetUnsafeFileHandleCallbackForTesting( |
| base::BindLambdaForTesting([&error_was_called]() -> bool { |
| error_was_called = true; |
| return true; |
| })); |
| |
| base::RunLoop run_loop; |
| test_service->PassWriteableFile(std::move(temp_file_writeable), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| // This test should only detect a violation if enforcement is enabled and the |
| // file has not been marked no-execute correctly. |
| bool should_violation_occur = |
| IsEnforcementEnabled() && !ShouldMarkNoExecute(); |
| EXPECT_EQ(should_violation_occur, error_was_called); |
| #endif // DCHECK_IS_ON() |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| RenderProcessHostWriteableFileTest, |
| testing::Combine(/*enforcement_enabled=*/testing::Bool(), |
| /*add_no_execute_flags=*/testing::Bool())); |
| |
| #endif // BUILDFLAG(IS_WIN) |
| |
| // This test verifies that the Pseudonymization salt that is generated in the |
| // browser process is correctly synchronized with a child process, in this case, |
| // two separate renderer processes. |
| IN_PROC_BROWSER_TEST_P(RenderProcessHostTest, |
| SetPseudonymizationSaltSynchronized) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Ensure all sites get dedicated processes during the test. |
| IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); |
| |
| // Create two renderer processes. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", "/simple_page.html"))); |
| RenderProcessHost* rph1 = |
| shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| Shell* second_shell = CreateBrowser(); |
| EXPECT_TRUE(NavigateToURL(second_shell, embedded_test_server()->GetURL( |
| "b.com", "/simple_page.html"))); |
| RenderProcessHost* rph2 = |
| second_shell->web_contents()->GetPrimaryMainFrame()->GetProcess(); |
| |
| // This test needs two processes. |
| EXPECT_NE(rph1->GetProcess().Pid(), rph2->GetProcess().Pid()); |
| |
| const std::string test_string = "testing123"; |
| uint32_t browser_result = |
| PseudonymizationUtil::PseudonymizeStringForTesting(test_string); |
| |
| for (RenderProcessHost* rph : {rph1, rph2}) { |
| mojo::Remote<mojom::TestService> service; |
| rph->BindReceiver(service.BindNewPipeAndPassReceiver()); |
| |
| base::RunLoop run_loop; |
| |
| absl::optional<uint32_t> renderer_result = absl::nullopt; |
| service->PseudonymizeString( |
| test_string, base::BindLambdaForTesting([&](uint32_t result) { |
| renderer_result = result; |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| |
| ASSERT_TRUE(renderer_result.has_value()); |
| EXPECT_EQ(*renderer_result, browser_result); |
| } |
| } |
| |
| class CreationObserver : public RenderProcessHostCreationObserver { |
| public: |
| explicit CreationObserver( |
| base::RepeatingClosure closure = base::RepeatingClosure()) |
| : closure_(std::move(closure)) {} |
| |
| // content::RenderProcessHostCreationObserver: |
| void OnRenderProcessHostCreated(RenderProcessHost* process_host) override { |
| if (closure_) { |
| closure_.Run(); |
| } |
| } |
| |
| private: |
| base::RepeatingClosure closure_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(RenderProcessHostTest, HostCreationObserved) { |
| int created_count = 0; |
| CreationObserver creation_observer( |
| base::BindLambdaForTesting([&created_count]() { ++created_count; })); |
| RenderProcessHost* process = RenderProcessHostImpl::CreateRenderProcessHost( |
| ShellContentBrowserClient::Get()->browser_context(), nullptr); |
| RenderProcessHostWatcher process_watcher( |
| process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_READY); |
| process->Init(); |
| process_watcher.Wait(); |
| ASSERT_TRUE(process->IsReady()); |
| EXPECT_EQ(1, created_count); |
| process->Cleanup(); |
| } |
| |
| // Notification of RenderProcessHost creation should not crash if creation |
| // observers are added during notification of another creation observer. |
| IN_PROC_BROWSER_TEST_P(RenderProcessHostTest, |
| HostCreationObserversAddedDuringNotification) { |
| std::vector<std::unique_ptr<CreationObserver>> added_creation_observers; |
| const int kObserversToAdd = 1000; |
| int added_observer_notification_count = 0; |
| const auto increment_added_observer_notification_count = |
| base::BindLambdaForTesting([&added_observer_notification_count]() { |
| ++added_observer_notification_count; |
| }); |
| CreationObserver creation_observer1(base::BindLambdaForTesting( |
| [&added_creation_observers, |
| increment_added_observer_notification_count]() { |
| for (int i = 0; i < kObserversToAdd; ++i) { |
| added_creation_observers.push_back(std::make_unique<CreationObserver>( |
| increment_added_observer_notification_count)); |
| } |
| })); |
| CreationObserver creation_observer2; |
| |
| 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()); |
| EXPECT_EQ(kObserversToAdd, added_observer_notification_count); |
| process->Cleanup(); |
| } |
| |
| // Notification of RenderProcessHost creation should not crash if a creation |
| // observer is destroyed during notification of another creation observer. |
| IN_PROC_BROWSER_TEST_P(RenderProcessHostTest, |
| HostCreationObserversDestroyedDuringNotification) { |
| base::OnceClosure destroy_second_observer; |
| CreationObserver creation_observer1( |
| base::BindLambdaForTesting([&destroy_second_observer]() { |
| std::move(destroy_second_observer).Run(); |
| })); |
| auto creation_observer2 = std::make_unique<CreationObserver>(); |
| destroy_second_observer = base::BindLambdaForTesting( |
| [&creation_observer2]() { creation_observer2.reset(); }); |
| |
| 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()); |
| EXPECT_EQ(nullptr, creation_observer2.get()); |
| process->Cleanup(); |
| } |
| |
| namespace { |
| |
| bool FetchScript(Shell* shell, GURL url) { |
| EvalJsResult result = EvalJs(shell, JsReplace(R"( |
| new Promise(resolve => { |
| const script = document.createElement("script"); |
| script.src = $1; |
| script.onerror = () => resolve("error"); |
| script.onload = () => resolve("fetched"); |
| document.body.appendChild(script); |
| }); |
| )", |
| url)); |
| return result.ExtractString() == "fetched"; |
| } |
| |
| } // namespace |
| |
| // Tests that BrowsingDataRemover clears renderer's in-memory resource cache. |
| IN_PROC_BROWSER_TEST_P(RenderProcessHostTest, ClearResourceCache) { |
| constexpr const char* kScriptPath = "/cacheable.js"; |
| |
| // Count the number of requests from the renderer. This doesn't count requests |
| // that are served via the renderer's in-memory cache. |
| size_t num_script_requests_from_renderer = 0; |
| embedded_test_server()->RegisterRequestMonitor(base::BindLambdaForTesting( |
| [&](const net::test_server::HttpRequest& request) { |
| if (request.relative_url == kScriptPath) { |
| ++num_script_requests_from_renderer; |
| } |
| })); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| const GURL kUrl = embedded_test_server()->GetURL("/title1.html"); |
| const GURL kScriptUrl = embedded_test_server()->GetURL(kScriptPath); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), kUrl)); |
| |
| // The first fetch. The renderer's in-memory cache doesn't contain a response |
| // so the counter should be incremented. |
| EXPECT_TRUE(FetchScript(shell(), kScriptUrl)); |
| ASSERT_EQ(num_script_requests_from_renderer, 1u); |
| |
| // The second fetch. The response will be served from the renderer's in-memory |
| // cache. The counter should not be incremented. |
| EXPECT_TRUE(FetchScript(shell(), kScriptUrl)); |
| ASSERT_EQ(num_script_requests_from_renderer, 1u); |
| |
| // Clear the renderer's in-memory cache. |
| BrowsingDataRemover* remover = |
| shell()->web_contents()->GetBrowserContext()->GetBrowsingDataRemover(); |
| BrowsingDataRemoverCompletionObserver observer(remover); |
| remover->RemoveAndReply( |
| /*delete_begin=*/base::Time(), /*delete_end=*/base::Time::Max(), |
| BrowsingDataRemover::DATA_TYPE_CACHE, |
| BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB, &observer); |
| observer.BlockUntilCompletion(); |
| |
| // Fetch again. The response in the renderer's in-memory cache was evicted so |
| // the counter should be incremented. |
| EXPECT_TRUE(FetchScript(shell(), kScriptUrl)); |
| ASSERT_EQ(num_script_requests_from_renderer, 2u); |
| } |
| |
| // Tests that RenderProcessHost reuse works correctly even if the site URL of a |
| // URL changes. |
| IN_PROC_BROWSER_TEST_P(RenderProcessHostTest, ReuseSiteURLChanges) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL kUrl = embedded_test_server()->GetURL("/title1.html"); |
| const GURL kModifiedSiteUrl("custom-scheme://custom"); |
| |
| // At first, trying to get a RenderProcessHost with the |
| // REUSE_PENDING_OR_COMMITTED_SITE policy should return a new process. |
| BrowserContext* context = shell()->web_contents()->GetBrowserContext(); |
| scoped_refptr<SiteInstanceImpl> site_instance = |
| SiteInstanceImpl::CreateReusableInstanceForTesting(context, kUrl); |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| EXPECT_NE(root->current_frame_host()->GetProcess(), |
| site_instance->GetProcess()); |
| |
| // Have the main frame navigate to the first url. Getting a RenderProcessHost |
| // with the REUSE_PENDING_OR_COMMITTED_SITE policy should now return the |
| // process of the main RFH. |
| EXPECT_TRUE(NavigateToURL(shell(), kUrl)); |
| site_instance = |
| SiteInstanceImpl::CreateReusableInstanceForTesting(context, kUrl); |
| EXPECT_EQ(root->current_frame_host()->GetProcess(), |
| site_instance->GetProcess()); |
| |
| // Install the custom ContentBrowserClient. Site URLs are now modified. |
| // Getting a RenderProcessHost with the REUSE_PENDING_OR_COMMITTED_SITE policy |
| // should no longer return the process of the main RFH, as the RFH is |
| // registered with the normal site URL. |
| { |
| EffectiveURLContentBrowserTestContentBrowserClient modified_client( |
| kUrl, kModifiedSiteUrl, |
| /* requires_dedicated_process */ false); |
| site_instance = |
| SiteInstanceImpl::CreateReusableInstanceForTesting(context, kUrl); |
| EXPECT_NE(root->current_frame_host()->GetProcess(), |
| site_instance->GetProcess()); |
| |
| // Reload. Getting a RenderProcessHost with the |
| // REUSE_PENDING_OR_COMMITTED_SITE policy should now return the process of |
| // the main RFH, as it is now registered with the modified site URL. |
| shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| site_instance = |
| SiteInstanceImpl::CreateReusableInstanceForTesting(context, kUrl); |
| EXPECT_EQ(root->current_frame_host()->GetProcess(), |
| site_instance->GetProcess()); |
| } |
| |
| // Remove the custom ContentBrowserClient. Site URLs are back to normal. |
| // Getting a RenderProcessHost with the REUSE_PENDING_OR_COMMITTED_SITE policy |
| // should no longer return the process of the main RFH, as it is registered |
| // with the modified site URL. |
| site_instance = |
| SiteInstanceImpl::CreateReusableInstanceForTesting(context, kUrl); |
| EXPECT_NE(root->current_frame_host()->GetProcess(), |
| site_instance->GetProcess()); |
| |
| // Reload. Getting a RenderProcessHost with the |
| // REUSE_PENDING_OR_COMMITTED_SITE policy should now return the process of the |
| // main RFH, as it is now registered with the regular site URL. |
| shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| site_instance = |
| SiteInstanceImpl::CreateReusableInstanceForTesting(context, kUrl); |
| EXPECT_EQ(root->current_frame_host()->GetProcess(), |
| site_instance->GetProcess()); |
| } |
| |
| #if BUILDFLAG(ALLOW_OOP_VIDEO_DECODER) |
| class FakeStableVideoDecoderFactoryService |
| : public media::stable::mojom::StableVideoDecoderFactory { |
| public: |
| FakeStableVideoDecoderFactoryService() = default; |
| FakeStableVideoDecoderFactoryService( |
| const FakeStableVideoDecoderFactoryService&) = delete; |
| FakeStableVideoDecoderFactoryService& operator=( |
| const FakeStableVideoDecoderFactoryService&) = delete; |
| ~FakeStableVideoDecoderFactoryService() override = default; |
| |
| // media::stable::mojom::StableVideoDecoderFactory implementation. |
| void CreateStableVideoDecoder( |
| mojo::PendingReceiver<media::stable::mojom::StableVideoDecoder> receiver, |
| mojo::PendingRemote<media::stable::mojom::StableVideoDecoderTracker> |
| tracker) final { |
| video_decoders_.Add( |
| std::make_unique<FakeStableVideoDecoderService>(std::move(tracker)), |
| std::move(receiver)); |
| } |
| |
| private: |
| class FakeStableVideoDecoderService |
| : public media::stable::mojom::StableVideoDecoder { |
| public: |
| explicit FakeStableVideoDecoderService( |
| mojo::PendingRemote<media::stable::mojom::StableVideoDecoderTracker> |
| tracker) |
| : tracker_(std::move(tracker)) {} |
| FakeStableVideoDecoderService(const FakeStableVideoDecoderService&) = |
| delete; |
| FakeStableVideoDecoderService& operator=( |
| const FakeStableVideoDecoderService&) = delete; |
| ~FakeStableVideoDecoderService() override = default; |
| |
| // media::stable::mojom::StableVideoDecoder implementation. |
| void GetSupportedConfigs(GetSupportedConfigsCallback callback) final { |
| std::move(callback).Run({}, media::VideoDecoderType::kTesting); |
| } |
| void Construct( |
| mojo::PendingAssociatedRemote<media::stable::mojom::VideoDecoderClient> |
| stable_video_decoder_client_remote, |
| mojo::PendingRemote<media::stable::mojom::MediaLog> |
| stable_media_log_remote, |
| mojo::PendingReceiver<media::stable::mojom::VideoFrameHandleReleaser> |
| stable_video_frame_handle_releaser_receiver, |
| mojo::ScopedDataPipeConsumerHandle decoder_buffer_pipe, |
| const gfx::ColorSpace& target_color_space) final {} |
| void Initialize( |
| const media::VideoDecoderConfig& config, |
| bool low_delay, |
| mojo::PendingRemote<media::stable::mojom::StableCdmContext> cdm_context, |
| InitializeCallback callback) final {} |
| void Decode(const scoped_refptr<media::DecoderBuffer>& buffer, |
| DecodeCallback callback) final {} |
| void Reset(ResetCallback callback) final {} |
| |
| private: |
| mojo::Remote<media::stable::mojom::StableVideoDecoderTracker> tracker_; |
| }; |
| |
| mojo::UniqueReceiverSet<media::stable::mojom::StableVideoDecoder> |
| video_decoders_; |
| }; |
| |
| class RenderProcessHostTestStableVideoDecoderTest |
| : public RenderProcessHostTestBase { |
| public: |
| RenderProcessHostTestStableVideoDecoderTest() |
| : stable_video_decoder_factory_receiver_( |
| &stable_video_decoder_factory_service_) {} |
| |
| void SetUp() override { |
| feature_list_.InitAndEnableFeature(media::kUseOutOfProcessVideoDecoding); |
| RenderProcessHostTestBase::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| RenderProcessHostImpl::SetStableVideoDecoderFactoryCreationCBForTesting( |
| stable_video_decoder_factory_creation_cb_.Get()); |
| RenderProcessHostImpl::SetStableVideoDecoderEventCBForTesting( |
| stable_video_decoder_event_cb_.Get()); |
| |
| #if BUILDFLAG(PLATFORM_HAS_OPTIONAL_HEVC_SUPPORT) |
| // When Chrome is compiled with |
| // BUILDFLAG(PLATFORM_HAS_OPTIONAL_HEVC_SUPPORT), renderer processes need a |
| // media::mojom::VideoDecoder during startup in order to query for supported |
| // configurations (see content::RenderMediaClient::Initialize()). With |
| // OOP-VD, this should cause the creation of a |
| // media::stable::mojom::StableVideoDecoderFactory in order to create the |
| // corresponding media::stable::mojom::StableVideoDecoder. When the |
| // supported configurations are obtained, the media::mojom::VideoDecoder and |
| // media::stable::mojom::StableVideoDecoder connections should be torn down |
| // thus causing the termination of the |
| // media::stable::mojom::StableVideoDecoderFactory connection. Here, we set |
| // up expectations for that. |
| base::RunLoop run_loop; |
| { |
| InSequence seq; |
| EXPECT_CALL(stable_video_decoder_factory_creation_cb_, Run(_)) |
| .WillOnce( |
| [&](mojo::PendingReceiver< |
| media::stable::mojom::StableVideoDecoderFactory> receiver) { |
| stable_video_decoder_factory_receiver_.Bind( |
| std::move(receiver)); |
| stable_video_decoder_factory_receiver_.set_disconnect_handler( |
| stable_video_decoder_factory_disconnect_cb_.Get()); |
| }); |
| EXPECT_CALL(stable_video_decoder_event_cb_, |
| Run(RenderProcessHostImpl::StableVideoDecoderEvent:: |
| kAllDecodersDisconnected)); |
| EXPECT_CALL(stable_video_decoder_factory_disconnect_cb_, Run()) |
| .WillOnce([&]() { |
| stable_video_decoder_factory_receiver_.reset(); |
| run_loop.Quit(); |
| }); |
| } |
| #endif // BUILDFLAG(PLATFORM_HAS_OPTIONAL_HEVC_SUPPORT) |
| |
| rph_ = RenderProcessHostImpl::CreateRenderProcessHost( |
| ShellContentBrowserClient::Get()->browser_context(), nullptr); |
| ASSERT_TRUE(rph_->Init()); |
| rph_initialized_ = true; |
| |
| #if BUILDFLAG(PLATFORM_HAS_OPTIONAL_HEVC_SUPPORT) |
| run_loop.Run(); |
| ASSERT_TRUE(VerifyAndClearExpectations()); |
| #endif // BUILDFLAG(PLATFORM_HAS_OPTIONAL_HEVC_SUPPORT) |
| } |
| |
| void TearDownOnMainThread() override { |
| // Reset the |stable_video_decoder_factory_receiver_| so that the |
| // disconnection callback is not called on tear down. |
| stable_video_decoder_factory_receiver_.reset(); |
| if (rph_initialized_) { |
| rph_->Cleanup(); |
| } |
| rph_ = nullptr; |
| } |
| |
| protected: |
| bool VerifyAndClearExpectations() { |
| // Note: we verify and clear the expectations for all the mocks. We |
| // intentionally don't early out if verifying one mock fails. |
| bool result = Mock::VerifyAndClearExpectations( |
| &stable_video_decoder_factory_creation_cb_); |
| result = Mock::VerifyAndClearExpectations( |
| &stable_video_decoder_factory_disconnect_cb_) && |
| result; |
| result = |
| Mock::VerifyAndClearExpectations(&stable_video_decoder_event_cb_) && |
| result; |
| return result; |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| |
| StrictMock<base::MockRepeatingCallback< |
| RenderProcessHostImpl::StableVideoDecoderFactoryCreationCB::RunType>> |
| stable_video_decoder_factory_creation_cb_; |
| StrictMock<base::MockOnceCallback<void()>> |
| stable_video_decoder_factory_disconnect_cb_; |
| StrictMock<base::MockRepeatingCallback< |
| RenderProcessHostImpl::StableVideoDecoderEventCB::RunType>> |
| stable_video_decoder_event_cb_; |
| |
| FakeStableVideoDecoderFactoryService stable_video_decoder_factory_service_; |
| mojo::Receiver<media::stable::mojom::StableVideoDecoderFactory> |
| stable_video_decoder_factory_receiver_; |
| |
| raw_ptr<RenderProcessHost> rph_ = nullptr; |
| bool rph_initialized_ = false; |
| }; |
| |
| // Ensures that the StableVideoDecoderFactory connection is terminated after a |
| // delay once all the StableVideoDecoders created with it have disconnected. |
| IN_PROC_BROWSER_TEST_F(RenderProcessHostTestStableVideoDecoderTest, |
| FactoryIsResetAfterDelay) { |
| ASSERT_FALSE(Test::HasFailure()); |
| |
| // First, let's ask the RPH to establish a StableVideoDecoder connection. This |
| // should cause the RPH's StableVideoDecoderFactory to be bound. |
| EXPECT_CALL(stable_video_decoder_factory_creation_cb_, Run(_)) |
| .WillOnce([&](mojo::PendingReceiver< |
| media::stable::mojom::StableVideoDecoderFactory> receiver) { |
| stable_video_decoder_factory_receiver_.Bind(std::move(receiver)); |
| stable_video_decoder_factory_receiver_.set_disconnect_handler( |
| stable_video_decoder_factory_disconnect_cb_.Get()); |
| }); |
| mojo::PendingRemote<media::stable::mojom::StableVideoDecoder> |
| stable_video_decoder_remote; |
| rph_->CreateStableVideoDecoder( |
| stable_video_decoder_remote.InitWithNewPipeAndPassReceiver()); |
| ASSERT_TRUE(VerifyAndClearExpectations()); |
| |
| // Now, let's destroy the StableVideoDecoder connection. Since this was the |
| // only StableVideoDecoder connection, destroying it should cause the RPH's |
| // StableVideoDecoderFactory connection to die after a delay. |
| base::RunLoop run_loop; |
| base::ElapsedTimer reset_stable_video_decoder_factory_timer; |
| { |
| InSequence seq; |
| EXPECT_CALL(stable_video_decoder_event_cb_, |
| Run(RenderProcessHostImpl::StableVideoDecoderEvent:: |
| kAllDecodersDisconnected)); |
| EXPECT_CALL(stable_video_decoder_factory_disconnect_cb_, Run()) |
| .WillOnce([&]() { run_loop.Quit(); }); |
| } |
| stable_video_decoder_remote.reset(); |
| run_loop.Run(); |
| EXPECT_GE(reset_stable_video_decoder_factory_timer.Elapsed(), |
| base::Seconds(3)); |
| } |
| |
| // Ensures that the timer that destroys the StableVideoDecoderFactory connection |
| // when all StableVideoDecoder connections die is stopped if a request to |
| // connect another StableVideoDecoder is received soon enough. |
| IN_PROC_BROWSER_TEST_F(RenderProcessHostTestStableVideoDecoderTest, |
| FactoryResetTimerIsStoppedOnRequestBeforeResetDelay) { |
| ASSERT_FALSE(Test::HasFailure()); |
| |
| // First, let's ask the RPH to establish a StableVideoDecoder connection. This |
| // should cause the RPH's StableVideoDecoderFactory to be bound. |
| EXPECT_CALL(stable_video_decoder_factory_creation_cb_, Run(_)) |
| .WillOnce([&](mojo::PendingReceiver< |
| media::stable::mojom::StableVideoDecoderFactory> receiver) { |
| stable_video_decoder_factory_receiver_.Bind(std::move(receiver)); |
| stable_video_decoder_factory_receiver_.set_disconnect_handler( |
| stable_video_decoder_factory_disconnect_cb_.Get()); |
| }); |
| mojo::PendingRemote<media::stable::mojom::StableVideoDecoder> |
| stable_video_decoder_remote; |
| rph_->CreateStableVideoDecoder( |
| stable_video_decoder_remote.InitWithNewPipeAndPassReceiver()); |
| ASSERT_TRUE(VerifyAndClearExpectations()); |
| |
| // Now, let's destroy the StableVideoDecoder connection. Since this was the |
| // only StableVideoDecoder connection, destroying it should trigger a |
| // kAllDecodersDisconnected event. |
| base::RunLoop run_loop_1; |
| EXPECT_CALL(stable_video_decoder_event_cb_, |
| Run(RenderProcessHostImpl::StableVideoDecoderEvent:: |
| kAllDecodersDisconnected)) |
| .WillOnce([&]() { run_loop_1.Quit(); }); |
| stable_video_decoder_remote.reset(); |
| run_loop_1.Run(); |
| ASSERT_TRUE(VerifyAndClearExpectations()); |
| |
| // Now, let's request another StableVideoDecoder connection immediately. This |
| // should stop the timer that resets the factory. |
| EXPECT_CALL(stable_video_decoder_event_cb_, |
| Run(RenderProcessHostImpl::StableVideoDecoderEvent:: |
| kFactoryResetTimerStopped)); |
| rph_->CreateStableVideoDecoder( |
| stable_video_decoder_remote.InitWithNewPipeAndPassReceiver()); |
| ASSERT_TRUE(VerifyAndClearExpectations()); |
| |
| // Finally, let's wait a few seconds (longer than the delay configured for the |
| // timer that kills the StableVideoDecoderFactory connection). Because the |
| // |stable_video_decoder_factory_disconnect_cb_| is a StrictMock, this should |
| // detect that the StableVideoDecoderFactory connection doesn't die. |
| base::RunLoop run_loop_2; |
| GetUIThreadTaskRunner()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](base::OnceClosure quit_closure) { std::move(quit_closure).Run(); }, |
| run_loop_2.QuitClosure()), |
| base::Seconds(5)); |
| run_loop_2.Run(); |
| ASSERT_TRUE(VerifyAndClearExpectations()); |
| } |
| |
| #endif // BUILDFLAG(ALLOW_OOP_VIDEO_DECODER) |
| |
| } // namespace content |