| // Copyright 2017 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/core/offscreencanvas/offscreen_canvas.h" |
| |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h" |
| #include "third_party/blink/renderer/core/testing/page_test_base.h" |
| #include "third_party/blink/renderer/modules/canvas/htmlcanvas/html_canvas_element_module.h" |
| #include "third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h" |
| #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" |
| #include "third_party/blink/renderer/platform/graphics/test/fake_gles2_interface.h" |
| #include "third_party/blink/renderer/platform/graphics/test/fake_web_graphics_context_3d_provider.h" |
| #include "third_party/blink/renderer/platform/graphics/test/mock_compositor_frame_sink.h" |
| #include "third_party/blink/renderer/platform/graphics/test/mock_embedded_frame_sink_provider.h" |
| #include "third_party/blink/renderer/platform/testing/testing_platform_support.h" |
| |
| using ::testing::_; |
| using ::testing::Combine; |
| using ::testing::ValuesIn; |
| |
| namespace blink { |
| |
| namespace { |
| constexpr uint32_t kClientId = 2; |
| constexpr uint32_t kSinkId = 1; |
| |
| struct TestParams { |
| bool alpha; |
| bool low_latency; |
| }; |
| } // unnamed namespace |
| |
| class OffscreenCanvasTest : public PageTestBase, |
| public ::testing::WithParamInterface<TestParams> { |
| protected: |
| OffscreenCanvasTest(); |
| void SetUp() override; |
| void TearDown() override; |
| |
| OffscreenCanvas& offscreen_canvas() const { return *offscreen_canvas_; } |
| CanvasResourceDispatcher* Dispatcher() const { |
| return offscreen_canvas_->GetOrCreateResourceDispatcher(); |
| } |
| ScriptState* GetScriptState() const { |
| return ToScriptStateForMainWorld(GetDocument().GetFrame()); |
| } |
| |
| private: |
| Persistent<OffscreenCanvas> offscreen_canvas_; |
| Persistent<OffscreenCanvasRenderingContext2D> context_; |
| FakeGLES2Interface gl_; |
| }; |
| |
| OffscreenCanvasTest::OffscreenCanvasTest() = default; |
| |
| void OffscreenCanvasTest::SetUp() { |
| auto factory = [](FakeGLES2Interface* gl, bool* gpu_compositing_disabled) |
| -> std::unique_ptr<WebGraphicsContext3DProvider> { |
| *gpu_compositing_disabled = false; |
| gl->SetIsContextLost(false); |
| return std::make_unique<FakeWebGraphicsContext3DProvider>(gl); |
| }; |
| SharedGpuContext::SetContextProviderFactoryForTesting( |
| WTF::BindRepeating(factory, WTF::Unretained(&gl_))); |
| PageTestBase::SetUp(); |
| SetHtmlInnerHTML("<body><canvas id='c'></canvas></body>"); |
| HTMLCanvasElement* canvas_element = ToHTMLCanvasElement(GetElementById("c")); |
| |
| DummyExceptionStateForTesting exception_state; |
| offscreen_canvas_ = HTMLCanvasElementModule::transferControlToOffscreen( |
| *canvas_element, exception_state); |
| // |offscreen_canvas_| should inherit the FrameSinkId from |canvas_element|s |
| // SurfaceLayerBridge, but in tests this id is zero; fill it up by hand. |
| offscreen_canvas_->SetFrameSinkId(kClientId, kSinkId); |
| |
| CanvasContextCreationAttributesCore attrs; |
| if (testing::UnitTest::GetInstance()->current_test_info()->value_param()) { |
| attrs.alpha = GetParam().alpha; |
| attrs.low_latency = GetParam().low_latency; |
| } |
| context_ = static_cast<OffscreenCanvasRenderingContext2D*>( |
| offscreen_canvas_->GetCanvasRenderingContext(&GetDocument(), String("2d"), |
| attrs)); |
| } |
| |
| void OffscreenCanvasTest::TearDown() { |
| SharedGpuContext::ResetForTesting(); |
| } |
| |
| TEST_F(OffscreenCanvasTest, AnimationNotInitiallySuspended) { |
| ScriptState::Scope scope(GetScriptState()); |
| EXPECT_FALSE(Dispatcher()->IsAnimationSuspended()); |
| } |
| |
| // Verifies that an offscreen_canvas()s PushFrame()/Commit() has the appropriate |
| // opacity/blending information sent to the CompositorFrameSink. |
| TEST_P(OffscreenCanvasTest, CompositorFrameOpacity) { |
| ScopedTestingPlatformSupport<TestingPlatformSupport> platform; |
| ScriptState::Scope scope(GetScriptState()); |
| ::testing::InSequence s; |
| |
| // To intercept SubmitCompositorFrame/SubmitCompositorFrameSync messages sent |
| // by OffscreenCanvas's CanvasResourceDispatcher, we have to override the Mojo |
| // EmbeddedFrameSinkProvider interface impl and its CompositorFrameSinkClient. |
| MockEmbeddedFrameSinkProvider mock_embedded_frame_sink_provider; |
| mojo::Binding<mojom::blink::EmbeddedFrameSinkProvider> |
| embedded_frame_sink_provider_binding(&mock_embedded_frame_sink_provider); |
| auto override = |
| mock_embedded_frame_sink_provider.CreateScopedOverrideMojoInterface( |
| &embedded_frame_sink_provider_binding); |
| |
| // Call here DidDraw() to simulate having drawn something before PushFrame()/ |
| // Commit(); DidDraw() will in turn cause a CanvasResourceDispatcher to be |
| // created and a CreateCompositorFrameSink() to be issued; this sink will get |
| // a SetNeedsBeginFrame() message sent upon construction. |
| mock_embedded_frame_sink_provider |
| .set_num_expected_set_needs_begin_frame_on_sink_construction(1); |
| EXPECT_CALL(mock_embedded_frame_sink_provider, |
| CreateCompositorFrameSink_(viz::FrameSinkId(kClientId, kSinkId))); |
| offscreen_canvas().DidDraw(); |
| platform->RunUntilIdle(); |
| |
| const bool context_alpha = GetParam().alpha; |
| |
| const auto canvas_resource = CanvasResourceSharedBitmap::Create( |
| offscreen_canvas().Size(), CanvasColorParams(), nullptr /* provider */, |
| kLow_SkFilterQuality); |
| EXPECT_TRUE(!!canvas_resource); |
| |
| EXPECT_CALL(mock_embedded_frame_sink_provider.mock_compositor_frame_sink(), |
| SubmitCompositorFrame_(_)) |
| .WillOnce(::testing::WithArg<0>( |
| ::testing::Invoke([context_alpha](const viz::CompositorFrame* frame) { |
| ASSERT_EQ(frame->render_pass_list.size(), 1u); |
| |
| const auto& quad_list = frame->render_pass_list[0]->quad_list; |
| ASSERT_EQ(quad_list.size(), 1u); |
| EXPECT_EQ(quad_list.front()->needs_blending, context_alpha); |
| |
| const auto& shared_quad_state_list = |
| frame->render_pass_list[0]->shared_quad_state_list; |
| ASSERT_EQ(shared_quad_state_list.size(), 1u); |
| EXPECT_NE(shared_quad_state_list.front()->are_contents_opaque, |
| context_alpha); |
| }))); |
| offscreen_canvas().PushFrame(canvas_resource, SkIRect::MakeWH(10, 10)); |
| platform->RunUntilIdle(); |
| |
| EXPECT_CALL(mock_embedded_frame_sink_provider.mock_compositor_frame_sink(), |
| SubmitCompositorFrameSync_(_)) |
| .WillOnce(::testing::WithArg<0>( |
| ::testing::Invoke([context_alpha](const viz::CompositorFrame* frame) { |
| ASSERT_EQ(frame->render_pass_list.size(), 1u); |
| |
| const auto& quad_list = frame->render_pass_list[0]->quad_list; |
| ASSERT_EQ(quad_list.size(), 1u); |
| EXPECT_EQ(quad_list.front()->needs_blending, context_alpha); |
| |
| const auto& shared_quad_state_list = |
| frame->render_pass_list[0]->shared_quad_state_list; |
| ASSERT_EQ(shared_quad_state_list.size(), 1u); |
| EXPECT_NE(shared_quad_state_list.front()->are_contents_opaque, |
| context_alpha); |
| }))); |
| offscreen_canvas().Commit(canvas_resource, SkIRect::MakeWH(10, 10)); |
| platform->RunUntilIdle(); |
| } |
| |
| const TestParams kTestCases[] = {{false /* alpha */, false /* low_latency */}, |
| {false, true}, |
| {true, false}, |
| {true, true}}; |
| |
| INSTANTIATE_TEST_CASE_P(, OffscreenCanvasTest, ValuesIn(kTestCases)); |
| } // namespace blink |