|
15 | 15 | #include "flutter/shell/common/shell_test.h"
|
16 | 16 | #include "flutter/shell/common/thread_host.h"
|
17 | 17 | #include "flutter/testing/testing.h"
|
| 18 | +#include "gmock/gmock.h" |
18 | 19 |
|
19 | 20 | namespace flutter {
|
| 21 | + |
| 22 | +namespace { |
| 23 | + |
| 24 | +static constexpr int64_t kImplicitViewId = 0; |
| 25 | + |
| 26 | +static void PostSync(const fml::RefPtr<fml::TaskRunner>& task_runner, |
| 27 | + const fml::closure& task) { |
| 28 | + fml::AutoResetWaitableEvent latch; |
| 29 | + fml::TaskRunner::RunNowOrPostTask(task_runner, [&latch, &task] { |
| 30 | + task(); |
| 31 | + latch.Signal(); |
| 32 | + }); |
| 33 | + latch.Wait(); |
| 34 | +} |
| 35 | + |
| 36 | +class MockRuntimeDelegate : public RuntimeDelegate { |
| 37 | + public: |
| 38 | + MOCK_METHOD(std::string, DefaultRouteName, (), (override)); |
| 39 | + MOCK_METHOD(void, ScheduleFrame, (bool), (override)); |
| 40 | + MOCK_METHOD(void, |
| 41 | + Render, |
| 42 | + (std::unique_ptr<flutter::LayerTree>, float), |
| 43 | + (override)); |
| 44 | + MOCK_METHOD(void, |
| 45 | + UpdateSemantics, |
| 46 | + (SemanticsNodeUpdates, CustomAccessibilityActionUpdates), |
| 47 | + (override)); |
| 48 | + MOCK_METHOD(void, |
| 49 | + HandlePlatformMessage, |
| 50 | + (std::unique_ptr<PlatformMessage>), |
| 51 | + (override)); |
| 52 | + MOCK_METHOD(FontCollection&, GetFontCollection, (), (override)); |
| 53 | + MOCK_METHOD(std::shared_ptr<AssetManager>, GetAssetManager, (), (override)); |
| 54 | + MOCK_METHOD(void, OnRootIsolateCreated, (), (override)); |
| 55 | + MOCK_METHOD(void, |
| 56 | + UpdateIsolateDescription, |
| 57 | + (const std::string, int64_t), |
| 58 | + (override)); |
| 59 | + MOCK_METHOD(void, SetNeedsReportTimings, (bool), (override)); |
| 60 | + MOCK_METHOD(std::unique_ptr<std::vector<std::string>>, |
| 61 | + ComputePlatformResolvedLocale, |
| 62 | + (const std::vector<std::string>&), |
| 63 | + (override)); |
| 64 | + MOCK_METHOD(void, RequestDartDeferredLibrary, (intptr_t), (override)); |
| 65 | + MOCK_METHOD(std::weak_ptr<PlatformMessageHandler>, |
| 66 | + GetPlatformMessageHandler, |
| 67 | + (), |
| 68 | + (const, override)); |
| 69 | + MOCK_METHOD(void, SendChannelUpdate, (std::string, bool), (override)); |
| 70 | + MOCK_METHOD(double, |
| 71 | + GetScaledFontSize, |
| 72 | + (double font_size, int configuration_id), |
| 73 | + (const, override)); |
| 74 | +}; |
| 75 | + |
| 76 | +class MockPlatformMessageHandler : public PlatformMessageHandler { |
| 77 | + public: |
| 78 | + MOCK_METHOD(void, |
| 79 | + HandlePlatformMessage, |
| 80 | + (std::unique_ptr<PlatformMessage> message), |
| 81 | + (override)); |
| 82 | + MOCK_METHOD(bool, |
| 83 | + DoesHandlePlatformMessageOnPlatformThread, |
| 84 | + (), |
| 85 | + (const, override)); |
| 86 | + MOCK_METHOD(void, |
| 87 | + InvokePlatformMessageResponseCallback, |
| 88 | + (int response_id, std::unique_ptr<fml::Mapping> mapping), |
| 89 | + (override)); |
| 90 | + MOCK_METHOD(void, |
| 91 | + InvokePlatformMessageEmptyResponseCallback, |
| 92 | + (int response_id), |
| 93 | + (override)); |
| 94 | +}; |
| 95 | + |
| 96 | +// A class that can launch a RuntimeController with the specified |
| 97 | +// RuntimeDelegate. |
| 98 | +// |
| 99 | +// To use this class, contruct this class with Create, call LaunchRootIsolate, |
| 100 | +// and use the controller with ControllerTaskSync(). |
| 101 | +class RuntimeControllerContext { |
| 102 | + public: |
| 103 | + using ControllerCallback = std::function<void(RuntimeController&)>; |
| 104 | + |
| 105 | + [[nodiscard]] static std::unique_ptr<RuntimeControllerContext> Create( |
| 106 | + Settings settings, // |
| 107 | + const TaskRunners& task_runners, // |
| 108 | + RuntimeDelegate& client) { |
| 109 | + auto [vm, isolate_snapshot] = Shell::InferVmInitDataFromSettings(settings); |
| 110 | + FML_CHECK(vm) << "Must be able to initialize the VM."; |
| 111 | + // Construct the class with `new` because `make_unique` has no access to the |
| 112 | + // private constructor. |
| 113 | + RuntimeControllerContext* raw_pointer = new RuntimeControllerContext( |
| 114 | + settings, task_runners, client, std::move(vm), isolate_snapshot); |
| 115 | + return std::unique_ptr<RuntimeControllerContext>(raw_pointer); |
| 116 | + } |
| 117 | + |
| 118 | + ~RuntimeControllerContext() { |
| 119 | + PostSync(task_runners_.GetUITaskRunner(), |
| 120 | + [&]() { runtime_controller_.reset(); }); |
| 121 | + } |
| 122 | + |
| 123 | + // Launch the root isolate. The post_launch callback will be executed in the |
| 124 | + // same UI task, which can be used to create initial views. |
| 125 | + void LaunchRootIsolate(RunConfiguration& configuration, |
| 126 | + ControllerCallback post_launch) { |
| 127 | + PostSync(task_runners_.GetUITaskRunner(), [&]() { |
| 128 | + bool launch_success = runtime_controller_->LaunchRootIsolate( |
| 129 | + settings_, // |
| 130 | + []() {}, // |
| 131 | + configuration.GetEntrypoint(), // |
| 132 | + configuration.GetEntrypointLibrary(), // |
| 133 | + configuration.GetEntrypointArgs(), // |
| 134 | + configuration.TakeIsolateConfiguration()); // |
| 135 | + ASSERT_TRUE(launch_success); |
| 136 | + post_launch(*runtime_controller_); |
| 137 | + }); |
| 138 | + } |
| 139 | + |
| 140 | + // Run a task that operates the RuntimeController on the UI thread, and wait |
| 141 | + // for the task to end. |
| 142 | + void ControllerTaskSync(ControllerCallback task) { |
| 143 | + ASSERT_TRUE(runtime_controller_); |
| 144 | + ASSERT_TRUE(task); |
| 145 | + PostSync(task_runners_.GetUITaskRunner(), |
| 146 | + [&]() { task(*runtime_controller_); }); |
| 147 | + } |
| 148 | + |
| 149 | + private: |
| 150 | + RuntimeControllerContext(const Settings& settings, |
| 151 | + const TaskRunners& task_runners, |
| 152 | + RuntimeDelegate& client, |
| 153 | + DartVMRef vm, |
| 154 | + fml::RefPtr<const DartSnapshot> isolate_snapshot) |
| 155 | + : settings_(settings), |
| 156 | + task_runners_(task_runners), |
| 157 | + isolate_snapshot_(std::move(isolate_snapshot)), |
| 158 | + vm_(std::move(vm)), |
| 159 | + runtime_controller_(std::make_unique<RuntimeController>( |
| 160 | + client, |
| 161 | + &vm_, |
| 162 | + std::move(isolate_snapshot_), |
| 163 | + settings.idle_notification_callback, // idle notification callback |
| 164 | + flutter::PlatformData(), // platform data |
| 165 | + settings.isolate_create_callback, // isolate create callback |
| 166 | + settings.isolate_shutdown_callback, // isolate shutdown callback |
| 167 | + settings.persistent_isolate_data, // persistent isolate data |
| 168 | + UIDartState::Context{task_runners})) {} |
| 169 | + |
| 170 | + Settings settings_; |
| 171 | + TaskRunners task_runners_; |
| 172 | + fml::RefPtr<const DartSnapshot> isolate_snapshot_; |
| 173 | + DartVMRef vm_; |
| 174 | + std::unique_ptr<RuntimeController> runtime_controller_; |
| 175 | +}; |
| 176 | +} // namespace |
| 177 | + |
20 | 178 | namespace testing {
|
21 | 179 |
|
| 180 | +using ::testing::_; |
| 181 | +using ::testing::Return; |
| 182 | + |
22 | 183 | class PlatformConfigurationTest : public ShellTest {};
|
23 | 184 |
|
24 | 185 | TEST_F(PlatformConfigurationTest, Initialization) {
|
@@ -332,5 +493,84 @@ TEST_F(PlatformConfigurationTest, SetDartPerformanceMode) {
|
332 | 493 | DestroyShell(std::move(shell), task_runners);
|
333 | 494 | }
|
334 | 495 |
|
| 496 | +TEST_F(PlatformConfigurationTest, OutOfScopeRenderCallsAreIgnored) { |
| 497 | + Settings settings = CreateSettingsForFixture(); |
| 498 | + TaskRunners task_runners = GetTaskRunnersForFixture(); |
| 499 | + |
| 500 | + MockRuntimeDelegate client; |
| 501 | + auto platform_message_handler = |
| 502 | + std::make_shared<MockPlatformMessageHandler>(); |
| 503 | + EXPECT_CALL(client, GetPlatformMessageHandler) |
| 504 | + .WillOnce(Return(platform_message_handler)); |
| 505 | + // Render should not be called. |
| 506 | + EXPECT_CALL(client, Render).Times(0); |
| 507 | + |
| 508 | + auto message_latch = std::make_shared<fml::AutoResetWaitableEvent>(); |
| 509 | + auto finish = [message_latch](Dart_NativeArguments args) { |
| 510 | + message_latch->Signal(); |
| 511 | + }; |
| 512 | + AddNativeCallback("Finish", CREATE_NATIVE_ENTRY(finish)); |
| 513 | + |
| 514 | + auto runtime_controller_context = |
| 515 | + RuntimeControllerContext::Create(settings, task_runners, client); |
| 516 | + |
| 517 | + auto configuration = RunConfiguration::InferFromSettings(settings); |
| 518 | + configuration.SetEntrypoint("incorrectImmediateRender"); |
| 519 | + runtime_controller_context->LaunchRootIsolate( |
| 520 | + configuration, [](RuntimeController& runtime_controller) { |
| 521 | + runtime_controller.AddView( |
| 522 | + kImplicitViewId, |
| 523 | + ViewportMetrics( |
| 524 | + /*pixel_ratio=*/1.0, /*width=*/20, /*height=*/20, |
| 525 | + /*touch_slop=*/2, /*display_id=*/0)); |
| 526 | + }); |
| 527 | + |
| 528 | + // Wait for the Dart main function to end. |
| 529 | + message_latch->Wait(); |
| 530 | +} |
| 531 | + |
| 532 | +TEST_F(PlatformConfigurationTest, DuplicateRenderCallsAreIgnored) { |
| 533 | + Settings settings = CreateSettingsForFixture(); |
| 534 | + TaskRunners task_runners = GetTaskRunnersForFixture(); |
| 535 | + |
| 536 | + MockRuntimeDelegate client; |
| 537 | + auto platform_message_handler = |
| 538 | + std::make_shared<MockPlatformMessageHandler>(); |
| 539 | + EXPECT_CALL(client, GetPlatformMessageHandler) |
| 540 | + .WillOnce(Return(platform_message_handler)); |
| 541 | + // Render should only be called once, because the second call is ignored. |
| 542 | + EXPECT_CALL(client, Render).Times(1); |
| 543 | + |
| 544 | + auto message_latch = std::make_shared<fml::AutoResetWaitableEvent>(); |
| 545 | + auto finish = [message_latch](Dart_NativeArguments args) { |
| 546 | + message_latch->Signal(); |
| 547 | + }; |
| 548 | + AddNativeCallback("Finish", CREATE_NATIVE_ENTRY(finish)); |
| 549 | + |
| 550 | + auto runtime_controller_context = |
| 551 | + RuntimeControllerContext::Create(settings, task_runners, client); |
| 552 | + |
| 553 | + auto configuration = RunConfiguration::InferFromSettings(settings); |
| 554 | + configuration.SetEntrypoint("incorrectDoubleRender"); |
| 555 | + runtime_controller_context->LaunchRootIsolate( |
| 556 | + configuration, [](RuntimeController& runtime_controller) { |
| 557 | + runtime_controller.AddView( |
| 558 | + kImplicitViewId, |
| 559 | + ViewportMetrics( |
| 560 | + /*pixel_ratio=*/1.0, /*width=*/20, /*height=*/20, |
| 561 | + /*touch_slop=*/2, /*display_id=*/0)); |
| 562 | + }); |
| 563 | + |
| 564 | + // Wait for the Dart main function to end. |
| 565 | + message_latch->Wait(); |
| 566 | + |
| 567 | + runtime_controller_context->ControllerTaskSync( |
| 568 | + [](RuntimeController& runtime_controller) { |
| 569 | + // This BeginFrame calls PlatformDispatcher's handleBeginFrame and |
| 570 | + // handleDrawFrame synchronously. Therefore don't wait after it. |
| 571 | + runtime_controller.BeginFrame(fml::TimePoint::Now(), 0); |
| 572 | + }); |
| 573 | +} |
| 574 | + |
335 | 575 | } // namespace testing
|
336 | 576 | } // namespace flutter
|
0 commit comments