| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/platform/graphics/paint_worklet_paint_dispatcher.h" |
| |
| #include "base/bind.h" |
| #include "base/run_loop.h" |
| #include "cc/paint/paint_worklet_job.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/renderer/platform/scheduler/public/thread.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread_type.h" |
| |
| using ::testing::_; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| |
| namespace blink { |
| namespace { |
| // We need a thread (or multiple threads) for the (mock) worklets to run on. |
| std::unique_ptr<Thread> CreateTestThread(const char* name) { |
| return Platform::Current()->CreateThread( |
| ThreadCreationParams(ThreadType::kTestThread).SetThreadNameForTest(name)); |
| } |
| |
| class PaintWorkletPaintDispatcherAsyncTest : public ::testing::Test { |
| public: |
| PlatformPaintWorkletLayerPainter::DoneCallback CreateTestCompleteCallback() { |
| return base::BindOnce( |
| &PaintWorkletPaintDispatcherAsyncTest::VerifyResultAndFinish, |
| base::Unretained(this)); |
| } |
| |
| // Allows a test to block on |VerifyResultAndFinish| being called. If a |
| // PaintWorkletPaintDispatcherAsyncTest test times out, it likely means the |
| // callback created by |CreateTestCompleteCallback| was never posted by the |
| // worklet thread. |
| void WaitForTestCompletion() { run_loop_.Run(); } |
| |
| private: |
| void VerifyResultAndFinish(cc::PaintWorkletJobMap results) { |
| run_loop_.Quit(); |
| } |
| |
| base::RunLoop run_loop_; |
| }; |
| |
| class MockPaintWorkletPainter |
| : public GarbageCollected<MockPaintWorkletPainter>, |
| public PaintWorkletPainter { |
| public: |
| MockPaintWorkletPainter(int worklet_id) { |
| ON_CALL(*this, GetWorkletId).WillByDefault(Return(worklet_id)); |
| } |
| ~MockPaintWorkletPainter() = default; |
| |
| MOCK_CONST_METHOD0(GetWorkletId, int()); |
| MOCK_METHOD2( |
| Paint, |
| sk_sp<PaintRecord>(const cc::PaintWorkletInput*, |
| const cc::PaintWorkletJob::AnimatedPropertyValues&)); |
| }; |
| |
| class MockPaintWorkletInput : public cc::PaintWorkletInput { |
| public: |
| explicit MockPaintWorkletInput(int worklet_id) { |
| ON_CALL(*this, WorkletId).WillByDefault(Return(worklet_id)); |
| } |
| ~MockPaintWorkletInput() = default; |
| |
| MOCK_CONST_METHOD0(GetSize, gfx::SizeF()); |
| MOCK_CONST_METHOD0(WorkletId, int()); |
| MOCK_CONST_METHOD0(GetPropertyKeys, |
| const std::vector<PaintWorkletInput::PropertyKey>&()); |
| }; |
| |
| cc::PaintWorkletInput* AddPaintWorkletInputToMap(cc::PaintWorkletJobMap& map, |
| int worklet_id) { |
| if (!map.contains(worklet_id)) |
| map[worklet_id] = base::MakeRefCounted<cc::PaintWorkletJobVector>(); |
| auto input = base::MakeRefCounted<MockPaintWorkletInput>(worklet_id); |
| MockPaintWorkletInput* input_ptr = input.get(); |
| cc::PaintWorkletJob::AnimatedPropertyValues animated_property_values; |
| map[worklet_id]->data.emplace_back(/*layer_id=*/1, std::move(input), |
| animated_property_values); |
| return input_ptr; |
| } |
| } // namespace |
| |
| TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchedWorkletIsPainted) { |
| auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>(); |
| |
| const int worklet_id = 4; |
| MockPaintWorkletPainter* mock_painter = |
| MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(worklet_id); |
| std::unique_ptr<Thread> worklet_thread = CreateTestThread("WorkletThread"); |
| dispatcher->RegisterPaintWorkletPainter(mock_painter, |
| worklet_thread->GetTaskRunner()); |
| |
| cc::PaintWorkletJobMap job_map; |
| Vector<cc::PaintWorkletInput*> inputs = { |
| AddPaintWorkletInputToMap(job_map, worklet_id), |
| AddPaintWorkletInputToMap(job_map, worklet_id), |
| AddPaintWorkletInputToMap(job_map, worklet_id), |
| }; |
| |
| // The input jobs match the registered painter, so we should see a series of |
| // calls to Paint() with the appropriate PaintWorkletInputs. |
| for (cc::PaintWorkletInput* input : inputs) |
| EXPECT_CALL(*mock_painter, Paint(input, _)).Times(1); |
| dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback()); |
| |
| WaitForTestCompletion(); |
| } |
| |
| TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchCompletesWithNoPainters) { |
| auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>(); |
| |
| cc::PaintWorkletJobMap job_map; |
| AddPaintWorkletInputToMap(job_map, /*worklet_id=*/2); |
| AddPaintWorkletInputToMap(job_map, /*worklet_id=*/2); |
| AddPaintWorkletInputToMap(job_map, /*worklet_id=*/5); |
| |
| // There are no painters to dispatch to, matching or otherwise, but the |
| // callback should still be called so this test passes if it doesn't hang on |
| // WaitForTestCompletion. |
| dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback()); |
| |
| WaitForTestCompletion(); |
| } |
| |
| TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchHandlesEmptyInput) { |
| auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>(); |
| |
| const int worklet_id = 4; |
| auto* mock_painter = |
| MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(worklet_id); |
| std::unique_ptr<Thread> worklet_thread = CreateTestThread("WorkletThread"); |
| dispatcher->RegisterPaintWorkletPainter(mock_painter, |
| worklet_thread->GetTaskRunner()); |
| |
| cc::PaintWorkletJobMap job_map; |
| |
| // The input job map is empty, so we should see no calls to Paint but the |
| // callback should still be called. |
| EXPECT_CALL(*mock_painter, Paint(_, _)).Times(0); |
| dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback()); |
| |
| WaitForTestCompletion(); |
| } |
| |
| TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchSelectsCorrectPainter) { |
| auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>(); |
| |
| const int first_worklet_id = 2; |
| auto* first_mock_painter = |
| MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(first_worklet_id); |
| std::unique_ptr<Thread> first_thread = CreateTestThread("WorkletThread1"); |
| dispatcher->RegisterPaintWorkletPainter(first_mock_painter, |
| first_thread->GetTaskRunner()); |
| |
| const int second_worklet_id = 3; |
| auto* second_mock_painter = |
| MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>( |
| second_worklet_id); |
| std::unique_ptr<Thread> second_thread = CreateTestThread("WorkletThread2"); |
| dispatcher->RegisterPaintWorkletPainter(second_mock_painter, |
| second_thread->GetTaskRunner()); |
| |
| cc::PaintWorkletJobMap job_map; |
| Vector<cc::PaintWorkletInput*> inputs{ |
| AddPaintWorkletInputToMap(job_map, second_worklet_id), |
| AddPaintWorkletInputToMap(job_map, second_worklet_id), |
| }; |
| |
| // Paint should only be called on the correct painter, with our input. |
| EXPECT_CALL(*first_mock_painter, Paint(_, _)).Times(0); |
| for (cc::PaintWorkletInput* input : inputs) { |
| EXPECT_CALL(*second_mock_painter, Paint(input, _)).Times(1); |
| } |
| dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback()); |
| |
| WaitForTestCompletion(); |
| } |
| |
| TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchIgnoresNonMatchingInput) { |
| auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>(); |
| |
| const int worklet_id = 2; |
| auto* mock_painter = |
| MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(worklet_id); |
| std::unique_ptr<Thread> worklet_thread = CreateTestThread("WorkletThread"); |
| dispatcher->RegisterPaintWorkletPainter(mock_painter, |
| worklet_thread->GetTaskRunner()); |
| |
| cc::PaintWorkletJobMap job_map; |
| const int non_registered_worklet_id = 3; |
| cc::PaintWorkletInput* matching_input = |
| AddPaintWorkletInputToMap(job_map, worklet_id); |
| AddPaintWorkletInputToMap(job_map, non_registered_worklet_id); |
| |
| // Only one job matches, so our painter should only be called once, and the |
| // callback should still be called. |
| EXPECT_CALL(*mock_painter, Paint(matching_input, _)).Times(1); |
| dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback()); |
| |
| WaitForTestCompletion(); |
| } |
| |
| TEST_F(PaintWorkletPaintDispatcherAsyncTest, |
| DispatchCorrectlyAssignsInputsToMultiplePainters) { |
| auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>(); |
| |
| const int first_worklet_id = 5; |
| auto* first_mock_painter = |
| MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(first_worklet_id); |
| std::unique_ptr<Thread> first_thread = CreateTestThread("WorkletThread1"); |
| dispatcher->RegisterPaintWorkletPainter(first_mock_painter, |
| first_thread->GetTaskRunner()); |
| |
| const int second_worklet_id = 1; |
| auto* second_mock_painter = |
| MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>( |
| second_worklet_id); |
| std::unique_ptr<Thread> second_thread = CreateTestThread("WorkletThread2"); |
| dispatcher->RegisterPaintWorkletPainter(second_mock_painter, |
| second_thread->GetTaskRunner()); |
| |
| cc::PaintWorkletJobMap job_map; |
| cc::PaintWorkletInput* first_input = |
| AddPaintWorkletInputToMap(job_map, first_worklet_id); |
| cc::PaintWorkletInput* second_input = |
| AddPaintWorkletInputToMap(job_map, second_worklet_id); |
| |
| // Both painters should be called with the correct inputs. |
| EXPECT_CALL(*first_mock_painter, Paint(first_input, _)).Times(1); |
| EXPECT_CALL(*second_mock_painter, Paint(second_input, _)).Times(1); |
| dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback()); |
| |
| WaitForTestCompletion(); |
| } |
| |
| TEST_F(PaintWorkletPaintDispatcherAsyncTest, |
| HasOngoingDispatchIsTrackedCorrectly) { |
| auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>(); |
| |
| const int first_worklet_id = 2; |
| auto* first_mock_painter = |
| MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(first_worklet_id); |
| std::unique_ptr<Thread> first_thread = CreateTestThread("WorkletThread1"); |
| dispatcher->RegisterPaintWorkletPainter(first_mock_painter, |
| first_thread->GetTaskRunner()); |
| |
| // Nothing going on; no dispatch. |
| EXPECT_FALSE(dispatcher->HasOngoingDispatch()); |
| |
| cc::PaintWorkletJobMap job_map; |
| AddPaintWorkletInputToMap(job_map, first_worklet_id); |
| |
| dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback()); |
| EXPECT_TRUE(dispatcher->HasOngoingDispatch()); |
| |
| WaitForTestCompletion(); |
| EXPECT_FALSE(dispatcher->HasOngoingDispatch()); |
| } |
| |
| } // namespace blink |