|
6 | 6 | #include "flutter/fml/mapping.h"
|
7 | 7 | #include "flutter/fml/synchronization/waitable_event.h"
|
8 | 8 | #include "flutter/lib/ui/painting/image_decoder.h"
|
| 9 | +#include "flutter/lib/ui/painting/image_decoder_test.h" |
| 10 | +#include "flutter/lib/ui/painting/multi_frame_codec.h" |
| 11 | +#include "flutter/runtime/dart_vm.h" |
| 12 | +#include "flutter/runtime/dart_vm_lifecycle.h" |
| 13 | +#include "flutter/testing/dart_isolate_runner.h" |
| 14 | +#include "flutter/testing/elf_loader.h" |
| 15 | +#include "flutter/testing/test_dart_native_resolver.h" |
9 | 16 | #include "flutter/testing/test_gl_surface.h"
|
10 | 17 | #include "flutter/testing/testing.h"
|
11 | 18 | #include "flutter/testing/thread_test.h"
|
|
14 | 21 | namespace flutter {
|
15 | 22 | namespace testing {
|
16 | 23 |
|
17 |
| -using ImageDecoderFixtureTest = ThreadTest; |
18 |
| - |
19 | 24 | class TestIOManager final : public IOManager {
|
20 | 25 | public:
|
21 | 26 | TestIOManager(fml::RefPtr<fml::TaskRunner> task_runner,
|
@@ -557,5 +562,96 @@ TEST(ImageDecoderTest, VerifySubpixelDecodingPreservesExifOrientation) {
|
557 | 562 | assert_image(decode({}, 100));
|
558 | 563 | }
|
559 | 564 |
|
| 565 | +TEST_F(ImageDecoderFixtureTest, |
| 566 | + MultiFrameCodecCanBeCollectedBeforeIOTasksFinish) { |
| 567 | + // This test verifies that the MultiFrameCodec safely shares state between |
| 568 | + // tasks on the IO and UI runners, and does not allow unsafe memory access if |
| 569 | + // the UI object is collected while the IO thread still has pending decode |
| 570 | + // work. This could happen in a real application if the engine is collected |
| 571 | + // while a multi-frame image is decoding. To exercise this, the test: |
| 572 | + // - Starts a Dart VM |
| 573 | + // - Latches the IO task runner |
| 574 | + // - Create a MultiFrameCodec for an animated gif pointed to a callback |
| 575 | + // in the Dart fixture |
| 576 | + // - Calls getNextFrame on the UI task runner |
| 577 | + // - Collects the MultiFrameCodec object before unlatching the IO task |
| 578 | + // runner. |
| 579 | + // - Unlatches the IO task runner |
| 580 | + auto settings = CreateSettingsForFixture(); |
| 581 | + auto vm_ref = DartVMRef::Create(settings); |
| 582 | + auto vm_data = vm_ref.GetVMData(); |
| 583 | + |
| 584 | + auto gif_mapping = OpenFixtureAsSkData("hello_loop_2.gif"); |
| 585 | + |
| 586 | + ASSERT_TRUE(gif_mapping); |
| 587 | + |
| 588 | + auto gif_codec = SkCodec::MakeFromData(gif_mapping); |
| 589 | + ASSERT_TRUE(gif_codec); |
| 590 | + |
| 591 | + TaskRunners runners(GetCurrentTestName(), // label |
| 592 | + CreateNewThread("platform"), // platform |
| 593 | + CreateNewThread("gpu"), // gpu |
| 594 | + CreateNewThread("ui"), // ui |
| 595 | + CreateNewThread("io") // io |
| 596 | + ); |
| 597 | + |
| 598 | + fml::AutoResetWaitableEvent latch; |
| 599 | + fml::AutoResetWaitableEvent io_latch; |
| 600 | + std::unique_ptr<TestIOManager> io_manager; |
| 601 | + |
| 602 | + // Setup the IO manager. |
| 603 | + runners.GetIOTaskRunner()->PostTask([&]() { |
| 604 | + io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner()); |
| 605 | + latch.Signal(); |
| 606 | + }); |
| 607 | + latch.Wait(); |
| 608 | + |
| 609 | + auto isolate = |
| 610 | + RunDartCodeInIsolate(vm_ref, settings, runners, "main", {}, |
| 611 | + GetFixturesPath(), io_manager->GetWeakIOManager()); |
| 612 | + |
| 613 | + // Latch the IO task runner. |
| 614 | + runners.GetIOTaskRunner()->PostTask([&]() { io_latch.Wait(); }); |
| 615 | + |
| 616 | + runners.GetUITaskRunner()->PostTask([&]() { |
| 617 | + fml::AutoResetWaitableEvent isolate_latch; |
| 618 | + fml::RefPtr<MultiFrameCodec> codec; |
| 619 | + EXPECT_TRUE(isolate->RunInIsolateScope([&]() -> bool { |
| 620 | + Dart_Handle library = Dart_RootLibrary(); |
| 621 | + if (Dart_IsError(library)) { |
| 622 | + isolate_latch.Signal(); |
| 623 | + return false; |
| 624 | + } |
| 625 | + Dart_Handle closure = |
| 626 | + Dart_GetField(library, Dart_NewStringFromCString("frameCallback")); |
| 627 | + if (Dart_IsError(closure) || !Dart_IsClosure(closure)) { |
| 628 | + isolate_latch.Signal(); |
| 629 | + return false; |
| 630 | + } |
| 631 | + |
| 632 | + codec = fml::MakeRefCounted<MultiFrameCodec>(std::move(gif_codec)); |
| 633 | + codec->getNextFrame(closure); |
| 634 | + codec = nullptr; |
| 635 | + isolate_latch.Signal(); |
| 636 | + return true; |
| 637 | + })); |
| 638 | + isolate_latch.Wait(); |
| 639 | + |
| 640 | + EXPECT_FALSE(codec); |
| 641 | + |
| 642 | + io_latch.Signal(); |
| 643 | + |
| 644 | + latch.Signal(); |
| 645 | + }); |
| 646 | + latch.Wait(); |
| 647 | + |
| 648 | + // Destroy the IO manager |
| 649 | + runners.GetIOTaskRunner()->PostTask([&]() { |
| 650 | + io_manager.reset(); |
| 651 | + latch.Signal(); |
| 652 | + }); |
| 653 | + latch.Wait(); |
| 654 | +} |
| 655 | + |
560 | 656 | } // namespace testing
|
561 | 657 | } // namespace flutter
|
0 commit comments