text: // 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/frame/ImageBitmap.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/ExpensiveCanvasHeuristicParameters.h"
  #include "platform/graphics/RecordingImageBufferSurface.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 {
  
  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 elementSize() 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;
  }
  
  //============================================================================
  
  class CanvasRenderingContext2DTest : public ::testing::Test {
  protected:
      CanvasRenderingContext2DTest();
      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;
  
  protected:
      // Pre-canned objects for testing
      Persistent<ImageData> m_fullImageData;
      Persistent<ImageData> m_partialImageData;
      FakeImageSource m_opaqueBitmap;
      FakeImageSource m_alphaBitmap;
      StringOrCanvasGradientOrCanvasPattern m_opaqueGradient;
      StringOrCanvasGradientOrCanvasPattern m_alphaGradient;
  };
  
  CanvasRenderingContext2DTest::CanvasRenderingContext2DTest()
      : m_opaqueBitmap(IntSize(10, 10), OpaqueBitmap)
      , m_alphaBitmap(IntSize(10, 10), TransparentBitmap)
  { }
  
  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()->updateLayoutAndStyleForPainting();
      m_canvasElement = toHTMLCanvasElement(m_document->getElementById("c"));
  
      m_fullImageData = ImageData::create(IntSize(10, 10));
      m_partialImageData = ImageData::create(IntSize(2, 2));
  
      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());
      m_opaqueGradient.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;
      m_alphaGradient.setCanvasGradient(alphaGradient);
  }
  
  //============================================================================
  
  class 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());
  };
  
  //============================================================================
  
  class MockCanvasObserver : public NoBaseWillBeGarbageCollectedFinalized<MockCanvasObserver>, public CanvasObserver {
      WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(MockCanvasObserver);
  public:
      static PassOwnPtrWillBeRawPtr<MockCanvasObserver> create()
      {
          return adoptPtrWillBeNoop(new MockCanvasObserver);
      }
  
      virtual ~MockCanvasObserver() { }
      MOCK_METHOD2(canvasChanged, void(HTMLCanvasElement*, const FloatRect&));
      MOCK_METHOD1(canvasResized, void(HTMLCanvasElement*));
  #if !ENABLE(OILPAN)
      void canvasDestroyed(HTMLCanvasElement*) override { };
  #endif
  };
  
  //============================================================================
  
  #define TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \
          OwnPtr<MockImageBufferSurfaceForOverwriteTesting> mockSurface = adoptPtr(new MockImageBufferSurfaceForOverwriteTesting(IntSize(10, 10), NonOpaque)); \
          MockImageBufferSurfaceForOverwriteTesting* surfacePtr = mockSurface.get(); \
          canvasElement().createImageBufferUsingSurfaceForTesting(mockSurface.release()); \
          EXPECT_CALL(*surfacePtr, willOverwriteCanvas()).Times(EXPECTED_OVERDRAWS); \
          context2d()->save();
  
  #define TEST_OVERDRAW_FINALIZE \
          context2d()->restore(); \
          Mock::VerifyAndClearExpectations(surfacePtr);
  
  #define TEST_OVERDRAW_1(EXPECTED_OVERDRAWS, CALL1) \
      do { \
          TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \
          context2d()->CALL1; \
          TEST_OVERDRAW_FINALIZE \
      } while (0)
  
  #define TEST_OVERDRAW_2(EXPECTED_OVERDRAWS, CALL1, CALL2) \
      do { \
          TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \
          context2d()->CALL1; \
          context2d()->CALL2; \
          TEST_OVERDRAW_FINALIZE \
      } while (0)
  
  #define TEST_OVERDRAW_3(EXPECTED_OVERDRAWS, CALL1, CALL2, CALL3) \
      do { \
          TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \
          context2d()->CALL1; \
          context2d()->CALL2; \
          context2d()->CALL3; \
          TEST_OVERDRAW_FINALIZE \
      } while (0)
  
  #define TEST_OVERDRAW_4(EXPECTED_OVERDRAWS, CALL1, CALL2, CALL3, CALL4) \
      do { \
          TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \
          context2d()->CALL1; \
          context2d()->CALL2; \
          context2d()->CALL3; \
          context2d()->CALL4; \
          TEST_OVERDRAW_FINALIZE \
      } while (0)
  
  //============================================================================
  
  class MockSurfaceFactory : public RecordingImageBufferFallbackSurfaceFactory {
  public:
      enum FallbackExpectation {
          ExpectFallback,
          ExpectNoFallback
      };
      static PassOwnPtr<MockSurfaceFactory> create(FallbackExpectation expectation) { return adoptPtr(new MockSurfaceFactory(expectation)); }
  
      PassOwnPtr<ImageBufferSurface> createSurface(const IntSize& size, OpacityMode mode) override
      {
          EXPECT_EQ(ExpectFallback, m_expectation);
          m_didFallback = true;
          return adoptPtr(new UnacceleratedImageBufferSurface(size, mode));
      }
  
      virtual ~MockSurfaceFactory()
      {
          if (m_expectation == ExpectFallback) {
              EXPECT_TRUE(m_didFallback);
          }
      }
  
  private:
      MockSurfaceFactory(FallbackExpectation expectation)
          : m_expectation(expectation)
          , m_didFallback(false) { }
  
      FallbackExpectation m_expectation;
      bool m_didFallback;
  };
  
  //============================================================================
  
  TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithFillRect)
  {
      createContext(NonOpaque);
  
      TEST_OVERDRAW_1(1, fillRect(-1, -1, 12, 12));
      TEST_OVERDRAW_1(1, fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_1(0, strokeRect(0, 0, 10, 10)); // stroking instead of filling does not overwrite
      TEST_OVERDRAW_2(0, setGlobalAlpha(0.5f), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_1(0, fillRect(0, 0, 9, 9));
      TEST_OVERDRAW_2(0, translate(1, 1), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_2(1, translate(1, 1), fillRect(-1, -1, 10, 10));
      TEST_OVERDRAW_2(1, setFillStyle(m_opaqueGradient), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_2(0, setFillStyle(m_alphaGradient), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_3(0, setGlobalAlpha(0.5), setFillStyle(m_opaqueGradient), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 9, 9));
      TEST_OVERDRAW_3(0, rect(0, 0, 5, 5), clip(), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_4(0, rect(0, 0, 5, 5), clip(), setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 10, 10));
  }
  
  TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithClearRect)
  {
      createContext(NonOpaque);
  
      TEST_OVERDRAW_1(1, clearRect(0, 0, 10, 10));
      TEST_OVERDRAW_1(0, clearRect(0, 0, 9, 9));
      TEST_OVERDRAW_2(1, setGlobalAlpha(0.5f), clearRect(0, 0, 10, 10));
      TEST_OVERDRAW_2(1, setFillStyle(m_alphaGradient), clearRect(0, 0, 10, 10));
      TEST_OVERDRAW_2(0, translate(1, 1), clearRect(0, 0, 10, 10));
      TEST_OVERDRAW_2(1, translate(1, 1), clearRect(-1, -1, 10, 10));
      TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("destination-in")), clearRect(0, 0, 10, 10)); // composite op ignored
      TEST_OVERDRAW_3(0, rect(0, 0, 5, 5), clip(), clearRect(0, 0, 10, 10));
  }
  
  TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithDrawImage)
  {
      createContext(NonOpaque);
      NonThrowableExceptionState exceptionState;
  
      TEST_OVERDRAW_1(1, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_1(1, drawImage(&m_opaqueBitmap, 0, 0, 1, 1, 0, 0, 10, 10, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_2(0, setGlobalAlpha(0.5f), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_1(0, drawImage(&m_alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_2(0, setGlobalAlpha(0.5f), drawImage(&m_alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_1(0, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_1(0, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 9, 9, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_1(1, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 11, 11, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_2(1, translate(-1, 0), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_2(0, translate(-1, 0), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_2(0, setFillStyle(m_opaqueGradient), drawImage(&m_alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); // fillStyle ignored by drawImage
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_2(1, setFillStyle(m_alphaGradient), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); // fillStyle ignored by drawImage
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
      TEST_OVERDRAW_3(0, rect(0, 0, 5, 5), clip(), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState));
      EXPECT_FALSE(exceptionState.hadException());
  }
  
  TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithPutImageData)
  {
      createContext(NonOpaque);
  
      // Test putImageData
      TEST_OVERDRAW_1(1, putImageData(m_fullImageData.get(), 0, 0));
      TEST_OVERDRAW_1(1, putImageData(m_fullImageData.get(), 0, 0, 0, 0, 10, 10));
      TEST_OVERDRAW_1(0, putImageData(m_fullImageData.get(), 0, 0, 1, 1, 8, 8));
      TEST_OVERDRAW_2(1, setGlobalAlpha(0.5f), putImageData(m_fullImageData.get(), 0, 0)); // alpha has no effect
      TEST_OVERDRAW_1(0, putImageData(m_partialImageData.get(), 0, 0));
      TEST_OVERDRAW_2(1, translate(1, 1), putImageData(m_fullImageData.get(), 0, 0)); // ignores tranforms
      TEST_OVERDRAW_1(0, putImageData(m_fullImageData.get(), 1, 0));
      TEST_OVERDRAW_3(1, rect(0, 0, 5, 5), clip(), putImageData(m_fullImageData.get(), 0, 0)); // ignores clip
  }
  
  TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithCompositeOperations)
  {
      createContext(NonOpaque);
  
      // 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_OVERDRAW_2(1, setGlobalCompositeOperation(String("clear")), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("source-over")), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_2(0, setGlobalCompositeOperation(String("source-in")), fillRect(0, 0, 10, 10));
      // Test composite operators with a transparent rect that covers the entire canvas
      TEST_OVERDRAW_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("clear")), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_3(0, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("source-over")), fillRect(0, 0, 10, 10));
      TEST_OVERDRAW_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_OVERDRAW_2(0, setGlobalCompositeOperation(String("clear")), fillRect(0, 0, 5, 5));
      TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 5, 5));
      TEST_OVERDRAW_2(0, setGlobalCompositeOperation(String("source-over")), fillRect(0, 0, 5, 5));
      TEST_OVERDRAW_2(0, setGlobalCompositeOperation(String("source-in")), fillRect(0, 0, 5, 5));
  }
  
  TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionByDefault)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      EXPECT_FALSE(canvasElement().shouldBeDirectComposited());
  }
  
  TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionUnderOverdrawLimit)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      context2d()->setGlobalAlpha(0.5f); // To prevent overdraw optimization
      for (int i = 0; i < ExpensiveCanvasHeuristicParameters::ExpensiveOverdrawThreshold - 1; i++) {
          context2d()->fillRect(0, 0, 10, 10);
      }
  
      EXPECT_FALSE(canvasElement().shouldBeDirectComposited());
  }
  
  TEST_F(CanvasRenderingContext2DTest, LayerPromotionOverOverdrawLimit)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      context2d()->setGlobalAlpha(0.5f); // To prevent overdraw optimization
      for (int i = 0; i < ExpensiveCanvasHeuristicParameters::ExpensiveOverdrawThreshold; i++) {
          context2d()->fillRect(0, 0, 10, 10);
      }
  
      EXPECT_TRUE(canvasElement().shouldBeDirectComposited());
  }
  
  TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionUnderImageSizeRatioLimit)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      NonThrowableExceptionState exceptionState;
      RefPtrWillBeRawPtr<Element> sourceCanvasElement = document().createElement("canvas", exceptionState);
      EXPECT_FALSE(exceptionState.hadException());
      HTMLCanvasElement* sourceCanvas = static_cast<HTMLCanvasElement*>(sourceCanvasElement.get());
      IntSize sourceSize(10, 10 * ExpensiveCanvasHeuristicParameters::ExpensiveImageSizeRatio);
      OwnPtr<UnacceleratedImageBufferSurface> sourceSurface = adoptPtr(new UnacceleratedImageBufferSurface(sourceSize, NonOpaque));
      sourceCanvas->createImageBufferUsingSurfaceForTesting(sourceSurface.release());
  
      // Go through an ImageBitmap to avoid triggering a display list fallback
      RefPtrWillBeRawPtr<ImageBitmap> sourceImageBitmap = ImageBitmap::create(sourceCanvas, IntRect(IntPoint(0, 0), sourceSize));
  
      context2d()->drawImage(sourceImageBitmap.get(), 0, 0, 1, 1, 0, 0, 1, 1, exceptionState);
      EXPECT_FALSE(exceptionState.hadException());
  
      EXPECT_FALSE(canvasElement().shouldBeDirectComposited());
  }
  
  TEST_F(CanvasRenderingContext2DTest, LayerPromotionOverImageSizeRatioLimit)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      NonThrowableExceptionState exceptionState;
      RefPtrWillBeRawPtr<Element> sourceCanvasElement = document().createElement("canvas", exceptionState);
      EXPECT_FALSE(exceptionState.hadException());
      HTMLCanvasElement* sourceCanvas = static_cast<HTMLCanvasElement*>(sourceCanvasElement.get());
      IntSize sourceSize(10, 10 * ExpensiveCanvasHeuristicParameters::ExpensiveImageSizeRatio + 1);
      OwnPtr<UnacceleratedImageBufferSurface> sourceSurface = adoptPtr(new UnacceleratedImageBufferSurface(sourceSize, NonOpaque));
      sourceCanvas->createImageBufferUsingSurfaceForTesting(sourceSurface.release());
  
      // Go through an ImageBitmap to avoid triggering a display list fallback
      RefPtrWillBeRawPtr<ImageBitmap> sourceImageBitmap = ImageBitmap::create(sourceCanvas, IntRect(IntPoint(0, 0), sourceSize));
  
      context2d()->drawImage(sourceImageBitmap.get(), 0, 0, 1, 1, 0, 0, 1, 1, exceptionState);
      EXPECT_FALSE(exceptionState.hadException());
  
      EXPECT_TRUE(canvasElement().shouldBeDirectComposited());
  }
  
  TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionUnderExpensivePathPointCount)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      context2d()->beginPath();
      context2d()->moveTo(7, 5);
      for (int i = 1; i < ExpensiveCanvasHeuristicParameters::ExpensivePathPointCount-1; i++) {
          float angleRad = twoPiFloat * i / (ExpensiveCanvasHeuristicParameters::ExpensivePathPointCount - 1);
          context2d()->lineTo(5 + 2 * cos(angleRad), 5 + 2 * sin(angleRad));
      }
      context2d()->fill();
  
      EXPECT_FALSE(canvasElement().shouldBeDirectComposited());
  }
  
  TEST_F(CanvasRenderingContext2DTest, LayerPromotionOverExpensivePathPointCount)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      context2d()->beginPath();
      context2d()->moveTo(7, 5);
      for (int i = 1; i < ExpensiveCanvasHeuristicParameters::ExpensivePathPointCount + 1; i++) {
          float angleRad = twoPiFloat * i / (ExpensiveCanvasHeuristicParameters::ExpensivePathPointCount + 1);
          context2d()->lineTo(5 + 2 * cos(angleRad), 5 + 2 * sin(angleRad));
      }
      context2d()->fill();
  
      EXPECT_TRUE(canvasElement().shouldBeDirectComposited());
  }
  
  TEST_F(CanvasRenderingContext2DTest, LayerPromotionWhenPathIsConcave)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      context2d()->beginPath();
      context2d()->moveTo(1, 1);
      context2d()->lineTo(5, 5);
      context2d()->lineTo(9, 1);
      context2d()->lineTo(5, 9);
      context2d()->fill();
  
      if (ExpensiveCanvasHeuristicParameters::ConcavePathsAreExpensive) {
          EXPECT_TRUE(canvasElement().shouldBeDirectComposited());
      } else {
          EXPECT_FALSE(canvasElement().shouldBeDirectComposited());
      }
  }
  
  TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionWithRectangleClip)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      context2d()->beginPath();
      context2d()->rect(1, 1, 2, 2);
      context2d()->clip();
      context2d()->fillRect(0, 0, 4, 4);
  
      EXPECT_FALSE(canvasElement().shouldBeDirectComposited());
  }
  
  TEST_F(CanvasRenderingContext2DTest, LayerPromotionWithComplexClip)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      context2d()->beginPath();
      context2d()->moveTo(1, 1);
      context2d()->lineTo(5, 5);
      context2d()->lineTo(9, 1);
      context2d()->lineTo(5, 9);
      context2d()->clip();
      context2d()->fillRect(0, 0, 4, 4);
  
      if (ExpensiveCanvasHeuristicParameters::ComplexClipsAreExpensive) {
          EXPECT_TRUE(canvasElement().shouldBeDirectComposited());
      } else {
          EXPECT_FALSE(canvasElement().shouldBeDirectComposited());
      }
  }
  
  TEST_F(CanvasRenderingContext2DTest, LayerPromotionWithBlurredShadow)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      context2d()->setShadowColor(String("red"));
      context2d()->setShadowBlur(1.0f);
      context2d()->fillRect(1, 1, 1, 1);
  
      if (ExpensiveCanvasHeuristicParameters::BlurredShadowsAreExpensive) {
          EXPECT_TRUE(canvasElement().shouldBeDirectComposited());
      } else {
          EXPECT_FALSE(canvasElement().shouldBeDirectComposited());
      }
  }
  
  TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionWithSharpShadow)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      context2d()->setShadowColor(String("red"));
      context2d()->setShadowOffsetX(1.0f);
      context2d()->fillRect(1, 1, 1, 1);
  
      EXPECT_FALSE(canvasElement().shouldBeDirectComposited());
  }
  
  TEST_F(CanvasRenderingContext2DTest, NoFallbackWithSmallState)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      context2d()->fillRect(0, 0, 1, 1); // To have a non-empty dirty rect
      for (int i = 0; i < ExpensiveCanvasHeuristicParameters::ExpensiveRecordingStackDepth - 2; ++i) {
          context2d()->save();
          context2d()->translate(1.0f, 0.0f);
      }
      canvasElement().doDeferredPaintInvalidation(); // To close the current frame
  }
  
  TEST_F(CanvasRenderingContext2DTest, FallbackWithLargeState)
  {
      createContext(NonOpaque);
      OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectFallback), NonOpaque));
      canvasElement().createImageBufferUsingSurfaceForTesting(surface.release());
  
      context2d()->fillRect(0, 0, 1, 1); // To have a non-empty dirty rect
      for (int i = 0; i < ExpensiveCanvasHeuristicParameters::ExpensiveRecordingStackDepth - 1; ++i) {
          context2d()->save();
          context2d()->translate(1.0f, 0.0f);
      }
      canvasElement().doDeferredPaintInvalidation(); // To close the current frame
  }
  
  TEST_F(CanvasRenderingContext2DTest, CanvasObserver)
  {
      createContext(NonOpaque);
      OwnPtrWillBeRawPtr<MockCanvasObserver> observer = MockCanvasObserver::create();
      canvasElement().addObserver(observer.get());
  
      // The canvasChanged notification must be immediate, and not deferred until paint time
      // because offscreen canvases, which are not painted, also need to emit notifications.
      EXPECT_CALL(*observer, canvasChanged(&canvasElement(), FloatRect(0, 0, 1, 1))).Times(1);
      context2d()->fillRect(0, 0, 1, 1);
      Mock::VerifyAndClearExpectations(observer.get());
  
      canvasElement().removeObserver(observer.get());
  }
  
  } // unnamed namespace
  