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 |