| // Copyright 2016 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 "core/html/canvas/CanvasAsyncBlobCreator.h" |
| |
| #include "core/html/ImageData.h" |
| #include "core/testing/DummyPageHolder.h" |
| #include "platform/testing/UnitTestHelpers.h" |
| #include "public/platform/Platform.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "wtf/Functional.h" |
| |
| namespace blink { |
| |
| typedef CanvasAsyncBlobCreator::IdleTaskStatus IdleTaskStatus; |
| |
| class MockCanvasAsyncBlobCreator : public CanvasAsyncBlobCreator { |
| public: |
| MockCanvasAsyncBlobCreator(DOMUint8ClampedArray* data, |
| const IntSize& size, |
| MimeType mimeType, |
| Document& document) |
| : CanvasAsyncBlobCreator(data, mimeType, size, nullptr, 0, document) {} |
| |
| CanvasAsyncBlobCreator::IdleTaskStatus idleTaskStatus() { |
| return m_idleTaskStatus; |
| } |
| |
| MOCK_METHOD0(signalTaskSwitchInStartTimeoutEventForTesting, void()); |
| MOCK_METHOD0(signalTaskSwitchInCompleteTimeoutEventForTesting, void()); |
| |
| protected: |
| void createBlobAndInvokeCallback() override{}; |
| void createNullAndInvokeCallback() override{}; |
| void signalAlternativeCodePathFinishedForTesting() override; |
| void postDelayedTaskToMainThread(const WebTraceLocation&, |
| std::unique_ptr<WTF::Closure>, |
| double delayMs) override; |
| }; |
| |
| void MockCanvasAsyncBlobCreator::signalAlternativeCodePathFinishedForTesting() { |
| testing::exitRunLoop(); |
| } |
| |
| void MockCanvasAsyncBlobCreator::postDelayedTaskToMainThread( |
| const WebTraceLocation& location, |
| std::unique_ptr<WTF::Closure> task, |
| double delayMs) { |
| DCHECK(isMainThread()); |
| Platform::current()->mainThread()->getWebTaskRunner()->postTask( |
| location, std::move(task)); |
| } |
| |
| //============================================================================== |
| //=================================PNG========================================== |
| //============================================================================== |
| |
| class MockCanvasAsyncBlobCreatorWithoutStartPng |
| : public MockCanvasAsyncBlobCreator { |
| public: |
| MockCanvasAsyncBlobCreatorWithoutStartPng(DOMUint8ClampedArray* data, |
| const IntSize& size, |
| Document& document) |
| : MockCanvasAsyncBlobCreator(data, size, MimeTypePng, document) {} |
| |
| protected: |
| void scheduleInitiatePngEncoding() override { |
| // Deliberately make scheduleInitiatePngEncoding do nothing so that idle |
| // task never starts |
| } |
| }; |
| |
| //============================================================================== |
| |
| class MockCanvasAsyncBlobCreatorWithoutCompletePng |
| : public MockCanvasAsyncBlobCreator { |
| public: |
| MockCanvasAsyncBlobCreatorWithoutCompletePng(DOMUint8ClampedArray* data, |
| const IntSize& size, |
| Document& document) |
| : MockCanvasAsyncBlobCreator(data, size, MimeTypePng, document) {} |
| |
| protected: |
| void scheduleInitiatePngEncoding() override { |
| Platform::current()->mainThread()->getWebTaskRunner()->postTask( |
| BLINK_FROM_HERE, |
| WTF::bind( |
| &MockCanvasAsyncBlobCreatorWithoutCompletePng::initiatePngEncoding, |
| wrapPersistent(this), std::numeric_limits<double>::max())); |
| } |
| |
| void idleEncodeRowsPng(double deadlineSeconds) override { |
| // Deliberately make idleEncodeRowsPng do nothing so that idle task never |
| // completes |
| } |
| }; |
| |
| //============================================================================== |
| //=================================JPEG========================================= |
| //============================================================================== |
| |
| class MockCanvasAsyncBlobCreatorWithoutStartJpeg |
| : public MockCanvasAsyncBlobCreator { |
| public: |
| MockCanvasAsyncBlobCreatorWithoutStartJpeg(DOMUint8ClampedArray* data, |
| const IntSize& size, |
| Document& document) |
| : MockCanvasAsyncBlobCreator(data, size, MimeTypeJpeg, document) {} |
| |
| protected: |
| void scheduleInitiateJpegEncoding(const double&) override { |
| // Deliberately make scheduleInitiateJpegEncoding do nothing so that idle |
| // task never starts |
| } |
| }; |
| |
| //============================================================================== |
| |
| class MockCanvasAsyncBlobCreatorWithoutCompleteJpeg |
| : public MockCanvasAsyncBlobCreator { |
| public: |
| MockCanvasAsyncBlobCreatorWithoutCompleteJpeg(DOMUint8ClampedArray* data, |
| const IntSize& size, |
| Document& document) |
| : MockCanvasAsyncBlobCreator(data, size, MimeTypeJpeg, document) {} |
| |
| protected: |
| void scheduleInitiateJpegEncoding(const double& quality) override { |
| Platform::current()->mainThread()->getWebTaskRunner()->postTask( |
| BLINK_FROM_HERE, |
| WTF::bind(&MockCanvasAsyncBlobCreatorWithoutCompleteJpeg:: |
| initiateJpegEncoding, |
| wrapPersistent(this), quality, |
| std::numeric_limits<double>::max())); |
| } |
| |
| void idleEncodeRowsJpeg(double deadlineSeconds) override { |
| // Deliberately make idleEncodeRowsJpeg do nothing so that idle task never |
| // completes |
| } |
| }; |
| |
| //============================================================================== |
| |
| class CanvasAsyncBlobCreatorTest : public ::testing::Test { |
| public: |
| // Png unit tests |
| void prepareMockCanvasAsyncBlobCreatorWithoutStartPng(); |
| void prepareMockCanvasAsyncBlobCreatorWithoutCompletePng(); |
| void prepareMockCanvasAsyncBlobCreatorFailPng(); |
| |
| // Jpeg unit tests |
| void prepareMockCanvasAsyncBlobCreatorWithoutStartJpeg(); |
| void prepareMockCanvasAsyncBlobCreatorWithoutCompleteJpeg(); |
| void prepareMockCanvasAsyncBlobCreatorFailJpeg(); |
| |
| protected: |
| CanvasAsyncBlobCreatorTest(); |
| MockCanvasAsyncBlobCreator* asyncBlobCreator() { |
| return m_asyncBlobCreator.get(); |
| } |
| void TearDown() override; |
| |
| private: |
| Document& document() { return m_dummyPageHolder->document(); } |
| |
| Persistent<MockCanvasAsyncBlobCreator> m_asyncBlobCreator; |
| std::unique_ptr<DummyPageHolder> m_dummyPageHolder; |
| }; |
| |
| CanvasAsyncBlobCreatorTest::CanvasAsyncBlobCreatorTest() { |
| m_dummyPageHolder = DummyPageHolder::create(); |
| } |
| |
| void CanvasAsyncBlobCreatorTest:: |
| prepareMockCanvasAsyncBlobCreatorWithoutStartPng() { |
| IntSize testSize(20, 20); |
| ImageData* imageData = ImageData::create(testSize); |
| |
| m_asyncBlobCreator = new MockCanvasAsyncBlobCreatorWithoutStartPng( |
| imageData->data(), testSize, document()); |
| } |
| |
| void CanvasAsyncBlobCreatorTest:: |
| prepareMockCanvasAsyncBlobCreatorWithoutCompletePng() { |
| IntSize testSize(20, 20); |
| ImageData* imageData = ImageData::create(testSize); |
| |
| m_asyncBlobCreator = new MockCanvasAsyncBlobCreatorWithoutCompletePng( |
| imageData->data(), testSize, document()); |
| } |
| |
| void CanvasAsyncBlobCreatorTest::prepareMockCanvasAsyncBlobCreatorFailPng() { |
| IntSize testSize(0, 0); |
| ImageData* imageData = ImageData::create(testSize); |
| |
| // We reuse the class MockCanvasAsyncBlobCreatorWithoutCompletePng because |
| // this test case is expected to fail at initialization step before |
| // completion. |
| m_asyncBlobCreator = new MockCanvasAsyncBlobCreatorWithoutCompletePng( |
| imageData->data(), testSize, document()); |
| } |
| |
| void CanvasAsyncBlobCreatorTest:: |
| prepareMockCanvasAsyncBlobCreatorWithoutStartJpeg() { |
| IntSize testSize(20, 20); |
| ImageData* imageData = ImageData::create(testSize); |
| |
| m_asyncBlobCreator = new MockCanvasAsyncBlobCreatorWithoutStartJpeg( |
| imageData->data(), testSize, document()); |
| } |
| |
| void CanvasAsyncBlobCreatorTest:: |
| prepareMockCanvasAsyncBlobCreatorWithoutCompleteJpeg() { |
| IntSize testSize(20, 20); |
| ImageData* imageData = ImageData::create(testSize); |
| |
| m_asyncBlobCreator = new MockCanvasAsyncBlobCreatorWithoutCompleteJpeg( |
| imageData->data(), testSize, document()); |
| } |
| |
| void CanvasAsyncBlobCreatorTest::prepareMockCanvasAsyncBlobCreatorFailJpeg() { |
| IntSize testSize(0, 0); |
| ImageData* imageData = ImageData::create(testSize); |
| |
| // We reuse the class MockCanvasAsyncBlobCreatorWithoutCompleteJpeg because |
| // this test case is expected to fail at initialization step before |
| // completion. |
| m_asyncBlobCreator = new MockCanvasAsyncBlobCreatorWithoutCompleteJpeg( |
| imageData->data(), testSize, document()); |
| } |
| |
| void CanvasAsyncBlobCreatorTest::TearDown() { |
| m_asyncBlobCreator = nullptr; |
| } |
| |
| //============================================================================== |
| |
| TEST_F(CanvasAsyncBlobCreatorTest, |
| PngIdleTaskNotStartedWhenStartTimeoutEventHappens) { |
| // This test mocks the scenario when idle task is not started when the |
| // StartTimeoutEvent is inspecting the idle task status. |
| // The whole image encoding process (including initialization) will then |
| // become carried out in the alternative code path instead. |
| this->prepareMockCanvasAsyncBlobCreatorWithoutStartPng(); |
| EXPECT_CALL(*(asyncBlobCreator()), |
| signalTaskSwitchInStartTimeoutEventForTesting()); |
| |
| this->asyncBlobCreator()->scheduleAsyncBlobCreation(true); |
| testing::enterRunLoop(); |
| |
| ::testing::Mock::VerifyAndClearExpectations(asyncBlobCreator()); |
| EXPECT_EQ(IdleTaskStatus::IdleTaskSwitchedToMainThreadTask, |
| this->asyncBlobCreator()->idleTaskStatus()); |
| } |
| |
| TEST_F(CanvasAsyncBlobCreatorTest, |
| PngIdleTaskNotCompletedWhenCompleteTimeoutEventHappens) { |
| // This test mocks the scenario when idle task is not completed when the |
| // CompleteTimeoutEvent is inspecting the idle task status. |
| // The remaining image encoding process (excluding initialization) will |
| // then become carried out in the alternative code path instead. |
| this->prepareMockCanvasAsyncBlobCreatorWithoutCompletePng(); |
| EXPECT_CALL(*(asyncBlobCreator()), |
| signalTaskSwitchInCompleteTimeoutEventForTesting()); |
| |
| this->asyncBlobCreator()->scheduleAsyncBlobCreation(true); |
| testing::enterRunLoop(); |
| |
| ::testing::Mock::VerifyAndClearExpectations(asyncBlobCreator()); |
| EXPECT_EQ(IdleTaskStatus::IdleTaskSwitchedToMainThreadTask, |
| this->asyncBlobCreator()->idleTaskStatus()); |
| } |
| |
| TEST_F(CanvasAsyncBlobCreatorTest, |
| PngIdleTaskFailedWhenStartTimeoutEventHappens) { |
| // This test mocks the scenario when idle task is not failed during when |
| // either the StartTimeoutEvent or the CompleteTimeoutEvent is inspecting |
| // the idle task status. |
| this->prepareMockCanvasAsyncBlobCreatorFailPng(); |
| |
| this->asyncBlobCreator()->scheduleAsyncBlobCreation(true); |
| testing::enterRunLoop(); |
| |
| EXPECT_EQ(IdleTaskStatus::IdleTaskFailed, |
| this->asyncBlobCreator()->idleTaskStatus()); |
| } |
| |
| // The below 3 unit tests have exactly same workflow as the above 3 unit tests |
| // except that they are encoding on JPEG image formats instead of PNG. |
| TEST_F(CanvasAsyncBlobCreatorTest, |
| JpegIdleTaskNotStartedWhenStartTimeoutEventHappens) { |
| this->prepareMockCanvasAsyncBlobCreatorWithoutStartJpeg(); |
| EXPECT_CALL(*(asyncBlobCreator()), |
| signalTaskSwitchInStartTimeoutEventForTesting()); |
| |
| this->asyncBlobCreator()->scheduleAsyncBlobCreation(true, 1.0); |
| testing::enterRunLoop(); |
| |
| ::testing::Mock::VerifyAndClearExpectations(asyncBlobCreator()); |
| EXPECT_EQ(IdleTaskStatus::IdleTaskSwitchedToMainThreadTask, |
| this->asyncBlobCreator()->idleTaskStatus()); |
| } |
| |
| TEST_F(CanvasAsyncBlobCreatorTest, |
| JpegIdleTaskNotCompletedWhenCompleteTimeoutEventHappens) { |
| this->prepareMockCanvasAsyncBlobCreatorWithoutCompleteJpeg(); |
| EXPECT_CALL(*(asyncBlobCreator()), |
| signalTaskSwitchInCompleteTimeoutEventForTesting()); |
| |
| this->asyncBlobCreator()->scheduleAsyncBlobCreation(true, 1.0); |
| testing::enterRunLoop(); |
| |
| ::testing::Mock::VerifyAndClearExpectations(asyncBlobCreator()); |
| EXPECT_EQ(IdleTaskStatus::IdleTaskSwitchedToMainThreadTask, |
| this->asyncBlobCreator()->idleTaskStatus()); |
| } |
| |
| TEST_F(CanvasAsyncBlobCreatorTest, |
| JpegIdleTaskFailedWhenStartTimeoutEventHappens) { |
| this->prepareMockCanvasAsyncBlobCreatorFailJpeg(); |
| |
| this->asyncBlobCreator()->scheduleAsyncBlobCreation(true, 1.0); |
| testing::enterRunLoop(); |
| |
| EXPECT_EQ(IdleTaskStatus::IdleTaskFailed, |
| this->asyncBlobCreator()->idleTaskStatus()); |
| } |
| } |