blob: 106a7bb5c184e019acaa8ee662e48006562dbe7f [file] [log] [blame]
// Copyright 2016 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/modules/canvas/htmlcanvas/html_canvas_element_module.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "components/viz/test/test_context_provider.h"
#include "components/viz/test/test_gles2_interface.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "services/viz/public/mojom/hit_test/hit_test_region_list.mojom-blink.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/frame_sinks/embedded_frame_sink.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_context_creation_attributes_core.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
#include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
#include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h"
#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
#include "third_party/blink/renderer/platform/graphics/test/gpu_memory_buffer_test_platform.h"
#include "third_party/blink/renderer/platform/graphics/test/gpu_test_utils.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::Values;
namespace blink {
namespace {
// This class allows for overriding GenerateFrameSinkId() so that the
// HTMLCanvasElement's SurfaceLayerBridge will get a syntactically correct
// FrameSinkId. It also returns a valid GpuMemoryBufferManager so that low
// latency mode is enabled.
class LowLatencyTestPlatform : public GpuMemoryBufferTestPlatform {
public:
viz::FrameSinkId GenerateFrameSinkId() override {
// Doesn't matter what we return as long as is not zero.
constexpr uint32_t kClientId = 2;
constexpr uint32_t kSinkId = 1;
return viz::FrameSinkId(kClientId, kSinkId);
}
};
} // unnamed namespace
class HTMLCanvasElementModuleTest : public ::testing::Test,
public ::testing::WithParamInterface<bool> {
protected:
void SetUp() override {
web_view_helper_.Initialize();
GetDocument().documentElement()->setInnerHTML(
String::FromUTF8("<body><canvas id='c'></canvas></body>"));
canvas_element_ = To<HTMLCanvasElement>(GetDocument().getElementById("c"));
}
LocalDOMWindow* GetWindow() const {
return web_view_helper_.GetWebView()
->MainFrameImpl()
->GetFrame()
->DomWindow();
}
Document& GetDocument() const { return *GetWindow()->document(); }
HTMLCanvasElement& canvas_element() const { return *canvas_element_; }
OffscreenCanvas* TransferControlToOffscreen(ExceptionState& exception_state) {
return HTMLCanvasElementModule::TransferControlToOffscreenInternal(
GetWindow(), canvas_element(), exception_state);
}
frame_test_helpers::WebViewHelper web_view_helper_;
Persistent<HTMLCanvasElement> canvas_element_;
Persistent<CanvasRenderingContext> context_;
};
// Tests if the Canvas Id is associated correctly.
TEST_F(HTMLCanvasElementModuleTest, TransferControlToOffscreen) {
NonThrowableExceptionState exception_state;
const OffscreenCanvas* offscreen_canvas =
TransferControlToOffscreen(exception_state);
const DOMNodeId canvas_id = offscreen_canvas->PlaceholderCanvasId();
EXPECT_EQ(canvas_id, DOMNodeIds::IdForNode(&(canvas_element())));
}
// Verifies that a desynchronized canvas has the appropriate opacity/blending
// information sent to the CompositorFrameSink.
TEST_P(HTMLCanvasElementModuleTest, LowLatencyCanvasCompositorFrameOpacity) {
// TODO(crbug.com/922218): enable desynchronized on Mac.
#if !defined(OS_MAC)
// This test relies on GpuMemoryBuffers being supported and enabled for low
// latency canvas. The latter is true only on ChromeOS in production.
ScopedTestingPlatformSupport<LowLatencyTestPlatform> platform;
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kLowLatencyCanvas2dImageChromium);
auto context_provider = viz::TestContextProvider::Create();
context_provider->UnboundTestContextGL()
->set_supports_gpu_memory_buffer_format(
CanvasColorParams().GetAsResourceParams().GetBufferFormat(), true);
InitializeSharedGpuContext(context_provider.get());
// To intercept SubmitCompositorFrame/SubmitCompositorFrameSync messages sent
// by a canvas's CanvasResourceDispatcher, we have to override the Mojo
// EmbeddedFrameSinkProvider interface impl and its CompositorFrameSinkClient.
MockEmbeddedFrameSinkProvider mock_embedded_frame_sink_provider;
mojo::Receiver<mojom::blink::EmbeddedFrameSinkProvider>
embedded_frame_sink_provider_receiver(&mock_embedded_frame_sink_provider);
auto override =
mock_embedded_frame_sink_provider.CreateScopedOverrideMojoInterface(
&embedded_frame_sink_provider_receiver);
const bool context_alpha = GetParam();
CanvasContextCreationAttributesCore attrs;
attrs.alpha = context_alpha;
attrs.desynchronized = true;
// |context_| creation triggers a SurfaceLayerBridge creation which connects
// to a MockEmbeddedFrameSinkProvider to create a new CompositorFrameSink,
// that will receive a SetNeedsBeginFrame() 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_(_));
context_ = canvas_element().GetCanvasRenderingContext(String("2d"), attrs);
EXPECT_EQ(context_->CreationAttributes().alpha, attrs.alpha);
EXPECT_TRUE(context_->CreationAttributes().desynchronized);
EXPECT_TRUE(canvas_element().LowLatencyEnabled());
EXPECT_TRUE(canvas_element().SurfaceLayerBridge());
platform->RunUntilIdle();
// This call simulates having drawn something before FinalizeFrame().
canvas_element().DidDraw();
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);
})));
canvas_element().PreFinalizeFrame();
context_->FinalizeFrame();
canvas_element().PostFinalizeFrame();
platform->RunUntilIdle();
SharedGpuContext::ResetForTesting();
#endif
}
INSTANTIATE_TEST_SUITE_P(All, HTMLCanvasElementModuleTest, Values(true, false));
} // namespace blink