| // 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/power_monitor/power_observer.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/renderer_host/spare_render_process_host_manager_impl.h" |
| #include "content/browser/service_host/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, |
| base::PowerStateObserver::BatteryPowerStatus expected_state) { |
| base::RunLoop run_loop; |
| power_monitor_test->QueryNextState(base::BindOnce( |
| [](base::RunLoop* loop, |
| base::PowerStateObserver::BatteryPowerStatus expected_state, |
| base::PowerStateObserver::BatteryPowerStatus battery_power_status) { |
| EXPECT_EQ(expected_state, battery_power_status); |
| 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(battery_power_status_); |
| clients_.Add(std::move(power_monitor_client)); |
| } |
| |
| void OnPowerStateChange( |
| base::PowerStateObserver::BatteryPowerStatus battery_power_status) { |
| battery_power_status_ = battery_power_status; |
| for (auto& client : clients_) |
| client->PowerStateChange(battery_power_status); |
| } |
| |
| private: |
| base::PowerStateObserver::BatteryPowerStatus battery_power_status_ = |
| base::PowerStateObserver::BatteryPowerStatus::kUnknown; |
| |
| 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(ChildProcessId 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), |
| render_process_id)); |
| } |
| |
| 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( |
| base::PowerStateObserver::BatteryPowerStatus battery_power_status) { |
| power_monitor_message_broadcaster_.OnPowerStateChange(battery_power_status); |
| } |
| |
| private: |
| void BindForRendererOnMainThread( |
| mojo::PendingReceiver<device::mojom::PowerMonitor> receiver, |
| ChildProcessId render_process_id) { |
| // We can receive binding requests for the spare RenderProcessHost -- this |
| // might happen before the test has provided the |renderer_bound_closure_|. |
| auto* render_process_host = |
| content::RenderProcessHost::FromID(render_process_id); |
| if (!render_process_host || render_process_host->IsSpare()) { |
| return; |
| } |
| |
| ASSERT_TRUE(renderer_bound_closure_); |
| ++request_count_from_renderer_; |
| std::move(renderer_bound_closure_).Run(); |
| |
| 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( |
| base::PowerStateObserver::BatteryPowerStatus::kBatteryPower); |
| // Verify renderer process battery_power_status changed to battery power. |
| VerifyPowerStateInChildProcess( |
| power_monitor_renderer.get(), |
| base::PowerStateObserver::BatteryPowerStatus::kBatteryPower); |
| |
| SimulatePowerStateChange( |
| base::PowerStateObserver::BatteryPowerStatus::kExternalPower); |
| // Verify renderer process battery_power_status changed to external power. |
| VerifyPowerStateInChildProcess( |
| power_monitor_renderer.get(), |
| base::PowerStateObserver::BatteryPowerStatus::kExternalPower); |
| |
| SimulatePowerStateChange( |
| base::PowerStateObserver::BatteryPowerStatus::kUnknown); |
| // Verify renderer process battery_power_status becomes unknown. |
| VerifyPowerStateInChildProcess( |
| power_monitor_renderer.get(), |
| base::PowerStateObserver::BatteryPowerStatus::kUnknown); |
| } |
| |
| 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( |
| base::PowerStateObserver::BatteryPowerStatus::kBatteryPower); |
| // Verify renderer process battery_power_status changed to battery power. |
| VerifyPowerStateInChildProcess( |
| power_monitor_utility.get(), |
| base::PowerStateObserver::BatteryPowerStatus::kBatteryPower); |
| |
| SimulatePowerStateChange( |
| base::PowerStateObserver::BatteryPowerStatus::kExternalPower); |
| // Verify renderer process battery_power_status changed to external power. |
| VerifyPowerStateInChildProcess( |
| power_monitor_utility.get(), |
| base::PowerStateObserver::BatteryPowerStatus::kExternalPower); |
| } |
| |
| 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( |
| base::PowerStateObserver::BatteryPowerStatus::kBatteryPower); |
| // Verify gpu process battery_power_status changed to battery power. |
| VerifyPowerStateInChildProcess( |
| power_monitor_gpu.get(), |
| base::PowerStateObserver::BatteryPowerStatus::kBatteryPower); |
| |
| SimulatePowerStateChange( |
| base::PowerStateObserver::BatteryPowerStatus::kExternalPower); |
| // Verify gpu process battery_power_status changed to external power. |
| VerifyPowerStateInChildProcess( |
| power_monitor_gpu.get(), |
| base::PowerStateObserver::BatteryPowerStatus::kExternalPower); |
| } |
| |
| } // namespace |
| |
| } // namespace content |