blob: f7f5fd9ab31ed4bc96506235784132b53e5996bf [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/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/canvas/canvas2d/hit_region_options.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()->Is2d());
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()->SetInnerHTMLFromString(
"<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 (unsigned i = 0; i < image_data->data()->length(); ++i)
image_data->data()->Data()[i] = 255;
EXPECT_EQ(255, image_data->data()->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()->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((unsigned)800, imgdata1->data()->length());
EXPECT_EQ((unsigned)800, imgdata2->data()->length());
EXPECT_EQ((unsigned)800, imgdata3->data()->length());
EXPECT_EQ((unsigned)800, imgdata4->data()->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()->SetInnerHTMLFromString(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()->Is2d());
}
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);
AXObjectCacheImpl* ax_object_cache =
ToAXObjectCacheImpl(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);
AXObjectCacheImpl* ax_object_cache =
ToAXObjectCacheImpl(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());
}
} // namespace blink