1// Copyright 2013 The Flutter 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 "flutter/common/task_runners.h"
6#include "flutter/fml/mapping.h"
7#include "flutter/fml/synchronization/waitable_event.h"
8#include "flutter/lib/ui/painting/image_decoder.h"
9#include "flutter/lib/ui/painting/multi_frame_codec.h"
10#include "flutter/runtime/dart_vm.h"
11#include "flutter/runtime/dart_vm_lifecycle.h"
12#include "flutter/testing/dart_isolate_runner.h"
13#include "flutter/testing/elf_loader.h"
14#include "flutter/testing/fixture_test.h"
15#include "flutter/testing/test_dart_native_resolver.h"
16#include "flutter/testing/test_gl_surface.h"
17#include "flutter/testing/testing.h"
18#include "third_party/skia/include/codec/SkCodec.h"
19
20namespace flutter {
21namespace testing {
22
23class TestIOManager final : public IOManager {
24 public:
25 explicit TestIOManager(fml::RefPtr<fml::TaskRunner> task_runner,
26 bool has_gpu_context = true)
27 : gl_surface_(SkISize::Make(1, 1)),
28 gl_context_(has_gpu_context ? gl_surface_.CreateGrContext() : nullptr),
29 weak_gl_context_factory_(
30 has_gpu_context
31 ? std::make_unique<fml::WeakPtrFactory<GrDirectContext>>(
32 gl_context_.get())
33 : nullptr),
34 unref_queue_(fml::MakeRefCounted<SkiaUnrefQueue>(
35 task_runner,
36 fml::TimeDelta::FromNanoseconds(0))),
37 runner_(task_runner),
38 weak_factory_(this),
39 is_gpu_disabled_sync_switch_(std::make_shared<fml::SyncSwitch>()) {
40 FML_CHECK(task_runner->RunsTasksOnCurrentThread())
41 << "The IO manager must be initialized its primary task runner. The "
42 "test harness may not be setup correctly/safely.";
43 weak_prototype_ = weak_factory_.GetWeakPtr();
44 }
45
46 ~TestIOManager() override {
47 fml::AutoResetWaitableEvent latch;
48 fml::TaskRunner::RunNowOrPostTask(runner_,
49 [&latch, queue = unref_queue_]() {
50 queue->Drain();
51 latch.Signal();
52 });
53 latch.Wait();
54 }
55
56 // |IOManager|
57 fml::WeakPtr<IOManager> GetWeakIOManager() const override {
58 return weak_prototype_;
59 }
60
61 // |IOManager|
62 fml::WeakPtr<GrDirectContext> GetResourceContext() const override {
63 return weak_gl_context_factory_ ? weak_gl_context_factory_->GetWeakPtr()
64 : fml::WeakPtr<GrDirectContext>{};
65 }
66
67 // |IOManager|
68 fml::RefPtr<flutter::SkiaUnrefQueue> GetSkiaUnrefQueue() const override {
69 return unref_queue_;
70 }
71
72 // |IOManager|
73 std::shared_ptr<fml::SyncSwitch> GetIsGpuDisabledSyncSwitch() override {
74 did_access_is_gpu_disabled_sync_switch_ = true;
75 return is_gpu_disabled_sync_switch_;
76 }
77
78 bool did_access_is_gpu_disabled_sync_switch_ = false;
79
80 private:
81 TestGLSurface gl_surface_;
82 sk_sp<GrDirectContext> gl_context_;
83 std::unique_ptr<fml::WeakPtrFactory<GrDirectContext>>
84 weak_gl_context_factory_;
85 fml::RefPtr<SkiaUnrefQueue> unref_queue_;
86 fml::WeakPtr<TestIOManager> weak_prototype_;
87 fml::RefPtr<fml::TaskRunner> runner_;
88 fml::WeakPtrFactory<TestIOManager> weak_factory_;
89 std::shared_ptr<fml::SyncSwitch> is_gpu_disabled_sync_switch_;
90
91 FML_DISALLOW_COPY_AND_ASSIGN(TestIOManager);
92};
93
94static sk_sp<SkData> OpenFixtureAsSkData(const char* name) {
95 auto fixtures_directory =
96 fml::OpenDirectory(GetFixturesPath(), false, fml::FilePermission::kRead);
97 if (!fixtures_directory.is_valid()) {
98 return nullptr;
99 }
100
101 auto fixture_mapping =
102 fml::FileMapping::CreateReadOnly(fixtures_directory, name);
103
104 if (!fixture_mapping) {
105 return nullptr;
106 }
107
108 SkData::ReleaseProc on_release = [](const void* ptr, void* context) -> void {
109 delete reinterpret_cast<fml::FileMapping*>(context);
110 };
111
112 auto data = SkData::MakeWithProc(fixture_mapping->GetMapping(),
113 fixture_mapping->GetSize(), on_release,
114 fixture_mapping.get());
115
116 if (!data) {
117 return nullptr;
118 }
119 // The data is now owned by Skia.
120 fixture_mapping.release();
121 return data;
122}
123
124class ImageDecoderFixtureTest : public FixtureTest {};
125
126TEST_F(ImageDecoderFixtureTest, CanCreateImageDecoder) {
127 auto loop = fml::ConcurrentMessageLoop::Create();
128 auto thread_task_runner = CreateNewThread();
129 TaskRunners runners(GetCurrentTestName(), // label
130 thread_task_runner, // platform
131 thread_task_runner, // raster
132 thread_task_runner, // ui
133 thread_task_runner // io
134
135 );
136
137 fml::AutoResetWaitableEvent latch;
138 runners.GetIOTaskRunner()->PostTask([&]() {
139 TestIOManager manager(runners.GetIOTaskRunner());
140 ImageDecoder decoder(std::move(runners), loop->GetTaskRunner(),
141 manager.GetWeakIOManager());
142 latch.Signal();
143 });
144 latch.Wait();
145}
146
147TEST_F(ImageDecoderFixtureTest, InvalidImageResultsError) {
148 auto loop = fml::ConcurrentMessageLoop::Create();
149 auto thread_task_runner = CreateNewThread();
150 TaskRunners runners(GetCurrentTestName(), // label
151 thread_task_runner, // platform
152 thread_task_runner, // raster
153 thread_task_runner, // ui
154 thread_task_runner // io
155 );
156
157 fml::AutoResetWaitableEvent latch;
158 thread_task_runner->PostTask([&]() {
159 TestIOManager manager(runners.GetIOTaskRunner());
160 ImageDecoder decoder(runners, loop->GetTaskRunner(),
161 manager.GetWeakIOManager());
162
163 auto data = OpenFixtureAsSkData("ThisDoesNotExist.jpg");
164 ASSERT_FALSE(data);
165
166 fml::RefPtr<ImageDescriptor> image_descriptor =
167 fml::MakeRefCounted<ImageDescriptor>(std::move(data),
168 std::unique_ptr<SkCodec>(nullptr));
169
170 ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
171 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
172 ASSERT_FALSE(image.get());
173 latch.Signal();
174 };
175 decoder.Decode(image_descriptor, 0, 0, callback);
176 });
177 latch.Wait();
178}
179
180TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) {
181 auto loop = fml::ConcurrentMessageLoop::Create();
182 TaskRunners runners(GetCurrentTestName(), // label
183 CreateNewThread("platform"), // platform
184 CreateNewThread("raster"), // raster
185 CreateNewThread("ui"), // ui
186 CreateNewThread("io") // io
187 );
188
189 fml::AutoResetWaitableEvent latch;
190
191 std::unique_ptr<TestIOManager> io_manager;
192
193 auto release_io_manager = [&]() {
194 io_manager.reset();
195 latch.Signal();
196 };
197 auto decode_image = [&]() {
198 std::unique_ptr<ImageDecoder> image_decoder =
199 std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(),
200 io_manager->GetWeakIOManager());
201
202 auto data = OpenFixtureAsSkData("DashInNooglerHat.jpg");
203
204 ASSERT_TRUE(data);
205 ASSERT_GE(data->size(), 0u);
206
207 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data);
208 ASSERT_TRUE(codec);
209
210 auto descriptor =
211 fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec));
212
213 ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
214 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
215 ASSERT_TRUE(image.get());
216 EXPECT_TRUE(io_manager->did_access_is_gpu_disabled_sync_switch_);
217 runners.GetIOTaskRunner()->PostTask(release_io_manager);
218 };
219 EXPECT_FALSE(io_manager->did_access_is_gpu_disabled_sync_switch_);
220 image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(),
221 callback);
222 };
223
224 auto setup_io_manager_and_decode = [&]() {
225 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
226 runners.GetUITaskRunner()->PostTask(decode_image);
227 };
228
229 runners.GetIOTaskRunner()->PostTask(setup_io_manager_and_decode);
230 latch.Wait();
231}
232
233TEST_F(ImageDecoderFixtureTest, ExifDataIsRespectedOnDecode) {
234 auto loop = fml::ConcurrentMessageLoop::Create();
235 TaskRunners runners(GetCurrentTestName(), // label
236 CreateNewThread("platform"), // platform
237 CreateNewThread("raster"), // raster
238 CreateNewThread("ui"), // ui
239 CreateNewThread("io") // io
240 );
241
242 fml::AutoResetWaitableEvent latch;
243
244 std::unique_ptr<IOManager> io_manager;
245
246 auto release_io_manager = [&]() {
247 io_manager.reset();
248 latch.Signal();
249 };
250
251 SkISize decoded_size = SkISize::MakeEmpty();
252 auto decode_image = [&]() {
253 std::unique_ptr<ImageDecoder> image_decoder =
254 std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(),
255 io_manager->GetWeakIOManager());
256
257 auto data = OpenFixtureAsSkData("Horizontal.jpg");
258
259 ASSERT_TRUE(data);
260 ASSERT_GE(data->size(), 0u);
261
262 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data);
263 ASSERT_TRUE(codec);
264
265 auto descriptor =
266 fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec));
267
268 ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
269 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
270 ASSERT_TRUE(image.get());
271 decoded_size = image.get()->dimensions();
272 runners.GetIOTaskRunner()->PostTask(release_io_manager);
273 };
274 image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(),
275 callback);
276 };
277
278 auto setup_io_manager_and_decode = [&]() {
279 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
280 runners.GetUITaskRunner()->PostTask(decode_image);
281 };
282
283 runners.GetIOTaskRunner()->PostTask(setup_io_manager_and_decode);
284
285 latch.Wait();
286
287 ASSERT_EQ(decoded_size.width(), 600);
288 ASSERT_EQ(decoded_size.height(), 200);
289}
290
291TEST_F(ImageDecoderFixtureTest, CanDecodeWithoutAGPUContext) {
292 auto loop = fml::ConcurrentMessageLoop::Create();
293 TaskRunners runners(GetCurrentTestName(), // label
294 CreateNewThread("platform"), // platform
295 CreateNewThread("raster"), // raster
296 CreateNewThread("ui"), // ui
297 CreateNewThread("io") // io
298 );
299
300 fml::AutoResetWaitableEvent latch;
301
302 std::unique_ptr<IOManager> io_manager;
303
304 auto release_io_manager = [&]() {
305 io_manager.reset();
306 latch.Signal();
307 };
308
309 auto decode_image = [&]() {
310 std::unique_ptr<ImageDecoder> image_decoder =
311 std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(),
312 io_manager->GetWeakIOManager());
313
314 auto data = OpenFixtureAsSkData("DashInNooglerHat.jpg");
315
316 ASSERT_TRUE(data);
317 ASSERT_GE(data->size(), 0u);
318
319 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data);
320 ASSERT_TRUE(codec);
321
322 auto descriptor =
323 fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec));
324
325 ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
326 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
327 ASSERT_TRUE(image.get());
328 runners.GetIOTaskRunner()->PostTask(release_io_manager);
329 };
330 image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(),
331 callback);
332 };
333
334 auto setup_io_manager_and_decode = [&]() {
335 io_manager =
336 std::make_unique<TestIOManager>(runners.GetIOTaskRunner(), false);
337 runners.GetUITaskRunner()->PostTask(decode_image);
338 };
339
340 runners.GetIOTaskRunner()->PostTask(setup_io_manager_and_decode);
341
342 latch.Wait();
343}
344
345TEST_F(ImageDecoderFixtureTest, CanDecodeWithResizes) {
346 const auto image_dimensions =
347 SkImage::MakeFromEncoded(OpenFixtureAsSkData("DashInNooglerHat.jpg"))
348 ->dimensions();
349
350 ASSERT_FALSE(image_dimensions.isEmpty());
351
352 ASSERT_NE(image_dimensions.width(), image_dimensions.height());
353
354 auto loop = fml::ConcurrentMessageLoop::Create();
355 TaskRunners runners(GetCurrentTestName(), // label
356 CreateNewThread("platform"), // platform
357 CreateNewThread("raster"), // raster
358 CreateNewThread("ui"), // ui
359 CreateNewThread("io") // io
360 );
361
362 fml::AutoResetWaitableEvent latch;
363 std::unique_ptr<IOManager> io_manager;
364 std::unique_ptr<ImageDecoder> image_decoder;
365
366 // Setup the IO manager.
367 runners.GetIOTaskRunner()->PostTask([&]() {
368 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
369 latch.Signal();
370 });
371 latch.Wait();
372
373 // Setup the image decoder.
374 runners.GetUITaskRunner()->PostTask([&]() {
375 image_decoder = std::make_unique<ImageDecoder>(
376 runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager());
377
378 latch.Signal();
379 });
380 latch.Wait();
381
382 // Setup a generic decoding utility that gives us the final decoded size.
383 auto decoded_size = [&](uint32_t target_width,
384 uint32_t target_height) -> SkISize {
385 SkISize final_size = SkISize::MakeEmpty();
386 runners.GetUITaskRunner()->PostTask([&]() {
387 auto data = OpenFixtureAsSkData("DashInNooglerHat.jpg");
388
389 ASSERT_TRUE(data);
390 ASSERT_GE(data->size(), 0u);
391
392 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data);
393 ASSERT_TRUE(codec);
394
395 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(std::move(data),
396 std::move(codec));
397
398 ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
399 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
400 ASSERT_TRUE(image.get());
401 final_size = image.get()->dimensions();
402 latch.Signal();
403 };
404 image_decoder->Decode(descriptor, target_width, target_height, callback);
405 });
406 latch.Wait();
407 return final_size;
408 };
409
410 ASSERT_EQ(SkISize::Make(3024, 4032), image_dimensions);
411 ASSERT_EQ(decoded_size(3024, 4032), image_dimensions);
412 ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100));
413
414 // Destroy the IO manager
415 runners.GetIOTaskRunner()->PostTask([&]() {
416 io_manager.reset();
417 latch.Signal();
418 });
419 latch.Wait();
420
421 // Destroy the image decoder
422 runners.GetUITaskRunner()->PostTask([&]() {
423 image_decoder.reset();
424 latch.Signal();
425 });
426 latch.Wait();
427}
428
429TEST_F(ImageDecoderFixtureTest, CanResizeWithoutDecode) {
430 SkImageInfo info = {};
431 size_t row_bytes;
432 sk_sp<SkData> decompressed_data;
433 SkISize image_dimensions = SkISize::MakeEmpty();
434 {
435 auto image =
436 SkImage::MakeFromEncoded(OpenFixtureAsSkData("DashInNooglerHat.jpg"))
437 ->makeRasterImage();
438 image_dimensions = image->dimensions();
439 SkPixmap pixmap;
440 ASSERT_TRUE(image->peekPixels(&pixmap));
441 info = SkImageInfo::MakeN32Premul(image_dimensions);
442 row_bytes = pixmap.rowBytes();
443 decompressed_data =
444 SkData::MakeWithCopy(pixmap.writable_addr(), pixmap.computeByteSize());
445 }
446
447 // This is not susceptible to changes in the underlying image decoder.
448 ASSERT_EQ(decompressed_data->size(), 48771072u);
449 ASSERT_EQ(decompressed_data->size(),
450 image_dimensions.width() * image_dimensions.height() * 4u);
451 ASSERT_EQ(row_bytes, image_dimensions.width() * 4u);
452 ASSERT_FALSE(image_dimensions.isEmpty());
453 ASSERT_NE(image_dimensions.width(), image_dimensions.height());
454
455 auto loop = fml::ConcurrentMessageLoop::Create();
456 TaskRunners runners(GetCurrentTestName(), // label
457 CreateNewThread("platform"), // platform
458 CreateNewThread("raster"), // raster
459 CreateNewThread("ui"), // ui
460 CreateNewThread("io") // io
461 );
462
463 fml::AutoResetWaitableEvent latch;
464 std::unique_ptr<IOManager> io_manager;
465 std::unique_ptr<ImageDecoder> image_decoder;
466
467 // Setup the IO manager.
468 runners.GetIOTaskRunner()->PostTask([&]() {
469 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
470 latch.Signal();
471 });
472 latch.Wait();
473
474 // Setup the image decoder.
475 runners.GetUITaskRunner()->PostTask([&]() {
476 image_decoder = std::make_unique<ImageDecoder>(
477 runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager());
478
479 latch.Signal();
480 });
481 latch.Wait();
482
483 // Setup a generic decoding utility that gives us the final decoded size.
484 auto decoded_size = [&](uint32_t target_width,
485 uint32_t target_height) -> SkISize {
486 SkISize final_size = SkISize::MakeEmpty();
487 runners.GetUITaskRunner()->PostTask([&]() {
488 ASSERT_TRUE(decompressed_data);
489 ASSERT_GE(decompressed_data->size(), 0u);
490
491 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(decompressed_data,
492 info, row_bytes);
493
494 ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
495 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
496 ASSERT_TRUE(image.get());
497 final_size = image.get()->dimensions();
498 latch.Signal();
499 };
500 image_decoder->Decode(descriptor, target_width, target_height, callback);
501 });
502 latch.Wait();
503 return final_size;
504 };
505
506 ASSERT_EQ(SkISize::Make(3024, 4032), image_dimensions);
507 ASSERT_EQ(decoded_size(3024, 4032), image_dimensions);
508 ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100));
509
510 // Destroy the IO manager
511 runners.GetIOTaskRunner()->PostTask([&]() {
512 io_manager.reset();
513 latch.Signal();
514 });
515 latch.Wait();
516
517 // Destroy the image decoder
518 runners.GetUITaskRunner()->PostTask([&]() {
519 image_decoder.reset();
520 latch.Signal();
521 });
522 latch.Wait();
523}
524
525// Verifies https://skia-review.googlesource.com/c/skia/+/259161 is present in
526// Flutter.
527TEST(ImageDecoderTest,
528 VerifyCodecRepeatCountsForGifAndWebPAreConsistentWithLoopCounts) {
529 auto gif_mapping = OpenFixtureAsSkData("hello_loop_2.gif");
530 auto webp_mapping = OpenFixtureAsSkData("hello_loop_2.webp");
531
532 ASSERT_TRUE(gif_mapping);
533 ASSERT_TRUE(webp_mapping);
534
535 auto gif_codec = SkCodec::MakeFromData(gif_mapping);
536 auto webp_codec = SkCodec::MakeFromData(webp_mapping);
537
538 ASSERT_TRUE(gif_codec);
539 ASSERT_TRUE(webp_codec);
540
541 // Both fixtures have a loop count of 2 which should lead to the repeat count
542 // of 1
543 ASSERT_EQ(gif_codec->getRepetitionCount(), 1);
544 ASSERT_EQ(webp_codec->getRepetitionCount(), 1);
545}
546
547TEST(ImageDecoderTest, VerifySimpleDecoding) {
548 auto data = OpenFixtureAsSkData("Horizontal.jpg");
549 auto image = SkImage::MakeFromEncoded(data);
550 ASSERT_TRUE(image != nullptr);
551 ASSERT_EQ(SkISize::Make(600, 200), image->dimensions());
552
553 auto codec = SkCodec::MakeFromData(data);
554 ASSERT_TRUE(codec);
555 auto descriptor =
556 fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec));
557
558 ASSERT_EQ(
559 ImageFromCompressedData(descriptor, 6, 2, fml::tracing::TraceFlow(""))
560 ->dimensions(),
561 SkISize::Make(6, 2));
562}
563
564TEST(ImageDecoderTest, VerifySubpixelDecodingPreservesExifOrientation) {
565 auto data = OpenFixtureAsSkData("Horizontal.jpg");
566 auto codec = SkCodec::MakeFromData(data);
567 ASSERT_TRUE(codec);
568 auto descriptor =
569 fml::MakeRefCounted<ImageDescriptor>(data, std::move(codec));
570
571 auto image = SkImage::MakeFromEncoded(data);
572 ASSERT_TRUE(image != nullptr);
573 ASSERT_EQ(SkISize::Make(600, 200), image->dimensions());
574
575 auto decode = [descriptor](uint32_t target_width, uint32_t target_height) {
576 return ImageFromCompressedData(descriptor, target_width, target_height,
577 fml::tracing::TraceFlow(""));
578 };
579
580 auto expected_data = OpenFixtureAsSkData("Horizontal.png");
581 ASSERT_TRUE(expected_data != nullptr);
582 ASSERT_FALSE(expected_data->isEmpty());
583
584 auto assert_image = [&](auto decoded_image) {
585 ASSERT_EQ(decoded_image->dimensions(), SkISize::Make(300, 100));
586 ASSERT_TRUE(decoded_image->encodeToData(SkEncodedImageFormat::kPNG, 100)
587 ->equals(expected_data.get()));
588 };
589
590 assert_image(decode(300, 100));
591}
592
593TEST_F(ImageDecoderFixtureTest,
594 MultiFrameCodecCanBeCollectedBeforeIOTasksFinish) {
595 // This test verifies that the MultiFrameCodec safely shares state between
596 // tasks on the IO and UI runners, and does not allow unsafe memory access if
597 // the UI object is collected while the IO thread still has pending decode
598 // work. This could happen in a real application if the engine is collected
599 // while a multi-frame image is decoding. To exercise this, the test:
600 // - Starts a Dart VM
601 // - Latches the IO task runner
602 // - Create a MultiFrameCodec for an animated gif pointed to a callback
603 // in the Dart fixture
604 // - Calls getNextFrame on the UI task runner
605 // - Collects the MultiFrameCodec object before unlatching the IO task
606 // runner.
607 // - Unlatches the IO task runner
608 auto settings = CreateSettingsForFixture();
609 auto vm_ref = DartVMRef::Create(settings);
610 auto vm_data = vm_ref.GetVMData();
611
612 auto gif_mapping = OpenFixtureAsSkData("hello_loop_2.gif");
613
614 ASSERT_TRUE(gif_mapping);
615
616 auto gif_codec = std::shared_ptr<SkCodecImageGenerator>(
617 static_cast<SkCodecImageGenerator*>(
618 SkCodecImageGenerator::MakeFromEncodedCodec(gif_mapping).release()));
619 ASSERT_TRUE(gif_codec);
620
621 TaskRunners runners(GetCurrentTestName(), // label
622 CreateNewThread("platform"), // platform
623 CreateNewThread("raster"), // raster
624 CreateNewThread("ui"), // ui
625 CreateNewThread("io") // io
626 );
627
628 fml::AutoResetWaitableEvent latch;
629 fml::AutoResetWaitableEvent io_latch;
630 std::unique_ptr<TestIOManager> io_manager;
631
632 // Setup the IO manager.
633 runners.GetIOTaskRunner()->PostTask([&]() {
634 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
635 latch.Signal();
636 });
637 latch.Wait();
638
639 auto isolate =
640 RunDartCodeInIsolate(vm_ref, settings, runners, "main", {},
641 GetFixturesPath(), io_manager->GetWeakIOManager());
642
643 // Latch the IO task runner.
644 runners.GetIOTaskRunner()->PostTask([&]() { io_latch.Wait(); });
645
646 runners.GetUITaskRunner()->PostTask([&]() {
647 fml::AutoResetWaitableEvent isolate_latch;
648 fml::RefPtr<MultiFrameCodec> codec;
649 EXPECT_TRUE(isolate->RunInIsolateScope([&]() -> bool {
650 Dart_Handle library = Dart_RootLibrary();
651 if (Dart_IsError(library)) {
652 isolate_latch.Signal();
653 return false;
654 }
655 Dart_Handle closure =
656 Dart_GetField(library, Dart_NewStringFromCString("frameCallback"));
657 if (Dart_IsError(closure) || !Dart_IsClosure(closure)) {
658 isolate_latch.Signal();
659 return false;
660 }
661
662 codec = fml::MakeRefCounted<MultiFrameCodec>(std::move(gif_codec));
663 codec->getNextFrame(closure);
664 codec = nullptr;
665 isolate_latch.Signal();
666 return true;
667 }));
668 isolate_latch.Wait();
669
670 EXPECT_FALSE(codec);
671
672 io_latch.Signal();
673
674 latch.Signal();
675 });
676 latch.Wait();
677
678 // Destroy the IO manager
679 runners.GetIOTaskRunner()->PostTask([&]() {
680 io_manager.reset();
681 latch.Signal();
682 });
683 latch.Wait();
684}
685
686} // namespace testing
687} // namespace flutter
688