Chromium Code Reviews| Index: Source/core/html/canvas/CanvasRenderingContext2DTest.cpp |
| diff --git a/Source/core/html/canvas/CanvasRenderingContext2DTest.cpp b/Source/core/html/canvas/CanvasRenderingContext2DTest.cpp |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..0c351fc463749a75954b0b5c2f25a2fe2d58afcc |
| --- /dev/null |
| +++ b/Source/core/html/canvas/CanvasRenderingContext2DTest.cpp |
| @@ -0,0 +1,262 @@ |
| +// 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 "config.h" |
| + |
| +#include "core/html/canvas/CanvasRenderingContext2D.h" |
| + |
| +#include "core/frame/FrameView.h" |
| +#include "core/html/HTMLDocument.h" |
| +#include "core/html/ImageData.h" |
| +#include "core/html/canvas/CanvasGradient.h" |
| +#include "core/html/canvas/CanvasPattern.h" |
| +#include "core/html/canvas/WebGLRenderingContext.h" |
| +#include "core/loader/EmptyClients.h" |
| +#include "core/testing/DummyPageHolder.h" |
| +#include "platform/graphics/StaticBitmapImage.h" |
| +#include "platform/graphics/UnacceleratedImageBufferSurface.h" |
| +#include "third_party/skia/include/core/SkSurface.h" |
| +#include <gmock/gmock.h> |
| +#include <gtest/gtest.h> |
| + |
| +using namespace blink; |
| +using ::testing::Mock; |
| + |
| +namespace { |
| + |
| +class CanvasRenderingContext2DTest : public ::testing::Test { |
| +protected: |
| + virtual void SetUp() override; |
| + |
| + DummyPageHolder& page() const { return *m_dummyPageHolder; } |
| + HTMLDocument& document() const { return *m_document; } |
| + HTMLCanvasElement& canvasElement() const { return *m_canvasElement; } |
| + CanvasRenderingContext2D* context2d() const { return static_cast<CanvasRenderingContext2D*>(canvasElement().renderingContext()); } |
| + |
| + void createContext(OpacityMode); |
| + |
| +private: |
| + OwnPtr<DummyPageHolder> m_dummyPageHolder; |
| + RefPtrWillBePersistent<HTMLDocument> m_document; |
| + RefPtrWillBePersistent<HTMLCanvasElement> m_canvasElement; |
| +}; |
| + |
| +void CanvasRenderingContext2DTest::createContext(OpacityMode opacityMode) |
| +{ |
| + String canvasType("2d"); |
| + CanvasContextCreationAttributes attributes; |
| + attributes.setAlpha(opacityMode == NonOpaque); |
| + CanvasRenderingContext2DOrWebGLRenderingContext result; |
| + m_canvasElement->getContext(canvasType, attributes, result); |
| +} |
| + |
| +void CanvasRenderingContext2DTest::SetUp() |
| +{ |
| + Page::PageClients pageClients; |
| + fillWithEmptyClients(pageClients); |
| + m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600), &pageClients); |
| + m_document = toHTMLDocument(&m_dummyPageHolder->document()); |
| + m_document->documentElement()->setInnerHTML("<body><canvas id='c'></canvas></body>", ASSERT_NO_EXCEPTION); |
| + m_document->view()->updateLayoutAndStyleIfNeededRecursive(); |
| + m_canvasElement = toHTMLCanvasElement(m_document->getElementById("c")); |
| +} |
| + |
| +//============================================================================ |
| + |
| +class PLATFORM_EXPORT MockImageBufferSurfaceForOverwriteTesting : public UnacceleratedImageBufferSurface { |
| +public: |
| + MockImageBufferSurfaceForOverwriteTesting(const IntSize& size, OpacityMode mode) : UnacceleratedImageBufferSurface(size, mode) { } |
| + virtual ~MockImageBufferSurfaceForOverwriteTesting() { } |
| + bool isRecording() const override { return true; } // otherwise overwrites are not tracked |
| + |
| + MOCK_METHOD0(willOverwriteCanvas, void()); |
| +}; |
| + |
| +//============================================================================ |
| + |
| +#define TEST_OVERWRITE_SETUP(EXPECTED_OVERWRITES) \ |
|
Ken Russell (switch to Gerrit)
2015/02/11 10:11:44
Throughout this test file, would it make sense to
Justin Novosad
2015/02/11 18:59:54
Done.
|
| + OwnPtr<MockImageBufferSurfaceForOverwriteTesting> mockSurface = adoptPtr(new MockImageBufferSurfaceForOverwriteTesting(IntSize(10, 10), NonOpaque)); \ |
| + MockImageBufferSurfaceForOverwriteTesting* surfacePtr = mockSurface.get(); \ |
| + canvasElement().createImageBufferUsingSurface(mockSurface.release()); \ |
| + EXPECT_CALL(*surfacePtr, willOverwriteCanvas()).Times(EXPECTED_OVERWRITES); \ |
| + context2d()->save(); |
| + |
| +#define TEST_OVERWRITE_FINALIZE \ |
| + context2d()->restore(); \ |
| + Mock::VerifyAndClearExpectations(surfacePtr); |
| + |
| +#define TEST_OVERWRITE_1(EXPECTED_OVERWRITES, CALL1) \ |
| + do { \ |
| + TEST_OVERWRITE_SETUP(EXPECTED_OVERWRITES) \ |
| + context2d()->CALL1; \ |
| + TEST_OVERWRITE_FINALIZE \ |
| + } while (0) |
| + |
| +#define TEST_OVERWRITE_2(EXPECTED_OVERWRITES, CALL1, CALL2) \ |
| + do { \ |
| + TEST_OVERWRITE_SETUP(EXPECTED_OVERWRITES) \ |
| + context2d()->CALL1; \ |
| + context2d()->CALL2; \ |
| + TEST_OVERWRITE_FINALIZE \ |
| + } while (0) |
| + |
| +#define TEST_OVERWRITE_3(EXPECTED_OVERWRITES, CALL1, CALL2, CALL3) \ |
| + do { \ |
| + TEST_OVERWRITE_SETUP(EXPECTED_OVERWRITES) \ |
| + context2d()->CALL1; \ |
| + context2d()->CALL2; \ |
| + context2d()->CALL3; \ |
| + TEST_OVERWRITE_FINALIZE \ |
| + } while (0) |
| + |
| +enum BitmapOpacity { |
| + OpaqueBitmap, |
| + TransparentBitmap |
| +}; |
| + |
| +//============================================================================ |
| + |
| +class FakeImageSource : public CanvasImageSource { |
| +public: |
| + FakeImageSource(IntSize, BitmapOpacity); |
| + |
| + PassRefPtr<Image> getSourceImageForCanvas(SourceImageMode, SourceImageStatus*) const override; |
| + |
| + bool wouldTaintOrigin(SecurityOrigin* destinationSecurityOrigin) const override { return false; } |
| + FloatSize sourceSize() const override { return FloatSize(m_size); } |
| + bool isOpaque() const override { return m_isOpaque; } |
| + |
| + virtual ~FakeImageSource() { } |
| + |
| +private: |
| + IntSize m_size; |
| + RefPtr<Image> m_image; |
| + bool m_isOpaque; |
| +}; |
| + |
| +FakeImageSource::FakeImageSource(IntSize size, BitmapOpacity opacity) |
| + : m_size(size) |
| + , m_isOpaque(opacity == OpaqueBitmap) |
| +{ |
| + SkAutoTUnref<SkSurface> surface(SkSurface::NewRasterN32Premul(m_size.width(), m_size.height())); |
| + surface->getCanvas()->clear(opacity == OpaqueBitmap ? SK_ColorWHITE : SK_ColorTRANSPARENT); |
| + RefPtr<SkImage> image = adoptRef(surface->newImageSnapshot()); |
| + m_image = StaticBitmapImage::create(image); |
| +} |
| + |
| +PassRefPtr<Image> FakeImageSource::getSourceImageForCanvas(SourceImageMode, SourceImageStatus* status) const |
| +{ |
| + if (status) |
| + *status = NormalSourceImageStatus; |
| + return m_image; |
| +} |
| + |
| +//============================================================================ |
| + |
| +TEST_F(CanvasRenderingContext2DTest, detectOverwrite) |
|
dshwang
2015/02/11 08:37:52
nice test!
Ken Russell (switch to Gerrit)
2015/02/11 10:11:44
Consider splitting up this test into a few so it's
Justin Novosad
2015/02/11 18:59:54
Done.
|
| +{ |
| + RefPtrWillBeRawPtr<ImageData> fullImageData = ImageData::create(IntSize(10, 10)); |
| + RefPtrWillBeRawPtr<ImageData> partialImageData = ImageData::create(IntSize(2, 2)); |
| + FakeImageSource opaqueBitmap(IntSize(10, 10), OpaqueBitmap); |
| + FakeImageSource alphaBitmap(IntSize(10, 10), TransparentBitmap); |
| + NonThrowableExceptionState exceptionState; |
| + |
| + RefPtrWillBeRawPtr<CanvasGradient> opaqueGradient = CanvasGradient::create(FloatPoint(0, 0), FloatPoint(10, 0)); |
| + opaqueGradient->addColorStop(0, String("green"), exceptionState); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + opaqueGradient->addColorStop(1, String("blue"), exceptionState); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + StringOrCanvasGradientOrCanvasPattern wrappedOpaqueGradient; |
| + wrappedOpaqueGradient.setCanvasGradient(opaqueGradient); |
| + |
| + RefPtrWillBeRawPtr<CanvasGradient> alphaGradient = CanvasGradient::create(FloatPoint(0, 0), FloatPoint(10, 0)); |
| + alphaGradient->addColorStop(0, String("green"), exceptionState); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + alphaGradient->addColorStop(1, String("rgba(0, 0, 255, 0.5)"), exceptionState); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + StringOrCanvasGradientOrCanvasPattern wrappedAlphaGradient; |
| + wrappedAlphaGradient.setCanvasGradient(alphaGradient); |
| + |
| + createContext(NonOpaque); |
| + |
| + // Test fillRect |
| + TEST_OVERWRITE_1(1, fillRect(-1, -1, 12, 12)); |
| + TEST_OVERWRITE_1(1, fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_1(0, strokeRect(0, 0, 10, 10)); // stroking instead of filling does not overwrite |
| + TEST_OVERWRITE_2(0, setGlobalAlpha(0.5f), fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_1(0, fillRect(0, 0, 9, 9)); |
| + TEST_OVERWRITE_2(0, translate(1, 1), fillRect(0, 0, 10, 10)); |
|
Ken Russell (switch to Gerrit)
2015/02/11 10:11:44
Also test transformed rects that would still overd
Justin Novosad
2015/02/11 18:59:54
Done.
|
| + TEST_OVERWRITE_2(1, setFillStyle(wrappedOpaqueGradient), fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_2(0, setFillStyle(wrappedAlphaGradient), fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_3(0, setGlobalAlpha(0.5), setFillStyle(wrappedOpaqueGradient), fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 9, 9)); |
|
Ken Russell (switch to Gerrit)
2015/02/11 10:11:44
I don't understand why setting the globalComposite
Justin Novosad
2015/02/11 18:59:54
Close. The way the spec defines the behavior: prim
|
| + TEST_OVERWRITE_3(0, rect(0, 0, 5, 5), clip(), fillRect(0, 0, 10, 10)); |
| + |
| + // Test clearRect |
| + TEST_OVERWRITE_1(1, clearRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_1(0, clearRect(0, 0, 9, 9)); |
| + TEST_OVERWRITE_2(1, setGlobalAlpha(0.5f), clearRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_2(1, setFillStyle(wrappedAlphaGradient), clearRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_2(1, setGlobalCompositeOperation(String("destination-in")), clearRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_3(0, rect(0, 0, 5, 5), clip(), clearRect(0, 0, 10, 10)); |
|
Ken Russell (switch to Gerrit)
2015/02/11 10:11:44
What about transforms in conjunction with clearRec
Justin Novosad
2015/02/11 18:59:54
Done.
|
| + |
| + // Test drawImage |
| + TEST_OVERWRITE_1(1, drawImage(&opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_1(1, drawImage(&opaqueBitmap, 0, 0, 1, 1, 0, 0, 10, 10, exceptionState)); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_2(0, setGlobalAlpha(0.5f), drawImage(&opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_1(0, drawImage(&alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_2(0, setGlobalAlpha(0.5f), drawImage(&alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_1(0, drawImage(&opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState)); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_1(0, drawImage(&opaqueBitmap, 0, 0, 10, 10, 0, 0, 9, 9, exceptionState)); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_1(1, drawImage(&opaqueBitmap, 0, 0, 10, 10, 0, 0, 11, 11, exceptionState)); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_2(1, translate(-1, 0), drawImage(&opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState)); |
|
Ken Russell (switch to Gerrit)
2015/02/11 10:11:44
Should also test the case where a transform makes
Justin Novosad
2015/02/11 18:59:54
Done.
|
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_2(0, setFillStyle(wrappedOpaqueGradient), drawImage(&alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); // fillStyle ignored by drawImage |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_2(1, setFillStyle(wrappedAlphaGradient), drawImage(&opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); // fillStyle ignored by drawImage |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_2(1, setGlobalCompositeOperation(String("copy")), drawImage(&opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState)); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + TEST_OVERWRITE_3(0, rect(0, 0, 5, 5), clip(), drawImage(&opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| + EXPECT_FALSE(exceptionState.hadException()); |
| + |
| + // Test putImageData |
| + TEST_OVERWRITE_1(1, putImageData(fullImageData.get(), 0, 0)); |
| + TEST_OVERWRITE_1(1, putImageData(fullImageData.get(), 0, 0, 0, 0, 10, 10)); |
| + TEST_OVERWRITE_1(0, putImageData(fullImageData.get(), 0, 0, 1, 1, 8, 8)); |
| + TEST_OVERWRITE_2(1, setGlobalAlpha(0.5f), putImageData(fullImageData.get(), 0, 0)); // alpha has no effect |
| + TEST_OVERWRITE_1(0, putImageData(partialImageData.get(), 0, 0)); |
| + TEST_OVERWRITE_2(1, translate(1, 1), putImageData(fullImageData.get(), 0, 0)); // ignores tranforms |
| + TEST_OVERWRITE_1(0, putImageData(fullImageData.get(), 1, 0)); |
| + TEST_OVERWRITE_3(1, rect(0, 0, 5, 5), clip(), putImageData(fullImageData.get(), 0, 0)); // ignores clip |
| + |
| + // Test composite operators with an opaque rect that covers the entire canvas |
| + // Note: all the untested composite operations take the same code path as source-in, |
| + // which assumes that the destination may not be overwritten |
| + TEST_OVERWRITE_2(1, setGlobalCompositeOperation(String("clear")), fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_2(1, setGlobalCompositeOperation(String("source-over")), fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_2(0, setGlobalCompositeOperation(String("source-in")), fillRect(0, 0, 10, 10)); |
| + // Test composite operators with a transparent rect that covers the entire canvas |
| + TEST_OVERWRITE_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("clear")), fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_3(0, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("source-over")), fillRect(0, 0, 10, 10)); |
| + TEST_OVERWRITE_3(0, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("source-in")), fillRect(0, 0, 10, 10)); |
| + // Test composite operators with an opaque rect that does not cover the entire canvas |
| + TEST_OVERWRITE_2(0, setGlobalCompositeOperation(String("clear")), fillRect(0, 0, 5, 5)); |
| + TEST_OVERWRITE_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 5, 5)); |
| + TEST_OVERWRITE_2(0, setGlobalCompositeOperation(String("source-over")), fillRect(0, 0, 5, 5)); |
| + TEST_OVERWRITE_2(0, setGlobalCompositeOperation(String("source-in")), fillRect(0, 0, 5, 5)); |
| +} |
| + |
| +} // unnamed namespace |