OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "config.h" |
| 6 |
| 7 #include "core/html/canvas/CanvasRenderingContext2D.h" |
| 8 |
| 9 #include "core/frame/FrameView.h" |
| 10 #include "core/html/HTMLDocument.h" |
| 11 #include "core/html/ImageData.h" |
| 12 #include "core/html/canvas/CanvasGradient.h" |
| 13 #include "core/html/canvas/CanvasPattern.h" |
| 14 #include "core/html/canvas/WebGLRenderingContext.h" |
| 15 #include "core/loader/EmptyClients.h" |
| 16 #include "core/testing/DummyPageHolder.h" |
| 17 #include "platform/graphics/StaticBitmapImage.h" |
| 18 #include "platform/graphics/UnacceleratedImageBufferSurface.h" |
| 19 #include "third_party/skia/include/core/SkSurface.h" |
| 20 #include <gmock/gmock.h> |
| 21 #include <gtest/gtest.h> |
| 22 |
| 23 using namespace blink; |
| 24 using ::testing::Mock; |
| 25 |
| 26 namespace { |
| 27 |
| 28 enum BitmapOpacity { |
| 29 OpaqueBitmap, |
| 30 TransparentBitmap |
| 31 }; |
| 32 |
| 33 class FakeImageSource : public CanvasImageSource { |
| 34 public: |
| 35 FakeImageSource(IntSize, BitmapOpacity); |
| 36 |
| 37 PassRefPtr<Image> getSourceImageForCanvas(SourceImageMode, SourceImageStatus
*) const override; |
| 38 |
| 39 bool wouldTaintOrigin(SecurityOrigin* destinationSecurityOrigin) const overr
ide { return false; } |
| 40 FloatSize sourceSize() const override { return FloatSize(m_size); } |
| 41 bool isOpaque() const override { return m_isOpaque; } |
| 42 |
| 43 virtual ~FakeImageSource() { } |
| 44 |
| 45 private: |
| 46 IntSize m_size; |
| 47 RefPtr<Image> m_image; |
| 48 bool m_isOpaque; |
| 49 }; |
| 50 |
| 51 FakeImageSource::FakeImageSource(IntSize size, BitmapOpacity opacity) |
| 52 : m_size(size) |
| 53 , m_isOpaque(opacity == OpaqueBitmap) |
| 54 { |
| 55 SkAutoTUnref<SkSurface> surface(SkSurface::NewRasterN32Premul(m_size.width()
, m_size.height())); |
| 56 surface->getCanvas()->clear(opacity == OpaqueBitmap ? SK_ColorWHITE : SK_Col
orTRANSPARENT); |
| 57 RefPtr<SkImage> image = adoptRef(surface->newImageSnapshot()); |
| 58 m_image = StaticBitmapImage::create(image); |
| 59 } |
| 60 |
| 61 PassRefPtr<Image> FakeImageSource::getSourceImageForCanvas(SourceImageMode, Sour
ceImageStatus* status) const |
| 62 { |
| 63 if (status) |
| 64 *status = NormalSourceImageStatus; |
| 65 return m_image; |
| 66 } |
| 67 |
| 68 //============================================================================ |
| 69 |
| 70 class CanvasRenderingContext2DTest : public ::testing::Test { |
| 71 protected: |
| 72 CanvasRenderingContext2DTest(); |
| 73 virtual void SetUp() override; |
| 74 |
| 75 DummyPageHolder& page() const { return *m_dummyPageHolder; } |
| 76 HTMLDocument& document() const { return *m_document; } |
| 77 HTMLCanvasElement& canvasElement() const { return *m_canvasElement; } |
| 78 CanvasRenderingContext2D* context2d() const { return static_cast<CanvasRende
ringContext2D*>(canvasElement().renderingContext()); } |
| 79 |
| 80 void createContext(OpacityMode); |
| 81 |
| 82 private: |
| 83 OwnPtr<DummyPageHolder> m_dummyPageHolder; |
| 84 RefPtrWillBePersistent<HTMLDocument> m_document; |
| 85 RefPtrWillBePersistent<HTMLCanvasElement> m_canvasElement; |
| 86 |
| 87 protected: |
| 88 // Pre-canned objects for testing |
| 89 RefPtrWillBePersistent<ImageData> m_fullImageData; |
| 90 RefPtrWillBePersistent<ImageData> m_partialImageData; |
| 91 FakeImageSource m_opaqueBitmap; |
| 92 FakeImageSource m_alphaBitmap; |
| 93 StringOrCanvasGradientOrCanvasPattern m_opaqueGradient; |
| 94 StringOrCanvasGradientOrCanvasPattern m_alphaGradient; |
| 95 }; |
| 96 |
| 97 CanvasRenderingContext2DTest::CanvasRenderingContext2DTest() |
| 98 : m_opaqueBitmap(IntSize(10, 10), OpaqueBitmap) |
| 99 , m_alphaBitmap(IntSize(10, 10), TransparentBitmap) |
| 100 { } |
| 101 |
| 102 void CanvasRenderingContext2DTest::createContext(OpacityMode opacityMode) |
| 103 { |
| 104 String canvasType("2d"); |
| 105 CanvasContextCreationAttributes attributes; |
| 106 attributes.setAlpha(opacityMode == NonOpaque); |
| 107 CanvasRenderingContext2DOrWebGLRenderingContext result; |
| 108 m_canvasElement->getContext(canvasType, attributes, result); |
| 109 } |
| 110 |
| 111 void CanvasRenderingContext2DTest::SetUp() |
| 112 { |
| 113 Page::PageClients pageClients; |
| 114 fillWithEmptyClients(pageClients); |
| 115 m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600), &pageClients)
; |
| 116 m_document = toHTMLDocument(&m_dummyPageHolder->document()); |
| 117 m_document->documentElement()->setInnerHTML("<body><canvas id='c'></canvas><
/body>", ASSERT_NO_EXCEPTION); |
| 118 m_document->view()->updateLayoutAndStyleIfNeededRecursive(); |
| 119 m_canvasElement = toHTMLCanvasElement(m_document->getElementById("c")); |
| 120 |
| 121 m_fullImageData = ImageData::create(IntSize(10, 10)); |
| 122 m_partialImageData = ImageData::create(IntSize(2, 2)); |
| 123 |
| 124 NonThrowableExceptionState exceptionState; |
| 125 RefPtrWillBeRawPtr<CanvasGradient> opaqueGradient = CanvasGradient::create(F
loatPoint(0, 0), FloatPoint(10, 0)); |
| 126 opaqueGradient->addColorStop(0, String("green"), exceptionState); |
| 127 EXPECT_FALSE(exceptionState.hadException()); |
| 128 opaqueGradient->addColorStop(1, String("blue"), exceptionState); |
| 129 EXPECT_FALSE(exceptionState.hadException()); |
| 130 m_opaqueGradient.setCanvasGradient(opaqueGradient); |
| 131 |
| 132 RefPtrWillBeRawPtr<CanvasGradient> alphaGradient = CanvasGradient::create(Fl
oatPoint(0, 0), FloatPoint(10, 0)); |
| 133 alphaGradient->addColorStop(0, String("green"), exceptionState); |
| 134 EXPECT_FALSE(exceptionState.hadException()); |
| 135 alphaGradient->addColorStop(1, String("rgba(0, 0, 255, 0.5)"), exceptionStat
e); |
| 136 EXPECT_FALSE(exceptionState.hadException()); |
| 137 StringOrCanvasGradientOrCanvasPattern wrappedAlphaGradient; |
| 138 m_alphaGradient.setCanvasGradient(alphaGradient); |
| 139 } |
| 140 |
| 141 //============================================================================ |
| 142 |
| 143 class MockImageBufferSurfaceForOverwriteTesting : public UnacceleratedImageBuffe
rSurface { |
| 144 public: |
| 145 MockImageBufferSurfaceForOverwriteTesting(const IntSize& size, OpacityMode m
ode) : UnacceleratedImageBufferSurface(size, mode) { } |
| 146 virtual ~MockImageBufferSurfaceForOverwriteTesting() { } |
| 147 bool isRecording() const override { return true; } // otherwise overwrites a
re not tracked |
| 148 |
| 149 MOCK_METHOD0(willOverwriteCanvas, void()); |
| 150 }; |
| 151 |
| 152 //============================================================================ |
| 153 |
| 154 #define TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \ |
| 155 OwnPtr<MockImageBufferSurfaceForOverwriteTesting> mockSurface = adoptPtr
(new MockImageBufferSurfaceForOverwriteTesting(IntSize(10, 10), NonOpaque)); \ |
| 156 MockImageBufferSurfaceForOverwriteTesting* surfacePtr = mockSurface.get(
); \ |
| 157 canvasElement().createImageBufferUsingSurface(mockSurface.release()); \ |
| 158 EXPECT_CALL(*surfacePtr, willOverwriteCanvas()).Times(EXPECTED_OVERDRAWS
); \ |
| 159 context2d()->save(); |
| 160 |
| 161 #define TEST_OVERDRAW_FINALIZE \ |
| 162 context2d()->restore(); \ |
| 163 Mock::VerifyAndClearExpectations(surfacePtr); |
| 164 |
| 165 #define TEST_OVERDRAW_1(EXPECTED_OVERDRAWS, CALL1) \ |
| 166 do { \ |
| 167 TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \ |
| 168 context2d()->CALL1; \ |
| 169 TEST_OVERDRAW_FINALIZE \ |
| 170 } while (0) |
| 171 |
| 172 #define TEST_OVERDRAW_2(EXPECTED_OVERDRAWS, CALL1, CALL2) \ |
| 173 do { \ |
| 174 TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \ |
| 175 context2d()->CALL1; \ |
| 176 context2d()->CALL2; \ |
| 177 TEST_OVERDRAW_FINALIZE \ |
| 178 } while (0) |
| 179 |
| 180 #define TEST_OVERDRAW_3(EXPECTED_OVERDRAWS, CALL1, CALL2, CALL3) \ |
| 181 do { \ |
| 182 TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \ |
| 183 context2d()->CALL1; \ |
| 184 context2d()->CALL2; \ |
| 185 context2d()->CALL3; \ |
| 186 TEST_OVERDRAW_FINALIZE \ |
| 187 } while (0) |
| 188 |
| 189 #define TEST_OVERDRAW_4(EXPECTED_OVERDRAWS, CALL1, CALL2, CALL3, CALL4) \ |
| 190 do { \ |
| 191 TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \ |
| 192 context2d()->CALL1; \ |
| 193 context2d()->CALL2; \ |
| 194 context2d()->CALL3; \ |
| 195 context2d()->CALL4; \ |
| 196 TEST_OVERDRAW_FINALIZE \ |
| 197 } while (0) |
| 198 |
| 199 //============================================================================ |
| 200 |
| 201 TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithFillRect) |
| 202 { |
| 203 createContext(NonOpaque); |
| 204 |
| 205 TEST_OVERDRAW_1(1, fillRect(-1, -1, 12, 12)); |
| 206 TEST_OVERDRAW_1(1, fillRect(0, 0, 10, 10)); |
| 207 TEST_OVERDRAW_1(0, strokeRect(0, 0, 10, 10)); // stroking instead of filling
does not overwrite |
| 208 TEST_OVERDRAW_2(0, setGlobalAlpha(0.5f), fillRect(0, 0, 10, 10)); |
| 209 TEST_OVERDRAW_1(0, fillRect(0, 0, 9, 9)); |
| 210 TEST_OVERDRAW_2(0, translate(1, 1), fillRect(0, 0, 10, 10)); |
| 211 TEST_OVERDRAW_2(1, translate(1, 1), fillRect(-1, -1, 10, 10)); |
| 212 TEST_OVERDRAW_2(1, setFillStyle(m_opaqueGradient), fillRect(0, 0, 10, 10)); |
| 213 TEST_OVERDRAW_2(0, setFillStyle(m_alphaGradient), fillRect(0, 0, 10, 10)); |
| 214 TEST_OVERDRAW_3(0, setGlobalAlpha(0.5), setFillStyle(m_opaqueGradient), fill
Rect(0, 0, 10, 10)); |
| 215 TEST_OVERDRAW_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String(
"copy")), fillRect(0, 0, 10, 10)); |
| 216 TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0,
0, 9, 9)); |
| 217 TEST_OVERDRAW_3(0, rect(0, 0, 5, 5), clip(), fillRect(0, 0, 10, 10)); |
| 218 TEST_OVERDRAW_4(0, rect(0, 0, 5, 5), clip(), setGlobalCompositeOperation(Str
ing("copy")), fillRect(0, 0, 10, 10)); |
| 219 } |
| 220 |
| 221 TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithClearRect) |
| 222 { |
| 223 createContext(NonOpaque); |
| 224 |
| 225 TEST_OVERDRAW_1(1, clearRect(0, 0, 10, 10)); |
| 226 TEST_OVERDRAW_1(0, clearRect(0, 0, 9, 9)); |
| 227 TEST_OVERDRAW_2(1, setGlobalAlpha(0.5f), clearRect(0, 0, 10, 10)); |
| 228 TEST_OVERDRAW_2(1, setFillStyle(m_alphaGradient), clearRect(0, 0, 10, 10)); |
| 229 TEST_OVERDRAW_2(0, translate(1, 1), clearRect(0, 0, 10, 10)); |
| 230 TEST_OVERDRAW_2(1, translate(1, 1), clearRect(-1, -1, 10, 10)); |
| 231 TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("destination-in")), cl
earRect(0, 0, 10, 10)); // composite op ignored |
| 232 TEST_OVERDRAW_3(0, rect(0, 0, 5, 5), clip(), clearRect(0, 0, 10, 10)); |
| 233 } |
| 234 |
| 235 TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithDrawImage) |
| 236 { |
| 237 createContext(NonOpaque); |
| 238 NonThrowableExceptionState exceptionState; |
| 239 |
| 240 TEST_OVERDRAW_1(1, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, ex
ceptionState)); |
| 241 EXPECT_FALSE(exceptionState.hadException()); |
| 242 TEST_OVERDRAW_1(1, drawImage(&m_opaqueBitmap, 0, 0, 1, 1, 0, 0, 10, 10, exce
ptionState)); |
| 243 EXPECT_FALSE(exceptionState.hadException()); |
| 244 TEST_OVERDRAW_2(0, setGlobalAlpha(0.5f), drawImage(&m_opaqueBitmap, 0, 0, 10
, 10, 0, 0, 10, 10, exceptionState)); |
| 245 EXPECT_FALSE(exceptionState.hadException()); |
| 246 TEST_OVERDRAW_1(0, drawImage(&m_alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exc
eptionState)); |
| 247 EXPECT_FALSE(exceptionState.hadException()); |
| 248 TEST_OVERDRAW_2(0, setGlobalAlpha(0.5f), drawImage(&m_alphaBitmap, 0, 0, 10,
10, 0, 0, 10, 10, exceptionState)); |
| 249 EXPECT_FALSE(exceptionState.hadException()); |
| 250 TEST_OVERDRAW_1(0, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, ex
ceptionState)); |
| 251 EXPECT_FALSE(exceptionState.hadException()); |
| 252 TEST_OVERDRAW_1(0, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 9, 9, exce
ptionState)); |
| 253 EXPECT_FALSE(exceptionState.hadException()); |
| 254 TEST_OVERDRAW_1(1, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 11, 11, ex
ceptionState)); |
| 255 EXPECT_FALSE(exceptionState.hadException()); |
| 256 TEST_OVERDRAW_2(1, translate(-1, 0), drawImage(&m_opaqueBitmap, 0, 0, 10, 10
, 1, 0, 10, 10, exceptionState)); |
| 257 EXPECT_FALSE(exceptionState.hadException()); |
| 258 TEST_OVERDRAW_2(0, translate(-1, 0), drawImage(&m_opaqueBitmap, 0, 0, 10, 10
, 0, 0, 10, 10, exceptionState)); |
| 259 EXPECT_FALSE(exceptionState.hadException()); |
| 260 TEST_OVERDRAW_2(0, setFillStyle(m_opaqueGradient), drawImage(&m_alphaBitmap,
0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); // fillStyle ignored by drawImage |
| 261 EXPECT_FALSE(exceptionState.hadException()); |
| 262 TEST_OVERDRAW_2(1, setFillStyle(m_alphaGradient), drawImage(&m_opaqueBitmap,
0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); // fillStyle ignored by drawImage |
| 263 EXPECT_FALSE(exceptionState.hadException()); |
| 264 TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), drawImage(&m
_opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState)); |
| 265 EXPECT_FALSE(exceptionState.hadException()); |
| 266 TEST_OVERDRAW_3(0, rect(0, 0, 5, 5), clip(), drawImage(&m_opaqueBitmap, 0, 0
, 10, 10, 0, 0, 10, 10, exceptionState)); |
| 267 EXPECT_FALSE(exceptionState.hadException()); |
| 268 } |
| 269 |
| 270 TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithPutImageData) |
| 271 { |
| 272 createContext(NonOpaque); |
| 273 |
| 274 // Test putImageData |
| 275 TEST_OVERDRAW_1(1, putImageData(m_fullImageData.get(), 0, 0)); |
| 276 TEST_OVERDRAW_1(1, putImageData(m_fullImageData.get(), 0, 0, 0, 0, 10, 10)); |
| 277 TEST_OVERDRAW_1(0, putImageData(m_fullImageData.get(), 0, 0, 1, 1, 8, 8)); |
| 278 TEST_OVERDRAW_2(1, setGlobalAlpha(0.5f), putImageData(m_fullImageData.get(),
0, 0)); // alpha has no effect |
| 279 TEST_OVERDRAW_1(0, putImageData(m_partialImageData.get(), 0, 0)); |
| 280 TEST_OVERDRAW_2(1, translate(1, 1), putImageData(m_fullImageData.get(), 0, 0
)); // ignores tranforms |
| 281 TEST_OVERDRAW_1(0, putImageData(m_fullImageData.get(), 1, 0)); |
| 282 TEST_OVERDRAW_3(1, rect(0, 0, 5, 5), clip(), putImageData(m_fullImageData.ge
t(), 0, 0)); // ignores clip |
| 283 } |
| 284 |
| 285 TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithCompositeOperations) |
| 286 { |
| 287 createContext(NonOpaque); |
| 288 |
| 289 // Test composite operators with an opaque rect that covers the entire canva
s |
| 290 // Note: all the untested composite operations take the same code path as so
urce-in, |
| 291 // which assumes that the destination may not be overwritten |
| 292 TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("clear")), fillRect(0,
0, 10, 10)); |
| 293 TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0,
0, 10, 10)); |
| 294 TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("source-over")), fillR
ect(0, 0, 10, 10)); |
| 295 TEST_OVERDRAW_2(0, setGlobalCompositeOperation(String("source-in")), fillRec
t(0, 0, 10, 10)); |
| 296 // Test composite operators with a transparent rect that covers the entire c
anvas |
| 297 TEST_OVERDRAW_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String(
"clear")), fillRect(0, 0, 10, 10)); |
| 298 TEST_OVERDRAW_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String(
"copy")), fillRect(0, 0, 10, 10)); |
| 299 TEST_OVERDRAW_3(0, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String(
"source-over")), fillRect(0, 0, 10, 10)); |
| 300 TEST_OVERDRAW_3(0, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String(
"source-in")), fillRect(0, 0, 10, 10)); |
| 301 // Test composite operators with an opaque rect that does not cover the enti
re canvas |
| 302 TEST_OVERDRAW_2(0, setGlobalCompositeOperation(String("clear")), fillRect(0,
0, 5, 5)); |
| 303 TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0,
0, 5, 5)); |
| 304 TEST_OVERDRAW_2(0, setGlobalCompositeOperation(String("source-over")), fillR
ect(0, 0, 5, 5)); |
| 305 TEST_OVERDRAW_2(0, setGlobalCompositeOperation(String("source-in")), fillRec
t(0, 0, 5, 5)); |
| 306 } |
| 307 |
| 308 } // unnamed namespace |
OLD | NEW |