// Copyright 2017 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <utility> #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "content/browser/renderer_host/render_process_host_impl.h" #include "content/browser/utility_process_host.h" #include "content/public/browser/browser_child_process_host.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_data.h" #include "content/public/browser/gpu_service_registry.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_features.h" #include "content/public/common/process_type.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" #include "content/shell/browser/shell.h" #include "content/shell/common/power_monitor_test.mojom.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/remote_set.h" #include "services/device/public/mojom/power_monitor.mojom.h" namespace content { namespace { void VerifyPowerStateInChildProcess(mojom::PowerMonitorTest* power_monitor_test, bool expected_state) { base::RunLoop run_loop; power_monitor_test->QueryNextState(base::BindOnce( [](base::RunLoop* loop, bool expected_state, bool on_battery_power) { EXPECT_EQ(expected_state, on_battery_power); loop->Quit(); }, &run_loop, expected_state)); run_loop.Run(); } class MockPowerMonitorMessageBroadcaster : public device::mojom::PowerMonitor { public: MockPowerMonitorMessageBroadcaster() = default; MockPowerMonitorMessageBroadcaster( const MockPowerMonitorMessageBroadcaster&) = delete; MockPowerMonitorMessageBroadcaster& operator=( const MockPowerMonitorMessageBroadcaster&) = delete; ~MockPowerMonitorMessageBroadcaster() override = default; void Bind(mojo::PendingReceiver<device::mojom::PowerMonitor> receiver) { receivers_.Add(this, std::move(receiver)); } // device::mojom::PowerMonitor: void AddClient(mojo::PendingRemote<device::mojom::PowerMonitorClient> pending_power_monitor_client) override { mojo::Remote<device::mojom::PowerMonitorClient> power_monitor_client( std::move(pending_power_monitor_client)); power_monitor_client->PowerStateChange(on_battery_power_); clients_.Add(std::move(power_monitor_client)); } void OnPowerStateChange(bool on_battery_power) { on_battery_power_ = on_battery_power; for (auto& client : clients_) client->PowerStateChange(on_battery_power); } private: bool on_battery_power_ = false; mojo::ReceiverSet<device::mojom::PowerMonitor> receivers_; mojo::RemoteSet<device::mojom::PowerMonitorClient> clients_; }; class PowerMonitorTest : public ContentBrowserTest { public: PowerMonitorTest() { // Intercept PowerMonitor binding requests from all types of child // processes. RenderProcessHost::InterceptBindHostReceiverForTesting(base::BindRepeating( &PowerMonitorTest::BindForRenderer, base::Unretained(this))); BrowserChildProcessHost::InterceptBindHostReceiverForTesting( base::BindRepeating(&PowerMonitorTest::BindForNonRenderer, base::Unretained(this))); } PowerMonitorTest(const PowerMonitorTest&) = delete; PowerMonitorTest& operator=(const PowerMonitorTest&) = delete; ~PowerMonitorTest() override { RenderProcessHost::InterceptBindHostReceiverForTesting( base::NullCallback()); BrowserChildProcessHost::InterceptBindHostReceiverForTesting( base::NullCallback()); } void BindForRenderer(int render_process_id, mojo::GenericPendingReceiver* receiver) { auto r = receiver->As<device::mojom::PowerMonitor>(); if (!r) return; GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&PowerMonitorTest::BindForRendererOnMainThread, base::Unretained(this), std::move(r))); } void BindForNonRenderer(BrowserChildProcessHost* process_host, mojo::GenericPendingReceiver* receiver) { auto r = receiver->As<device::mojom::PowerMonitor>(); if (!r) return; GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&PowerMonitorTest::BindForNonRendererOnMainThread, base::Unretained(this), process_host->GetData().process_type, std::move(r))); } protected: void StartUtilityProcess( mojo::Remote<mojom::PowerMonitorTest>* power_monitor_test, base::OnceClosure utility_bound_closure) { utility_bound_closure_ = std::move(utility_bound_closure); UtilityProcessHost* host = new UtilityProcessHost(); host->SetMetricsName("test_process"); host->SetName(u"TestProcess"); EXPECT_TRUE(host->Start()); host->GetChildProcess()->BindReceiver( power_monitor_test->BindNewPipeAndPassReceiver()); } void set_renderer_bound_closure(base::OnceClosure closure) { renderer_bound_closure_ = std::move(closure); } void set_gpu_bound_closure(base::OnceClosure closure) { gpu_bound_closure_ = std::move(closure); } int request_count_from_renderer() { return request_count_from_renderer_; } int request_count_from_utility() { return request_count_from_utility_; } int request_count_from_gpu() { return request_count_from_gpu_; } void SimulatePowerStateChange(bool on_battery_power) { power_monitor_message_broadcaster_.OnPowerStateChange(on_battery_power); } private: void BindForRendererOnMainThread( mojo::PendingReceiver<device::mojom::PowerMonitor> receiver) { // We can receiver binding requests for the spare RenderProcessHost -- this // might happen before the test has provided the |renderer_bound_closure_|. if (renderer_bound_closure_) { ++request_count_from_renderer_; std::move(renderer_bound_closure_).Run(); } else { DCHECK(RenderProcessHostImpl::GetSpareRenderProcessHostForTesting()); } power_monitor_message_broadcaster_.Bind(std::move(receiver)); } void BindForNonRendererOnMainThread( int process_type, mojo::PendingReceiver<device::mojom::PowerMonitor> receiver) { if (process_type == PROCESS_TYPE_UTILITY) { if (utility_bound_closure_) { ++request_count_from_utility_; std::move(utility_bound_closure_).Run(); } } else if (process_type == PROCESS_TYPE_GPU) { ++request_count_from_gpu_; // We ignore null gpu_bound_closure_ here for two possible scenarios: // - TestRendererProcess and TestUtilityProcess also result in spinning // up GPU processes as a side effect, but they do not set valid // gpu_bound_closure_. // - As GPU process is started during setup of browser test suite, so // it's possible that TestGpuProcess execution may have not started // yet when the PowerMonitor bind request comes here, in such case // gpu_bound_closure_ will also be null. if (gpu_bound_closure_) std::move(gpu_bound_closure_).Run(); } power_monitor_message_broadcaster_.Bind(std::move(receiver)); } int request_count_from_renderer_ = 0; int request_count_from_utility_ = 0; int request_count_from_gpu_ = 0; base::OnceClosure renderer_bound_closure_; base::OnceClosure gpu_bound_closure_; base::OnceClosure utility_bound_closure_; MockPowerMonitorMessageBroadcaster power_monitor_message_broadcaster_; }; IN_PROC_BROWSER_TEST_F(PowerMonitorTest, TestRendererProcess) { ASSERT_EQ(0, request_count_from_renderer()); base::RunLoop run_loop; set_renderer_bound_closure(run_loop.QuitClosure()); ASSERT_TRUE(NavigateToURL(shell(), GetTestUrl(".", "simple_page.html"))); run_loop.Run(); EXPECT_EQ(1, request_count_from_renderer()); mojo::Remote<mojom::PowerMonitorTest> power_monitor_renderer; RenderProcessHost* rph = shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(); rph->BindReceiver(power_monitor_renderer.BindNewPipeAndPassReceiver()); // Ensure that the PowerMonitorTestImpl instance has been created and is // observing power state changes in the child process before simulating a // power state change. power_monitor_renderer.FlushForTesting(); SimulatePowerStateChange(true); // Verify renderer process on_battery_power changed to true. VerifyPowerStateInChildProcess(power_monitor_renderer.get(), true); SimulatePowerStateChange(false); // Verify renderer process on_battery_power changed to false. VerifyPowerStateInChildProcess(power_monitor_renderer.get(), false); } IN_PROC_BROWSER_TEST_F(PowerMonitorTest, TestUtilityProcess) { mojo::Remote<mojom::PowerMonitorTest> power_monitor_utility; ASSERT_EQ(0, request_count_from_utility()); base::RunLoop run_loop; StartUtilityProcess(&power_monitor_utility, run_loop.QuitClosure()); run_loop.Run(); EXPECT_EQ(1, request_count_from_utility()); // Ensure that the PowerMonitorTestImpl instance has been created and is // observing power state changes in the child process before simulating a // power state change. power_monitor_utility.FlushForTesting(); SimulatePowerStateChange(true); // Verify utility process on_battery_power changed to true. VerifyPowerStateInChildProcess(power_monitor_utility.get(), true); SimulatePowerStateChange(false); // Verify utility process on_battery_power changed to false. VerifyPowerStateInChildProcess(power_monitor_utility.get(), false); } IN_PROC_BROWSER_TEST_F(PowerMonitorTest, TestGpuProcess) { // As gpu process is started automatically during the setup period of browser // test suite, it may have already started and bound PowerMonitor interface to // Device Service before execution of this TestGpuProcess test. So here we // do not wait for the connection if we found it has already been established. if (request_count_from_gpu() != 1) { ASSERT_EQ(0, request_count_from_gpu()); base::RunLoop run_loop; set_gpu_bound_closure(run_loop.QuitClosure()); // Wait for the connection from gpu process. run_loop.Run(); } EXPECT_EQ(1, request_count_from_gpu()); mojo::Remote<mojom::PowerMonitorTest> power_monitor_gpu; BindInterfaceInGpuProcess(power_monitor_gpu.BindNewPipeAndPassReceiver()); // Ensure that the PowerMonitorTestImpl instance has been created and is // observing power state changes in the child process before simulating a // power state change. power_monitor_gpu.FlushForTesting(); SimulatePowerStateChange(true); // Verify gpu process on_battery_power changed to true. VerifyPowerStateInChildProcess(power_monitor_gpu.get(), true); SimulatePowerStateChange(false); // Verify gpu process on_battery_power changed to false. VerifyPowerStateInChildProcess(power_monitor_gpu.get(), false); } } // namespace } // namespace content