1 // Copyright 2016 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 "third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.h"
6 
7 #include "components/ukm/test_ukm_recorder.h"
8 #include "testing/gmock/include/gmock/gmock.h"
9 #include "testing/gtest/include/gtest/gtest.h"
10 #include "third_party/blink/public/platform/platform.h"
11 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
12 #include "third_party/blink/renderer/core/html/canvas/image_data.h"
13 #include "third_party/blink/renderer/core/testing/page_test_base.h"
14 #include "third_party/blink/renderer/platform/graphics/color_correction_test_utils.h"
15 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
16 #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
17 #include "third_party/blink/renderer/platform/heap/heap.h"
18 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
19 #include "third_party/blink/renderer/platform/wtf/functional.h"
20 #include "third_party/skia/include/core/SkSurface.h"
21 
22 namespace blink {
23 
24 typedef CanvasAsyncBlobCreator::IdleTaskStatus IdleTaskStatus;
25 
26 class MockCanvasAsyncBlobCreator : public CanvasAsyncBlobCreator {
27  public:
MockCanvasAsyncBlobCreator(scoped_refptr<StaticBitmapImage> image,ImageEncodingMimeType mime_type,Document * document,bool fail_encoder_initialization=false)28   MockCanvasAsyncBlobCreator(scoped_refptr<StaticBitmapImage> image,
29                              ImageEncodingMimeType mime_type,
30                              Document* document,
31                              bool fail_encoder_initialization = false)
32       : CanvasAsyncBlobCreator(
33             image,
34             CanvasAsyncBlobCreator::GetImageEncodeOptionsForMimeType(mime_type),
35             kHTMLCanvasToBlobCallback,
36             nullptr,
37             base::TimeTicks(),
38             document->GetExecutionContext(),
39             0,
40             nullptr) {
41     if (fail_encoder_initialization)
42       fail_encoder_initialization_for_test_ = true;
43     enforce_idle_encoding_for_test_ = true;
44   }
45 
GetIdleTaskStatus()46   CanvasAsyncBlobCreator::IdleTaskStatus GetIdleTaskStatus() {
47     return idle_task_status_;
48   }
49 
50   MOCK_METHOD0(SignalTaskSwitchInStartTimeoutEventForTesting, void());
51   MOCK_METHOD0(SignalTaskSwitchInCompleteTimeoutEventForTesting, void());
52 
53  protected:
CreateBlobAndReturnResult()54   void CreateBlobAndReturnResult() override {}
CreateNullAndReturnResult()55   void CreateNullAndReturnResult() override {}
56   void SignalAlternativeCodePathFinishedForTesting() override;
57   void PostDelayedTaskToCurrentThread(const base::Location&,
58                                       base::OnceClosure,
59                                       double delay_ms) override;
60 };
61 
SignalAlternativeCodePathFinishedForTesting()62 void MockCanvasAsyncBlobCreator::SignalAlternativeCodePathFinishedForTesting() {
63   test::ExitRunLoop();
64 }
65 
PostDelayedTaskToCurrentThread(const base::Location & location,base::OnceClosure task,double delay_ms)66 void MockCanvasAsyncBlobCreator::PostDelayedTaskToCurrentThread(
67     const base::Location& location,
68     base::OnceClosure task,
69     double delay_ms) {
70   DCHECK(IsMainThread());
71   Thread::Current()->GetTaskRunner()->PostTask(location, std::move(task));
72 }
73 
74 //==============================================================================
75 
76 class MockCanvasAsyncBlobCreatorWithoutStart
77     : public MockCanvasAsyncBlobCreator {
78  public:
MockCanvasAsyncBlobCreatorWithoutStart(scoped_refptr<StaticBitmapImage> image,Document * document)79   MockCanvasAsyncBlobCreatorWithoutStart(scoped_refptr<StaticBitmapImage> image,
80                                          Document* document)
81       : MockCanvasAsyncBlobCreator(image, kMimeTypePng, document) {}
82 
83  protected:
ScheduleInitiateEncoding(double)84   void ScheduleInitiateEncoding(double) override {
85     // Deliberately make scheduleInitiateEncoding do nothing so that idle
86     // task never starts
87   }
88 };
89 
90 //==============================================================================
91 
92 class MockCanvasAsyncBlobCreatorWithoutComplete
93     : public MockCanvasAsyncBlobCreator {
94  public:
MockCanvasAsyncBlobCreatorWithoutComplete(scoped_refptr<StaticBitmapImage> image,Document * document,bool fail_encoder_initialization=false)95   MockCanvasAsyncBlobCreatorWithoutComplete(
96       scoped_refptr<StaticBitmapImage> image,
97       Document* document,
98       bool fail_encoder_initialization = false)
99       : MockCanvasAsyncBlobCreator(image,
100                                    kMimeTypePng,
101                                    document,
102                                    fail_encoder_initialization) {}
103 
104  protected:
ScheduleInitiateEncoding(double quality)105   void ScheduleInitiateEncoding(double quality) override {
106     Thread::Current()->GetTaskRunner()->PostTask(
107         FROM_HERE,
108         WTF::Bind(&MockCanvasAsyncBlobCreatorWithoutComplete::InitiateEncoding,
109                   WrapPersistent(this), quality, base::TimeTicks::Max()));
110   }
111 
IdleEncodeRows(base::TimeTicks deadline)112   void IdleEncodeRows(base::TimeTicks deadline) override {
113     // Deliberately make idleEncodeRows do nothing so that idle task never
114     // completes
115   }
116 };
117 
118 //==============================================================================
119 
120 class CanvasAsyncBlobCreatorTest : public PageTestBase {
121  public:
122   void PrepareMockCanvasAsyncBlobCreatorWithoutStart();
123   void PrepareMockCanvasAsyncBlobCreatorWithoutComplete();
124   void PrepareMockCanvasAsyncBlobCreatorFail();
125 
126  protected:
127   CanvasAsyncBlobCreatorTest();
AsyncBlobCreator()128   MockCanvasAsyncBlobCreator* AsyncBlobCreator() {
129     return async_blob_creator_.Get();
130   }
UkmRecorder()131   ukm::UkmRecorder* UkmRecorder() { return &ukm_recorder_; }
132   void TearDown() override;
133 
134  private:
135   Persistent<MockCanvasAsyncBlobCreator> async_blob_creator_;
136   ukm::TestUkmRecorder ukm_recorder_;
137 };
138 
139 CanvasAsyncBlobCreatorTest::CanvasAsyncBlobCreatorTest() = default;
140 
CreateTransparentImage(int width,int height)141 scoped_refptr<StaticBitmapImage> CreateTransparentImage(int width, int height) {
142   sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(width, height);
143   if (!surface)
144     return nullptr;
145   return UnacceleratedStaticBitmapImage::Create(surface->makeImageSnapshot());
146 }
147 
148 void CanvasAsyncBlobCreatorTest::
PrepareMockCanvasAsyncBlobCreatorWithoutStart()149     PrepareMockCanvasAsyncBlobCreatorWithoutStart() {
150   async_blob_creator_ =
151       MakeGarbageCollected<MockCanvasAsyncBlobCreatorWithoutStart>(
152           CreateTransparentImage(20, 20), &GetDocument());
153 }
154 
155 void CanvasAsyncBlobCreatorTest::
PrepareMockCanvasAsyncBlobCreatorWithoutComplete()156     PrepareMockCanvasAsyncBlobCreatorWithoutComplete() {
157   async_blob_creator_ =
158       MakeGarbageCollected<MockCanvasAsyncBlobCreatorWithoutComplete>(
159           CreateTransparentImage(20, 20), &GetDocument());
160 }
161 
PrepareMockCanvasAsyncBlobCreatorFail()162 void CanvasAsyncBlobCreatorTest::PrepareMockCanvasAsyncBlobCreatorFail() {
163   // We reuse the class MockCanvasAsyncBlobCreatorWithoutComplete because
164   // this test case is expected to fail at initialization step before
165   // completion.
166   async_blob_creator_ =
167       MakeGarbageCollected<MockCanvasAsyncBlobCreatorWithoutComplete>(
168           CreateTransparentImage(20, 20), &GetDocument(), true);
169 }
170 
TearDown()171 void CanvasAsyncBlobCreatorTest::TearDown() {
172   async_blob_creator_ = nullptr;
173 }
174 
175 //==============================================================================
176 
TEST_F(CanvasAsyncBlobCreatorTest,IdleTaskNotStartedWhenStartTimeoutEventHappens)177 TEST_F(CanvasAsyncBlobCreatorTest,
178        IdleTaskNotStartedWhenStartTimeoutEventHappens) {
179   // This test mocks the scenario when idle task is not started when the
180   // StartTimeoutEvent is inspecting the idle task status.
181   // The whole image encoding process (including initialization)  will then
182   // become carried out in the alternative code path instead.
183   PrepareMockCanvasAsyncBlobCreatorWithoutStart();
184   EXPECT_CALL(*(AsyncBlobCreator()),
185               SignalTaskSwitchInStartTimeoutEventForTesting());
186 
187   AsyncBlobCreator()->ScheduleAsyncBlobCreation(1.0);
188   test::EnterRunLoop();
189 
190   testing::Mock::VerifyAndClearExpectations(AsyncBlobCreator());
191   EXPECT_EQ(IdleTaskStatus::kIdleTaskSwitchedToImmediateTask,
192             AsyncBlobCreator()->GetIdleTaskStatus());
193 }
194 
TEST_F(CanvasAsyncBlobCreatorTest,IdleTaskNotCompletedWhenCompleteTimeoutEventHappens)195 TEST_F(CanvasAsyncBlobCreatorTest,
196        IdleTaskNotCompletedWhenCompleteTimeoutEventHappens) {
197   // This test mocks the scenario when idle task is not completed when the
198   // CompleteTimeoutEvent is inspecting the idle task status.
199   // The remaining image encoding process (excluding initialization)  will
200   // then become carried out in the alternative code path instead.
201   PrepareMockCanvasAsyncBlobCreatorWithoutComplete();
202   EXPECT_CALL(*(AsyncBlobCreator()),
203               SignalTaskSwitchInCompleteTimeoutEventForTesting());
204 
205   AsyncBlobCreator()->ScheduleAsyncBlobCreation(1.0);
206   test::EnterRunLoop();
207 
208   testing::Mock::VerifyAndClearExpectations(AsyncBlobCreator());
209   EXPECT_EQ(IdleTaskStatus::kIdleTaskSwitchedToImmediateTask,
210             AsyncBlobCreator()->GetIdleTaskStatus());
211 }
212 
TEST_F(CanvasAsyncBlobCreatorTest,IdleTaskFailedWhenStartTimeoutEventHappens)213 TEST_F(CanvasAsyncBlobCreatorTest, IdleTaskFailedWhenStartTimeoutEventHappens) {
214   // This test mocks the scenario when idle task is not failed during when
215   // either the StartTimeoutEvent or the CompleteTimeoutEvent is inspecting
216   // the idle task status.
217   PrepareMockCanvasAsyncBlobCreatorFail();
218 
219   AsyncBlobCreator()->ScheduleAsyncBlobCreation(1.0);
220   test::EnterRunLoop();
221 
222   EXPECT_EQ(IdleTaskStatus::kIdleTaskFailed,
223             AsyncBlobCreator()->GetIdleTaskStatus());
224 }
225 
DrawAndReturnImage(const std::pair<sk_sp<SkColorSpace>,SkColorType> & color_space_param)226 static sk_sp<SkImage> DrawAndReturnImage(
227     const std::pair<sk_sp<SkColorSpace>, SkColorType>& color_space_param) {
228   SkPaint transparentRed, transparentGreen, transparentBlue, transparentBlack;
229   transparentRed.setARGB(128, 155, 27, 27);
230   transparentGreen.setARGB(128, 27, 155, 27);
231   transparentBlue.setARGB(128, 27, 27, 155);
232   transparentBlack.setARGB(128, 27, 27, 27);
233 
234   SkImageInfo info = SkImageInfo::Make(2, 2, color_space_param.second,
235                                        SkAlphaType::kPremul_SkAlphaType,
236                                        color_space_param.first);
237   sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
238   surface->getCanvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), transparentRed);
239   surface->getCanvas()->drawRect(SkRect::MakeXYWH(1, 0, 1, 1),
240                                  transparentGreen);
241   surface->getCanvas()->drawRect(SkRect::MakeXYWH(0, 1, 1, 1), transparentBlue);
242   surface->getCanvas()->drawRect(SkRect::MakeXYWH(1, 1, 1, 1),
243                                  transparentBlack);
244   return surface->makeImageSnapshot();
245 }
246 
TEST_F(CanvasAsyncBlobCreatorTest,ColorManagedConvertToBlob)247 TEST_F(CanvasAsyncBlobCreatorTest, ColorManagedConvertToBlob) {
248   std::list<std::pair<sk_sp<SkColorSpace>, SkColorType>> color_space_params;
249   color_space_params.push_back(std::pair<sk_sp<SkColorSpace>, SkColorType>(
250       SkColorSpace::MakeSRGB(), kN32_SkColorType));
251   color_space_params.push_back(std::pair<sk_sp<SkColorSpace>, SkColorType>(
252       SkColorSpace::MakeSRGBLinear(), kRGBA_F16_SkColorType));
253   color_space_params.push_back(std::pair<sk_sp<SkColorSpace>, SkColorType>(
254       SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear,
255                             SkNamedGamut::kDisplayP3),
256       kRGBA_F16_SkColorType));
257   color_space_params.push_back(std::pair<sk_sp<SkColorSpace>, SkColorType>(
258       SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, SkNamedGamut::kRec2020),
259       kRGBA_F16_SkColorType));
260   color_space_params.push_back(std::pair<sk_sp<SkColorSpace>, SkColorType>(
261       nullptr, kRGBA_F16_SkColorType));
262   color_space_params.push_back(
263       std::pair<sk_sp<SkColorSpace>, SkColorType>(nullptr, kN32_SkColorType));
264 
265   std::list<String> blob_mime_types = {"image/png", "image/webp", "image/jpeg"};
266   std::list<String> blob_color_spaces = {kSRGBImageColorSpaceName,
267                                          kDisplayP3ImageColorSpaceName,
268                                          kRec2020ImageColorSpaceName};
269   std::list<String> blob_pixel_formats = {
270       kRGBA8ImagePixelFormatName,
271       kRGBA16ImagePixelFormatName,
272   };
273 
274   // Maximum differences are both observed locally with
275   // kRGBA16ImagePixelFormatName, kSRGBImageColorSpaceName and nil input color
276   // space
277   const unsigned uint8_color_tolerance = 3;
278   const float f16_color_tolerance = 0.015;
279 
280   for (auto color_space_param : color_space_params) {
281     for (auto blob_mime_type : blob_mime_types) {
282       for (auto blob_color_space : blob_color_spaces) {
283         for (auto blob_pixel_format : blob_pixel_formats) {
284           // Create the StaticBitmapImage in canvas_color_space
285           sk_sp<SkImage> source_image = DrawAndReturnImage(color_space_param);
286           scoped_refptr<StaticBitmapImage> source_bitmap_image =
287               UnacceleratedStaticBitmapImage::Create(source_image);
288 
289           // Prepare encoding options
290           ImageEncodeOptions* options = ImageEncodeOptions::Create();
291           options->setQuality(1);
292           options->setType(blob_mime_type);
293           options->setColorSpace(blob_color_space);
294           options->setPixelFormat(blob_pixel_format);
295 
296           // Encode the image using CanvasAsyncBlobCreator
297           auto* async_blob_creator =
298               MakeGarbageCollected<CanvasAsyncBlobCreator>(
299                   source_bitmap_image, options,
300                   CanvasAsyncBlobCreator::ToBlobFunctionType::
301                       kHTMLCanvasConvertToBlobPromise,
302                   base::TimeTicks(), GetFrame().DomWindow(), 0, nullptr);
303           ASSERT_TRUE(async_blob_creator->EncodeImageForConvertToBlobTest());
304 
305           sk_sp<SkData> sk_data = SkData::MakeWithCopy(
306               async_blob_creator->GetEncodedImageForConvertToBlobTest().data(),
307               async_blob_creator->GetEncodedImageForConvertToBlobTest().size());
308           sk_sp<SkImage> decoded_img = SkImage::MakeFromEncoded(sk_data);
309 
310           sk_sp<SkColorSpace> expected_color_space =
311               CanvasAsyncBlobCreator::BlobColorSpaceToSkColorSpace(
312                   blob_color_space);
313           SkColorType expected_color_type =
314               (blob_pixel_format == kRGBA8ImagePixelFormatName)
315                   ? kN32_SkColorType
316                   : kRGBA_F16_SkColorType;
317           scoped_refptr<StaticBitmapImage> ref_bitmap =
318               source_bitmap_image->ConvertToColorSpace(expected_color_space,
319                                                        expected_color_type);
320           sk_sp<SkImage> ref_image =
321               ref_bitmap->PaintImageForCurrentFrame().GetSwSkImage();
322 
323           // Jpeg does not support transparent images.
324           bool compare_alpha = (blob_mime_type != "image/jpeg");
325           ASSERT_TRUE(ColorCorrectionTestUtils::MatchSkImages(
326               ref_image, decoded_img, uint8_color_tolerance,
327               f16_color_tolerance, compare_alpha));
328         }
329       }
330     }
331   }
332 }
333 }  // namespace blink
334