blob: 9b5fe27f2172fa06cbfa1e830326ce396b62854a [file] [log] [blame]
// Copyright 2014 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/canvas2d/canvas_rendering_context_2d.h"
#include <memory>
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_study_settings_provider.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_hit_region_options.h"
#include "third_party/blink/renderer/core/accessibility/ax_context.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/html/canvas/image_data.h"
#include "third_party/blink/renderer/core/loader/empty_clients.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/modules/accessibility/ax_object.h"
#include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
#include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_gradient.h"
#include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.h"
#include "third_party/blink/renderer/modules/webgl/webgl_rendering_context.h"
using testing::Mock;
namespace blink {
class CanvasRenderingContext2DAPITest : public PageTestBase {
protected:
CanvasRenderingContext2DAPITest();
void SetUp() override;
HTMLCanvasElement& CanvasElement() const { return *canvas_element_; }
CanvasRenderingContext2D* Context2D() const;
void CreateContext(OpacityMode);
private:
Persistent<HTMLCanvasElement> canvas_element_;
};
CanvasRenderingContext2DAPITest::CanvasRenderingContext2DAPITest() = default;
CanvasRenderingContext2D* CanvasRenderingContext2DAPITest::Context2D() const {
// If the following check fails, perhaps you forgot to call createContext
// in your test?
EXPECT_NE(nullptr, CanvasElement().RenderingContext());
EXPECT_TRUE(CanvasElement().RenderingContext()->IsRenderingContext2D());
return static_cast<CanvasRenderingContext2D*>(
CanvasElement().RenderingContext());
}
void CanvasRenderingContext2DAPITest::CreateContext(OpacityMode opacity_mode) {
String canvas_type("2d");
CanvasContextCreationAttributesCore attributes;
attributes.alpha = opacity_mode == kNonOpaque;
canvas_element_->GetCanvasRenderingContext(canvas_type, attributes);
Context2D(); // Calling this for the checks
}
void CanvasRenderingContext2DAPITest::SetUp() {
PageTestBase::SetUp();
GetDocument().documentElement()->setInnerHTML(
"<body><canvas id='c'></canvas></body>");
UpdateAllLifecyclePhasesForTest();
canvas_element_ = To<HTMLCanvasElement>(GetDocument().getElementById("c"));
}
TEST_F(CanvasRenderingContext2DAPITest, SetShadowColor_Clamping) {
CreateContext(kNonOpaque);
Context2D()->setShadowColor("rgba(0,0,0,0)");
EXPECT_EQ(String("rgba(0, 0, 0, 0)"), Context2D()->shadowColor());
Context2D()->setShadowColor("rgb(0,0,0)");
EXPECT_EQ(String("#000000"), Context2D()->shadowColor());
Context2D()->setShadowColor("rgb(0,999,0)");
EXPECT_EQ(String("#00ff00"), Context2D()->shadowColor());
Context2D()->setShadowColor(
"rgb(0,"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
",0)");
EXPECT_EQ(String("#00ff00"), Context2D()->shadowColor());
Context2D()->setShadowColor("rgb(0,0,256)");
EXPECT_EQ(String("#0000ff"), Context2D()->shadowColor());
Context2D()->setShadowColor(
"rgb(999999999999999999999999,0,-9999999999999999999999999999)");
EXPECT_EQ(String("#ff0000"), Context2D()->shadowColor());
Context2D()->setShadowColor(
"rgba("
"999999999999999999999999999999999999999999999999999999999999999999999999"
"9999999999,9,0,1)");
EXPECT_EQ(String("#ff0900"), Context2D()->shadowColor());
Context2D()->setShadowColor(
"rgba("
"999999999999999999999999999999999999999999999999999999999999999999999999"
"9999999999,9,0,-99999999999999999999999999999999999999)");
EXPECT_EQ(String("rgba(255, 9, 0, 0)"), Context2D()->shadowColor());
Context2D()->setShadowColor(
"rgba(7,"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"9999999999,0,"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"99999999999999999)");
EXPECT_EQ(String("#07ff00"), Context2D()->shadowColor());
Context2D()->setShadowColor(
"rgba(-7,"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"9999999999,0,"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999999999999999999999999999999999999999"
"99999999999999999)");
EXPECT_EQ(String("#00ff00"), Context2D()->shadowColor());
Context2D()->setShadowColor("rgba(0%,100%,0%,0.4)");
EXPECT_EQ(String("rgba(0, 255, 0, 0.4)"), Context2D()->shadowColor());
}
String TrySettingStrokeStyle(CanvasRenderingContext2D* ctx,
const String& value) {
StringOrCanvasGradientOrCanvasPattern arg1, arg2, arg3;
arg1.SetString("#666");
ctx->setStrokeStyle(arg1);
arg2.SetString(value);
ctx->setStrokeStyle(arg2);
ctx->strokeStyle(arg3);
EXPECT_TRUE(arg3.IsString());
return arg3.GetAsString();
}
String TrySettingFillStyle(CanvasRenderingContext2D* ctx, const String& value) {
StringOrCanvasGradientOrCanvasPattern arg1, arg2, arg3;
arg1.SetString("#666");
ctx->setFillStyle(arg1);
arg2.SetString(value);
ctx->setFillStyle(arg2);
ctx->fillStyle(arg3);
EXPECT_TRUE(arg3.IsString());
return arg3.GetAsString();
}
String TrySettingShadowColor(CanvasRenderingContext2D* ctx,
const String& value) {
ctx->setShadowColor("#666");
ctx->setShadowColor(value);
return ctx->shadowColor();
}
void TrySettingColor(CanvasRenderingContext2D* ctx,
const String& value,
const String& expected) {
EXPECT_EQ(expected, TrySettingStrokeStyle(ctx, value));
EXPECT_EQ(expected, TrySettingFillStyle(ctx, value));
EXPECT_EQ(expected, TrySettingShadowColor(ctx, value));
}
TEST_F(CanvasRenderingContext2DAPITest, ColorSerialization) {
CreateContext(kNonOpaque);
// Check round trips
TrySettingColor(Context2D(), "transparent", "rgba(0, 0, 0, 0)");
TrySettingColor(Context2D(), "red", "#ff0000");
TrySettingColor(Context2D(), "white", "#ffffff");
TrySettingColor(Context2D(), "", "#666666");
TrySettingColor(Context2D(), "RGBA(0, 0, 0, 0)", "rgba(0, 0, 0, 0)");
TrySettingColor(Context2D(), "rgba(0,255,0,1.0)", "#00ff00");
TrySettingColor(Context2D(), "rgba(1,2,3,0.4)", "rgba(1, 2, 3, 0.4)");
TrySettingColor(Context2D(), "RgB(1,2,3)", "#010203");
TrySettingColor(Context2D(), "rGbA(1,2,3,0)", "rgba(1, 2, 3, 0)");
}
TEST_F(CanvasRenderingContext2DAPITest, DefaultAttributeValues) {
CreateContext(kNonOpaque);
{
StringOrCanvasGradientOrCanvasPattern value;
Context2D()->strokeStyle(value);
EXPECT_TRUE(value.IsString());
EXPECT_EQ(String("#000000"), value.GetAsString());
}
{
StringOrCanvasGradientOrCanvasPattern value;
Context2D()->fillStyle(value);
EXPECT_TRUE(value.IsString());
EXPECT_EQ(String("#000000"), value.GetAsString());
}
EXPECT_EQ(String("rgba(0, 0, 0, 0)"), Context2D()->shadowColor());
}
TEST_F(CanvasRenderingContext2DAPITest, LineDashStateSave) {
CreateContext(kNonOpaque);
Vector<double> simple_dash;
simple_dash.push_back(4);
simple_dash.push_back(2);
Context2D()->setLineDash(simple_dash);
EXPECT_EQ(simple_dash, Context2D()->getLineDash());
Context2D()->save();
// Realize the save.
Context2D()->scale(2, 2);
EXPECT_EQ(simple_dash, Context2D()->getLineDash());
Context2D()->restore();
EXPECT_EQ(simple_dash, Context2D()->getLineDash());
}
TEST_F(CanvasRenderingContext2DAPITest, CreateImageData) {
CreateContext(kNonOpaque);
NonThrowableExceptionState exception_state;
// create a 100x50 imagedata and fill it with white pixels
ImageData* image_data =
Context2D()->createImageData(100, 50, exception_state);
EXPECT_FALSE(exception_state.HadException());
EXPECT_EQ(100, image_data->width());
EXPECT_EQ(50, image_data->height());
for (size_t i = 0; i < image_data->data().GetAsUint8ClampedArray()->length();
++i) {
image_data->data().GetAsUint8ClampedArray()->Data()[i] = 255;
}
EXPECT_EQ(255, image_data->data().GetAsUint8ClampedArray()->Data()[32]);
// createImageData(imageData) should create a new ImageData of the same size
// as 'imageData' but filled with transparent black
ImageData* same_size_image_data =
Context2D()->createImageData(image_data, exception_state);
EXPECT_FALSE(exception_state.HadException());
EXPECT_EQ(100, same_size_image_data->width());
EXPECT_EQ(50, same_size_image_data->height());
EXPECT_EQ(0,
same_size_image_data->data().GetAsUint8ClampedArray()->Data()[32]);
// createImageData(width, height) takes the absolute magnitude of the size
// arguments
ImageData* imgdata1 = Context2D()->createImageData(10, 20, exception_state);
EXPECT_FALSE(exception_state.HadException());
ImageData* imgdata2 = Context2D()->createImageData(-10, 20, exception_state);
EXPECT_FALSE(exception_state.HadException());
ImageData* imgdata3 = Context2D()->createImageData(10, -20, exception_state);
EXPECT_FALSE(exception_state.HadException());
ImageData* imgdata4 = Context2D()->createImageData(-10, -20, exception_state);
EXPECT_FALSE(exception_state.HadException());
EXPECT_EQ(800u, imgdata1->data().GetAsUint8ClampedArray()->length());
EXPECT_EQ(800u, imgdata2->data().GetAsUint8ClampedArray()->length());
EXPECT_EQ(800u, imgdata3->data().GetAsUint8ClampedArray()->length());
EXPECT_EQ(800u, imgdata4->data().GetAsUint8ClampedArray()->length());
}
TEST_F(CanvasRenderingContext2DAPITest, CreateImageDataTooBig) {
CreateContext(kNonOpaque);
DummyExceptionStateForTesting exception_state;
ImageData* too_big_image_data =
Context2D()->createImageData(1000000, 1000000, exception_state);
EXPECT_EQ(nullptr, too_big_image_data);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ(ESErrorType::kRangeError, exception_state.CodeAs<ESErrorType>());
}
TEST_F(CanvasRenderingContext2DAPITest, GetImageDataTooBig) {
CreateContext(kNonOpaque);
DummyExceptionStateForTesting exception_state;
ImageData* image_data =
Context2D()->getImageData(0, 0, 1000000, 1000000, exception_state);
EXPECT_EQ(nullptr, image_data);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ(ESErrorType::kRangeError, exception_state.CodeAs<ESErrorType>());
}
TEST_F(CanvasRenderingContext2DAPITest,
GetImageDataIntegerOverflowNegativeParams) {
CreateContext(kNonOpaque);
DummyExceptionStateForTesting exception_state;
ImageData* image_data = Context2D()->getImageData(
1, -2147483647, 1, -2147483647, exception_state);
EXPECT_EQ(nullptr, image_data);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ(ESErrorType::kRangeError, exception_state.CodeAs<ESErrorType>());
exception_state.ClearException();
image_data = Context2D()->getImageData(-2147483647, 1, -2147483647, 1,
exception_state);
EXPECT_EQ(nullptr, image_data);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ(ESErrorType::kRangeError, exception_state.CodeAs<ESErrorType>());
}
void ResetCanvasForAccessibilityRectTest(Document& document) {
document.documentElement()->setInnerHTML(R"HTML(
<canvas id='canvas' style='position:absolute; top:0px; left:0px;
padding:10px; margin:5px;'>
<button id='button'></button></canvas>
)HTML");
auto* canvas = To<HTMLCanvasElement>(document.getElementById("canvas"));
String canvas_type("2d");
CanvasContextCreationAttributesCore attributes;
attributes.alpha = true;
canvas->GetCanvasRenderingContext(canvas_type, attributes);
EXPECT_NE(nullptr, canvas->RenderingContext());
EXPECT_TRUE(canvas->RenderingContext()->IsRenderingContext2D());
}
TEST_F(CanvasRenderingContext2DAPITest, AccessibilityRectTestForAddHitRegion) {
ResetCanvasForAccessibilityRectTest(GetDocument());
AXContext ax_context(GetDocument());
Element* button_element = GetDocument().getElementById("button");
auto* canvas = To<HTMLCanvasElement>(GetDocument().getElementById("canvas"));
CanvasRenderingContext2D* context =
static_cast<CanvasRenderingContext2D*>(canvas->RenderingContext());
NonThrowableExceptionState exception_state;
HitRegionOptions* options = HitRegionOptions::Create();
options->setControl(button_element);
context->beginPath();
context->rect(10, 10, 40, 40);
context->addHitRegion(options, exception_state);
auto* ax_object_cache =
To<AXObjectCacheImpl>(GetDocument().ExistingAXObjectCache());
AXObject* ax_object = ax_object_cache->GetOrCreate(button_element);
LayoutRect ax_bounds = ax_object->GetBoundsInFrameCoordinates();
EXPECT_EQ(25, ax_bounds.X().ToInt());
EXPECT_EQ(25, ax_bounds.Y().ToInt());
EXPECT_EQ(40, ax_bounds.Width().ToInt());
EXPECT_EQ(40, ax_bounds.Height().ToInt());
}
TEST_F(CanvasRenderingContext2DAPITest,
AccessibilityRectTestForDrawFocusIfNeeded) {
ResetCanvasForAccessibilityRectTest(GetDocument());
AXContext ax_context(GetDocument());
Element* button_element = GetDocument().getElementById("button");
auto* canvas = To<HTMLCanvasElement>(GetDocument().getElementById("canvas"));
CanvasRenderingContext2D* context =
static_cast<CanvasRenderingContext2D*>(canvas->RenderingContext());
GetDocument().UpdateStyleAndLayoutTreeForNode(canvas);
context->beginPath();
context->rect(10, 10, 40, 40);
context->drawFocusIfNeeded(button_element);
auto* ax_object_cache =
To<AXObjectCacheImpl>(GetDocument().ExistingAXObjectCache());
AXObject* ax_object = ax_object_cache->GetOrCreate(button_element);
LayoutRect ax_bounds = ax_object->GetBoundsInFrameCoordinates();
EXPECT_EQ(25, ax_bounds.X().ToInt());
EXPECT_EQ(25, ax_bounds.Y().ToInt());
EXPECT_EQ(40, ax_bounds.Width().ToInt());
EXPECT_EQ(40, ax_bounds.Height().ToInt());
}
// A IdentifiabilityStudySettingsProvider implementation that opts-into study
// participation.
class ActiveSettingsProvider : public IdentifiabilityStudySettingsProvider {
public:
bool IsActive() const override { return true; }
bool IsAnyTypeOrSurfaceBlocked() const override { return false; }
bool IsSurfaceAllowed(IdentifiableSurface surface) const override {
return true;
}
bool IsTypeAllowed(IdentifiableSurface::Type type) const override {
return true;
}
int SampleRate(IdentifiableSurface surface) const override { return 1; }
int SampleRate(IdentifiableSurface::Type type) const override { return 1; }
};
// An RAII class that opts into study participation using
// ActiveSettingsProvider.
class StudyParticipationRaii {
public:
StudyParticipationRaii() {
IdentifiabilityStudySettings::SetGlobalProvider(
std::make_unique<ActiveSettingsProvider>());
}
~StudyParticipationRaii() {
IdentifiabilityStudySettings::ResetStateForTesting();
}
};
TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyMaxOperations) {
StudyParticipationRaii study_participation_raii;
constexpr int kMaxOperations = 5;
IdentifiabilityStudyHelper::ScopedMaxOperationsSetter max_operations_setter(
kMaxOperations);
CreateContext(kNonOpaque);
int64_t last_digest = INT64_C(0);
for (int i = 0; i < kMaxOperations; i++) {
Context2D()->setFont("Arial");
EXPECT_NE(last_digest,
Context2D()->IdentifiableTextToken().ToUkmMetricValue())
<< i;
last_digest = Context2D()->IdentifiableTextToken().ToUkmMetricValue();
}
Context2D()->setFont("Arial");
EXPECT_EQ(last_digest,
Context2D()->IdentifiableTextToken().ToUkmMetricValue());
EXPECT_TRUE(Context2D()->IdentifiabilityEncounteredSkippedOps());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
}
TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_Font) {
StudyParticipationRaii study_participation_raii;
CreateContext(kNonOpaque);
Context2D()->setFont("Arial");
EXPECT_EQ(INT64_C(4260982106376580867),
Context2D()->IdentifiableTextToken().ToUkmMetricValue());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
}
TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_StrokeText) {
StudyParticipationRaii study_participation_raii;
CreateContext(kNonOpaque);
Context2D()->strokeText("Sensitive message", 1.0, 1.0);
EXPECT_EQ(INT64_C(-2943272460643878232),
Context2D()->IdentifiableTextToken().ToUkmMetricValue());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
EXPECT_TRUE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
}
TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_FillText) {
StudyParticipationRaii study_participation_raii;
CreateContext(kNonOpaque);
Context2D()->fillText("Sensitive message", 1.0, 1.0);
EXPECT_EQ(INT64_C(8733208206881150098),
Context2D()->IdentifiableTextToken().ToUkmMetricValue());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
EXPECT_TRUE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
}
TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_TextAlign) {
StudyParticipationRaii study_participation_raii;
CreateContext(kNonOpaque);
Context2D()->setTextAlign("center");
EXPECT_EQ(INT64_C(-4778938416456134710),
Context2D()->IdentifiableTextToken().ToUkmMetricValue());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
}
TEST_F(CanvasRenderingContext2DAPITest,
IdentifiabilityStudyDigest_TextBaseline) {
StudyParticipationRaii study_participation_raii;
CreateContext(kNonOpaque);
Context2D()->setTextBaseline("top");
EXPECT_EQ(INT64_C(-3065573128425485855),
Context2D()->IdentifiableTextToken().ToUkmMetricValue());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
}
TEST_F(CanvasRenderingContext2DAPITest,
IdentifiabilityStudyDigest_StrokeStyle) {
StudyParticipationRaii study_participation_raii;
CreateContext(kNonOpaque);
StringOrCanvasGradientOrCanvasPattern style;
style.SetString("blue");
Context2D()->setStrokeStyle(style);
EXPECT_EQ(INT64_C(2059186787917525779),
Context2D()->IdentifiableTextToken().ToUkmMetricValue());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
}
TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_FillStyle) {
StudyParticipationRaii study_participation_raii;
CreateContext(kNonOpaque);
StringOrCanvasGradientOrCanvasPattern style;
style.SetString("blue");
Context2D()->setFillStyle(style);
EXPECT_EQ(INT64_C(-6322980727372024031),
Context2D()->IdentifiableTextToken().ToUkmMetricValue());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
}
TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_Combo) {
StudyParticipationRaii study_participation_raii;
CreateContext(kNonOpaque);
Context2D()->fillText("Sensitive message", 1.0, 1.0);
EXPECT_EQ(INT64_C(8733208206881150098),
Context2D()->IdentifiableTextToken().ToUkmMetricValue());
Context2D()->setFont("Helvetica");
Context2D()->setTextBaseline("bottom");
Context2D()->setTextAlign("right");
StringOrCanvasGradientOrCanvasPattern style;
style.SetString("red");
Context2D()->setFillStyle(style);
Context2D()->fillText("Bye", 4.0, 3.0);
EXPECT_EQ(INT64_C(2368400155273386771),
Context2D()->IdentifiableTextToken().ToUkmMetricValue());
EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
EXPECT_TRUE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
}
} // namespace blink