1 // Copyright 2015 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 "base/location.h"
8 #include "base/metrics/histogram_functions.h"
9 #include "base/metrics/histogram_macros.h"
10 #include "build/build_config.h"
11 #include "third_party/blink/public/platform/platform.h"
12 #include "third_party/blink/public/platform/task_type.h"
13 #include "third_party/blink/renderer/core/dom/document.h"
14 #include "third_party/blink/renderer/core/dom/dom_exception.h"
15 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
16 #include "third_party/blink/renderer/core/fileapi/blob.h"
17 #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
18 #include "third_party/blink/renderer/platform/graphics/image_data_buffer.h"
19 #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
20 #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
21 #include "third_party/blink/renderer/platform/heap/heap.h"
22 #include "third_party/blink/renderer/platform/image-encoders/image_encoder_utils.h"
23 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
24 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
25 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
26 #include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h"
27 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
28 #include "third_party/blink/renderer/platform/wtf/functional.h"
29 
30 #include "third_party/skia/include/core/SkSurface.h"
31 
32 namespace blink {
33 
34 namespace {
35 
36 // small slack period between deadline and current time for safety
37 constexpr base::TimeDelta kCreateBlobSlackBeforeDeadline =
38     base::TimeDelta::FromMilliseconds(1);
39 constexpr base::TimeDelta kEncodeRowSlackBeforeDeadline =
40     base::TimeDelta::FromMicroseconds(100);
41 
42 /* The value is based on user statistics on Nov 2017. */
43 #if (defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_WIN)) || defined(OS_BSD)
44 const double kIdleTaskStartTimeoutDelayMs = 1000.0;
45 #else
46 const double kIdleTaskStartTimeoutDelayMs = 4000.0;  // For ChromeOS, Mobile
47 #endif
48 
49 /* The value is based on user statistics on May 2018. */
50 // We should be more lenient on completion timeout delay to ensure that the
51 // switch from idle to main thread only happens to a minority of toBlob calls
52 #if !defined(OS_ANDROID)
53 // Png image encoding on 4k by 4k canvas on Mac HDD takes 5.7+ seconds
54 // We see that 99% users require less than 5 seconds.
55 const double kIdleTaskCompleteTimeoutDelayMs = 5700.0;
56 #else
57 // Png image encoding on 4k by 4k canvas on Android One takes 9.0+ seconds
58 // We see that 99% users require less than 9 seconds.
59 const double kIdleTaskCompleteTimeoutDelayMs = 9000.0;
60 #endif
61 
IsCreateBlobDeadlineNearOrPassed(base::TimeTicks deadline)62 bool IsCreateBlobDeadlineNearOrPassed(base::TimeTicks deadline) {
63   return base::TimeTicks::Now() >= deadline - kCreateBlobSlackBeforeDeadline;
64 }
65 
IsEncodeRowDeadlineNearOrPassed(base::TimeTicks deadline,size_t image_width)66 bool IsEncodeRowDeadlineNearOrPassed(base::TimeTicks deadline,
67                                      size_t image_width) {
68   // Rough estimate of the row encoding time in micro seconds. We will consider
69   // a slack time later to not pass the idle task deadline.
70   int row_encode_time_us = 1000 * (kIdleTaskCompleteTimeoutDelayMs / 4000.0) *
71                            (image_width / 4000.0);
72   base::TimeDelta row_encode_time_delta =
73       base::TimeDelta::FromMicroseconds(row_encode_time_us);
74   return base::TimeTicks::Now() >=
75          deadline - row_encode_time_delta - kEncodeRowSlackBeforeDeadline;
76 }
77 
RecordIdleTaskStatusHistogram(CanvasAsyncBlobCreator::IdleTaskStatus status)78 void RecordIdleTaskStatusHistogram(
79     CanvasAsyncBlobCreator::IdleTaskStatus status) {
80   UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.ToBlob.IdleTaskStatus", status);
81 }
82 
RecordInitiateEncodingTimeHistogram(ImageEncodingMimeType mime_type,base::TimeDelta elapsed_time)83 void RecordInitiateEncodingTimeHistogram(ImageEncodingMimeType mime_type,
84                                          base::TimeDelta elapsed_time) {
85   // TODO(crbug.com/983261) Change this to use UmaHistogramMicrosecondsTimes.
86   if (mime_type == kMimeTypePng) {
87     UmaHistogramMicrosecondsTimesUnderTenMilliseconds(
88         "Blink.Canvas.ToBlob.InitiateEncodingDelay.PNG", elapsed_time);
89   } else if (mime_type == kMimeTypeJpeg) {
90     UmaHistogramMicrosecondsTimesUnderTenMilliseconds(
91         "Blink.Canvas.ToBlob.InitiateEncodingDelay.JPEG", elapsed_time);
92   }
93 }
94 
RecordCompleteEncodingTimeHistogram(ImageEncodingMimeType mime_type,base::TimeDelta elapsed_time)95 void RecordCompleteEncodingTimeHistogram(ImageEncodingMimeType mime_type,
96                                          base::TimeDelta elapsed_time) {
97   if (mime_type == kMimeTypePng) {
98     UmaHistogramMicrosecondsTimesUnderTenMilliseconds(
99         "Blink.Canvas.ToBlob.CompleteEncodingDelay.PNG", elapsed_time);
100   } else if (mime_type == kMimeTypeJpeg) {
101     UmaHistogramMicrosecondsTimesUnderTenMilliseconds(
102         "Blink.Canvas.ToBlob.CompleteEncodingDelay.JPEG", elapsed_time);
103   }
104 }
105 
RecordScaledDurationHistogram(ImageEncodingMimeType mime_type,base::TimeDelta elapsed_time,float width,float height)106 void RecordScaledDurationHistogram(ImageEncodingMimeType mime_type,
107                                    base::TimeDelta elapsed_time,
108                                    float width,
109                                    float height) {
110   float sqrt_pixels = std::sqrt(width) * std::sqrt(height);
111   float scaled_time_float =
112       elapsed_time.InMicrosecondsF() / (sqrt_pixels == 0 ? 1.0f : sqrt_pixels);
113 
114   // If scaled_time_float overflows as integer, CheckedNumeric will store it
115   // as invalid, then ValueOrDefault will return the maximum int.
116   base::CheckedNumeric<int> checked_scaled_time = scaled_time_float;
117   int scaled_time_int =
118       checked_scaled_time.ValueOrDefault(std::numeric_limits<int>::max());
119 
120   if (mime_type == kMimeTypePng) {
121     UMA_HISTOGRAM_COUNTS_100000("Blink.Canvas.ToBlob.ScaledDuration.PNG",
122                                 scaled_time_int);
123   } else if (mime_type == kMimeTypeJpeg) {
124     UMA_HISTOGRAM_COUNTS_100000("Blink.Canvas.ToBlob.ScaledDuration.JPEG",
125                                 scaled_time_int);
126   } else if (mime_type == kMimeTypeWebp) {
127     UMA_HISTOGRAM_COUNTS_100000("Blink.Canvas.ToBlob.ScaledDuration.WEBP",
128                                 scaled_time_int);
129   }
130 }
131 
GetColorTypeForConversion(SkColorType color_type)132 SkColorType GetColorTypeForConversion(SkColorType color_type) {
133   if (color_type == kRGBA_8888_SkColorType ||
134       color_type == kBGRA_8888_SkColorType) {
135     return color_type;
136   }
137 
138   return kN32_SkColorType;
139 }
140 
141 }  // anonymous namespace
142 
CanvasAsyncBlobCreator(scoped_refptr<StaticBitmapImage> image,const ImageEncodeOptions * options,ToBlobFunctionType function_type,base::TimeTicks start_time,ExecutionContext * context,ScriptPromiseResolver * resolver)143 CanvasAsyncBlobCreator::CanvasAsyncBlobCreator(
144     scoped_refptr<StaticBitmapImage> image,
145     const ImageEncodeOptions* options,
146     ToBlobFunctionType function_type,
147     base::TimeTicks start_time,
148     ExecutionContext* context,
149     ScriptPromiseResolver* resolver)
150     : CanvasAsyncBlobCreator(image,
151                              options,
152                              function_type,
153                              nullptr,
154                              start_time,
155                              context,
156                              resolver) {}
157 
CanvasAsyncBlobCreator(scoped_refptr<StaticBitmapImage> image,const ImageEncodeOptions * options,ToBlobFunctionType function_type,V8BlobCallback * callback,base::TimeTicks start_time,ExecutionContext * context,ScriptPromiseResolver * resolver)158 CanvasAsyncBlobCreator::CanvasAsyncBlobCreator(
159     scoped_refptr<StaticBitmapImage> image,
160     const ImageEncodeOptions* options,
161     ToBlobFunctionType function_type,
162     V8BlobCallback* callback,
163     base::TimeTicks start_time,
164     ExecutionContext* context,
165     ScriptPromiseResolver* resolver)
166     : fail_encoder_initialization_for_test_(false),
167       enforce_idle_encoding_for_test_(false),
168       image_(image),
169       context_(context),
170       encode_options_(options),
171       function_type_(function_type),
172       start_time_(start_time),
173       static_bitmap_image_loaded_(false),
174       callback_(callback),
175       script_promise_resolver_(resolver) {
176   DCHECK(image);
177 
178   mime_type_ = ImageEncoderUtils::ToEncodingMimeType(
179       encode_options_->type(),
180       ImageEncoderUtils::kEncodeReasonConvertToBlobPromise);
181 
182   // We use pixmap to access the image pixels. Make the image unaccelerated if
183   // necessary.
184   image_ = image_->MakeUnaccelerated();
185 
186   sk_sp<SkImage> skia_image = image_->PaintImageForCurrentFrame().GetSkImage();
187   DCHECK(skia_image);
188   DCHECK(!skia_image->isTextureBacked());
189 
190   // If image is lazy decoded, call readPixels() to trigger decoding.
191   if (skia_image->isLazyGenerated()) {
192     SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
193     std::vector<uint8_t> pixel(info.bytesPerPixel());
194     skia_image->readPixels(info, pixel.data(), info.minRowBytes(), 0, 0);
195   }
196 
197   // For kHTMLCanvasToBlobCallback and kOffscreenCanvasConvertToBlobPromise
198   // to-blob function types, we color convert to sRGB and do not tag the image
199   // with any color space info.
200   // For kHTMLCanvasConvertToBlobPromise to-blob function type, we color
201   // covnert to the requested color space and pixel format.
202   if (function_type_ != kHTMLCanvasConvertToBlobPromise) {
203     if (skia_image->colorSpace()) {
204       image_ = image_->ConvertToColorSpace(
205           SkColorSpace::MakeSRGB(),
206           GetColorTypeForConversion(skia_image->colorType()));
207       skia_image = image_->PaintImageForCurrentFrame().GetSkImage();
208     }
209 
210     if (skia_image->peekPixels(&src_data_)) {
211       src_data_.setColorSpace(nullptr);
212       static_bitmap_image_loaded_ = true;
213     }
214     DCHECK(!src_data_.colorSpace());
215   } else {
216     sk_sp<SkColorSpace> blob_color_space =
217         BlobColorSpaceToSkColorSpace(encode_options_->colorSpace());
218     bool needs_color_space_conversion = !ApproximatelyEqualSkColorSpaces(
219         skia_image->refColorSpace(), blob_color_space);
220     if (needs_color_space_conversion && !skia_image->colorSpace()) {
221       skia_image->peekPixels(&src_data_);
222       src_data_.setColorSpace(SkColorSpace::MakeSRGB());
223       skia_image = SkImage::MakeRasterCopy(src_data_);
224       DCHECK(skia_image->colorSpace());
225     }
226 
227     SkColorType target_color_type =
228         GetColorTypeForConversion(skia_image->colorType());
229     if (encode_options_->pixelFormat() == kRGBA16ImagePixelFormatName)
230       target_color_type = kRGBA_F16_SkColorType;
231     // We can do color space and color type conversion together.
232     if (needs_color_space_conversion) {
233       image_ = UnacceleratedStaticBitmapImage::Create(skia_image);
234       image_ = image_->ConvertToColorSpace(blob_color_space, target_color_type);
235       skia_image = image_->PaintImageForCurrentFrame().GetSkImage();
236     } else if (skia_image->colorType() != target_color_type) {
237       size_t data_length = skia_image->width() * skia_image->height() *
238                            SkColorTypeBytesPerPixel(target_color_type);
239       png_data_helper_ = SkData::MakeUninitialized(data_length);
240       SkImageInfo info = SkImageInfo::Make(
241           skia_image->width(), skia_image->height(), target_color_type,
242           skia_image->alphaType(), skia_image->refColorSpace());
243       SkPixmap src_data_f16(info, png_data_helper_->writable_data(),
244                             info.minRowBytes());
245       skia_image->readPixels(src_data_f16, 0, 0);
246       skia_image = SkImage::MakeFromRaster(src_data_f16, nullptr, nullptr);
247       image_ = UnacceleratedStaticBitmapImage::Create(skia_image);
248     }
249 
250     if (skia_image->peekPixels(&src_data_))
251       static_bitmap_image_loaded_ = true;
252   }
253 
254   if (static_bitmap_image_loaded_) {
255     // Ensure that the size of the to-be-encoded-image does not pass the maximum
256     // size supported by the encoders.
257     int max_dimension = ImageEncoder::MaxDimension(mime_type_);
258     if (std::max(src_data_.width(), src_data_.height()) > max_dimension) {
259       SkImageInfo info = src_data_.info();
260       info = info.makeWH(std::min(info.width(), max_dimension),
261                          std::min(info.height(), max_dimension));
262       src_data_.reset(info, src_data_.addr(), src_data_.rowBytes());
263     }
264   }
265 
266   idle_task_status_ = kIdleTaskNotSupported;
267   num_rows_completed_ = 0;
268   if (context->IsDocument()) {
269     parent_frame_task_runner_ =
270         context->GetTaskRunner(TaskType::kCanvasBlobSerialization);
271   }
272 }
273 
274 CanvasAsyncBlobCreator::~CanvasAsyncBlobCreator() = default;
275 
Dispose()276 void CanvasAsyncBlobCreator::Dispose() {
277   // Eagerly let go of references to prevent retention of these
278   // resources while any remaining posted tasks are queued.
279   context_.Clear();
280   callback_.Clear();
281   script_promise_resolver_.Clear();
282   image_ = nullptr;
283 }
284 
GetImageEncodeOptionsForMimeType(ImageEncodingMimeType mime_type)285 ImageEncodeOptions* CanvasAsyncBlobCreator::GetImageEncodeOptionsForMimeType(
286     ImageEncodingMimeType mime_type) {
287   ImageEncodeOptions* encode_options = ImageEncodeOptions::Create();
288   encode_options->setType(ImageEncodingMimeTypeName(mime_type));
289   return encode_options;
290 }
291 
EncodeImage(const double & quality)292 bool CanvasAsyncBlobCreator::EncodeImage(const double& quality) {
293   std::unique_ptr<ImageDataBuffer> buffer = ImageDataBuffer::Create(src_data_);
294   if (!buffer)
295     return false;
296   return buffer->EncodeImage(mime_type_, quality, &encoded_image_);
297 }
298 
ScheduleAsyncBlobCreation(const double & quality)299 void CanvasAsyncBlobCreator::ScheduleAsyncBlobCreation(const double& quality) {
300   if (!static_bitmap_image_loaded_) {
301     context_->GetTaskRunner(TaskType::kCanvasBlobSerialization)
302         ->PostTask(FROM_HERE,
303                    WTF::Bind(&CanvasAsyncBlobCreator::CreateNullAndReturnResult,
304                              WrapPersistent(this)));
305     return;
306   }
307   // Webp encoder does not support progressive encoding. We also don't use idle
308   // encoding for web tests, since the idle task start and completition
309   // deadlines (6.7s or 13s) bypass the web test running deadline (6s)
310   // and result in timeouts on different tests. We use
311   // enforce_idle_encoding_for_test_ to test idle encoding in unit tests.
312   // We also don't use idle tasks in workers because there's no proper idle
313   // queue there and tasks can take too long without requestAnimationFrame.
314   bool use_idle_encoding =
315       WTF::IsMainThread() && (mime_type_ != kMimeTypeWebp) &&
316       (enforce_idle_encoding_for_test_ ||
317        !RuntimeEnabledFeatures::NoIdleEncodingForWebTestsEnabled());
318 
319   if (!use_idle_encoding) {
320     if (!IsMainThread()) {
321       DCHECK(function_type_ == kHTMLCanvasConvertToBlobPromise ||
322              function_type_ == kOffscreenCanvasConvertToBlobPromise);
323       // When OffscreenCanvas.convertToBlob() occurs on worker thread,
324       // we do not need to use background task runner to reduce load on main.
325       // So we just directly encode images on the worker thread.
326       if (!EncodeImage(quality)) {
327         context_->GetTaskRunner(TaskType::kCanvasBlobSerialization)
328             ->PostTask(
329                 FROM_HERE,
330                 WTF::Bind(&CanvasAsyncBlobCreator::CreateNullAndReturnResult,
331                           WrapPersistent(this)));
332 
333         return;
334       }
335       context_->GetTaskRunner(TaskType::kCanvasBlobSerialization)
336           ->PostTask(
337               FROM_HERE,
338               WTF::Bind(&CanvasAsyncBlobCreator::CreateBlobAndReturnResult,
339                         WrapPersistent(this)));
340 
341     } else {
342       worker_pool::PostTask(
343           FROM_HERE, CrossThreadBindOnce(
344                          &CanvasAsyncBlobCreator::EncodeImageOnEncoderThread,
345                          WrapCrossThreadPersistent(this), quality));
346     }
347   } else {
348     idle_task_status_ = kIdleTaskNotStarted;
349     ScheduleInitiateEncoding(quality);
350 
351     // We post the below task to check if the above idle task isn't late.
352     // There's no risk of concurrency as both tasks are on the same thread.
353     PostDelayedTaskToCurrentThread(
354         FROM_HERE,
355         WTF::Bind(&CanvasAsyncBlobCreator::IdleTaskStartTimeoutEvent,
356                   WrapPersistent(this), quality),
357         kIdleTaskStartTimeoutDelayMs);
358   }
359 }
360 
ScheduleInitiateEncoding(double quality)361 void CanvasAsyncBlobCreator::ScheduleInitiateEncoding(double quality) {
362   schedule_idle_task_start_time_ = base::TimeTicks::Now();
363   ThreadScheduler::Current()->PostIdleTask(
364       FROM_HERE, WTF::Bind(&CanvasAsyncBlobCreator::InitiateEncoding,
365                            WrapPersistent(this), quality));
366 }
367 
InitiateEncoding(double quality,base::TimeTicks deadline)368 void CanvasAsyncBlobCreator::InitiateEncoding(double quality,
369                                               base::TimeTicks deadline) {
370   if (idle_task_status_ == kIdleTaskSwitchedToImmediateTask) {
371     return;
372   }
373   RecordInitiateEncodingTimeHistogram(
374       mime_type_, base::TimeTicks::Now() - schedule_idle_task_start_time_);
375 
376   DCHECK(idle_task_status_ == kIdleTaskNotStarted);
377   idle_task_status_ = kIdleTaskStarted;
378 
379   if (!InitializeEncoder(quality)) {
380     idle_task_status_ = kIdleTaskFailed;
381     return;
382   }
383 
384   // Re-use this time variable to collect data on complete encoding delay
385   schedule_idle_task_start_time_ = base::TimeTicks::Now();
386   IdleEncodeRows(deadline);
387 }
388 
IdleEncodeRows(base::TimeTicks deadline)389 void CanvasAsyncBlobCreator::IdleEncodeRows(base::TimeTicks deadline) {
390   if (idle_task_status_ == kIdleTaskSwitchedToImmediateTask) {
391     return;
392   }
393 
394   for (int y = num_rows_completed_; y < src_data_.height(); ++y) {
395     if (IsEncodeRowDeadlineNearOrPassed(deadline, src_data_.width())) {
396       num_rows_completed_ = y;
397       ThreadScheduler::Current()->PostIdleTask(
398           FROM_HERE, WTF::Bind(&CanvasAsyncBlobCreator::IdleEncodeRows,
399                                WrapPersistent(this)));
400       return;
401     }
402 
403     if (!encoder_->encodeRows(1)) {
404       idle_task_status_ = kIdleTaskFailed;
405       CreateNullAndReturnResult();
406       return;
407     }
408   }
409   num_rows_completed_ = src_data_.height();
410 
411   idle_task_status_ = kIdleTaskCompleted;
412   base::TimeDelta elapsed_time =
413       base::TimeTicks::Now() - schedule_idle_task_start_time_;
414   RecordCompleteEncodingTimeHistogram(mime_type_, elapsed_time);
415   if (IsCreateBlobDeadlineNearOrPassed(deadline)) {
416     context_->GetTaskRunner(TaskType::kCanvasBlobSerialization)
417         ->PostTask(FROM_HERE,
418                    WTF::Bind(&CanvasAsyncBlobCreator::CreateBlobAndReturnResult,
419                              WrapPersistent(this)));
420   } else {
421     CreateBlobAndReturnResult();
422   }
423 }
424 
ForceEncodeRowsOnCurrentThread()425 void CanvasAsyncBlobCreator::ForceEncodeRowsOnCurrentThread() {
426   DCHECK(idle_task_status_ == kIdleTaskSwitchedToImmediateTask);
427 
428   // Continue encoding from the last completed row
429   for (int y = num_rows_completed_; y < src_data_.height(); ++y) {
430     if (!encoder_->encodeRows(1)) {
431       idle_task_status_ = kIdleTaskFailed;
432       CreateNullAndReturnResult();
433       return;
434     }
435   }
436   num_rows_completed_ = src_data_.height();
437 
438   if (IsMainThread()) {
439     CreateBlobAndReturnResult();
440   } else {
441     PostCrossThreadTask(
442         *context_->GetTaskRunner(TaskType::kCanvasBlobSerialization), FROM_HERE,
443         CrossThreadBindOnce(&CanvasAsyncBlobCreator::CreateBlobAndReturnResult,
444                             WrapCrossThreadPersistent(this)));
445   }
446 
447   SignalAlternativeCodePathFinishedForTesting();
448 }
449 
CreateBlobAndReturnResult()450 void CanvasAsyncBlobCreator::CreateBlobAndReturnResult() {
451   RecordIdleTaskStatusHistogram(idle_task_status_);
452 
453   Blob* result_blob = Blob::Create(encoded_image_.data(), encoded_image_.size(),
454                                    ImageEncodingMimeTypeName(mime_type_));
455   if (function_type_ == kHTMLCanvasToBlobCallback) {
456     context_->GetTaskRunner(TaskType::kCanvasBlobSerialization)
457         ->PostTask(FROM_HERE,
458                    WTF::Bind(&V8BlobCallback::InvokeAndReportException,
459                              WrapPersistent(callback_.Get()), nullptr,
460                              WrapPersistent(result_blob)));
461   } else {
462     context_->GetTaskRunner(TaskType::kCanvasBlobSerialization)
463         ->PostTask(FROM_HERE,
464                    WTF::Bind(&ScriptPromiseResolver::Resolve<Blob*>,
465                              WrapPersistent(script_promise_resolver_.Get()),
466                              WrapPersistent(result_blob)));
467   }
468 
469   RecordScaledDurationHistogram(mime_type_,
470                                 base::TimeTicks::Now() - start_time_,
471                                 image_->width(), image_->height());
472   // Avoid unwanted retention, see dispose().
473   Dispose();
474 }
475 
CreateNullAndReturnResult()476 void CanvasAsyncBlobCreator::CreateNullAndReturnResult() {
477   RecordIdleTaskStatusHistogram(idle_task_status_);
478   if (function_type_ == kHTMLCanvasToBlobCallback) {
479     DCHECK(IsMainThread());
480     RecordIdleTaskStatusHistogram(idle_task_status_);
481     context_->GetTaskRunner(TaskType::kCanvasBlobSerialization)
482         ->PostTask(
483             FROM_HERE,
484             WTF::Bind(&V8BlobCallback::InvokeAndReportException,
485                       WrapPersistent(callback_.Get()), nullptr, nullptr));
486   } else {
487     context_->GetTaskRunner(TaskType::kCanvasBlobSerialization)
488         ->PostTask(FROM_HERE,
489                    WTF::Bind(&ScriptPromiseResolver::Reject<DOMException*>,
490                              WrapPersistent(script_promise_resolver_.Get()),
491                              WrapPersistent(MakeGarbageCollected<DOMException>(
492                                  DOMExceptionCode::kEncodingError,
493                                  "Encoding of the source image has failed."))));
494   }
495   // Avoid unwanted retention, see dispose().
496   Dispose();
497 }
498 
EncodeImageOnEncoderThread(double quality)499 void CanvasAsyncBlobCreator::EncodeImageOnEncoderThread(double quality) {
500   DCHECK(!IsMainThread());
501   if (!EncodeImage(quality)) {
502     PostCrossThreadTask(
503         *parent_frame_task_runner_, FROM_HERE,
504         CrossThreadBindOnce(&CanvasAsyncBlobCreator::CreateNullAndReturnResult,
505                             WrapCrossThreadPersistent(this)));
506     return;
507   }
508 
509   PostCrossThreadTask(
510       *parent_frame_task_runner_, FROM_HERE,
511       CrossThreadBindOnce(&CanvasAsyncBlobCreator::CreateBlobAndReturnResult,
512                           WrapCrossThreadPersistent(this)));
513 }
514 
InitializeEncoder(double quality)515 bool CanvasAsyncBlobCreator::InitializeEncoder(double quality) {
516   // This is solely used for unit tests.
517   if (fail_encoder_initialization_for_test_)
518     return false;
519   if (mime_type_ == kMimeTypeJpeg) {
520     SkJpegEncoder::Options options;
521     options.fQuality = ImageEncoder::ComputeJpegQuality(quality);
522     options.fAlphaOption = SkJpegEncoder::AlphaOption::kBlendOnBlack;
523     if (options.fQuality == 100) {
524       options.fDownsample = SkJpegEncoder::Downsample::k444;
525     }
526     encoder_ = ImageEncoder::Create(&encoded_image_, src_data_, options);
527   } else {
528     // Progressive encoding is only applicable to png and jpeg image format,
529     // and thus idle tasks scheduling can only be applied to these image
530     // formats.
531     // TODO(zakerinasab): Progressive encoding on webp image formats
532     // (crbug.com/571399)
533     DCHECK_EQ(kMimeTypePng, mime_type_);
534     SkPngEncoder::Options options;
535     options.fFilterFlags = SkPngEncoder::FilterFlag::kSub;
536     options.fZLibLevel = 3;
537     encoder_ = ImageEncoder::Create(&encoded_image_, src_data_, options);
538   }
539 
540   return encoder_.get();
541 }
542 
IdleTaskStartTimeoutEvent(double quality)543 void CanvasAsyncBlobCreator::IdleTaskStartTimeoutEvent(double quality) {
544   if (idle_task_status_ == kIdleTaskStarted) {
545     // Even if the task started quickly, we still want to ensure completion
546     PostDelayedTaskToCurrentThread(
547         FROM_HERE,
548         WTF::Bind(&CanvasAsyncBlobCreator::IdleTaskCompleteTimeoutEvent,
549                   WrapPersistent(this)),
550         kIdleTaskCompleteTimeoutDelayMs);
551   } else if (idle_task_status_ == kIdleTaskNotStarted) {
552     // If the idle task does not start after a delay threshold, we will
553     // force it to happen on main thread (even though it may cause more
554     // janks) to prevent toBlob being postponed forever in extreme cases.
555     idle_task_status_ = kIdleTaskSwitchedToImmediateTask;
556     SignalTaskSwitchInStartTimeoutEventForTesting();
557 
558     DCHECK(mime_type_ == kMimeTypePng || mime_type_ == kMimeTypeJpeg);
559     if (InitializeEncoder(quality)) {
560       context_->GetTaskRunner(TaskType::kCanvasBlobSerialization)
561           ->PostTask(
562               FROM_HERE,
563               WTF::Bind(&CanvasAsyncBlobCreator::ForceEncodeRowsOnCurrentThread,
564                         WrapPersistent(this)));
565     } else {
566       // Failing in initialization of encoder
567       SignalAlternativeCodePathFinishedForTesting();
568     }
569   } else {
570     DCHECK(idle_task_status_ == kIdleTaskFailed ||
571            idle_task_status_ == kIdleTaskCompleted);
572     SignalAlternativeCodePathFinishedForTesting();
573   }
574 }
575 
IdleTaskCompleteTimeoutEvent()576 void CanvasAsyncBlobCreator::IdleTaskCompleteTimeoutEvent() {
577   DCHECK(idle_task_status_ != kIdleTaskNotStarted);
578 
579   if (idle_task_status_ == kIdleTaskStarted) {
580     // It has taken too long to complete for the idle task.
581     idle_task_status_ = kIdleTaskSwitchedToImmediateTask;
582     SignalTaskSwitchInCompleteTimeoutEventForTesting();
583 
584     DCHECK(mime_type_ == kMimeTypePng || mime_type_ == kMimeTypeJpeg);
585     context_->GetTaskRunner(TaskType::kCanvasBlobSerialization)
586         ->PostTask(
587             FROM_HERE,
588             WTF::Bind(&CanvasAsyncBlobCreator::ForceEncodeRowsOnCurrentThread,
589                       WrapPersistent(this)));
590   } else {
591     DCHECK(idle_task_status_ == kIdleTaskFailed ||
592            idle_task_status_ == kIdleTaskCompleted);
593     SignalAlternativeCodePathFinishedForTesting();
594   }
595 }
596 
PostDelayedTaskToCurrentThread(const base::Location & location,base::OnceClosure task,double delay_ms)597 void CanvasAsyncBlobCreator::PostDelayedTaskToCurrentThread(
598     const base::Location& location,
599     base::OnceClosure task,
600     double delay_ms) {
601   context_->GetTaskRunner(TaskType::kCanvasBlobSerialization)
602       ->PostDelayedTask(location, std::move(task),
603                         base::TimeDelta::FromMillisecondsD(delay_ms));
604 }
605 
Trace(Visitor * visitor)606 void CanvasAsyncBlobCreator::Trace(Visitor* visitor) {
607   visitor->Trace(context_);
608   visitor->Trace(encode_options_);
609   visitor->Trace(callback_);
610   visitor->Trace(script_promise_resolver_);
611 }
612 
BlobColorSpaceToSkColorSpace(String blob_color_space)613 sk_sp<SkColorSpace> CanvasAsyncBlobCreator::BlobColorSpaceToSkColorSpace(
614     String blob_color_space) {
615   skcms_Matrix3x3 gamut = SkNamedGamut::kSRGB;
616   if (blob_color_space == kDisplayP3ImageColorSpaceName)
617     gamut = SkNamedGamut::kDCIP3;
618   else if (blob_color_space == kRec2020ImageColorSpaceName)
619     gamut = SkNamedGamut::kRec2020;
620   return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
621 }
622 
EncodeImageForConvertToBlobTest()623 bool CanvasAsyncBlobCreator::EncodeImageForConvertToBlobTest() {
624   if (!static_bitmap_image_loaded_)
625     return false;
626   std::unique_ptr<ImageDataBuffer> buffer = ImageDataBuffer::Create(src_data_);
627   if (!buffer)
628     return false;
629   return buffer->EncodeImage(mime_type_, encode_options_->quality(),
630                              &encoded_image_);
631 }
632 
633 }  // namespace blink
634