| // 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/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/debug/leak_annotations.h" |
| #include "base/location.h" |
| #include "base/memory/discardable_memory.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.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/threading/sequenced_task_runner_handle.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "content/app/mojo/mojo_init.h" |
| #include "content/common/in_process_child_thread_params.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/common/child_process_host.h" |
| #include "content/public/common/child_process_host_delegate.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/GLES2/gl2extchromium.h" |
| #include "gpu/command_buffer/client/gpu_memory_buffer_manager.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/common/switches.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" |
| #include "ui/gfx/buffer_format_util.h" |
| #include "ui/gfx/switches.h" |
| |
| // IPC messages for testing ---------------------------------------------------- |
| |
| // 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) |
| |
| // ----------------------------------------------------------------------------- |
| |
| // 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_; |
| }; |
| |
| 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_; |
| }; |
| |
| 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()); |
| |
| browser_threads_ = std::make_unique<BrowserTaskEnvironment>( |
| BrowserTaskEnvironment::REAL_IO_THREAD); |
| 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(blink::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()); |
| |
| base::FieldTrialList::CreateTrialsFromCommandLine(*cmd, -1); |
| 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>(); |
| test_msg_filter_ = base::MakeRefCounted<QuitOnTestMsgFilter>( |
| run_loop_->QuitWhenIdleClosure()); |
| thread_->AddFilter(test_msg_filter_.get()); |
| |
| main_thread_scheduler_ = |
| static_cast<blink::scheduler::WebMockThreadScheduler*>( |
| thread_->GetWebMainThreadScheduler()); |
| } |
| |
| void TearDown() override { |
| 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/1219038): `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( |
| mojom::RenderProcessBackgroundState background_state) { |
| mojom::Renderer* renderer_interface = thread_; |
| const mojom::RenderProcessVisibleState visible_state = |
| RendererIsHidden() ? mojom::RenderProcessVisibleState::kHidden |
| : mojom::RenderProcessVisibleState::kVisible; |
| renderer_interface->SetProcessState(background_state, visible_state); |
| } |
| |
| void SetVisibleState(mojom::RenderProcessVisibleState visible_state) { |
| mojom::Renderer* renderer_interface = thread_; |
| const mojom::RenderProcessBackgroundState background_state = |
| RendererIsBackgrounded() |
| ? mojom::RenderProcessBackgroundState::kBackgrounded |
| : mojom::RenderProcessBackgroundState::kForegrounded; |
| renderer_interface->SetProcessState(background_state, visible_state); |
| } |
| |
| bool RendererIsBackgrounded() { return thread_->RendererIsBackgrounded(); } |
| bool RendererIsHidden() { return thread_->RendererIsHidden(); } |
| |
| scoped_refptr<TestTaskCounter> test_task_counter_; |
| TestContentClientInitializer content_client_initializer_; |
| std::unique_ptr<ContentRendererClient> content_renderer_client_; |
| |
| std::unique_ptr<BrowserTaskEnvironment> browser_threads_; |
| const base::Process null_process_; |
| std::unique_ptr<ChildProcessHost> process_host_; |
| |
| std::unique_ptr<RenderProcess> process_; |
| scoped_refptr<QuitOnTestMsgFilter> test_msg_filter_; |
| |
| 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. |
| RenderThreadImpl* thread_; |
| |
| std::unique_ptr<base::RunLoop> run_loop_; |
| }; |
| |
| // 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()); |
| |
| run_loop_->Run(); |
| |
| EXPECT_EQ(0, test_task_counter_->NumTasksPosted()); |
| } |
| |
| TEST_F(RenderThreadImplBrowserTest, RendererIsBackgrounded) { |
| SetBackgroundState(mojom::RenderProcessBackgroundState::kBackgrounded); |
| EXPECT_TRUE(RendererIsBackgrounded()); |
| |
| SetBackgroundState(mojom::RenderProcessBackgroundState::kForegrounded); |
| 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(mojom::RenderProcessBackgroundState::kBackgrounded); |
| 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(mojom::RenderProcessBackgroundState::kForegrounded); |
| 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(mojom::RenderProcessBackgroundState::kBackgrounded); |
| 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(mojom::RenderProcessBackgroundState::kForegrounded); |
| testing::Mock::VerifyAndClear(main_thread_scheduler_); |
| |
| testing::Mock::AllowLeak(main_thread_scheduler_); |
| } |
| |
| enum NativeBufferFlag { kDisableNativeBuffers, kEnableNativeBuffers }; |
| |
| class RenderThreadImplGpuMemoryBufferBrowserTest |
| : public ContentBrowserTest, |
| public testing::WithParamInterface< |
| ::testing::tuple<NativeBufferFlag, gfx::BufferFormat>> { |
| public: |
| RenderThreadImplGpuMemoryBufferBrowserTest() {} |
| |
| RenderThreadImplGpuMemoryBufferBrowserTest( |
| const RenderThreadImplGpuMemoryBufferBrowserTest&) = delete; |
| RenderThreadImplGpuMemoryBufferBrowserTest& operator=( |
| const RenderThreadImplGpuMemoryBufferBrowserTest&) = delete; |
| |
| ~RenderThreadImplGpuMemoryBufferBrowserTest() override {} |
| |
| gpu::GpuMemoryBufferManager* memory_buffer_manager() { |
| return memory_buffer_manager_; |
| } |
| |
| private: |
| void SetUpOnRenderThread() { |
| memory_buffer_manager_ = |
| RenderThreadImpl::current()->GetGpuMemoryBufferManager(); |
| } |
| |
| // Overridden from BrowserTestBase: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch(switches::kSingleProcess); |
| NativeBufferFlag native_buffer_flag = ::testing::get<0>(GetParam()); |
| if (native_buffer_flag == kEnableNativeBuffers) { |
| command_line->AppendSwitch(switches::kEnableNativeGpuMemoryBuffers); |
| } |
| } |
| |
| void SetUpOnMainThread() override { |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| PostTaskToInProcessRendererAndWait(base::BindOnce( |
| &RenderThreadImplGpuMemoryBufferBrowserTest::SetUpOnRenderThread, |
| base::Unretained(this))); |
| } |
| |
| gpu::GpuMemoryBufferManager* memory_buffer_manager_ = nullptr; |
| }; |
| |
| // https://crbug.com/652531 |
| IN_PROC_BROWSER_TEST_P(RenderThreadImplGpuMemoryBufferBrowserTest, |
| DISABLED_Map) { |
| gfx::BufferFormat format = ::testing::get<1>(GetParam()); |
| gfx::Size buffer_size(4, 4); |
| |
| std::unique_ptr<gfx::GpuMemoryBuffer> buffer = |
| memory_buffer_manager()->CreateGpuMemoryBuffer( |
| buffer_size, format, gfx::BufferUsage::GPU_READ_CPU_READ_WRITE, |
| gpu::kNullSurfaceHandle, nullptr); |
| ASSERT_TRUE(buffer); |
| EXPECT_EQ(format, buffer->GetFormat()); |
| |
| // Map buffer planes. |
| ASSERT_TRUE(buffer->Map()); |
| |
| // Write to buffer and check result. |
| size_t num_planes = gfx::NumberOfPlanesForLinearBufferFormat(format); |
| for (size_t plane = 0; plane < num_planes; ++plane) { |
| ASSERT_TRUE(buffer->memory(plane)); |
| ASSERT_TRUE(buffer->stride(plane)); |
| size_t row_size_in_bytes = |
| gfx::RowSizeForBufferFormat(buffer_size.width(), format, plane); |
| EXPECT_GT(row_size_in_bytes, 0u); |
| |
| std::unique_ptr<char[]> data(new char[row_size_in_bytes]); |
| memset(data.get(), 0x2a + plane, row_size_in_bytes); |
| size_t height = buffer_size.height() / |
| gfx::SubsamplingFactorForBufferFormat(format, plane); |
| for (size_t y = 0; y < height; ++y) { |
| // Copy |data| to row |y| of |plane| and verify result. |
| memcpy( |
| static_cast<char*>(buffer->memory(plane)) + y * buffer->stride(plane), |
| data.get(), row_size_in_bytes); |
| EXPECT_EQ(0, memcmp(static_cast<char*>(buffer->memory(plane)) + |
| y * buffer->stride(plane), |
| data.get(), row_size_in_bytes)); |
| } |
| } |
| |
| buffer->Unmap(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| RenderThreadImplGpuMemoryBufferBrowserTests, |
| RenderThreadImplGpuMemoryBufferBrowserTest, |
| ::testing::Combine(::testing::Values(kDisableNativeBuffers, |
| kEnableNativeBuffers), |
| // These formats are guaranteed to work on all platforms. |
| ::testing::Values(gfx::BufferFormat::R_8, |
| gfx::BufferFormat::BGR_565, |
| gfx::BufferFormat::RGBA_4444, |
| gfx::BufferFormat::RGBA_8888, |
| gfx::BufferFormat::BGRA_8888, |
| gfx::BufferFormat::YVU_420))); |
| |
| } // namespace content |