| // 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 "content/renderer/render_thread_impl.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/base_switches.h" |
| #include "base/command_line.h" |
| #include "base/containers/heap_array.h" |
| #include "base/debug/leak_annotations.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/location.h" |
| #include "base/memory/discardable_memory.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/read_only_shared_memory_region.h" |
| #include "base/memory/structured_shared_memory.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/thread_pool/thread_pool_instance.h" |
| #include "base/test/test_switches.h" |
| #include "base/time/time.h" |
| #include "cc/base/switches.h" |
| #include "content/app/mojo/mojo_init.h" |
| #include "content/common/in_process_child_thread_params.h" |
| #include "content/common/pseudonymization_salt.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/child_process_host.h" |
| #include "content/public/browser/child_process_host_delegate.h" |
| #include "content/public/browser/content_browser_client.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/renderer/content_renderer_client.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/content_test_suite_base.h" |
| #include "content/public/test/test_content_client_initializer.h" |
| #include "content/public/test/test_launcher.h" |
| #include "content/renderer/render_process_impl.h" |
| #include "gpu/config/gpu_switches.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h" |
| #include "third_party/blink/public/platform/scheduler/test/web_mock_thread_scheduler.h" |
| #include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h" |
| #include "ui/base/ui_base_switches.h" |
| |
| // IPC messages for testing ---------------------------------------------------- |
| |
| #if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC) |
| // TODO(mdempsky): Fix properly by moving into a separate |
| // browsertest_message_generator.cc file. |
| #undef IPC_IPC_MESSAGE_MACROS_H_ |
| #undef IPC_MESSAGE_EXTRA |
| #define IPC_MESSAGE_IMPL |
| #include "ipc/ipc_message_macros.h" |
| #include "ipc/ipc_message_start.h" |
| #include "ipc/ipc_message_templates_impl.h" |
| |
| #undef IPC_MESSAGE_START |
| #define IPC_MESSAGE_START TestMsgStart |
| IPC_MESSAGE_CONTROL0(TestMsg_QuitRunLoop) |
| |
| #endif |
| |
| // ----------------------------------------------------------------------------- |
| |
| // These tests leak memory, this macro disables the test when under the |
| // LeakSanitizer. |
| #ifdef LEAK_SANITIZER |
| #define WILL_LEAK(NAME) DISABLED_##NAME |
| #else |
| #define WILL_LEAK(NAME) NAME |
| #endif |
| |
| namespace content { |
| |
| // FIXME: It would be great if there was a reusable mock SingleThreadTaskRunner |
| class TestTaskCounter : public base::SingleThreadTaskRunner { |
| public: |
| TestTaskCounter() : count_(0) {} |
| |
| // SingleThreadTaskRunner implementation. |
| bool PostDelayedTask(const base::Location&, |
| base::OnceClosure, |
| base::TimeDelta) override { |
| base::AutoLock auto_lock(lock_); |
| count_++; |
| return true; |
| } |
| |
| bool PostNonNestableDelayedTask(const base::Location&, |
| base::OnceClosure, |
| base::TimeDelta) override { |
| base::AutoLock auto_lock(lock_); |
| count_++; |
| return true; |
| } |
| |
| bool RunsTasksInCurrentSequence() const override { return true; } |
| |
| int NumTasksPosted() const { |
| base::AutoLock auto_lock(lock_); |
| return count_; |
| } |
| |
| private: |
| ~TestTaskCounter() override {} |
| |
| mutable base::Lock lock_; |
| int count_; |
| }; |
| |
| #if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC) |
| class QuitOnTestMsgFilter : public IPC::MessageFilter { |
| public: |
| explicit QuitOnTestMsgFilter(base::OnceClosure quit_closure) |
| : origin_task_runner_( |
| blink::scheduler::GetSequencedTaskRunnerForTesting()), |
| quit_closure_(std::move(quit_closure)) {} |
| |
| // IPC::MessageFilter overrides: |
| bool OnMessageReceived(const IPC::Message& message) override { |
| origin_task_runner_->PostTask(FROM_HERE, std::move(quit_closure_)); |
| return true; |
| } |
| |
| bool GetSupportedMessageClasses( |
| std::vector<uint32_t>* supported_message_classes) const override { |
| supported_message_classes->push_back(TestMsgStart); |
| return true; |
| } |
| |
| private: |
| ~QuitOnTestMsgFilter() override {} |
| |
| scoped_refptr<base::SequencedTaskRunner> origin_task_runner_; |
| base::OnceClosure quit_closure_; |
| }; |
| #endif |
| |
| class RenderThreadImplBrowserTest : public testing::Test, |
| public ChildProcessHostDelegate { |
| public: |
| RenderThreadImplBrowserTest() {} |
| |
| RenderThreadImplBrowserTest(const RenderThreadImplBrowserTest&) = delete; |
| RenderThreadImplBrowserTest& operator=(const RenderThreadImplBrowserTest&) = |
| delete; |
| |
| void SetUp() override { |
| content_renderer_client_ = std::make_unique<ContentRendererClient>(); |
| SetRendererClientForTesting(content_renderer_client_.get()); |
| |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = |
| GetIOThreadTaskRunner({}); |
| |
| InitializeMojo(); |
| process_host_ = |
| ChildProcessHost::Create(this, ChildProcessHost::IpcMode::kNormal); |
| process_host_->CreateChannelMojo(); |
| |
| CHECK(!process_.get()); |
| process_ = std::make_unique<RenderProcess>(); |
| test_task_counter_ = base::MakeRefCounted<TestTaskCounter>(); |
| |
| // RenderThreadImpl expects the browser to pass these flags. |
| base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); |
| base::CommandLine::StringVector old_argv = cmd->argv(); |
| |
| cmd->AppendSwitchASCII(switches::kLang, "en-US"); |
| |
| cmd->AppendSwitchASCII(switches::kNumRasterThreads, "1"); |
| |
| // To avoid creating a GPU channel to query if |
| // accelerated_video_decode is blocklisted on older Android system |
| // in RenderThreadImpl::Init(). |
| cmd->AppendSwitch(switches::kIgnoreGpuBlocklist); |
| |
| ContentTestSuiteBase::InitializeResourceBundle(); |
| |
| blink::Platform::InitializeBlink(); |
| auto main_thread_scheduler = |
| blink::scheduler::CreateMockWebMainThreadSchedulerForTests(); |
| scoped_refptr<base::SingleThreadTaskRunner> test_task_counter( |
| test_task_counter_.get()); |
| |
| // This handles the --force-fieldtrials flag passed to content_browsertests. |
| base::FieldTrialList::CreateTrialsFromString( |
| cmd->GetSwitchValueASCII(::switches::kForceFieldTrials)); |
| thread_ = new RenderThreadImpl( |
| InProcessChildThreadParams(io_task_runner, |
| &process_host_->GetMojoInvitation().value()), |
| /*renderer_client_id=*/1, std::move(main_thread_scheduler)); |
| cmd->InitFromArgv(old_argv); |
| |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| #if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC) |
| test_msg_filter_ = base::MakeRefCounted<QuitOnTestMsgFilter>( |
| run_loop_->QuitWhenIdleClosure()); |
| thread_->AddFilter(test_msg_filter_.get()); |
| #endif |
| |
| main_thread_scheduler_ = |
| static_cast<blink::scheduler::WebMockThreadScheduler*>( |
| thread_->GetWebMainThreadScheduler()); |
| } |
| |
| void TearDown() override { |
| SetRendererClientForTesting(nullptr); |
| CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kSingleProcessTests)); |
| // In a single-process mode, we need to avoid destructing `process_` |
| // because it will call _exit(0) and kill the process before the browser |
| // side is ready to exit. |
| ANNOTATE_LEAKING_OBJECT_PTR(process_.get()); |
| // TODO(crbug.com/40771960): `StopIOThreadForTesting()` is a stop-gap |
| // solution to fix flaky tests (see crbug.com/1126157). The underlying |
| // reason for this issue is that the `RenderThreadImpl` created in `SetUp()` |
| // above actually shares its main thread with the browser's, which is |
| // inconsistent with how in-process renderers work in production and other |
| // tests. Despite sharing its main thread with the browser, it still has its |
| // own IO thread (owned and created by `ChildProcess`). In these tests, the |
| // `BrowserTaskEnvironment` has no idea about this separate renderer IO |
| // thread, which can post tasks back to the browser's main thread. During |
| // `BrowserTaskEnvironment` shutdown, it CHECK()s that after the threads are |
| // stopped and flushed, no other tasks exist on its SequenceManager's task |
| // queues. However if we don't stop the IO thread here, then it may continue |
| // to post tasks to the `BrowserTaskEnvironment`'s main thread, causing the |
| // CHECK() to get hit. We should really fix the above tests to create a |
| // `RenderThreadImpl` on its own thread the traditional route, but this fix |
| // will work until we have the time to explore that option. |
| process_->StopIOThreadForTesting(); |
| process_.release(); |
| } |
| |
| // ChildProcessHostDelegate implementation: |
| bool OnMessageReceived(const IPC::Message&) override { return true; } |
| const base::Process& GetProcess() override { return null_process_; } |
| |
| protected: |
| IPC::Sender* sender() { return process_host_.get(); } |
| |
| void SetBackgroundState(base::Process::Priority process_priority) { |
| mojom::Renderer* renderer_interface = thread_; |
| const mojom::RenderProcessVisibleState visible_state = |
| RendererIsHidden() ? mojom::RenderProcessVisibleState::kHidden |
| : mojom::RenderProcessVisibleState::kVisible; |
| renderer_interface->SetProcessState(process_priority, visible_state); |
| } |
| |
| void SetVisibleState(mojom::RenderProcessVisibleState visible_state) { |
| mojom::Renderer* renderer_interface = thread_; |
| const base::Process::Priority process_priority = |
| RendererIsBackgrounded() ? base::Process::Priority::kBestEffort |
| : base::Process::Priority::kUserBlocking; |
| renderer_interface->SetProcessState(process_priority, visible_state); |
| } |
| |
| bool RendererIsBackgrounded() { return thread_->RendererIsBackgrounded(); } |
| bool RendererIsHidden() { return thread_->RendererIsHidden(); } |
| |
| scoped_refptr<TestTaskCounter> test_task_counter_; |
| |
| // Must be created before TestContentClientInitializer, since with |
| // --force-renderer-accessibility that creates a BrowserAccessibilityStateImpl |
| // that uses a browser thread. |
| BrowserTaskEnvironment browser_threads_{ |
| BrowserTaskEnvironment::REAL_IO_THREAD}; |
| |
| TestContentClientInitializer content_client_initializer_; |
| std::unique_ptr<ContentRendererClient> content_renderer_client_; |
| |
| const base::Process null_process_; |
| std::unique_ptr<ChildProcessHost> process_host_; |
| |
| std::unique_ptr<RenderProcess> process_; |
| #if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC) |
| scoped_refptr<QuitOnTestMsgFilter> test_msg_filter_; |
| #endif |
| |
| raw_ptr<blink::scheduler::WebMockThreadScheduler> main_thread_scheduler_; |
| |
| // RenderThreadImpl doesn't currently support a proper shutdown sequence |
| // and it's okay when we're running in multi-process mode because renderers |
| // get killed by the OS. Memory leaks aren't nice but it's test-only. |
| raw_ptr<RenderThreadImpl> thread_; |
| |
| std::unique_ptr<base::RunLoop> run_loop_; |
| }; |
| |
| #if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC) |
| // Disabled under LeakSanitizer due to memory leaks. |
| TEST_F(RenderThreadImplBrowserTest, |
| WILL_LEAK(NonResourceDispatchIPCTasksDontGoThroughScheduler)) { |
| // This seems to deflake the test on Android. |
| browser_threads_.RunIOThreadUntilIdle(); |
| |
| // NOTE other than not being a resource message, the actual message is |
| // unimportant. |
| sender()->Send(new TestMsg_QuitRunLoop()); |
| |
| // In-process RenderThreadImpl does not start a browser loop so the random |
| // browser seed is never generated. To allow the ChildProcessHost to correctly |
| // send a seed to the ChildProcess without hitting a DCHECK, set the seed to |
| // an arbitrary non-zero value. |
| SetPseudonymizationSalt(0xDEADBEEF); |
| |
| run_loop_->Run(); |
| |
| EXPECT_EQ(0, test_task_counter_->NumTasksPosted()); |
| } |
| #endif |
| |
| TEST_F(RenderThreadImplBrowserTest, RendererIsBackgrounded) { |
| SetBackgroundState(base::Process::Priority::kBestEffort); |
| EXPECT_TRUE(RendererIsBackgrounded()); |
| |
| SetBackgroundState(base::Process::Priority::kUserBlocking); |
| EXPECT_FALSE(RendererIsBackgrounded()); |
| } |
| |
| TEST_F(RenderThreadImplBrowserTest, RendererIsHidden) { |
| SetVisibleState(mojom::RenderProcessVisibleState::kHidden); |
| EXPECT_TRUE(RendererIsHidden()); |
| |
| SetVisibleState(mojom::RenderProcessVisibleState::kVisible); |
| EXPECT_FALSE(RendererIsHidden()); |
| } |
| |
| TEST_F(RenderThreadImplBrowserTest, RendererStateTransitionVisible) { |
| // Going from an unknown to a visible state should mark the renderer as |
| // foregrounded and visible. |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(false)); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(false)); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(true)).Times(0); |
| SetVisibleState(mojom::RenderProcessVisibleState::kVisible); |
| testing::Mock::VerifyAndClear(main_thread_scheduler_); |
| |
| // Going from a hidden to a visible state should mark the renderer as visible. |
| SetVisibleState(mojom::RenderProcessVisibleState::kHidden); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(false)); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(false)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(true)).Times(0); |
| SetVisibleState(mojom::RenderProcessVisibleState::kVisible); |
| testing::Mock::VerifyAndClear(main_thread_scheduler_); |
| |
| // Going from a visible to a hidden state should mark the renderer as hidden. |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(false)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(false)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(true)); |
| SetVisibleState(mojom::RenderProcessVisibleState::kHidden); |
| testing::Mock::VerifyAndClear(main_thread_scheduler_); |
| |
| testing::Mock::AllowLeak(main_thread_scheduler_); |
| } |
| |
| TEST_F(RenderThreadImplBrowserTest, RendererStateTransitionHidden) { |
| // Going from an unknown to a visible state should mark the renderer as |
| // foregrounded and hidden. |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(false)); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(true)); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(false)).Times(0); |
| SetVisibleState(mojom::RenderProcessVisibleState::kHidden); |
| testing::Mock::VerifyAndClear(main_thread_scheduler_); |
| |
| testing::Mock::AllowLeak(main_thread_scheduler_); |
| } |
| |
| TEST_F(RenderThreadImplBrowserTest, RendererStateTransitionBackgrounded) { |
| // Going from an unknown to a backgrounded state should mark the renderer as |
| // backgrounded but not hidden. |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(true)); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(false)); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(false)).Times(0); |
| SetBackgroundState(base::Process::Priority::kBestEffort); |
| testing::Mock::VerifyAndClear(main_thread_scheduler_); |
| |
| // Going from a backgrounded to a foregrounded state should mark the renderer |
| // as foregrounded. |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(false)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(false)); |
| SetBackgroundState(base::Process::Priority::kUserBlocking); |
| testing::Mock::VerifyAndClear(main_thread_scheduler_); |
| |
| // Going from a foregrounded state to another foregrounded state should not |
| // remark the renderer as foregrounded. |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(false)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(false)).Times(0); |
| SetBackgroundState(base::Process::Priority::kUserVisible); |
| testing::Mock::VerifyAndClear(main_thread_scheduler_); |
| |
| // Going from a foregrounded to a backgrounded state should mark the renderer |
| // as backgrounded. |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(true)); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(false)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(false)).Times(0); |
| SetBackgroundState(base::Process::Priority::kBestEffort); |
| testing::Mock::VerifyAndClear(main_thread_scheduler_); |
| |
| testing::Mock::AllowLeak(main_thread_scheduler_); |
| } |
| |
| TEST_F(RenderThreadImplBrowserTest, RendererStateTransitionForegrounded) { |
| // Going from an unknown to a foregrounded state should mark the renderer as |
| // foregrounded and visible. |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(false)); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererBackgrounded(true)).Times(0); |
| EXPECT_CALL(*main_thread_scheduler_, SetRendererHidden(false)); |
| SetBackgroundState(base::Process::Priority::kUserBlocking); |
| testing::Mock::VerifyAndClear(main_thread_scheduler_); |
| |
| testing::Mock::AllowLeak(main_thread_scheduler_); |
| } |
| |
| TEST_F(RenderThreadImplBrowserTest, TransferSharedLastForegroundTime) { |
| auto time_memory = base::AtomicSharedMemory<base::TimeTicks>::Create(); |
| ASSERT_TRUE(time_memory.has_value()); |
| |
| // No shared memory region mapped by default. |
| EXPECT_EQ(base::internal::GetSharedLastForegroundTimeForMetricsForTesting(), |
| nullptr); |
| |
| // SharedLastForegroundTimeForMetrics should never be overwritten after it's |
| // set, in case a thread is accessing the memory as it's unmapped. |
| thread_->TransferSharedLastForegroundTime( |
| time_memory->DuplicateReadOnlyRegion()); |
| const auto* last_foreground_time_ptr = |
| base::internal::GetSharedLastForegroundTimeForMetricsForTesting(); |
| EXPECT_NE(last_foreground_time_ptr, nullptr); |
| |
| thread_->TransferSharedLastForegroundTime( |
| time_memory->DuplicateReadOnlyRegion()); |
| EXPECT_EQ(base::internal::GetSharedLastForegroundTimeForMetricsForTesting(), |
| last_foreground_time_ptr); |
| } |
| |
| } // namespace content |