blob: 93955ace6fdd16b73f4c8e338ff94bdc771466dd [file] [log] [blame]
// 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