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