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 <strstream>
6#define FML_USED_ON_EMBEDDER
7
8#include <algorithm>
9#include <chrono>
10#include <ctime>
11#include <future>
12#include <memory>
13#include <thread>
14#include <utility>
15#include <vector>
16
17#if SHELL_ENABLE_GL
18#include <EGL/egl.h>
19#endif // SHELL_ENABLE_GL
20
21#include "assets/directory_asset_bundle.h"
22#include "common/graphics/persistent_cache.h"
23#include "flutter/flow/layers/backdrop_filter_layer.h"
24#include "flutter/flow/layers/clip_rect_layer.h"
25#include "flutter/flow/layers/display_list_layer.h"
26#include "flutter/flow/layers/layer_raster_cache_item.h"
27#include "flutter/flow/layers/platform_view_layer.h"
28#include "flutter/flow/layers/transform_layer.h"
29#include "flutter/fml/backtrace.h"
30#include "flutter/fml/command_line.h"
31#include "flutter/fml/dart/dart_converter.h"
32#include "flutter/fml/make_copyable.h"
33#include "flutter/fml/message_loop.h"
34#include "flutter/fml/synchronization/count_down_latch.h"
35#include "flutter/fml/synchronization/waitable_event.h"
36#include "flutter/runtime/dart_vm.h"
37#include "flutter/shell/common/platform_view.h"
38#include "flutter/shell/common/rasterizer.h"
39#include "flutter/shell/common/shell_test.h"
40#include "flutter/shell/common/shell_test_external_view_embedder.h"
41#include "flutter/shell/common/shell_test_platform_view.h"
42#include "flutter/shell/common/switches.h"
43#include "flutter/shell/common/thread_host.h"
44#include "flutter/shell/common/vsync_waiter_fallback.h"
45#include "flutter/shell/version/version.h"
46#include "flutter/testing/mock_canvas.h"
47#include "flutter/testing/testing.h"
48#include "gmock/gmock.h"
49#include "third_party/rapidjson/include/rapidjson/writer.h"
50#include "third_party/skia/include/codec/SkCodecAnimation.h"
51#include "third_party/tonic/converter/dart_converter.h"
52
53#ifdef SHELL_ENABLE_VULKAN
54#include "flutter/vulkan/vulkan_application.h" // nogncheck
55#endif
56
57// CREATE_NATIVE_ENTRY is leaky by design
58// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
59
60namespace flutter {
61namespace testing {
62
63constexpr int64_t kImplicitViewId = 0ll;
64
65using ::testing::_;
66using ::testing::Return;
67
68namespace {
69class MockPlatformViewDelegate : public PlatformView::Delegate {
70 MOCK_METHOD1(OnPlatformViewCreated, void(std::unique_ptr<Surface> surface));
71
72 MOCK_METHOD0(OnPlatformViewDestroyed, void());
73
74 MOCK_METHOD0(OnPlatformViewScheduleFrame, void());
75
76 MOCK_METHOD1(OnPlatformViewSetNextFrameCallback,
77 void(const fml::closure& closure));
78
79 MOCK_METHOD2(OnPlatformViewSetViewportMetrics,
80 void(int64_t view_id, const ViewportMetrics& metrics));
81
82 MOCK_METHOD1(OnPlatformViewDispatchPlatformMessage,
83 void(std::unique_ptr<PlatformMessage> message));
84
85 MOCK_METHOD1(OnPlatformViewDispatchPointerDataPacket,
86 void(std::unique_ptr<PointerDataPacket> packet));
87
88 MOCK_METHOD3(OnPlatformViewDispatchSemanticsAction,
89 void(int32_t id,
90 SemanticsAction action,
91 fml::MallocMapping args));
92
93 MOCK_METHOD1(OnPlatformViewSetSemanticsEnabled, void(bool enabled));
94
95 MOCK_METHOD1(OnPlatformViewSetAccessibilityFeatures, void(int32_t flags));
96
97 MOCK_METHOD1(OnPlatformViewRegisterTexture,
98 void(std::shared_ptr<Texture> texture));
99
100 MOCK_METHOD1(OnPlatformViewUnregisterTexture, void(int64_t texture_id));
101
102 MOCK_METHOD1(OnPlatformViewMarkTextureFrameAvailable,
103 void(int64_t texture_id));
104
105 MOCK_METHOD(const Settings&,
106 OnPlatformViewGetSettings,
107 (),
108 (const, override));
109
110 MOCK_METHOD3(LoadDartDeferredLibrary,
111 void(intptr_t loading_unit_id,
112 std::unique_ptr<const fml::Mapping> snapshot_data,
113 std::unique_ptr<const fml::Mapping> snapshot_instructions));
114
115 MOCK_METHOD3(LoadDartDeferredLibraryError,
116 void(intptr_t loading_unit_id,
117 const std::string error_message,
118 bool transient));
119
120 MOCK_METHOD2(UpdateAssetResolverByType,
121 void(std::unique_ptr<AssetResolver> updated_asset_resolver,
122 AssetResolver::AssetResolverType type));
123};
124
125class MockSurface : public Surface {
126 public:
127 MOCK_METHOD0(IsValid, bool());
128
129 MOCK_METHOD1(AcquireFrame,
130 std::unique_ptr<SurfaceFrame>(const SkISize& size));
131
132 MOCK_CONST_METHOD0(GetRootTransformation, SkMatrix());
133
134 MOCK_METHOD0(GetContext, GrDirectContext*());
135
136 MOCK_METHOD0(MakeRenderContextCurrent, std::unique_ptr<GLContextResult>());
137
138 MOCK_METHOD0(ClearRenderContext, bool());
139};
140
141class MockPlatformView : public PlatformView {
142 public:
143 MockPlatformView(MockPlatformViewDelegate& delegate,
144 const TaskRunners& task_runners)
145 : PlatformView(delegate, task_runners) {}
146 MOCK_METHOD0(CreateRenderingSurface, std::unique_ptr<Surface>());
147 MOCK_CONST_METHOD0(GetPlatformMessageHandler,
148 std::shared_ptr<PlatformMessageHandler>());
149};
150
151class TestPlatformView : public PlatformView {
152 public:
153 TestPlatformView(Shell& shell, const TaskRunners& task_runners)
154 : PlatformView(shell, task_runners) {}
155 MOCK_METHOD0(CreateRenderingSurface, std::unique_ptr<Surface>());
156};
157
158class MockPlatformMessageHandler : public PlatformMessageHandler {
159 public:
160 MOCK_METHOD1(HandlePlatformMessage,
161 void(std::unique_ptr<PlatformMessage> message));
162 MOCK_CONST_METHOD0(DoesHandlePlatformMessageOnPlatformThread, bool());
163 MOCK_METHOD2(InvokePlatformMessageResponseCallback,
164 void(int response_id, std::unique_ptr<fml::Mapping> mapping));
165 MOCK_METHOD1(InvokePlatformMessageEmptyResponseCallback,
166 void(int response_id));
167};
168
169class MockPlatformMessageResponse : public PlatformMessageResponse {
170 public:
171 static fml::RefPtr<MockPlatformMessageResponse> Create() {
172 return fml::AdoptRef(ptr: new MockPlatformMessageResponse());
173 }
174 MOCK_METHOD1(Complete, void(std::unique_ptr<fml::Mapping> data));
175 MOCK_METHOD0(CompleteEmpty, void());
176};
177} // namespace
178
179class TestAssetResolver : public AssetResolver {
180 public:
181 TestAssetResolver(bool valid, AssetResolver::AssetResolverType type)
182 : valid_(valid), type_(type) {}
183
184 bool IsValid() const override { return true; }
185
186 // This is used to identify if replacement was made or not.
187 bool IsValidAfterAssetManagerChange() const override { return valid_; }
188
189 AssetResolver::AssetResolverType GetType() const override { return type_; }
190
191 std::unique_ptr<fml::Mapping> GetAsMapping(
192 const std::string& asset_name) const override {
193 return nullptr;
194 }
195
196 std::vector<std::unique_ptr<fml::Mapping>> GetAsMappings(
197 const std::string& asset_pattern,
198 const std::optional<std::string>& subdir) const override {
199 return {};
200 };
201
202 private:
203 bool valid_;
204 AssetResolver::AssetResolverType type_;
205};
206
207class ThreadCheckingAssetResolver : public AssetResolver {
208 public:
209 explicit ThreadCheckingAssetResolver(
210 std::shared_ptr<fml::ConcurrentMessageLoop> concurrent_loop)
211 : concurrent_loop_(std::move(concurrent_loop)) {}
212
213 // |AssetResolver|
214 bool IsValid() const override { return true; }
215
216 // |AssetResolver|
217 bool IsValidAfterAssetManagerChange() const override { return true; }
218
219 // |AssetResolver|
220 AssetResolverType GetType() const {
221 return AssetResolverType::kApkAssetProvider;
222 }
223
224 // |AssetResolver|
225 std::unique_ptr<fml::Mapping> GetAsMapping(
226 const std::string& asset_name) const override {
227 if (asset_name == "FontManifest.json") {
228 // This file is loaded directly by the engine.
229 return nullptr;
230 }
231 mapping_requests.push_back(x: asset_name);
232 EXPECT_TRUE(concurrent_loop_->RunsTasksOnCurrentThread())
233 << fml::BacktraceHere();
234 return nullptr;
235 }
236
237 mutable std::vector<std::string> mapping_requests;
238
239 private:
240 std::shared_ptr<fml::ConcurrentMessageLoop> concurrent_loop_;
241};
242
243static bool ValidateShell(Shell* shell) {
244 if (!shell) {
245 return false;
246 }
247
248 if (!shell->IsSetup()) {
249 return false;
250 }
251
252 ShellTest::PlatformViewNotifyCreated(shell);
253
254 {
255 fml::AutoResetWaitableEvent latch;
256 fml::TaskRunner::RunNowOrPostTask(
257 runner: shell->GetTaskRunners().GetPlatformTaskRunner(), task: [shell, &latch]() {
258 shell->GetPlatformView()->NotifyDestroyed();
259 latch.Signal();
260 });
261 latch.Wait();
262 }
263
264 return true;
265}
266
267static bool RasterizerHasLayerTree(Shell* shell) {
268 fml::AutoResetWaitableEvent latch;
269 bool has_layer_tree = false;
270 fml::TaskRunner::RunNowOrPostTask(
271 runner: shell->GetTaskRunners().GetRasterTaskRunner(),
272 task: [shell, &latch, &has_layer_tree]() {
273 has_layer_tree = shell->GetRasterizer()->GetLastLayerTree() != nullptr;
274 latch.Signal();
275 });
276 latch.Wait();
277 return has_layer_tree;
278}
279
280static void ValidateDestroyPlatformView(Shell* shell) {
281 ASSERT_TRUE(shell != nullptr);
282 ASSERT_TRUE(shell->IsSetup());
283
284 // To validate destroy platform view, we must ensure the rasterizer has a
285 // layer tree before the platform view is destroyed.
286 ASSERT_TRUE(RasterizerHasLayerTree(shell));
287
288 ShellTest::PlatformViewNotifyDestroyed(shell);
289 // Validate the layer tree is destroyed
290 ASSERT_FALSE(RasterizerHasLayerTree(shell));
291}
292
293static std::string CreateFlagsString(std::vector<const char*>& flags) {
294 if (flags.empty()) {
295 return "";
296 }
297 std::string flags_string = flags[0];
298 for (size_t i = 1; i < flags.size(); ++i) {
299 flags_string += ",";
300 flags_string += flags[i];
301 }
302 return flags_string;
303}
304
305static void TestDartVmFlags(std::vector<const char*>& flags) {
306 std::string flags_string = CreateFlagsString(flags);
307 const std::vector<fml::CommandLine::Option> options = {
308 fml::CommandLine::Option("dart-flags", flags_string)};
309 fml::CommandLine command_line("", options, std::vector<std::string>());
310 flutter::Settings settings = flutter::SettingsFromCommandLine(command_line);
311 EXPECT_EQ(settings.dart_flags.size(), flags.size());
312 for (size_t i = 0; i < flags.size(); ++i) {
313 EXPECT_EQ(settings.dart_flags[i], flags[i]);
314 }
315}
316
317static void PostSync(const fml::RefPtr<fml::TaskRunner>& task_runner,
318 const fml::closure& task) {
319 fml::AutoResetWaitableEvent latch;
320 fml::TaskRunner::RunNowOrPostTask(runner: task_runner, task: [&latch, &task] {
321 task();
322 latch.Signal();
323 });
324 latch.Wait();
325}
326
327static sk_sp<DisplayList> MakeSizedDisplayList(int width, int height) {
328 DisplayListBuilder builder(SkRect::MakeXYWH(x: 0, y: 0, w: width, h: height));
329 builder.DrawRect(rect: SkRect::MakeXYWH(x: 0, y: 0, w: width, h: height),
330 paint: DlPaint(DlColor::kRed()));
331 return builder.Build();
332}
333
334TEST_F(ShellTest, InitializeWithInvalidThreads) {
335 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
336 Settings settings = CreateSettingsForFixture();
337 TaskRunners task_runners("test", nullptr, nullptr, nullptr, nullptr);
338 auto shell = CreateShell(settings, task_runners);
339 ASSERT_FALSE(shell);
340 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
341}
342
343TEST_F(ShellTest, InitializeWithDifferentThreads) {
344 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
345 Settings settings = CreateSettingsForFixture();
346 std::string name_prefix = "io.flutter.test." + GetCurrentTestName() + ".";
347 ThreadHost thread_host(ThreadHost::ThreadHostConfig(
348 name_prefix, ThreadHost::Type::Platform | ThreadHost::Type::RASTER |
349 ThreadHost::Type::IO | ThreadHost::Type::UI));
350 ASSERT_EQ(thread_host.name_prefix, name_prefix);
351
352 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
353 thread_host.raster_thread->GetTaskRunner(),
354 thread_host.ui_thread->GetTaskRunner(),
355 thread_host.io_thread->GetTaskRunner());
356 auto shell = CreateShell(settings, task_runners);
357 ASSERT_TRUE(ValidateShell(shell.get()));
358 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
359 DestroyShell(shell: std::move(shell), task_runners);
360 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
361}
362
363TEST_F(ShellTest, InitializeWithSingleThread) {
364 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
365 Settings settings = CreateSettingsForFixture();
366 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
367 ThreadHost::Type::Platform);
368 auto task_runner = thread_host.platform_thread->GetTaskRunner();
369 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
370 task_runner);
371 auto shell = CreateShell(settings, task_runners);
372 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
373 ASSERT_TRUE(ValidateShell(shell.get()));
374 DestroyShell(shell: std::move(shell), task_runners);
375 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
376}
377
378TEST_F(ShellTest, InitializeWithSingleThreadWhichIsTheCallingThread) {
379 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
380 Settings settings = CreateSettingsForFixture();
381 fml::MessageLoop::EnsureInitializedForCurrentThread();
382 auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
383 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
384 task_runner);
385 auto shell = CreateShell(settings, task_runners);
386 ASSERT_TRUE(ValidateShell(shell.get()));
387 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
388 DestroyShell(shell: std::move(shell), task_runners);
389 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
390}
391
392TEST_F(ShellTest,
393 InitializeWithMultipleThreadButCallingThreadAsPlatformThread) {
394 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
395 Settings settings = CreateSettingsForFixture();
396 ThreadHost thread_host(
397 "io.flutter.test." + GetCurrentTestName() + ".",
398 ThreadHost::Type::RASTER | ThreadHost::Type::IO | ThreadHost::Type::UI);
399 fml::MessageLoop::EnsureInitializedForCurrentThread();
400 TaskRunners task_runners("test",
401 fml::MessageLoop::GetCurrent().GetTaskRunner(),
402 thread_host.raster_thread->GetTaskRunner(),
403 thread_host.ui_thread->GetTaskRunner(),
404 thread_host.io_thread->GetTaskRunner());
405 auto shell = Shell::Create(
406 platform_data: flutter::PlatformData(), task_runners, settings,
407 on_create_platform_view: [](Shell& shell) {
408 // This is unused in the platform view as we are not using the simulated
409 // vsync mechanism. We should have better DI in the tests.
410 const auto vsync_clock = std::make_shared<ShellTestVsyncClock>();
411 return ShellTestPlatformView::Create(
412 delegate&: shell, task_runners: shell.GetTaskRunners(), vsync_clock,
413 create_vsync_waiter: [task_runners = shell.GetTaskRunners()]() {
414 return static_cast<std::unique_ptr<VsyncWaiter>>(
415 std::make_unique<VsyncWaiterFallback>(args: task_runners));
416 },
417 backend: ShellTestPlatformView::BackendType::kDefaultBackend, shell_test_external_view_embedder: nullptr,
418 is_gpu_disabled_sync_switch: shell.GetIsGpuDisabledSyncSwitch());
419 },
420 on_create_rasterizer: [](Shell& shell) { return std::make_unique<Rasterizer>(args&: shell); });
421 ASSERT_TRUE(ValidateShell(shell.get()));
422 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
423 DestroyShell(shell: std::move(shell), task_runners);
424 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
425}
426
427TEST_F(ShellTest, InitializeWithDisabledGpu) {
428 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
429 Settings settings = CreateSettingsForFixture();
430 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
431 ThreadHost::Type::Platform);
432 auto task_runner = thread_host.platform_thread->GetTaskRunner();
433 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
434 task_runner);
435 auto shell = CreateShell(config: {
436 .settings = settings,
437 .task_runners = task_runners,
438 .is_gpu_disabled = true,
439 });
440 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
441 ASSERT_TRUE(ValidateShell(shell.get()));
442
443 bool is_disabled = false;
444 shell->GetIsGpuDisabledSyncSwitch()->Execute(
445 handlers: fml::SyncSwitch::Handlers().SetIfTrue([&] { is_disabled = true; }));
446 ASSERT_TRUE(is_disabled);
447
448 DestroyShell(shell: std::move(shell), task_runners);
449 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
450}
451
452TEST_F(ShellTest, InitializeWithGPUAndPlatformThreadsTheSame) {
453 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
454 Settings settings = CreateSettingsForFixture();
455 ThreadHost thread_host(
456 "io.flutter.test." + GetCurrentTestName() + ".",
457 ThreadHost::Type::Platform | ThreadHost::Type::IO | ThreadHost::Type::UI);
458 TaskRunners task_runners(
459 "test",
460 thread_host.platform_thread->GetTaskRunner(), // platform
461 thread_host.platform_thread->GetTaskRunner(), // raster
462 thread_host.ui_thread->GetTaskRunner(), // ui
463 thread_host.io_thread->GetTaskRunner() // io
464 );
465 auto shell = CreateShell(settings, task_runners);
466 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
467 ASSERT_TRUE(ValidateShell(shell.get()));
468 DestroyShell(shell: std::move(shell), task_runners);
469 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
470}
471
472TEST_F(ShellTest, FixturesAreFunctional) {
473 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
474 auto settings = CreateSettingsForFixture();
475 auto shell = CreateShell(settings);
476 ASSERT_TRUE(ValidateShell(shell.get()));
477
478 auto configuration = RunConfiguration::InferFromSettings(settings);
479 ASSERT_TRUE(configuration.IsValid());
480 configuration.SetEntrypoint("fixturesAreFunctionalMain");
481
482 fml::AutoResetWaitableEvent main_latch;
483 AddNativeCallback(
484 name: "SayHiFromFixturesAreFunctionalMain",
485 CREATE_NATIVE_ENTRY([&main_latch](auto args) { main_latch.Signal(); }));
486
487 RunEngine(shell: shell.get(), configuration: std::move(configuration));
488 main_latch.Wait();
489 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
490 DestroyShell(shell: std::move(shell));
491 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
492}
493
494TEST_F(ShellTest, SecondaryIsolateBindingsAreSetupViaShellSettings) {
495 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
496 auto settings = CreateSettingsForFixture();
497 auto shell = CreateShell(settings);
498 ASSERT_TRUE(ValidateShell(shell.get()));
499
500 auto configuration = RunConfiguration::InferFromSettings(settings);
501 ASSERT_TRUE(configuration.IsValid());
502 configuration.SetEntrypoint("testCanLaunchSecondaryIsolate");
503
504 fml::CountDownLatch latch(2);
505 AddNativeCallback(name: "NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) {
506 latch.CountDown();
507 }));
508
509 RunEngine(shell: shell.get(), configuration: std::move(configuration));
510
511 latch.Wait();
512
513 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
514 DestroyShell(shell: std::move(shell));
515 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
516}
517
518TEST_F(ShellTest, LastEntrypoint) {
519 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
520 auto settings = CreateSettingsForFixture();
521 auto shell = CreateShell(settings);
522 ASSERT_TRUE(ValidateShell(shell.get()));
523
524 auto configuration = RunConfiguration::InferFromSettings(settings);
525 ASSERT_TRUE(configuration.IsValid());
526 std::string entry_point = "fixturesAreFunctionalMain";
527 configuration.SetEntrypoint(entry_point);
528
529 fml::AutoResetWaitableEvent main_latch;
530 std::string last_entry_point;
531 AddNativeCallback(
532 name: "SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) {
533 last_entry_point = shell->GetEngine()->GetLastEntrypoint();
534 main_latch.Signal();
535 }));
536
537 RunEngine(shell: shell.get(), configuration: std::move(configuration));
538 main_latch.Wait();
539 EXPECT_EQ(entry_point, last_entry_point);
540 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
541 DestroyShell(shell: std::move(shell));
542 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
543}
544
545TEST_F(ShellTest, LastEntrypointArgs) {
546 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
547 auto settings = CreateSettingsForFixture();
548 auto shell = CreateShell(settings);
549 ASSERT_TRUE(ValidateShell(shell.get()));
550
551 auto configuration = RunConfiguration::InferFromSettings(settings);
552 ASSERT_TRUE(configuration.IsValid());
553 std::string entry_point = "fixturesAreFunctionalMain";
554 std::vector<std::string> entry_point_args = {"arg1"};
555 configuration.SetEntrypoint(entry_point);
556 configuration.SetEntrypointArgs(entry_point_args);
557
558 fml::AutoResetWaitableEvent main_latch;
559 std::vector<std::string> last_entry_point_args;
560 AddNativeCallback(
561 name: "SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) {
562 last_entry_point_args = shell->GetEngine()->GetLastEntrypointArgs();
563 main_latch.Signal();
564 }));
565
566 RunEngine(shell: shell.get(), configuration: std::move(configuration));
567 main_latch.Wait();
568#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
569 EXPECT_EQ(last_entry_point_args, entry_point_args);
570#else
571 ASSERT_TRUE(last_entry_point_args.empty());
572#endif
573 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
574 DestroyShell(shell: std::move(shell));
575 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
576}
577
578TEST_F(ShellTest, DisallowedDartVMFlag) {
579#if defined(OS_FUCHSIA)
580 GTEST_SKIP() << "This test flakes on Fuchsia. https://fxbug.dev/110006 ";
581#endif // OS_FUCHSIA
582
583 // Run this test in a thread-safe manner, otherwise gtest will complain.
584 ::testing::FLAGS_gtest_death_test_style = "threadsafe";
585
586 const std::vector<fml::CommandLine::Option> options = {
587 fml::CommandLine::Option("dart-flags", "--verify_after_gc")};
588 fml::CommandLine command_line("", options, std::vector<std::string>());
589
590 // Upon encountering a disallowed Dart flag the process terminates.
591 const char* expected =
592 "Encountered disallowed Dart VM flag: --verify_after_gc";
593 ASSERT_DEATH(flutter::SettingsFromCommandLine(command_line), expected);
594}
595
596TEST_F(ShellTest, AllowedDartVMFlag) {
597 std::vector<const char*> flags = {
598 "--enable-isolate-groups",
599 "--no-enable-isolate-groups",
600 };
601#if !FLUTTER_RELEASE
602 flags.push_back(x: "--max_profile_depth 1");
603 flags.push_back(x: "--random_seed 42");
604 flags.push_back(x: "--max_subtype_cache_entries=22");
605 if (!DartVM::IsRunningPrecompiledCode()) {
606 flags.push_back(x: "--enable_mirrors");
607 }
608#endif
609
610 TestDartVmFlags(flags);
611}
612
613TEST_F(ShellTest, NoNeedToReportTimingsByDefault) {
614 auto settings = CreateSettingsForFixture();
615 std::unique_ptr<Shell> shell = CreateShell(settings);
616
617 // Create the surface needed by rasterizer
618 PlatformViewNotifyCreated(shell: shell.get());
619
620 auto configuration = RunConfiguration::InferFromSettings(settings);
621 configuration.SetEntrypoint("emptyMain");
622
623 RunEngine(shell: shell.get(), configuration: std::move(configuration));
624 PumpOneFrame(shell: shell.get());
625 ASSERT_FALSE(GetNeedsReportTimings(shell.get()));
626
627 // This assertion may or may not be the direct result of needs_report_timings_
628 // being false. The count could be 0 simply because we just cleared
629 // unreported timings by reporting them. Hence this can't replace the
630 // ASSERT_FALSE(GetNeedsReportTimings(shell.get())) check. We added
631 // this assertion for an additional confidence that we're not pushing
632 // back to unreported timings unnecessarily.
633 //
634 // Conversely, do not assert UnreportedTimingsCount(shell.get()) to be
635 // positive in any tests. Otherwise those tests will be flaky as the clearing
636 // of unreported timings is unpredictive.
637 ASSERT_EQ(UnreportedTimingsCount(shell.get()), 0);
638 DestroyShell(shell: std::move(shell));
639}
640
641TEST_F(ShellTest, NeedsReportTimingsIsSetWithCallback) {
642 auto settings = CreateSettingsForFixture();
643 std::unique_ptr<Shell> shell = CreateShell(settings);
644
645 // Create the surface needed by rasterizer
646 PlatformViewNotifyCreated(shell: shell.get());
647
648 auto configuration = RunConfiguration::InferFromSettings(settings);
649 configuration.SetEntrypoint("dummyReportTimingsMain");
650
651 RunEngine(shell: shell.get(), configuration: std::move(configuration));
652 PumpOneFrame(shell: shell.get());
653 ASSERT_TRUE(GetNeedsReportTimings(shell.get()));
654 DestroyShell(shell: std::move(shell));
655}
656
657static void CheckFrameTimings(const std::vector<FrameTiming>& timings,
658 fml::TimePoint start,
659 fml::TimePoint finish) {
660 fml::TimePoint last_frame_start;
661 for (size_t i = 0; i < timings.size(); i += 1) {
662 // Ensure that timings are sorted.
663 ASSERT_TRUE(timings[i].Get(FrameTiming::kPhases[0]) >= last_frame_start);
664 last_frame_start = timings[i].Get(phase: FrameTiming::kPhases[0]);
665
666 fml::TimePoint last_phase_time;
667 for (auto phase : FrameTiming::kPhases) {
668 // raster finish wall time doesn't use the same clock base
669 // as rest of the frame timings.
670 if (phase == FrameTiming::kRasterFinishWallTime) {
671 continue;
672 }
673
674 ASSERT_TRUE(timings[i].Get(phase) >= start);
675 ASSERT_TRUE(timings[i].Get(phase) <= finish);
676
677 // phases should have weakly increasing time points
678 ASSERT_TRUE(last_phase_time <= timings[i].Get(phase));
679 last_phase_time = timings[i].Get(phase);
680 }
681 }
682}
683
684TEST_F(ShellTest, ReportTimingsIsCalled) {
685 auto settings = CreateSettingsForFixture();
686 std::unique_ptr<Shell> shell = CreateShell(settings);
687
688 // We MUST put |start| after |CreateShell| because the clock source will be
689 // reset through |TimePoint::SetClockSource()| in
690 // |DartVMInitializer::Initialize()| within |CreateShell()|.
691 fml::TimePoint start = fml::TimePoint::Now();
692
693 // Create the surface needed by rasterizer
694 PlatformViewNotifyCreated(shell: shell.get());
695
696 auto configuration = RunConfiguration::InferFromSettings(settings);
697 ASSERT_TRUE(configuration.IsValid());
698 configuration.SetEntrypoint("reportTimingsMain");
699 fml::AutoResetWaitableEvent reportLatch;
700 std::vector<int64_t> timestamps;
701 auto nativeTimingCallback = [&reportLatch,
702 &timestamps](Dart_NativeArguments args) {
703 Dart_Handle exception = nullptr;
704 ASSERT_EQ(timestamps.size(), 0ul);
705 timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
706 args, index: 0, exception);
707 reportLatch.Signal();
708 };
709 AddNativeCallback(name: "NativeReportTimingsCallback",
710 CREATE_NATIVE_ENTRY(nativeTimingCallback));
711 RunEngine(shell: shell.get(), configuration: std::move(configuration));
712
713 // Pump many frames so we can trigger the report quickly instead of waiting
714 // for the 1 second threshold.
715 for (int i = 0; i < 200; i += 1) {
716 PumpOneFrame(shell: shell.get());
717 }
718
719 reportLatch.Wait();
720 DestroyShell(shell: std::move(shell));
721
722 fml::TimePoint finish = fml::TimePoint::Now();
723 ASSERT_TRUE(!timestamps.empty());
724 ASSERT_TRUE(timestamps.size() % FrameTiming::kCount == 0);
725 std::vector<FrameTiming> timings(timestamps.size() / FrameTiming::kCount);
726
727 for (size_t i = 0; i * FrameTiming::kCount < timestamps.size(); i += 1) {
728 for (auto phase : FrameTiming::kPhases) {
729 timings[i].Set(
730 phase,
731 value: fml::TimePoint::FromEpochDelta(ticks: fml::TimeDelta::FromMicroseconds(
732 micros: timestamps[i * FrameTiming::kCount + phase])));
733 }
734 }
735 CheckFrameTimings(timings, start, finish);
736}
737
738TEST_F(ShellTest, FrameRasterizedCallbackIsCalled) {
739 auto settings = CreateSettingsForFixture();
740
741 FrameTiming timing;
742 fml::AutoResetWaitableEvent timingLatch;
743 settings.frame_rasterized_callback = [&timing,
744 &timingLatch](const FrameTiming& t) {
745 timing = t;
746 timingLatch.Signal();
747 };
748
749 std::unique_ptr<Shell> shell = CreateShell(settings);
750
751 // Wait to make |start| bigger than zero
752 using namespace std::chrono_literals;
753 std::this_thread::sleep_for(d: 1ms);
754
755 // We MUST put |start| after |CreateShell()| because the clock source will be
756 // reset through |TimePoint::SetClockSource()| in
757 // |DartVMInitializer::Initialize()| within |CreateShell()|.
758 fml::TimePoint start = fml::TimePoint::Now();
759
760 for (auto phase : FrameTiming::kPhases) {
761 timing.Set(phase, value: fml::TimePoint());
762 // Check that the time points are initially smaller than start, so
763 // CheckFrameTimings will fail if they're not properly set later.
764 ASSERT_TRUE(timing.Get(phase) < start);
765 }
766
767 // Create the surface needed by rasterizer
768 PlatformViewNotifyCreated(shell: shell.get());
769
770 auto configuration = RunConfiguration::InferFromSettings(settings);
771 configuration.SetEntrypoint("onBeginFrameMain");
772
773 int64_t frame_target_time;
774 auto nativeOnBeginFrame = [&frame_target_time](Dart_NativeArguments args) {
775 Dart_Handle exception = nullptr;
776 frame_target_time =
777 tonic::DartConverter<int64_t>::FromArguments(args, index: 0, exception);
778 };
779 AddNativeCallback(name: "NativeOnBeginFrame",
780 CREATE_NATIVE_ENTRY(nativeOnBeginFrame));
781
782 RunEngine(shell: shell.get(), configuration: std::move(configuration));
783 PumpOneFrame(shell: shell.get());
784
785 // Check that timing is properly set. This implies that
786 // settings.frame_rasterized_callback is called.
787 timingLatch.Wait();
788 fml::TimePoint finish = fml::TimePoint::Now();
789 std::vector<FrameTiming> timings = {timing};
790 CheckFrameTimings(timings, start, finish);
791
792 // Check that onBeginFrame, which is the frame_target_time, is after
793 // FrameTiming's build start
794 int64_t build_start =
795 timing.Get(phase: FrameTiming::kBuildStart).ToEpochDelta().ToMicroseconds();
796 ASSERT_GT(frame_target_time, build_start);
797 DestroyShell(shell: std::move(shell));
798}
799
800TEST_F(ShellTest, ExternalEmbedderNoThreadMerger) {
801 auto settings = CreateSettingsForFixture();
802 fml::AutoResetWaitableEvent end_frame_latch;
803 bool end_frame_called = false;
804 auto end_frame_callback =
805 [&](bool should_resubmit_frame,
806 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
807 ASSERT_TRUE(raster_thread_merger.get() == nullptr);
808 ASSERT_FALSE(should_resubmit_frame);
809 end_frame_called = true;
810 end_frame_latch.Signal();
811 };
812 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
813 args&: end_frame_callback, args: PostPrerollResult::kResubmitFrame, args: false);
814 auto shell = CreateShell(config: {
815 .settings = settings,
816 .platform_view_create_callback = ShellTestPlatformViewBuilder({
817 .shell_test_external_view_embedder = external_view_embedder,
818 }),
819 });
820
821 // Create the surface needed by rasterizer
822 PlatformViewNotifyCreated(shell: shell.get());
823
824 auto configuration = RunConfiguration::InferFromSettings(settings);
825 configuration.SetEntrypoint("emptyMain");
826
827 RunEngine(shell: shell.get(), configuration: std::move(configuration));
828
829 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
830 auto display_list_layer = std::make_shared<DisplayListLayer>(
831 args: SkPoint::Make(x: 10, y: 10), args: MakeSizedDisplayList(width: 80, height: 80), args: false, args: false);
832 root->Add(layer: display_list_layer);
833 };
834
835 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
836 end_frame_latch.Wait();
837 ASSERT_TRUE(end_frame_called);
838
839 DestroyShell(shell: std::move(shell));
840}
841
842TEST_F(ShellTest, PushBackdropFilterToVisitedPlatformViews) {
843#if defined(OS_FUCHSIA)
844 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
845 "https://github.com/flutter/flutter/issues/59816 ";
846#endif
847
848 auto settings = CreateSettingsForFixture();
849
850 std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
851
852 fml::AutoResetWaitableEvent end_frame_latch;
853 bool end_frame_called = false;
854 std::vector<int64_t> visited_platform_views;
855 MutatorsStack stack_50;
856 MutatorsStack stack_75;
857 auto end_frame_callback =
858 [&](bool should_resubmit_frame,
859 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
860 if (end_frame_called) {
861 return;
862 }
863 ASSERT_TRUE(raster_thread_merger.get() == nullptr);
864 ASSERT_FALSE(should_resubmit_frame);
865 end_frame_called = true;
866 visited_platform_views =
867 external_view_embedder->GetVisitedPlatformViews();
868 stack_50 = external_view_embedder->GetStack(50);
869 stack_75 = external_view_embedder->GetStack(75);
870 end_frame_latch.Signal();
871 };
872
873 external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
874 args&: end_frame_callback, args: PostPrerollResult::kResubmitFrame, args: false);
875 auto shell = CreateShell(config: {
876 .settings = settings,
877 .platform_view_create_callback = ShellTestPlatformViewBuilder({
878 .shell_test_external_view_embedder = external_view_embedder,
879 }),
880 });
881
882 // Create the surface needed by rasterizer
883 PlatformViewNotifyCreated(shell: shell.get());
884
885 auto configuration = RunConfiguration::InferFromSettings(settings);
886 configuration.SetEntrypoint("emptyMain");
887
888 RunEngine(shell: shell.get(), configuration: std::move(configuration));
889
890 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
891 auto platform_view_layer = std::make_shared<PlatformViewLayer>(
892 args: SkPoint::Make(x: 10, y: 10), args: SkSize::Make(w: 10, h: 10), args: 50);
893 root->Add(layer: platform_view_layer);
894 auto transform_layer =
895 std::make_shared<TransformLayer>(args: SkMatrix::Translate(dx: 1, dy: 1));
896 root->Add(layer: transform_layer);
897 auto clip_rect_layer = std::make_shared<ClipRectLayer>(
898 args: SkRect::MakeLTRB(l: 0, t: 0, r: 30, b: 30), args: Clip::hardEdge);
899 transform_layer->Add(layer: clip_rect_layer);
900 auto filter = std::make_shared<DlBlurImageFilter>(args: 5, args: 5, args: DlTileMode::kClamp);
901 auto backdrop_filter_layer =
902 std::make_shared<BackdropFilterLayer>(args&: filter, args: DlBlendMode::kSrcOver);
903 clip_rect_layer->Add(layer: backdrop_filter_layer);
904 auto platform_view_layer2 = std::make_shared<PlatformViewLayer>(
905 args: SkPoint::Make(x: 10, y: 10), args: SkSize::Make(w: 10, h: 10), args: 75);
906 backdrop_filter_layer->Add(layer: platform_view_layer2);
907 };
908
909 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
910 end_frame_latch.Wait();
911 ASSERT_EQ(visited_platform_views, (std::vector<int64_t>{50, 75}));
912 ASSERT_TRUE(stack_75.is_empty());
913 ASSERT_FALSE(stack_50.is_empty());
914
915 auto filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp);
916 auto mutator = *stack_50.Begin();
917 ASSERT_EQ(mutator->GetType(), MutatorType::kBackdropFilter);
918 ASSERT_EQ(mutator->GetFilterMutation().GetFilter(), filter);
919 // Make sure the filterRect is in global coordinates (contains the (1,1)
920 // translation).
921 ASSERT_EQ(mutator->GetFilterMutation().GetFilterRect(),
922 SkRect::MakeLTRB(1, 1, 31, 31));
923
924 DestroyShell(shell: std::move(shell));
925}
926
927// TODO(https://github.com/flutter/flutter/issues/59816): Enable on fuchsia.
928TEST_F(ShellTest,
929 ExternalEmbedderEndFrameIsCalledWhenPostPrerollResultIsResubmit) {
930#if defined(OS_FUCHSIA)
931 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
932 "https://github.com/flutter/flutter/issues/59816 ";
933#endif
934
935 auto settings = CreateSettingsForFixture();
936 fml::AutoResetWaitableEvent end_frame_latch;
937 bool end_frame_called = false;
938 auto end_frame_callback =
939 [&](bool should_resubmit_frame,
940 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
941 ASSERT_TRUE(raster_thread_merger.get() != nullptr);
942 ASSERT_TRUE(should_resubmit_frame);
943 end_frame_called = true;
944 end_frame_latch.Signal();
945 };
946 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
947 args&: end_frame_callback, args: PostPrerollResult::kResubmitFrame, args: true);
948 auto shell = CreateShell(config: {
949 .settings = settings,
950 .platform_view_create_callback = ShellTestPlatformViewBuilder({
951 .shell_test_external_view_embedder = external_view_embedder,
952 }),
953 });
954
955 // Create the surface needed by rasterizer
956 PlatformViewNotifyCreated(shell: shell.get());
957
958 auto configuration = RunConfiguration::InferFromSettings(settings);
959 configuration.SetEntrypoint("emptyMain");
960
961 RunEngine(shell: shell.get(), configuration: std::move(configuration));
962
963 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
964 auto display_list_layer = std::make_shared<DisplayListLayer>(
965 args: SkPoint::Make(x: 10, y: 10), args: MakeSizedDisplayList(width: 80, height: 80), args: false, args: false);
966 root->Add(layer: display_list_layer);
967 };
968
969 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
970 end_frame_latch.Wait();
971
972 ASSERT_TRUE(end_frame_called);
973
974 DestroyShell(shell: std::move(shell));
975}
976
977TEST_F(ShellTest, OnPlatformViewDestroyDisablesThreadMerger) {
978#if defined(OS_FUCHSIA)
979 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
980 "https://github.com/flutter/flutter/issues/59816 ";
981#endif
982
983 auto settings = CreateSettingsForFixture();
984 fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger;
985 auto end_frame_callback =
986 [&](bool should_resubmit_frame,
987 fml::RefPtr<fml::RasterThreadMerger> thread_merger) {
988 raster_thread_merger = std::move(thread_merger);
989 };
990 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
991 args&: end_frame_callback, args: PostPrerollResult::kSuccess, args: true);
992
993 auto shell = CreateShell(config: {
994 .settings = settings,
995 .platform_view_create_callback = ShellTestPlatformViewBuilder({
996 .shell_test_external_view_embedder = external_view_embedder,
997 }),
998 });
999
1000 // Create the surface needed by rasterizer
1001 PlatformViewNotifyCreated(shell: shell.get());
1002
1003 auto configuration = RunConfiguration::InferFromSettings(settings);
1004 configuration.SetEntrypoint("emptyMain");
1005
1006 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1007
1008 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1009 auto display_list_layer = std::make_shared<DisplayListLayer>(
1010 args: SkPoint::Make(x: 10, y: 10), args: MakeSizedDisplayList(width: 80, height: 80), args: false, args: false);
1011 root->Add(layer: display_list_layer);
1012 };
1013
1014 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
1015
1016 auto result = shell->WaitForFirstFrame(timeout: fml::TimeDelta::Max());
1017 ASSERT_TRUE(result.ok()) << "Result: " << static_cast<int>(result.code())
1018 << ": " << result.message();
1019
1020 ASSERT_TRUE(raster_thread_merger->IsEnabled());
1021
1022 ValidateDestroyPlatformView(shell: shell.get());
1023 ASSERT_TRUE(raster_thread_merger->IsEnabled());
1024
1025 // Validate the platform view can be recreated and destroyed again
1026 ValidateShell(shell: shell.get());
1027 ASSERT_TRUE(raster_thread_merger->IsEnabled());
1028 DestroyShell(shell: std::move(shell));
1029}
1030
1031TEST_F(ShellTest, OnPlatformViewDestroyAfterMergingThreads) {
1032#if defined(OS_FUCHSIA)
1033 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1034 "https://github.com/flutter/flutter/issues/59816 ";
1035#endif
1036
1037 const int ThreadMergingLease = 10;
1038 auto settings = CreateSettingsForFixture();
1039 fml::AutoResetWaitableEvent end_frame_latch;
1040 std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
1041
1042 auto end_frame_callback =
1043 [&](bool should_resubmit_frame,
1044 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1045 if (should_resubmit_frame && !raster_thread_merger->IsMerged()) {
1046 raster_thread_merger->MergeWithLease(lease_term: ThreadMergingLease);
1047
1048 ASSERT_TRUE(raster_thread_merger->IsMerged());
1049 external_view_embedder->UpdatePostPrerollResult(
1050 post_preroll_result: PostPrerollResult::kSuccess);
1051 }
1052 end_frame_latch.Signal();
1053 };
1054 external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1055 args&: end_frame_callback, args: PostPrerollResult::kSuccess, args: true);
1056 // Set resubmit once to trigger thread merging.
1057 external_view_embedder->UpdatePostPrerollResult(
1058 post_preroll_result: PostPrerollResult::kResubmitFrame);
1059 auto shell = CreateShell(config: {
1060 .settings = settings,
1061 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1062 .shell_test_external_view_embedder = external_view_embedder,
1063 }),
1064 });
1065
1066 // Create the surface needed by rasterizer
1067 PlatformViewNotifyCreated(shell: shell.get());
1068
1069 auto configuration = RunConfiguration::InferFromSettings(settings);
1070 configuration.SetEntrypoint("emptyMain");
1071
1072 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1073
1074 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1075 auto display_list_layer = std::make_shared<DisplayListLayer>(
1076 args: SkPoint::Make(x: 10, y: 10), args: MakeSizedDisplayList(width: 80, height: 80), args: false, args: false);
1077 root->Add(layer: display_list_layer);
1078 };
1079
1080 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
1081 // Pump one frame to trigger thread merging.
1082 end_frame_latch.Wait();
1083 // Pump another frame to ensure threads are merged and a regular layer tree is
1084 // submitted.
1085 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
1086 // Threads are merged here. PlatformViewNotifyDestroy should be executed
1087 // successfully.
1088 ASSERT_TRUE(fml::TaskRunnerChecker::RunsOnTheSameThread(
1089 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1090 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1091 ValidateDestroyPlatformView(shell: shell.get());
1092
1093 // Ensure threads are unmerged after platform view destroy
1094 ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
1095 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1096 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1097
1098 // Validate the platform view can be recreated and destroyed again
1099 ValidateShell(shell: shell.get());
1100
1101 DestroyShell(shell: std::move(shell));
1102}
1103
1104TEST_F(ShellTest, OnPlatformViewDestroyWhenThreadsAreMerging) {
1105#if defined(OS_FUCHSIA)
1106 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1107 "https://github.com/flutter/flutter/issues/59816 ";
1108#endif
1109
1110 const int kThreadMergingLease = 10;
1111 auto settings = CreateSettingsForFixture();
1112 fml::AutoResetWaitableEvent end_frame_latch;
1113 auto end_frame_callback =
1114 [&](bool should_resubmit_frame,
1115 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1116 if (should_resubmit_frame && !raster_thread_merger->IsMerged()) {
1117 raster_thread_merger->MergeWithLease(lease_term: kThreadMergingLease);
1118 }
1119 end_frame_latch.Signal();
1120 };
1121 // Start with a regular layer tree with `PostPrerollResult::kSuccess` so we
1122 // can later check if the rasterizer is tore down using
1123 // |ValidateDestroyPlatformView|
1124 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1125 args&: end_frame_callback, args: PostPrerollResult::kSuccess, args: true);
1126
1127 auto shell = CreateShell(config: {
1128 .settings = settings,
1129 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1130 .shell_test_external_view_embedder = external_view_embedder,
1131 }),
1132 });
1133
1134 // Create the surface needed by rasterizer
1135 PlatformViewNotifyCreated(shell: shell.get());
1136
1137 auto configuration = RunConfiguration::InferFromSettings(settings);
1138 configuration.SetEntrypoint("emptyMain");
1139
1140 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1141
1142 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1143 auto display_list_layer = std::make_shared<DisplayListLayer>(
1144 args: SkPoint::Make(x: 10, y: 10), args: MakeSizedDisplayList(width: 80, height: 80), args: false, args: false);
1145 root->Add(layer: display_list_layer);
1146 };
1147
1148 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
1149 // Pump one frame and threads aren't merged
1150 end_frame_latch.Wait();
1151 ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
1152 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1153 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1154
1155 // Pump a frame with `PostPrerollResult::kResubmitFrame` to start merging
1156 // threads
1157 external_view_embedder->UpdatePostPrerollResult(
1158 post_preroll_result: PostPrerollResult::kResubmitFrame);
1159 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
1160
1161 // Now destroy the platform view immediately.
1162 // Two things can happen here:
1163 // 1. Threads haven't merged. 2. Threads has already merged.
1164 // |Shell:OnPlatformViewDestroy| should be able to handle both cases.
1165 ValidateDestroyPlatformView(shell: shell.get());
1166
1167 // Ensure threads are unmerged after platform view destroy
1168 ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
1169 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1170 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1171
1172 // Validate the platform view can be recreated and destroyed again
1173 ValidateShell(shell: shell.get());
1174
1175 DestroyShell(shell: std::move(shell));
1176}
1177
1178TEST_F(ShellTest,
1179 OnPlatformViewDestroyWithThreadMergerWhileThreadsAreUnmerged) {
1180#if defined(OS_FUCHSIA)
1181 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1182 "https://github.com/flutter/flutter/issues/59816 ";
1183#endif
1184
1185 auto settings = CreateSettingsForFixture();
1186 fml::AutoResetWaitableEvent end_frame_latch;
1187 auto end_frame_callback =
1188 [&](bool should_resubmit_frame,
1189 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1190 end_frame_latch.Signal();
1191 };
1192 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1193 args&: end_frame_callback, args: PostPrerollResult::kSuccess, args: true);
1194 auto shell = CreateShell(config: {
1195 .settings = settings,
1196 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1197 .shell_test_external_view_embedder = external_view_embedder,
1198 }),
1199 });
1200
1201 // Create the surface needed by rasterizer
1202 PlatformViewNotifyCreated(shell: shell.get());
1203
1204 auto configuration = RunConfiguration::InferFromSettings(settings);
1205 configuration.SetEntrypoint("emptyMain");
1206
1207 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1208
1209 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1210 auto display_list_layer = std::make_shared<DisplayListLayer>(
1211 args: SkPoint::Make(x: 10, y: 10), args: MakeSizedDisplayList(width: 80, height: 80), args: false, args: false);
1212 root->Add(layer: display_list_layer);
1213 };
1214 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
1215 end_frame_latch.Wait();
1216
1217 // Threads should not be merged.
1218 ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
1219 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1220 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1221 ValidateDestroyPlatformView(shell: shell.get());
1222
1223 // Ensure threads are unmerged after platform view destroy
1224 ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
1225 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1226 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1227
1228 // Validate the platform view can be recreated and destroyed again
1229 ValidateShell(shell: shell.get());
1230
1231 DestroyShell(shell: std::move(shell));
1232}
1233
1234TEST_F(ShellTest, OnPlatformViewDestroyWithoutRasterThreadMerger) {
1235 auto settings = CreateSettingsForFixture();
1236
1237 auto shell = CreateShell(settings);
1238
1239 // Create the surface needed by rasterizer
1240 PlatformViewNotifyCreated(shell: shell.get());
1241
1242 auto configuration = RunConfiguration::InferFromSettings(settings);
1243 configuration.SetEntrypoint("emptyMain");
1244
1245 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1246
1247 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1248 auto display_list_layer = std::make_shared<DisplayListLayer>(
1249 args: SkPoint::Make(x: 10, y: 10), args: MakeSizedDisplayList(width: 80, height: 80), args: false, args: false);
1250 root->Add(layer: display_list_layer);
1251 };
1252 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
1253
1254 // Threads should not be merged.
1255 ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
1256 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1257 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1258 ValidateDestroyPlatformView(shell: shell.get());
1259
1260 // Ensure threads are unmerged after platform view destroy
1261 ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
1262 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1263 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1264
1265 // Validate the platform view can be recreated and destroyed again
1266 ValidateShell(shell: shell.get());
1267
1268 DestroyShell(shell: std::move(shell));
1269}
1270
1271// TODO(https://github.com/flutter/flutter/issues/59816): Enable on fuchsia.
1272TEST_F(ShellTest, OnPlatformViewDestroyWithStaticThreadMerging) {
1273#if defined(OS_FUCHSIA)
1274 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1275 "https://github.com/flutter/flutter/issues/59816 ";
1276#endif
1277
1278 auto settings = CreateSettingsForFixture();
1279 fml::AutoResetWaitableEvent end_frame_latch;
1280 auto end_frame_callback =
1281 [&](bool should_resubmit_frame,
1282 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1283 end_frame_latch.Signal();
1284 };
1285 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1286 args&: end_frame_callback, args: PostPrerollResult::kSuccess, args: true);
1287 ThreadHost thread_host(
1288 "io.flutter.test." + GetCurrentTestName() + ".",
1289 ThreadHost::Type::Platform | ThreadHost::Type::IO | ThreadHost::Type::UI);
1290 TaskRunners task_runners(
1291 "test",
1292 thread_host.platform_thread->GetTaskRunner(), // platform
1293 thread_host.platform_thread->GetTaskRunner(), // raster
1294 thread_host.ui_thread->GetTaskRunner(), // ui
1295 thread_host.io_thread->GetTaskRunner() // io
1296 );
1297 auto shell = CreateShell(config: {
1298 .settings = settings,
1299 .task_runners = task_runners,
1300 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1301 .shell_test_external_view_embedder = external_view_embedder,
1302 }),
1303 });
1304
1305 // Create the surface needed by rasterizer
1306 PlatformViewNotifyCreated(shell: shell.get());
1307
1308 auto configuration = RunConfiguration::InferFromSettings(settings);
1309 configuration.SetEntrypoint("emptyMain");
1310
1311 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1312
1313 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1314 auto display_list_layer = std::make_shared<DisplayListLayer>(
1315 args: SkPoint::Make(x: 10, y: 10), args: MakeSizedDisplayList(width: 80, height: 80), args: false, args: false);
1316 root->Add(layer: display_list_layer);
1317 };
1318 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
1319 end_frame_latch.Wait();
1320
1321 ValidateDestroyPlatformView(shell: shell.get());
1322
1323 // Validate the platform view can be recreated and destroyed again
1324 ValidateShell(shell: shell.get());
1325
1326 DestroyShell(shell: std::move(shell), task_runners);
1327}
1328
1329TEST_F(ShellTest, GetUsedThisFrameShouldBeSetBeforeEndFrame) {
1330 auto settings = CreateSettingsForFixture();
1331 fml::AutoResetWaitableEvent end_frame_latch;
1332 std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
1333 bool used_this_frame = true;
1334 auto end_frame_callback =
1335 [&](bool should_resubmit_frame,
1336 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1337 // We expect `used_this_frame` to be false.
1338 used_this_frame = external_view_embedder->GetUsedThisFrame();
1339 end_frame_latch.Signal();
1340 };
1341 external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1342 args&: end_frame_callback, args: PostPrerollResult::kSuccess, args: true);
1343 auto shell = CreateShell(config: {
1344 .settings = settings,
1345 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1346 .shell_test_external_view_embedder = external_view_embedder,
1347 }),
1348 });
1349
1350 // Create the surface needed by rasterizer
1351 PlatformViewNotifyCreated(shell: shell.get());
1352
1353 auto configuration = RunConfiguration::InferFromSettings(settings);
1354 configuration.SetEntrypoint("emptyMain");
1355
1356 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1357
1358 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1359 auto display_list_layer = std::make_shared<DisplayListLayer>(
1360 args: SkPoint::Make(x: 10, y: 10), args: MakeSizedDisplayList(width: 80, height: 80), args: false, args: false);
1361 root->Add(layer: display_list_layer);
1362 };
1363 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
1364 end_frame_latch.Wait();
1365 ASSERT_FALSE(used_this_frame);
1366
1367 // Validate the platform view can be recreated and destroyed again
1368 ValidateShell(shell: shell.get());
1369
1370 DestroyShell(shell: std::move(shell));
1371}
1372
1373// TODO(https://github.com/flutter/flutter/issues/66056): Deflake on all other
1374// platforms
1375TEST_F(ShellTest, DISABLED_SkipAndSubmitFrame) {
1376#if defined(OS_FUCHSIA)
1377 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1378 "https://github.com/flutter/flutter/issues/59816 ";
1379#endif
1380
1381 auto settings = CreateSettingsForFixture();
1382 fml::AutoResetWaitableEvent end_frame_latch;
1383 std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
1384
1385 auto end_frame_callback =
1386 [&](bool should_resubmit_frame,
1387 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1388 if (should_resubmit_frame && !raster_thread_merger->IsMerged()) {
1389 raster_thread_merger->MergeWithLease(lease_term: 10);
1390 external_view_embedder->UpdatePostPrerollResult(
1391 post_preroll_result: PostPrerollResult::kSuccess);
1392 }
1393 end_frame_latch.Signal();
1394 };
1395 external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1396 args&: end_frame_callback, args: PostPrerollResult::kSkipAndRetryFrame, args: true);
1397
1398 auto shell = CreateShell(config: {
1399 .settings = settings,
1400 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1401 .shell_test_external_view_embedder = external_view_embedder,
1402 }),
1403 });
1404
1405 PlatformViewNotifyCreated(shell: shell.get());
1406
1407 auto configuration = RunConfiguration::InferFromSettings(settings);
1408 configuration.SetEntrypoint("emptyMain");
1409 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1410
1411 ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
1412
1413 PumpOneFrame(shell: shell.get());
1414
1415 // `EndFrame` changed the post preroll result to `kSuccess`.
1416 end_frame_latch.Wait();
1417
1418 // Let the resubmitted frame to run and `GetSubmittedFrameCount` should be
1419 // called.
1420 end_frame_latch.Wait();
1421 // 2 frames are submitted because `kSkipAndRetryFrame`, but only the 2nd frame
1422 // should be submitted with `external_view_embedder`, hence the below check.
1423 ASSERT_EQ(1, external_view_embedder->GetSubmittedFrameCount());
1424
1425 PlatformViewNotifyDestroyed(shell: shell.get());
1426 DestroyShell(shell: std::move(shell));
1427}
1428
1429TEST(SettingsTest, FrameTimingSetsAndGetsProperly) {
1430 // Ensure that all phases are in kPhases.
1431 ASSERT_EQ(sizeof(FrameTiming::kPhases),
1432 FrameTiming::kCount * sizeof(FrameTiming::Phase));
1433
1434 int lastPhaseIndex = -1;
1435 FrameTiming timing;
1436 for (auto phase : FrameTiming::kPhases) {
1437 ASSERT_TRUE(phase > lastPhaseIndex); // Ensure that kPhases are in order.
1438 lastPhaseIndex = phase;
1439 auto fake_time =
1440 fml::TimePoint::FromEpochDelta(ticks: fml::TimeDelta::FromMicroseconds(micros: phase));
1441 timing.Set(phase, value: fake_time);
1442 ASSERT_TRUE(timing.Get(phase) == fake_time);
1443 }
1444}
1445
1446TEST_F(ShellTest, ReportTimingsIsCalledImmediatelyAfterTheFirstFrame) {
1447 auto settings = CreateSettingsForFixture();
1448 std::unique_ptr<Shell> shell = CreateShell(settings);
1449
1450 // Create the surface needed by rasterizer
1451 PlatformViewNotifyCreated(shell: shell.get());
1452
1453 auto configuration = RunConfiguration::InferFromSettings(settings);
1454 ASSERT_TRUE(configuration.IsValid());
1455 configuration.SetEntrypoint("reportTimingsMain");
1456 fml::AutoResetWaitableEvent reportLatch;
1457 std::vector<int64_t> timestamps;
1458 auto nativeTimingCallback = [&reportLatch,
1459 &timestamps](Dart_NativeArguments args) {
1460 Dart_Handle exception = nullptr;
1461 ASSERT_EQ(timestamps.size(), 0ul);
1462 timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
1463 args, index: 0, exception);
1464 reportLatch.Signal();
1465 };
1466 AddNativeCallback(name: "NativeReportTimingsCallback",
1467 CREATE_NATIVE_ENTRY(nativeTimingCallback));
1468 ASSERT_TRUE(configuration.IsValid());
1469 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1470
1471 for (int i = 0; i < 10; i += 1) {
1472 PumpOneFrame(shell: shell.get());
1473 }
1474
1475 reportLatch.Wait();
1476 DestroyShell(shell: std::move(shell));
1477
1478 // Check for the immediate callback of the first frame that doesn't wait for
1479 // the other 9 frames to be rasterized.
1480 ASSERT_EQ(timestamps.size(), FrameTiming::kCount);
1481}
1482
1483TEST_F(ShellTest, WaitForFirstFrame) {
1484 auto settings = CreateSettingsForFixture();
1485 std::unique_ptr<Shell> shell = CreateShell(settings);
1486
1487 // Create the surface needed by rasterizer
1488 PlatformViewNotifyCreated(shell: shell.get());
1489
1490 auto configuration = RunConfiguration::InferFromSettings(settings);
1491 configuration.SetEntrypoint("emptyMain");
1492
1493 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1494 PumpOneFrame(shell: shell.get());
1495 fml::Status result = shell->WaitForFirstFrame(timeout: fml::TimeDelta::Max());
1496 ASSERT_TRUE(result.ok());
1497
1498 DestroyShell(shell: std::move(shell));
1499}
1500
1501TEST_F(ShellTest, WaitForFirstFrameZeroSizeFrame) {
1502 auto settings = CreateSettingsForFixture();
1503 std::unique_ptr<Shell> shell = CreateShell(settings);
1504
1505 // Create the surface needed by rasterizer
1506 PlatformViewNotifyCreated(shell: shell.get());
1507
1508 auto configuration = RunConfiguration::InferFromSettings(settings);
1509 configuration.SetEntrypoint("emptyMain");
1510
1511 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1512 PumpOneFrame(shell: shell.get(), viewport_metrics: {1.0, 0.0, 0.0, 22, 0});
1513 fml::Status result = shell->WaitForFirstFrame(timeout: fml::TimeDelta::Zero());
1514 ASSERT_FALSE(result.ok());
1515 ASSERT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded);
1516
1517 DestroyShell(shell: std::move(shell));
1518}
1519
1520TEST_F(ShellTest, WaitForFirstFrameTimeout) {
1521 auto settings = CreateSettingsForFixture();
1522 std::unique_ptr<Shell> shell = CreateShell(settings);
1523
1524 // Create the surface needed by rasterizer
1525 PlatformViewNotifyCreated(shell: shell.get());
1526
1527 auto configuration = RunConfiguration::InferFromSettings(settings);
1528 configuration.SetEntrypoint("emptyMain");
1529
1530 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1531 fml::Status result = shell->WaitForFirstFrame(timeout: fml::TimeDelta::Zero());
1532 ASSERT_FALSE(result.ok());
1533 ASSERT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded);
1534
1535 DestroyShell(shell: std::move(shell));
1536}
1537
1538TEST_F(ShellTest, WaitForFirstFrameMultiple) {
1539 auto settings = CreateSettingsForFixture();
1540 std::unique_ptr<Shell> shell = CreateShell(settings);
1541
1542 // Create the surface needed by rasterizer
1543 PlatformViewNotifyCreated(shell: shell.get());
1544
1545 auto configuration = RunConfiguration::InferFromSettings(settings);
1546 configuration.SetEntrypoint("emptyMain");
1547
1548 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1549 PumpOneFrame(shell: shell.get());
1550 fml::Status result = shell->WaitForFirstFrame(timeout: fml::TimeDelta::Max());
1551 ASSERT_TRUE(result.ok());
1552 for (int i = 0; i < 100; ++i) {
1553 result = shell->WaitForFirstFrame(timeout: fml::TimeDelta::Zero());
1554 ASSERT_TRUE(result.ok());
1555 }
1556
1557 DestroyShell(shell: std::move(shell));
1558}
1559
1560/// Makes sure that WaitForFirstFrame works if we rendered a frame with the
1561/// single-thread setup.
1562TEST_F(ShellTest, WaitForFirstFrameInlined) {
1563 Settings settings = CreateSettingsForFixture();
1564 auto task_runner = CreateNewThread();
1565 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
1566 task_runner);
1567 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
1568
1569 // Create the surface needed by rasterizer
1570 PlatformViewNotifyCreated(shell: shell.get());
1571
1572 auto configuration = RunConfiguration::InferFromSettings(settings);
1573 configuration.SetEntrypoint("emptyMain");
1574
1575 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1576 PumpOneFrame(shell: shell.get());
1577 fml::AutoResetWaitableEvent event;
1578 task_runner->PostTask(task: [&shell, &event] {
1579 fml::Status result = shell->WaitForFirstFrame(timeout: fml::TimeDelta::Max());
1580 ASSERT_FALSE(result.ok());
1581 ASSERT_EQ(result.code(), fml::StatusCode::kFailedPrecondition);
1582 event.Signal();
1583 });
1584 ASSERT_FALSE(event.WaitWithTimeout(fml::TimeDelta::Max()));
1585
1586 DestroyShell(shell: std::move(shell), task_runners);
1587}
1588
1589static size_t GetRasterizerResourceCacheBytesSync(const Shell& shell) {
1590 size_t bytes = 0;
1591 fml::AutoResetWaitableEvent latch;
1592 fml::TaskRunner::RunNowOrPostTask(
1593 runner: shell.GetTaskRunners().GetRasterTaskRunner(), task: [&]() {
1594 if (auto rasterizer = shell.GetRasterizer()) {
1595 bytes = rasterizer->GetResourceCacheMaxBytes().value_or(v: 0U);
1596 }
1597 latch.Signal();
1598 });
1599 latch.Wait();
1600 return bytes;
1601}
1602
1603TEST_F(ShellTest, MultipleFluttersSetResourceCacheBytes) {
1604 TaskRunners task_runners = GetTaskRunnersForFixture();
1605 auto settings = CreateSettingsForFixture();
1606 settings.resource_cache_max_bytes_threshold = 4000000U;
1607 GrMockOptions main_context_options;
1608 sk_sp<GrDirectContext> main_context =
1609 GrDirectContext::MakeMock(&main_context_options);
1610 Shell::CreateCallback<PlatformView> platform_view_create_callback =
1611 [task_runners, main_context](flutter::Shell& shell) {
1612 auto result = std::make_unique<TestPlatformView>(args&: shell, args: task_runners);
1613 ON_CALL(*result, CreateRenderingSurface())
1614 .WillByDefault(action: ::testing::Invoke(function_impl: [main_context] {
1615 auto surface = std::make_unique<MockSurface>();
1616 ON_CALL(*surface, GetContext())
1617 .WillByDefault(action: Return(value: main_context.get()));
1618 ON_CALL(*surface, IsValid()).WillByDefault(action: Return(value: true));
1619 ON_CALL(*surface, MakeRenderContextCurrent())
1620 .WillByDefault(action: ::testing::Invoke(function_impl: [] {
1621 return std::make_unique<GLContextDefaultResult>(args: true);
1622 }));
1623 return surface;
1624 }));
1625 return result;
1626 };
1627
1628 auto shell = CreateShell(config: {
1629 .settings = settings,
1630 .task_runners = task_runners,
1631 .platform_view_create_callback = platform_view_create_callback,
1632 });
1633
1634 // Create the surface needed by rasterizer
1635 PlatformViewNotifyCreated(shell: shell.get());
1636
1637 auto configuration = RunConfiguration::InferFromSettings(settings);
1638 configuration.SetEntrypoint("emptyMain");
1639
1640 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1641 PostSync(task_runner: shell->GetTaskRunners().GetPlatformTaskRunner(), task: [&shell]() {
1642 shell->GetPlatformView()->SetViewportMetrics(view_id: kImplicitViewId,
1643 metrics: {1.0, 100, 100, 22, 0});
1644 });
1645
1646 // first cache bytes
1647 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1648 static_cast<size_t>(480000U));
1649
1650 auto shell_spawn_callback = [&]() {
1651 std::unique_ptr<Shell> spawn;
1652 PostSync(
1653 task_runner: shell->GetTaskRunners().GetPlatformTaskRunner(),
1654 task: [this, &spawn, &spawner = shell, platform_view_create_callback]() {
1655 auto configuration =
1656 RunConfiguration::InferFromSettings(settings: CreateSettingsForFixture());
1657 configuration.SetEntrypoint("emptyMain");
1658 spawn = spawner->Spawn(
1659 run_configuration: std::move(configuration), initial_route: "", on_create_platform_view: platform_view_create_callback,
1660 on_create_rasterizer: [](Shell& shell) { return std::make_unique<Rasterizer>(args&: shell); });
1661 ASSERT_NE(nullptr, spawn.get());
1662 ASSERT_TRUE(ValidateShell(spawn.get()));
1663 });
1664 return spawn;
1665 };
1666
1667 std::unique_ptr<Shell> second_shell = shell_spawn_callback();
1668 PlatformViewNotifyCreated(shell: second_shell.get());
1669 PostSync(task_runner: second_shell->GetTaskRunners().GetPlatformTaskRunner(),
1670 task: [&second_shell]() {
1671 second_shell->GetPlatformView()->SetViewportMetrics(
1672 view_id: kImplicitViewId, metrics: {1.0, 100, 100, 22, 0});
1673 });
1674 // first cache bytes + second cache bytes
1675 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1676 static_cast<size_t>(960000U));
1677
1678 PostSync(task_runner: second_shell->GetTaskRunners().GetPlatformTaskRunner(),
1679 task: [&second_shell]() {
1680 second_shell->GetPlatformView()->SetViewportMetrics(
1681 view_id: kImplicitViewId, metrics: {1.0, 100, 300, 22, 0});
1682 });
1683 // first cache bytes + second cache bytes
1684 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1685 static_cast<size_t>(1920000U));
1686
1687 std::unique_ptr<Shell> third_shell = shell_spawn_callback();
1688 PlatformViewNotifyCreated(shell: third_shell.get());
1689 PostSync(task_runner: third_shell->GetTaskRunners().GetPlatformTaskRunner(),
1690 task: [&third_shell]() {
1691 third_shell->GetPlatformView()->SetViewportMetrics(
1692 view_id: kImplicitViewId, metrics: {1.0, 400, 100, 22, 0});
1693 });
1694 // first cache bytes + second cache bytes + third cache bytes
1695 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1696 static_cast<size_t>(3840000U));
1697
1698 PostSync(task_runner: third_shell->GetTaskRunners().GetPlatformTaskRunner(),
1699 task: [&third_shell]() {
1700 third_shell->GetPlatformView()->SetViewportMetrics(
1701 view_id: kImplicitViewId, metrics: {1.0, 800, 100, 22, 0});
1702 });
1703 // max bytes threshold
1704 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1705 static_cast<size_t>(4000000U));
1706 DestroyShell(shell: std::move(third_shell), task_runners);
1707 // max bytes threshold
1708 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1709 static_cast<size_t>(4000000U));
1710
1711 PostSync(task_runner: second_shell->GetTaskRunners().GetPlatformTaskRunner(),
1712 task: [&second_shell]() {
1713 second_shell->GetPlatformView()->SetViewportMetrics(
1714 view_id: kImplicitViewId, metrics: {1.0, 100, 100, 22, 0});
1715 });
1716 // first cache bytes + second cache bytes
1717 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1718 static_cast<size_t>(960000U));
1719
1720 DestroyShell(shell: std::move(second_shell), task_runners);
1721 DestroyShell(shell: std::move(shell), task_runners);
1722}
1723
1724TEST_F(ShellTest, SetResourceCacheSize) {
1725 Settings settings = CreateSettingsForFixture();
1726 auto task_runner = CreateNewThread();
1727 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
1728 task_runner);
1729 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
1730
1731 // Create the surface needed by rasterizer
1732 PlatformViewNotifyCreated(shell: shell.get());
1733
1734 auto configuration = RunConfiguration::InferFromSettings(settings);
1735 configuration.SetEntrypoint("emptyMain");
1736
1737 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1738 PumpOneFrame(shell: shell.get());
1739
1740 // The Vulkan and GL backends set different default values for the resource
1741 // cache size. The default backend (specified by the default param of
1742 // `CreateShell` in this test) will only resolve to Vulkan (in
1743 // `ShellTestPlatformView::Create`) if GL is disabled. This situation arises
1744 // when targeting the Fuchsia Emulator.
1745#if defined(SHELL_ENABLE_VULKAN) && !defined(SHELL_ENABLE_GL)
1746 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1747 vulkan::kGrCacheMaxByteSize);
1748#elif defined(SHELL_ENABLE_METAL)
1749 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1750 static_cast<size_t>(256 * (1 << 20)));
1751#else
1752 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1753 static_cast<size_t>(24 * (1 << 20)));
1754#endif
1755
1756 fml::TaskRunner::RunNowOrPostTask(
1757 runner: shell->GetTaskRunners().GetPlatformTaskRunner(), task: [&shell]() {
1758 shell->GetPlatformView()->SetViewportMetrics(view_id: kImplicitViewId,
1759 metrics: {1.0, 400, 200, 22, 0});
1760 });
1761 PumpOneFrame(shell: shell.get());
1762
1763 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 3840000U);
1764
1765 std::string request_json = R"json({
1766 "method": "Skia.setResourceCacheMaxBytes",
1767 "args": 10000
1768 })json";
1769 auto data =
1770 fml::MallocMapping::Copy(begin: request_json.c_str(), length: request_json.length());
1771 auto platform_message = std::make_unique<PlatformMessage>(
1772 args: "flutter/skia", args: std::move(data), args: nullptr);
1773 SendEnginePlatformMessage(shell: shell.get(), message: std::move(platform_message));
1774 PumpOneFrame(shell: shell.get());
1775 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U);
1776
1777 fml::TaskRunner::RunNowOrPostTask(
1778 runner: shell->GetTaskRunners().GetPlatformTaskRunner(), task: [&shell]() {
1779 shell->GetPlatformView()->SetViewportMetrics(view_id: kImplicitViewId,
1780 metrics: {1.0, 800, 400, 22, 0});
1781 });
1782 PumpOneFrame(shell: shell.get());
1783
1784 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U);
1785 DestroyShell(shell: std::move(shell), task_runners);
1786}
1787
1788TEST_F(ShellTest, SetResourceCacheSizeEarly) {
1789 Settings settings = CreateSettingsForFixture();
1790 auto task_runner = CreateNewThread();
1791 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
1792 task_runner);
1793 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
1794
1795 fml::TaskRunner::RunNowOrPostTask(
1796 runner: shell->GetTaskRunners().GetPlatformTaskRunner(), task: [&shell]() {
1797 shell->GetPlatformView()->SetViewportMetrics(view_id: kImplicitViewId,
1798 metrics: {1.0, 400, 200, 22, 0});
1799 });
1800 PumpOneFrame(shell: shell.get());
1801
1802 // Create the surface needed by rasterizer
1803 PlatformViewNotifyCreated(shell: shell.get());
1804
1805 auto configuration = RunConfiguration::InferFromSettings(settings);
1806 configuration.SetEntrypoint("emptyMain");
1807
1808 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1809 PumpOneFrame(shell: shell.get());
1810
1811 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1812 static_cast<size_t>(3840000U));
1813 DestroyShell(shell: std::move(shell), task_runners);
1814}
1815
1816TEST_F(ShellTest, SetResourceCacheSizeNotifiesDart) {
1817 Settings settings = CreateSettingsForFixture();
1818 auto task_runner = CreateNewThread();
1819 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
1820 task_runner);
1821 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
1822
1823 fml::TaskRunner::RunNowOrPostTask(
1824 runner: shell->GetTaskRunners().GetPlatformTaskRunner(), task: [&shell]() {
1825 shell->GetPlatformView()->SetViewportMetrics(view_id: kImplicitViewId,
1826 metrics: {1.0, 400, 200, 22, 0});
1827 });
1828 PumpOneFrame(shell: shell.get());
1829
1830 // Create the surface needed by rasterizer
1831 PlatformViewNotifyCreated(shell: shell.get());
1832
1833 auto configuration = RunConfiguration::InferFromSettings(settings);
1834 configuration.SetEntrypoint("testSkiaResourceCacheSendsResponse");
1835
1836 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1837 static_cast<size_t>(3840000U));
1838
1839 fml::AutoResetWaitableEvent latch;
1840 AddNativeCallback(name: "NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) {
1841 latch.Signal();
1842 }));
1843
1844 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1845 PumpOneFrame(shell: shell.get());
1846
1847 latch.Wait();
1848
1849 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1850 static_cast<size_t>(10000U));
1851 DestroyShell(shell: std::move(shell), task_runners);
1852}
1853
1854TEST_F(ShellTest, CanCreateImagefromDecompressedBytes) {
1855 Settings settings = CreateSettingsForFixture();
1856 auto task_runner = CreateNewThread();
1857
1858 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
1859 task_runner);
1860
1861 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
1862
1863 // Create the surface needed by rasterizer
1864 PlatformViewNotifyCreated(shell: shell.get());
1865
1866 auto configuration = RunConfiguration::InferFromSettings(settings);
1867 configuration.SetEntrypoint("canCreateImageFromDecompressedData");
1868
1869 fml::AutoResetWaitableEvent latch;
1870 AddNativeCallback(name: "NotifyWidthHeight",
1871 CREATE_NATIVE_ENTRY([&latch](auto args) {
1872 auto width = tonic::DartConverter<int>::FromDart(
1873 Dart_GetNativeArgument(args, 0));
1874 auto height = tonic::DartConverter<int>::FromDart(
1875 Dart_GetNativeArgument(args, 1));
1876 ASSERT_EQ(width, 10);
1877 ASSERT_EQ(height, 10);
1878 latch.Signal();
1879 }));
1880
1881 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1882
1883 latch.Wait();
1884 DestroyShell(shell: std::move(shell), task_runners);
1885}
1886
1887class MockTexture : public Texture {
1888 public:
1889 MockTexture(int64_t textureId,
1890 std::shared_ptr<fml::AutoResetWaitableEvent> latch)
1891 : Texture(textureId), latch_(std::move(latch)) {}
1892
1893 ~MockTexture() override = default;
1894
1895 // Called from raster thread.
1896 void Paint(PaintContext& context,
1897 const SkRect& bounds,
1898 bool freeze,
1899 const DlImageSampling) override {}
1900
1901 void OnGrContextCreated() override {}
1902
1903 void OnGrContextDestroyed() override {}
1904
1905 void MarkNewFrameAvailable() override {
1906 frames_available_++;
1907 latch_->Signal();
1908 }
1909
1910 void OnTextureUnregistered() override {
1911 unregistered_ = true;
1912 latch_->Signal();
1913 }
1914
1915 bool unregistered() { return unregistered_; }
1916 int frames_available() { return frames_available_; }
1917
1918 private:
1919 bool unregistered_ = false;
1920 int frames_available_ = 0;
1921 std::shared_ptr<fml::AutoResetWaitableEvent> latch_;
1922};
1923
1924TEST_F(ShellTest, TextureFrameMarkedAvailableAndUnregister) {
1925 Settings settings = CreateSettingsForFixture();
1926 auto configuration = RunConfiguration::InferFromSettings(settings);
1927 auto task_runner = CreateNewThread();
1928 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
1929 task_runner);
1930 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
1931
1932 ASSERT_TRUE(ValidateShell(shell.get()));
1933 PlatformViewNotifyCreated(shell: shell.get());
1934
1935 RunEngine(shell: shell.get(), configuration: std::move(configuration));
1936
1937 std::shared_ptr<fml::AutoResetWaitableEvent> latch =
1938 std::make_shared<fml::AutoResetWaitableEvent>();
1939
1940 std::shared_ptr<MockTexture> mockTexture =
1941 std::make_shared<MockTexture>(args: 0, args&: latch);
1942
1943 fml::TaskRunner::RunNowOrPostTask(
1944 runner: shell->GetTaskRunners().GetRasterTaskRunner(), task: [&]() {
1945 shell->GetPlatformView()->RegisterTexture(texture: mockTexture);
1946 shell->GetPlatformView()->MarkTextureFrameAvailable(texture_id: 0);
1947 });
1948 latch->Wait();
1949
1950 EXPECT_EQ(mockTexture->frames_available(), 1);
1951
1952 fml::TaskRunner::RunNowOrPostTask(
1953 runner: shell->GetTaskRunners().GetRasterTaskRunner(),
1954 task: [&]() { shell->GetPlatformView()->UnregisterTexture(texture_id: 0); });
1955 latch->Wait();
1956
1957 EXPECT_EQ(mockTexture->unregistered(), true);
1958 DestroyShell(shell: std::move(shell), task_runners);
1959}
1960
1961TEST_F(ShellTest, IsolateCanAccessPersistentIsolateData) {
1962 const std::string message = "dummy isolate launch data.";
1963
1964 Settings settings = CreateSettingsForFixture();
1965 settings.persistent_isolate_data =
1966 std::make_shared<fml::DataMapping>(args: message);
1967 TaskRunners task_runners("test", // label
1968 GetCurrentTaskRunner(), // platform
1969 CreateNewThread(), // raster
1970 CreateNewThread(), // ui
1971 CreateNewThread() // io
1972 );
1973
1974 fml::AutoResetWaitableEvent message_latch;
1975 AddNativeCallback(name: "NotifyMessage",
1976 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
1977 const auto message_from_dart =
1978 tonic::DartConverter<std::string>::FromDart(
1979 Dart_GetNativeArgument(args, 0));
1980 ASSERT_EQ(message, message_from_dart);
1981 message_latch.Signal();
1982 }));
1983
1984 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
1985
1986 ASSERT_TRUE(shell->IsSetup());
1987 auto configuration = RunConfiguration::InferFromSettings(settings);
1988 configuration.SetEntrypoint("canAccessIsolateLaunchData");
1989
1990 fml::AutoResetWaitableEvent event;
1991 shell->RunEngine(run_configuration: std::move(configuration), result_callback: [&](auto result) {
1992 ASSERT_EQ(result, Engine::RunStatus::Success);
1993 });
1994
1995 message_latch.Wait();
1996 DestroyShell(shell: std::move(shell), task_runners);
1997}
1998
1999TEST_F(ShellTest, CanScheduleFrameFromPlatform) {
2000 Settings settings = CreateSettingsForFixture();
2001 TaskRunners task_runners = GetTaskRunnersForFixture();
2002 fml::AutoResetWaitableEvent latch;
2003 AddNativeCallback(
2004 name: "NotifyNative",
2005 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }));
2006 fml::AutoResetWaitableEvent check_latch;
2007 AddNativeCallback(name: "NativeOnBeginFrame",
2008 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
2009 check_latch.Signal();
2010 }));
2011 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2012 ASSERT_TRUE(shell->IsSetup());
2013
2014 auto configuration = RunConfiguration::InferFromSettings(settings);
2015 configuration.SetEntrypoint("onBeginFrameWithNotifyNativeMain");
2016 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2017
2018 // Wait for the application to attach the listener.
2019 latch.Wait();
2020
2021 fml::TaskRunner::RunNowOrPostTask(
2022 runner: shell->GetTaskRunners().GetPlatformTaskRunner(),
2023 task: [&shell]() { shell->GetPlatformView()->ScheduleFrame(); });
2024 check_latch.Wait();
2025 DestroyShell(shell: std::move(shell), task_runners);
2026}
2027
2028TEST_F(ShellTest, SecondaryVsyncCallbackShouldBeCalledAfterVsyncCallback) {
2029 bool is_on_begin_frame_called = false;
2030 bool is_secondary_callback_called = false;
2031 Settings settings = CreateSettingsForFixture();
2032 TaskRunners task_runners = GetTaskRunnersForFixture();
2033 fml::AutoResetWaitableEvent latch;
2034 AddNativeCallback(
2035 name: "NotifyNative",
2036 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }));
2037 fml::CountDownLatch count_down_latch(2);
2038 AddNativeCallback(name: "NativeOnBeginFrame",
2039 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
2040 EXPECT_FALSE(is_on_begin_frame_called);
2041 EXPECT_FALSE(is_secondary_callback_called);
2042 is_on_begin_frame_called = true;
2043 count_down_latch.CountDown();
2044 }));
2045 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2046 ASSERT_TRUE(shell->IsSetup());
2047
2048 auto configuration = RunConfiguration::InferFromSettings(settings);
2049 configuration.SetEntrypoint("onBeginFrameWithNotifyNativeMain");
2050 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2051
2052 // Wait for the application to attach the listener.
2053 latch.Wait();
2054
2055 fml::TaskRunner::RunNowOrPostTask(
2056 runner: shell->GetTaskRunners().GetUITaskRunner(), task: [&]() {
2057 shell->GetEngine()->ScheduleSecondaryVsyncCallback(id: 0, callback: [&]() {
2058 EXPECT_TRUE(is_on_begin_frame_called);
2059 EXPECT_FALSE(is_secondary_callback_called);
2060 is_secondary_callback_called = true;
2061 count_down_latch.CountDown();
2062 });
2063 shell->GetEngine()->ScheduleFrame();
2064 });
2065 count_down_latch.Wait();
2066 EXPECT_TRUE(is_on_begin_frame_called);
2067 EXPECT_TRUE(is_secondary_callback_called);
2068 DestroyShell(shell: std::move(shell), task_runners);
2069}
2070
2071static void LogSkData(const sk_sp<SkData>& data, const char* title) {
2072 FML_LOG(ERROR) << "---------- " << title;
2073 std::ostringstream ostr;
2074 for (size_t i = 0; i < data->size();) {
2075 ostr << std::hex << std::setfill('0') << std::setw(2)
2076 << static_cast<int>(data->bytes()[i]) << " ";
2077 i++;
2078 if (i % 16 == 0 || i == data->size()) {
2079 FML_LOG(ERROR) << ostr.str();
2080 ostr.str(s: "");
2081 ostr.clear();
2082 }
2083 }
2084}
2085
2086TEST_F(ShellTest, Screenshot) {
2087 auto settings = CreateSettingsForFixture();
2088 fml::AutoResetWaitableEvent firstFrameLatch;
2089 settings.frame_rasterized_callback =
2090 [&firstFrameLatch](const FrameTiming& t) { firstFrameLatch.Signal(); };
2091
2092 std::unique_ptr<Shell> shell = CreateShell(settings);
2093
2094 // Create the surface needed by rasterizer
2095 PlatformViewNotifyCreated(shell: shell.get());
2096
2097 auto configuration = RunConfiguration::InferFromSettings(settings);
2098 configuration.SetEntrypoint("emptyMain");
2099
2100 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2101
2102 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
2103 auto display_list_layer = std::make_shared<DisplayListLayer>(
2104 args: SkPoint::Make(x: 10, y: 10), args: MakeSizedDisplayList(width: 80, height: 80), args: false, args: false);
2105 root->Add(layer: display_list_layer);
2106 };
2107
2108 PumpOneFrame(shell: shell.get(), width: 100, height: 100, builder);
2109 firstFrameLatch.Wait();
2110
2111 std::promise<Rasterizer::Screenshot> screenshot_promise;
2112 auto screenshot_future = screenshot_promise.get_future();
2113
2114 fml::TaskRunner::RunNowOrPostTask(
2115 runner: shell->GetTaskRunners().GetRasterTaskRunner(),
2116 task: [&screenshot_promise, &shell]() {
2117 auto rasterizer = shell->GetRasterizer();
2118 screenshot_promise.set_value(rasterizer->ScreenshotLastLayerTree(
2119 type: Rasterizer::ScreenshotType::CompressedImage, base64_encode: false));
2120 });
2121
2122 auto fixtures_dir =
2123 fml::OpenDirectory(path: GetFixturesPath(), create_if_necessary: false, permission: fml::FilePermission::kRead);
2124
2125 auto reference_png = fml::FileMapping::CreateReadOnly(
2126 base_fd: fixtures_dir, sub_path: "shelltest_screenshot.png");
2127
2128 // Use MakeWithoutCopy instead of MakeWithCString because we don't want to
2129 // encode the null sentinel
2130 sk_sp<SkData> reference_data = SkData::MakeWithoutCopy(
2131 data: reference_png->GetMapping(), length: reference_png->GetSize());
2132
2133 sk_sp<SkData> screenshot_data = screenshot_future.get().data;
2134 if (!reference_data->equals(other: screenshot_data.get())) {
2135 LogSkData(data: reference_data, title: "reference");
2136 LogSkData(data: screenshot_data, title: "screenshot");
2137 ASSERT_TRUE(false);
2138 }
2139
2140 DestroyShell(shell: std::move(shell));
2141}
2142
2143TEST_F(ShellTest, CanConvertToAndFromMappings) {
2144 const size_t buffer_size = 2 << 20;
2145
2146 uint8_t* buffer = static_cast<uint8_t*>(::malloc(size: buffer_size));
2147 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
2148 ASSERT_TRUE(buffer != nullptr);
2149 ASSERT_TRUE(MemsetPatternSetOrCheck(
2150 buffer, buffer_size, MemsetPatternOp::kMemsetPatternOpSetBuffer));
2151
2152 std::unique_ptr<fml::Mapping> mapping =
2153 std::make_unique<fml::MallocMapping>(args&: buffer, args: buffer_size);
2154
2155 ASSERT_EQ(mapping->GetSize(), buffer_size);
2156
2157 fml::AutoResetWaitableEvent latch;
2158 AddNativeCallback(
2159 name: "SendFixtureMapping", CREATE_NATIVE_ENTRY([&](auto args) {
2160 auto mapping_from_dart =
2161 tonic::DartConverter<std::unique_ptr<fml::Mapping>>::FromDart(
2162 Dart_GetNativeArgument(args, 0));
2163 ASSERT_NE(mapping_from_dart, nullptr);
2164 ASSERT_EQ(mapping_from_dart->GetSize(), buffer_size);
2165 ASSERT_TRUE(MemsetPatternSetOrCheck(
2166 const_cast<uint8_t*>(mapping_from_dart->GetMapping()), // buffer
2167 mapping_from_dart->GetSize(), // size
2168 MemsetPatternOp::kMemsetPatternOpCheckBuffer // op
2169 ));
2170 latch.Signal();
2171 }));
2172
2173 AddNativeCallback(
2174 name: "GetFixtureMapping", CREATE_NATIVE_ENTRY([&](auto args) {
2175 tonic::DartConverter<tonic::DartConverterMapping>::SetReturnValue(
2176 args, mapping);
2177 }));
2178
2179 auto settings = CreateSettingsForFixture();
2180 auto configuration = RunConfiguration::InferFromSettings(settings);
2181 configuration.SetEntrypoint("canConvertMappings");
2182 std::unique_ptr<Shell> shell = CreateShell(settings);
2183 ASSERT_NE(shell.get(), nullptr);
2184 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2185 latch.Wait();
2186 DestroyShell(shell: std::move(shell));
2187}
2188
2189// Compares local times as seen by the dart isolate and as seen by this test
2190// fixture, to a resolution of 1 hour.
2191//
2192// This verifies that (1) the isolate is able to get a timezone (doesn't lock
2193// up for example), and (2) that the host and the isolate agree on what the
2194// timezone is.
2195TEST_F(ShellTest, LocaltimesMatch) {
2196 fml::AutoResetWaitableEvent latch;
2197 std::string dart_isolate_time_str;
2198
2199 // See fixtures/shell_test.dart, the callback NotifyLocalTime is declared
2200 // there.
2201 AddNativeCallback(name: "NotifyLocalTime", CREATE_NATIVE_ENTRY([&](auto args) {
2202 dart_isolate_time_str =
2203 tonic::DartConverter<std::string>::FromDart(
2204 Dart_GetNativeArgument(args, 0));
2205 latch.Signal();
2206 }));
2207
2208 auto settings = CreateSettingsForFixture();
2209 auto configuration = RunConfiguration::InferFromSettings(settings);
2210 configuration.SetEntrypoint("localtimesMatch");
2211 std::unique_ptr<Shell> shell = CreateShell(settings);
2212 ASSERT_NE(shell.get(), nullptr);
2213 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2214 latch.Wait();
2215
2216 char timestr[200];
2217 const time_t timestamp = time(timer: nullptr);
2218 const struct tm* local_time = localtime(timer: &timestamp);
2219 ASSERT_NE(local_time, nullptr)
2220 << "Could not get local time: errno=" << errno << ": " << strerror(errno);
2221 // Example: "2020-02-26 14" for 2pm on February 26, 2020.
2222 const size_t format_size =
2223 strftime(s: timestr, maxsize: sizeof(timestr), format: "%Y-%m-%d %H", tp: local_time);
2224 ASSERT_NE(format_size, 0UL)
2225 << "strftime failed: host time: " << std::string(timestr)
2226 << " dart isolate time: " << dart_isolate_time_str;
2227
2228 const std::string host_local_time_str = timestr;
2229
2230 ASSERT_EQ(dart_isolate_time_str, host_local_time_str)
2231 << "Local times in the dart isolate and the local time seen by the test "
2232 << "differ by more than 1 hour, but are expected to be about equal";
2233
2234 DestroyShell(shell: std::move(shell));
2235}
2236
2237TEST_F(ShellTest, CanDecompressImageFromAsset) {
2238 fml::AutoResetWaitableEvent latch;
2239 AddNativeCallback(name: "NotifyWidthHeight", CREATE_NATIVE_ENTRY([&](auto args) {
2240 auto width = tonic::DartConverter<int>::FromDart(
2241 Dart_GetNativeArgument(args, 0));
2242 auto height = tonic::DartConverter<int>::FromDart(
2243 Dart_GetNativeArgument(args, 1));
2244 ASSERT_EQ(width, 100);
2245 ASSERT_EQ(height, 100);
2246 latch.Signal();
2247 }));
2248
2249 AddNativeCallback(
2250 name: "GetFixtureImage", CREATE_NATIVE_ENTRY([](auto args) {
2251 auto fixture = OpenFixtureAsMapping("shelltest_screenshot.png");
2252 tonic::DartConverter<tonic::DartConverterMapping>::SetReturnValue(
2253 args, fixture);
2254 }));
2255
2256 auto settings = CreateSettingsForFixture();
2257 auto configuration = RunConfiguration::InferFromSettings(settings);
2258 configuration.SetEntrypoint("canDecompressImageFromAsset");
2259 std::unique_ptr<Shell> shell = CreateShell(settings);
2260 ASSERT_NE(shell.get(), nullptr);
2261 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2262 latch.Wait();
2263 DestroyShell(shell: std::move(shell));
2264}
2265
2266/// An image generator that always creates a 1x1 single-frame green image.
2267class SinglePixelImageGenerator : public ImageGenerator {
2268 public:
2269 SinglePixelImageGenerator()
2270 : info_(SkImageInfo::MakeN32(width: 1, height: 1, at: SkAlphaType::kOpaque_SkAlphaType)){};
2271 ~SinglePixelImageGenerator() = default;
2272 const SkImageInfo& GetInfo() { return info_; }
2273
2274 unsigned int GetFrameCount() const { return 1; }
2275
2276 unsigned int GetPlayCount() const { return 1; }
2277
2278 const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index) {
2279 return {.required_frame: std::nullopt, .duration: 0, .disposal_method: SkCodecAnimation::DisposalMethod::kKeep};
2280 }
2281
2282 SkISize GetScaledDimensions(float scale) {
2283 return SkISize::Make(w: info_.width(), h: info_.height());
2284 }
2285
2286 bool GetPixels(const SkImageInfo& info,
2287 void* pixels,
2288 size_t row_bytes,
2289 unsigned int frame_index,
2290 std::optional<unsigned int> prior_frame) {
2291 assert(info.width() == 1);
2292 assert(info.height() == 1);
2293 assert(row_bytes == 4);
2294
2295 reinterpret_cast<uint32_t*>(pixels)[0] = 0x00ff00ff;
2296 return true;
2297 };
2298
2299 private:
2300 SkImageInfo info_;
2301};
2302
2303TEST_F(ShellTest, CanRegisterImageDecoders) {
2304 fml::AutoResetWaitableEvent latch;
2305 AddNativeCallback(name: "NotifyWidthHeight", CREATE_NATIVE_ENTRY([&](auto args) {
2306 auto width = tonic::DartConverter<int>::FromDart(
2307 Dart_GetNativeArgument(args, 0));
2308 auto height = tonic::DartConverter<int>::FromDart(
2309 Dart_GetNativeArgument(args, 1));
2310 ASSERT_EQ(width, 1);
2311 ASSERT_EQ(height, 1);
2312 latch.Signal();
2313 }));
2314
2315 auto settings = CreateSettingsForFixture();
2316 auto configuration = RunConfiguration::InferFromSettings(settings);
2317 configuration.SetEntrypoint("canRegisterImageDecoders");
2318 std::unique_ptr<Shell> shell = CreateShell(settings);
2319 ASSERT_NE(shell.get(), nullptr);
2320
2321 fml::TaskRunner::RunNowOrPostTask(
2322 runner: shell->GetTaskRunners().GetPlatformTaskRunner(), task: [&shell]() {
2323 shell->RegisterImageDecoder(
2324 factory: [](const sk_sp<SkData>& buffer) {
2325 return std::make_unique<SinglePixelImageGenerator>();
2326 },
2327 priority: 100);
2328 });
2329
2330 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2331 latch.Wait();
2332 DestroyShell(shell: std::move(shell));
2333}
2334
2335TEST_F(ShellTest, OnServiceProtocolGetSkSLsWorks) {
2336 fml::ScopedTemporaryDirectory base_dir;
2337 ASSERT_TRUE(base_dir.fd().is_valid());
2338 PersistentCache::SetCacheDirectoryPath(base_dir.path());
2339 PersistentCache::ResetCacheForProcess();
2340
2341 // Create 2 dummy SkSL cache file IE (base32 encoding of A), II (base32
2342 // encoding of B) with content x and y.
2343 std::vector<std::string> components = {
2344 "flutter_engine", GetFlutterEngineVersion(), "skia", GetSkiaVersion(),
2345 PersistentCache::kSkSLSubdirName};
2346 auto sksl_dir = fml::CreateDirectory(base_directory: base_dir.fd(), components,
2347 permission: fml::FilePermission::kReadWrite);
2348 const std::string x_key_str = "A";
2349 const std::string x_value_str = "x";
2350 sk_sp<SkData> x_key =
2351 SkData::MakeWithCopy(data: x_key_str.data(), length: x_key_str.size());
2352 sk_sp<SkData> x_value =
2353 SkData::MakeWithCopy(data: x_value_str.data(), length: x_value_str.size());
2354 auto x_data = PersistentCache::BuildCacheObject(key: *x_key, data: *x_value);
2355
2356 const std::string y_key_str = "B";
2357 const std::string y_value_str = "y";
2358 sk_sp<SkData> y_key =
2359 SkData::MakeWithCopy(data: y_key_str.data(), length: y_key_str.size());
2360 sk_sp<SkData> y_value =
2361 SkData::MakeWithCopy(data: y_value_str.data(), length: y_value_str.size());
2362 auto y_data = PersistentCache::BuildCacheObject(key: *y_key, data: *y_value);
2363
2364 ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "x_cache", *x_data));
2365 ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "y_cache", *y_data));
2366
2367 Settings settings = CreateSettingsForFixture();
2368 std::unique_ptr<Shell> shell = CreateShell(settings);
2369 ServiceProtocol::Handler::ServiceProtocolMap empty_params;
2370 rapidjson::Document document;
2371 OnServiceProtocol(shell: shell.get(), some_protocol: ServiceProtocolEnum::kGetSkSLs,
2372 task_runner: shell->GetTaskRunners().GetIOTaskRunner(), params: empty_params,
2373 response: &document);
2374 rapidjson::StringBuffer buffer;
2375 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
2376 document.Accept(handler&: writer);
2377 DestroyShell(shell: std::move(shell));
2378
2379 const std::string expected_json1 =
2380 "{\"type\":\"GetSkSLs\",\"SkSLs\":{\"II\":\"eQ==\",\"IE\":\"eA==\"}}";
2381 const std::string expected_json2 =
2382 "{\"type\":\"GetSkSLs\",\"SkSLs\":{\"IE\":\"eA==\",\"II\":\"eQ==\"}}";
2383 bool json_is_expected = (expected_json1 == buffer.GetString()) ||
2384 (expected_json2 == buffer.GetString());
2385 ASSERT_TRUE(json_is_expected) << buffer.GetString() << " is not equal to "
2386 << expected_json1 << " or " << expected_json2;
2387}
2388
2389TEST_F(ShellTest, RasterizerScreenshot) {
2390 Settings settings = CreateSettingsForFixture();
2391 auto configuration = RunConfiguration::InferFromSettings(settings);
2392 auto task_runner = CreateNewThread();
2393 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
2394 task_runner);
2395 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2396
2397 ASSERT_TRUE(ValidateShell(shell.get()));
2398 PlatformViewNotifyCreated(shell: shell.get());
2399
2400 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2401
2402 auto latch = std::make_shared<fml::AutoResetWaitableEvent>();
2403
2404 PumpOneFrame(shell: shell.get());
2405
2406 fml::TaskRunner::RunNowOrPostTask(
2407 runner: shell->GetTaskRunners().GetRasterTaskRunner(), task: [&shell, &latch]() {
2408 Rasterizer::Screenshot screenshot =
2409 shell->GetRasterizer()->ScreenshotLastLayerTree(
2410 type: Rasterizer::ScreenshotType::CompressedImage, base64_encode: true);
2411 EXPECT_NE(screenshot.data, nullptr);
2412
2413 latch->Signal();
2414 });
2415 latch->Wait();
2416 DestroyShell(shell: std::move(shell), task_runners);
2417}
2418
2419TEST_F(ShellTest, RasterizerMakeRasterSnapshot) {
2420 Settings settings = CreateSettingsForFixture();
2421 auto configuration = RunConfiguration::InferFromSettings(settings);
2422 auto task_runner = CreateNewThread();
2423 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
2424 task_runner);
2425 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2426
2427 ASSERT_TRUE(ValidateShell(shell.get()));
2428 PlatformViewNotifyCreated(shell: shell.get());
2429
2430 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2431
2432 auto latch = std::make_shared<fml::AutoResetWaitableEvent>();
2433
2434 PumpOneFrame(shell: shell.get());
2435
2436 fml::TaskRunner::RunNowOrPostTask(
2437 runner: shell->GetTaskRunners().GetRasterTaskRunner(), task: [&shell, &latch]() {
2438 SnapshotDelegate* delegate =
2439 reinterpret_cast<Rasterizer*>(shell->GetRasterizer().get());
2440 sk_sp<DlImage> image = delegate->MakeRasterSnapshot(
2441 display_list: MakeSizedDisplayList(width: 50, height: 50), picture_size: SkISize::Make(w: 50, h: 50));
2442 EXPECT_NE(image, nullptr);
2443
2444 latch->Signal();
2445 });
2446 latch->Wait();
2447 DestroyShell(shell: std::move(shell), task_runners);
2448}
2449
2450TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) {
2451 Settings settings = CreateSettingsForFixture();
2452 std::unique_ptr<Shell> shell = CreateShell(settings);
2453
2454 // 1. Construct a picture and a picture layer to be raster cached.
2455 sk_sp<DisplayList> display_list = MakeSizedDisplayList(width: 10, height: 10);
2456 auto display_list_layer = std::make_shared<DisplayListLayer>(
2457 args: SkPoint::Make(x: 0, y: 0), args: MakeSizedDisplayList(width: 100, height: 100), args: false, args: false);
2458 display_list_layer->set_paint_bounds(SkRect::MakeWH(w: 100, h: 100));
2459
2460 // 2. Rasterize the picture and the picture layer in the raster cache.
2461 std::promise<bool> rasterized;
2462
2463 shell->GetTaskRunners().GetRasterTaskRunner()->PostTask(
2464 task: [&shell, &rasterized, &display_list, &display_list_layer] {
2465 std::vector<RasterCacheItem*> raster_cache_items;
2466 auto* compositor_context = shell->GetRasterizer()->compositor_context();
2467 auto& raster_cache = compositor_context->raster_cache();
2468
2469 LayerStateStack state_stack;
2470 FixedRefreshRateStopwatch raster_time;
2471 FixedRefreshRateStopwatch ui_time;
2472 PaintContext paint_context = {
2473 // clang-format off
2474 .state_stack = state_stack,
2475 .canvas = nullptr,
2476 .gr_context = nullptr,
2477 .dst_color_space = nullptr,
2478 .view_embedder = nullptr,
2479 .raster_time = raster_time,
2480 .ui_time = ui_time,
2481 .texture_registry = nullptr,
2482 .raster_cache = &raster_cache,
2483 // clang-format on
2484 };
2485
2486 PrerollContext preroll_context = {
2487 // clang-format off
2488 .raster_cache = &raster_cache,
2489 .gr_context = nullptr,
2490 .view_embedder = nullptr,
2491 .state_stack = state_stack,
2492 .dst_color_space = nullptr,
2493 .surface_needs_readback = false,
2494 .raster_time = raster_time,
2495 .ui_time = ui_time,
2496 .texture_registry = nullptr,
2497 .has_platform_view = false,
2498 .has_texture_layer = false,
2499 .raster_cached_entries = &raster_cache_items,
2500 // clang-format on
2501 };
2502
2503 // 2.1. Rasterize the picture. Call Draw multiple times to pass the
2504 // access threshold (default to 3) so a cache can be generated.
2505 MockCanvas dummy_canvas;
2506 DlPaint paint;
2507 bool picture_cache_generated;
2508 DisplayListRasterCacheItem display_list_raster_cache_item(
2509 display_list, SkPoint(), true, false);
2510 for (int i = 0; i < 4; i += 1) {
2511 SkMatrix matrix = SkMatrix::I();
2512 state_stack.set_preroll_delegate(matrix);
2513 display_list_raster_cache_item.PrerollSetup(context: &preroll_context, matrix);
2514 display_list_raster_cache_item.PrerollFinalize(context: &preroll_context,
2515 matrix);
2516 picture_cache_generated =
2517 display_list_raster_cache_item.need_caching();
2518 state_stack.set_delegate(&dummy_canvas);
2519 display_list_raster_cache_item.TryToPrepareRasterCache(context: paint_context);
2520 display_list_raster_cache_item.Draw(context: paint_context, canvas: &dummy_canvas,
2521 paint: &paint);
2522 }
2523 ASSERT_TRUE(picture_cache_generated);
2524
2525 // 2.2. Rasterize the picture layer.
2526 LayerRasterCacheItem layer_raster_cache_item(display_list_layer.get());
2527 state_stack.set_preroll_delegate(SkMatrix::I());
2528 layer_raster_cache_item.PrerollSetup(context: &preroll_context, matrix: SkMatrix::I());
2529 layer_raster_cache_item.PrerollFinalize(context: &preroll_context,
2530 matrix: SkMatrix::I());
2531 state_stack.set_delegate(&dummy_canvas);
2532 layer_raster_cache_item.TryToPrepareRasterCache(context: paint_context);
2533 layer_raster_cache_item.Draw(context: paint_context, canvas: &dummy_canvas, paint: &paint);
2534 rasterized.set_value(true);
2535 });
2536 rasterized.get_future().wait();
2537
2538 // 3. Call the service protocol and check its output.
2539 ServiceProtocol::Handler::ServiceProtocolMap empty_params;
2540 rapidjson::Document document;
2541 OnServiceProtocol(
2542 shell: shell.get(), some_protocol: ServiceProtocolEnum::kEstimateRasterCacheMemory,
2543 task_runner: shell->GetTaskRunners().GetRasterTaskRunner(), params: empty_params, response: &document);
2544 rapidjson::StringBuffer buffer;
2545 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
2546 document.Accept(handler&: writer);
2547 std::string expected_json =
2548 "{\"type\":\"EstimateRasterCacheMemory\",\"layerBytes\":40024,\"picture"
2549 "Bytes\":424}";
2550 std::string actual_json = buffer.GetString();
2551 ASSERT_EQ(actual_json, expected_json);
2552
2553 DestroyShell(shell: std::move(shell));
2554}
2555
2556// ktz
2557TEST_F(ShellTest, OnServiceProtocolRenderFrameWithRasterStatsWorks) {
2558 auto settings = CreateSettingsForFixture();
2559 std::unique_ptr<Shell> shell = CreateShell(settings);
2560
2561 // Create the surface needed by rasterizer
2562 PlatformViewNotifyCreated(shell: shell.get());
2563
2564 auto configuration = RunConfiguration::InferFromSettings(settings);
2565 configuration.SetEntrypoint("scene_with_red_box");
2566
2567 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2568 PumpOneFrame(shell: shell.get());
2569
2570 ServiceProtocol::Handler::ServiceProtocolMap empty_params;
2571 rapidjson::Document document;
2572 OnServiceProtocol(
2573 shell: shell.get(), some_protocol: ServiceProtocolEnum::kRenderFrameWithRasterStats,
2574 task_runner: shell->GetTaskRunners().GetRasterTaskRunner(), params: empty_params, response: &document);
2575 rapidjson::StringBuffer buffer;
2576 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
2577 document.Accept(handler&: writer);
2578
2579 // It would be better to parse out the json and check for the validity of
2580 // fields. Below checks approximate what needs to be checked, this can not be
2581 // an exact check since duration will not exactly match.
2582#ifdef SHELL_ENABLE_METAL
2583 std::string expected_json =
2584 "\"snapshot\":[137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,3,32,0,"
2585 "0,2,88,8,6,0,0,0,154,118,130,112,0,0,0,1,115,82,71,66,0,174,206,28,233,"
2586 "0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,7,103,73,68,65,84,120,"
2587 "156,237,206,65,13,192,48,0,3,177,211,248,115,78,73,172,234,199,70,224,"
2588 "86,91,45,0,0,128,203,190,215,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2589 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2590 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2591 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2592 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2593 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2594 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2595 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2596 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2597 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2598 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2599 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2600 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2601 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2602 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2603 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2604 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2605 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2606 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2607 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2608 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2609 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2610 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2611 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2612 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2613 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2614 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2615 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2616 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2617 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2618 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2619 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2620 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2621 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2622 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2623 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2624 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2625 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2626 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2627 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2628 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2629 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2630 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2631 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2632 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2633 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2634 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2635 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2636 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2637 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2638 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2639 "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
2640 "0,224,47,7,195,182,3,255,101,111,186,90,0,0,0,0,73,69,78,68,174,66,96,"
2641 "130]";
2642#else
2643 std::string expected_json =
2644 "\"snapshot\":[137,80,78,71,13,10,26,10,0,"
2645 "0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,6,0,0,0,31,21,196,137,0,0,0,1,115,"
2646 "82,71,66,0,174,206,28,233,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,"
2647 "0,0,13,73,68,65,84,8,153,99,248,207,192,240,31,0,5,0,1,255,171,206,54,"
2648 "137,0,0,0,0,73,69,78,68,174,66,96,130]";
2649#endif
2650 std::string actual_json = buffer.GetString();
2651
2652 EXPECT_THAT(actual_json, ::testing::HasSubstr(expected_json));
2653 EXPECT_THAT(actual_json,
2654 ::testing::HasSubstr("{\"type\":\"RenderFrameWithRasterStats\""));
2655 EXPECT_THAT(actual_json, ::testing::HasSubstr("\"duration_micros\""));
2656
2657 PlatformViewNotifyDestroyed(shell: shell.get());
2658 DestroyShell(shell: std::move(shell));
2659}
2660
2661// TODO(https://github.com/flutter/flutter/issues/100273): Disabled due to
2662// flakiness.
2663// TODO(https://github.com/flutter/flutter/issues/100299): Fix it when
2664// re-enabling.
2665TEST_F(ShellTest, DISABLED_DiscardLayerTreeOnResize) {
2666 auto settings = CreateSettingsForFixture();
2667
2668 SkISize wrong_size = SkISize::Make(w: 400, h: 100);
2669 SkISize expected_size = SkISize::Make(w: 400, h: 200);
2670
2671 fml::AutoResetWaitableEvent end_frame_latch;
2672 auto end_frame_callback =
2673 [&](bool should_merge_thread,
2674 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
2675 end_frame_latch.Signal();
2676 };
2677 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
2678 args: std::move(end_frame_callback), args: PostPrerollResult::kSuccess, args: false);
2679 std::unique_ptr<Shell> shell = CreateShell(config: {
2680 .settings = settings,
2681 .platform_view_create_callback = ShellTestPlatformViewBuilder({
2682 .shell_test_external_view_embedder = external_view_embedder,
2683 }),
2684 });
2685
2686 // Create the surface needed by rasterizer
2687 PlatformViewNotifyCreated(shell: shell.get());
2688
2689 fml::TaskRunner::RunNowOrPostTask(
2690 runner: shell->GetTaskRunners().GetPlatformTaskRunner(),
2691 task: [&shell, &expected_size]() {
2692 shell->GetPlatformView()->SetViewportMetrics(
2693 view_id: kImplicitViewId,
2694 metrics: {1.0, static_cast<double>(expected_size.width()),
2695 static_cast<double>(expected_size.height()), 22, 0});
2696 });
2697
2698 auto configuration = RunConfiguration::InferFromSettings(settings);
2699 configuration.SetEntrypoint("emptyMain");
2700
2701 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2702
2703 PumpOneFrame(shell: shell.get(), width: static_cast<double>(wrong_size.width()),
2704 height: static_cast<double>(wrong_size.height()));
2705 end_frame_latch.Wait();
2706 // Wrong size, no frames are submitted.
2707 ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
2708
2709 PumpOneFrame(shell: shell.get(), width: static_cast<double>(expected_size.width()),
2710 height: static_cast<double>(expected_size.height()));
2711 end_frame_latch.Wait();
2712 // Expected size, 1 frame submitted.
2713 ASSERT_EQ(1, external_view_embedder->GetSubmittedFrameCount());
2714 ASSERT_EQ(expected_size, external_view_embedder->GetLastSubmittedFrameSize());
2715
2716 PlatformViewNotifyDestroyed(shell: shell.get());
2717 DestroyShell(shell: std::move(shell));
2718}
2719
2720// TODO(https://github.com/flutter/flutter/issues/100273): Disabled due to
2721// flakiness.
2722// TODO(https://github.com/flutter/flutter/issues/100299): Fix it when
2723// re-enabling.
2724TEST_F(ShellTest, DISABLED_DiscardResubmittedLayerTreeOnResize) {
2725 auto settings = CreateSettingsForFixture();
2726
2727 SkISize origin_size = SkISize::Make(w: 400, h: 100);
2728 SkISize new_size = SkISize::Make(w: 400, h: 200);
2729
2730 fml::AutoResetWaitableEvent end_frame_latch;
2731
2732 fml::AutoResetWaitableEvent resize_latch;
2733
2734 std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
2735 fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger_ref;
2736 auto end_frame_callback =
2737 [&](bool should_merge_thread,
2738 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
2739 if (!raster_thread_merger_ref) {
2740 raster_thread_merger_ref = raster_thread_merger;
2741 }
2742 if (should_merge_thread) {
2743 raster_thread_merger->MergeWithLease(lease_term: 10);
2744 external_view_embedder->UpdatePostPrerollResult(
2745 post_preroll_result: PostPrerollResult::kSuccess);
2746 }
2747 end_frame_latch.Signal();
2748
2749 if (should_merge_thread) {
2750 resize_latch.Wait();
2751 }
2752 };
2753
2754 external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
2755 args: std::move(end_frame_callback), args: PostPrerollResult::kResubmitFrame, args: true);
2756
2757 std::unique_ptr<Shell> shell = CreateShell(config: {
2758 .settings = settings,
2759 .platform_view_create_callback = ShellTestPlatformViewBuilder({
2760 .shell_test_external_view_embedder = external_view_embedder,
2761 }),
2762 });
2763
2764 // Create the surface needed by rasterizer
2765 PlatformViewNotifyCreated(shell: shell.get());
2766
2767 fml::TaskRunner::RunNowOrPostTask(
2768 runner: shell->GetTaskRunners().GetPlatformTaskRunner(),
2769 task: [&shell, &origin_size]() {
2770 shell->GetPlatformView()->SetViewportMetrics(
2771 view_id: kImplicitViewId,
2772 metrics: {1.0, static_cast<double>(origin_size.width()),
2773 static_cast<double>(origin_size.height()), 22, 0});
2774 });
2775
2776 auto configuration = RunConfiguration::InferFromSettings(settings);
2777 configuration.SetEntrypoint("emptyMain");
2778
2779 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2780
2781 PumpOneFrame(shell: shell.get(), width: static_cast<double>(origin_size.width()),
2782 height: static_cast<double>(origin_size.height()));
2783
2784 end_frame_latch.Wait();
2785 ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
2786
2787 fml::TaskRunner::RunNowOrPostTask(
2788 runner: shell->GetTaskRunners().GetPlatformTaskRunner(),
2789 task: [&shell, &new_size, &resize_latch]() {
2790 shell->GetPlatformView()->SetViewportMetrics(
2791 view_id: kImplicitViewId, metrics: {1.0, static_cast<double>(new_size.width()),
2792 static_cast<double>(new_size.height()), 22, 0});
2793 resize_latch.Signal();
2794 });
2795
2796 end_frame_latch.Wait();
2797
2798 // The frame resubmitted with origin size should be discarded after the
2799 // viewport metrics changed.
2800 ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
2801
2802 // Threads will be merged at the end of this frame.
2803 PumpOneFrame(shell: shell.get(), width: static_cast<double>(new_size.width()),
2804 height: static_cast<double>(new_size.height()));
2805
2806 end_frame_latch.Wait();
2807 ASSERT_TRUE(raster_thread_merger_ref->IsMerged());
2808 ASSERT_EQ(1, external_view_embedder->GetSubmittedFrameCount());
2809 ASSERT_EQ(new_size, external_view_embedder->GetLastSubmittedFrameSize());
2810
2811 PlatformViewNotifyDestroyed(shell: shell.get());
2812 DestroyShell(shell: std::move(shell));
2813}
2814
2815TEST_F(ShellTest, IgnoresInvalidMetrics) {
2816 fml::AutoResetWaitableEvent latch;
2817 double last_device_pixel_ratio;
2818 double last_width;
2819 double last_height;
2820 auto native_report_device_pixel_ratio = [&](Dart_NativeArguments args) {
2821 auto dpr_handle = Dart_GetNativeArgument(args, index: 0);
2822 ASSERT_TRUE(Dart_IsDouble(dpr_handle));
2823 Dart_DoubleValue(double_obj: dpr_handle, value: &last_device_pixel_ratio);
2824 ASSERT_FALSE(last_device_pixel_ratio == 0.0);
2825
2826 auto width_handle = Dart_GetNativeArgument(args, index: 1);
2827 ASSERT_TRUE(Dart_IsDouble(width_handle));
2828 Dart_DoubleValue(double_obj: width_handle, value: &last_width);
2829 ASSERT_FALSE(last_width == 0.0);
2830
2831 auto height_handle = Dart_GetNativeArgument(args, index: 2);
2832 ASSERT_TRUE(Dart_IsDouble(height_handle));
2833 Dart_DoubleValue(double_obj: height_handle, value: &last_height);
2834 ASSERT_FALSE(last_height == 0.0);
2835
2836 latch.Signal();
2837 };
2838
2839 Settings settings = CreateSettingsForFixture();
2840 auto task_runner = CreateNewThread();
2841 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
2842 task_runner);
2843
2844 AddNativeCallback(name: "ReportMetrics",
2845 CREATE_NATIVE_ENTRY(native_report_device_pixel_ratio));
2846
2847 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2848
2849 auto configuration = RunConfiguration::InferFromSettings(settings);
2850 configuration.SetEntrypoint("reportMetrics");
2851
2852 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2853
2854 task_runner->PostTask(task: [&]() {
2855 // This one is invalid for having 0 pixel ratio.
2856 shell->GetPlatformView()->SetViewportMetrics(view_id: kImplicitViewId,
2857 metrics: {0.0, 400, 200, 22, 0});
2858 task_runner->PostTask(task: [&]() {
2859 // This one is invalid for having 0 width.
2860 shell->GetPlatformView()->SetViewportMetrics(view_id: kImplicitViewId,
2861 metrics: {0.8, 0.0, 200, 22, 0});
2862 task_runner->PostTask(task: [&]() {
2863 // This one is invalid for having 0 height.
2864 shell->GetPlatformView()->SetViewportMetrics(view_id: kImplicitViewId,
2865 metrics: {0.8, 400, 0.0, 22, 0});
2866 task_runner->PostTask(task: [&]() {
2867 // This one makes it through.
2868 shell->GetPlatformView()->SetViewportMetrics(
2869 view_id: kImplicitViewId, metrics: {0.8, 400, 200.0, 22, 0});
2870 });
2871 });
2872 });
2873 });
2874 latch.Wait();
2875 ASSERT_EQ(last_device_pixel_ratio, 0.8);
2876 ASSERT_EQ(last_width, 400.0);
2877 ASSERT_EQ(last_height, 200.0);
2878 latch.Reset();
2879
2880 task_runner->PostTask(task: [&]() {
2881 shell->GetPlatformView()->SetViewportMetrics(view_id: kImplicitViewId,
2882 metrics: {1.2, 600, 300, 22, 0});
2883 });
2884 latch.Wait();
2885 ASSERT_EQ(last_device_pixel_ratio, 1.2);
2886 ASSERT_EQ(last_width, 600.0);
2887 ASSERT_EQ(last_height, 300.0);
2888
2889 DestroyShell(shell: std::move(shell), task_runners);
2890}
2891
2892TEST_F(ShellTest, IgnoresMetricsUpdateToInvalidView) {
2893 fml::AutoResetWaitableEvent latch;
2894 double last_device_pixel_ratio;
2895 // This callback will be called whenever any view's metrics change.
2896 auto native_report_device_pixel_ratio = [&](Dart_NativeArguments args) {
2897 // The correct call will have a DPR of 3.
2898 auto dpr_handle = Dart_GetNativeArgument(args, index: 0);
2899 ASSERT_TRUE(Dart_IsDouble(dpr_handle));
2900 Dart_DoubleValue(double_obj: dpr_handle, value: &last_device_pixel_ratio);
2901 ASSERT_TRUE(last_device_pixel_ratio > 2.5);
2902
2903 latch.Signal();
2904 };
2905
2906 Settings settings = CreateSettingsForFixture();
2907 auto task_runner = CreateNewThread();
2908 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
2909 task_runner);
2910
2911 AddNativeCallback(name: "ReportMetrics",
2912 CREATE_NATIVE_ENTRY(native_report_device_pixel_ratio));
2913
2914 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2915
2916 auto configuration = RunConfiguration::InferFromSettings(settings);
2917 configuration.SetEntrypoint("reportMetrics");
2918
2919 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2920
2921 task_runner->PostTask(task: [&]() {
2922 // This one is invalid for having an nonexistent view ID.
2923 // Also, it has a DPR of 2.0 for detection.
2924 shell->GetPlatformView()->SetViewportMetrics(view_id: 2, metrics: {2.0, 400, 200, 22, 0});
2925 task_runner->PostTask(task: [&]() {
2926 // This one is valid with DPR 3.0.
2927 shell->GetPlatformView()->SetViewportMetrics(view_id: kImplicitViewId,
2928 metrics: {3.0, 400, 200, 22, 0});
2929 });
2930 });
2931 latch.Wait();
2932 ASSERT_EQ(last_device_pixel_ratio, 3.0);
2933 latch.Reset();
2934
2935 DestroyShell(shell: std::move(shell), task_runners);
2936}
2937
2938TEST_F(ShellTest, OnServiceProtocolSetAssetBundlePathWorks) {
2939 Settings settings = CreateSettingsForFixture();
2940 std::unique_ptr<Shell> shell = CreateShell(settings);
2941 RunConfiguration configuration =
2942 RunConfiguration::InferFromSettings(settings);
2943 configuration.SetEntrypoint("canAccessResourceFromAssetDir");
2944
2945 // Verify isolate can load a known resource with the
2946 // default asset directory - kernel_blob.bin
2947 fml::AutoResetWaitableEvent latch;
2948
2949 // Callback used to signal whether the resource was loaded successfully.
2950 bool can_access_resource = false;
2951 auto native_can_access_resource = [&can_access_resource,
2952 &latch](Dart_NativeArguments args) {
2953 Dart_Handle exception = nullptr;
2954 can_access_resource =
2955 tonic::DartConverter<bool>::FromArguments(args, index: 0, exception);
2956 latch.Signal();
2957 };
2958 AddNativeCallback(name: "NotifyCanAccessResource",
2959 CREATE_NATIVE_ENTRY(native_can_access_resource));
2960
2961 // Callback used to delay the asset load until after the service
2962 // protocol method has finished.
2963 auto native_notify_set_asset_bundle_path =
2964 [&shell](Dart_NativeArguments args) {
2965 // Update the asset directory to a bonus path.
2966 ServiceProtocol::Handler::ServiceProtocolMap params;
2967 params["assetDirectory"] = "assetDirectory";
2968 rapidjson::Document document;
2969 OnServiceProtocol(shell: shell.get(), some_protocol: ServiceProtocolEnum::kSetAssetBundlePath,
2970 task_runner: shell->GetTaskRunners().GetUITaskRunner(), params,
2971 response: &document);
2972 rapidjson::StringBuffer buffer;
2973 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
2974 document.Accept(handler&: writer);
2975 };
2976 AddNativeCallback(name: "NotifySetAssetBundlePath",
2977 CREATE_NATIVE_ENTRY(native_notify_set_asset_bundle_path));
2978
2979 RunEngine(shell: shell.get(), configuration: std::move(configuration));
2980
2981 latch.Wait();
2982 ASSERT_TRUE(can_access_resource);
2983
2984 DestroyShell(shell: std::move(shell));
2985}
2986
2987TEST_F(ShellTest, EngineRootIsolateLaunchesDontTakeVMDataSettings) {
2988 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
2989 // Make sure the shell launch does not kick off the creation of the VM
2990 // instance by already creating one upfront.
2991 auto vm_settings = CreateSettingsForFixture();
2992 auto vm_ref = DartVMRef::Create(settings: vm_settings);
2993 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
2994
2995 auto settings = vm_settings;
2996 fml::AutoResetWaitableEvent isolate_create_latch;
2997 settings.root_isolate_create_callback = [&](const auto& isolate) {
2998 isolate_create_latch.Signal();
2999 };
3000 auto shell = CreateShell(settings);
3001 ASSERT_TRUE(ValidateShell(shell.get()));
3002 auto configuration = RunConfiguration::InferFromSettings(settings);
3003 ASSERT_TRUE(configuration.IsValid());
3004 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3005 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3006 DestroyShell(shell: std::move(shell));
3007 isolate_create_latch.Wait();
3008}
3009
3010TEST_F(ShellTest, AssetManagerSingle) {
3011 fml::ScopedTemporaryDirectory asset_dir;
3012 fml::UniqueFD asset_dir_fd = fml::OpenDirectory(
3013 path: asset_dir.path().c_str(), create_if_necessary: false, permission: fml::FilePermission::kRead);
3014
3015 std::string filename = "test_name";
3016 std::string content = "test_content";
3017
3018 bool success = fml::WriteAtomically(base_directory: asset_dir_fd, file_name: filename.c_str(),
3019 mapping: fml::DataMapping(content));
3020 ASSERT_TRUE(success);
3021
3022 AssetManager asset_manager;
3023 asset_manager.PushBack(
3024 resolver: std::make_unique<DirectoryAssetBundle>(args: std::move(asset_dir_fd), args: false));
3025
3026 auto mapping = asset_manager.GetAsMapping(asset_name: filename);
3027 ASSERT_TRUE(mapping != nullptr);
3028
3029 std::string result(reinterpret_cast<const char*>(mapping->GetMapping()),
3030 mapping->GetSize());
3031
3032 ASSERT_TRUE(result == content);
3033}
3034
3035TEST_F(ShellTest, AssetManagerMulti) {
3036 fml::ScopedTemporaryDirectory asset_dir;
3037 fml::UniqueFD asset_dir_fd = fml::OpenDirectory(
3038 path: asset_dir.path().c_str(), create_if_necessary: false, permission: fml::FilePermission::kRead);
3039
3040 std::vector<std::string> filenames = {
3041 "good0",
3042 "bad0",
3043 "good1",
3044 "bad1",
3045 };
3046
3047 for (auto filename : filenames) {
3048 bool success = fml::WriteAtomically(base_directory: asset_dir_fd, file_name: filename.c_str(),
3049 mapping: fml::DataMapping(filename));
3050 ASSERT_TRUE(success);
3051 }
3052
3053 AssetManager asset_manager;
3054 asset_manager.PushBack(
3055 resolver: std::make_unique<DirectoryAssetBundle>(args: std::move(asset_dir_fd), args: false));
3056
3057 auto mappings = asset_manager.GetAsMappings(asset_pattern: "(.*)", subdir: std::nullopt);
3058 EXPECT_EQ(mappings.size(), 4u);
3059
3060 std::vector<std::string> expected_results = {
3061 "good0",
3062 "good1",
3063 };
3064
3065 mappings = asset_manager.GetAsMappings(asset_pattern: "(.*)good(.*)", subdir: std::nullopt);
3066 ASSERT_EQ(mappings.size(), expected_results.size());
3067
3068 for (auto& mapping : mappings) {
3069 std::string result(reinterpret_cast<const char*>(mapping->GetMapping()),
3070 mapping->GetSize());
3071 EXPECT_NE(
3072 std::find(expected_results.begin(), expected_results.end(), result),
3073 expected_results.end());
3074 }
3075}
3076
3077#if defined(OS_FUCHSIA)
3078TEST_F(ShellTest, AssetManagerMultiSubdir) {
3079 std::string subdir_path = "subdir";
3080
3081 fml::ScopedTemporaryDirectory asset_dir;
3082 fml::UniqueFD asset_dir_fd = fml::OpenDirectory(
3083 asset_dir.path().c_str(), false, fml::FilePermission::kRead);
3084 fml::UniqueFD subdir_fd =
3085 fml::OpenDirectory((asset_dir.path() + "/" + subdir_path).c_str(), true,
3086 fml::FilePermission::kReadWrite);
3087
3088 std::vector<std::string> filenames = {
3089 "bad0",
3090 "notgood", // this is to make sure the pattern (.*)good(.*) only
3091 // matches things in the subdirectory
3092 };
3093
3094 std::vector<std::string> subdir_filenames = {
3095 "good0",
3096 "good1",
3097 "bad1",
3098 };
3099
3100 for (auto filename : filenames) {
3101 bool success = fml::WriteAtomically(asset_dir_fd, filename.c_str(),
3102 fml::DataMapping(filename));
3103 ASSERT_TRUE(success);
3104 }
3105
3106 for (auto filename : subdir_filenames) {
3107 bool success = fml::WriteAtomically(subdir_fd, filename.c_str(),
3108 fml::DataMapping(filename));
3109 ASSERT_TRUE(success);
3110 }
3111
3112 AssetManager asset_manager;
3113 asset_manager.PushBack(
3114 std::make_unique<DirectoryAssetBundle>(std::move(asset_dir_fd), false));
3115
3116 auto mappings = asset_manager.GetAsMappings("(.*)", std::nullopt);
3117 EXPECT_EQ(mappings.size(), 5u);
3118
3119 mappings = asset_manager.GetAsMappings("(.*)", subdir_path);
3120 EXPECT_EQ(mappings.size(), 3u);
3121
3122 std::vector<std::string> expected_results = {
3123 "good0",
3124 "good1",
3125 };
3126
3127 mappings = asset_manager.GetAsMappings("(.*)good(.*)", subdir_path);
3128 ASSERT_EQ(mappings.size(), expected_results.size());
3129
3130 for (auto& mapping : mappings) {
3131 std::string result(reinterpret_cast<const char*>(mapping->GetMapping()),
3132 mapping->GetSize());
3133 ASSERT_NE(
3134 std::find(expected_results.begin(), expected_results.end(), result),
3135 expected_results.end());
3136 }
3137}
3138#endif // OS_FUCHSIA
3139
3140TEST_F(ShellTest, Spawn) {
3141 auto settings = CreateSettingsForFixture();
3142 auto shell = CreateShell(settings);
3143 ASSERT_TRUE(ValidateShell(shell.get()));
3144
3145 auto configuration = RunConfiguration::InferFromSettings(settings);
3146 ASSERT_TRUE(configuration.IsValid());
3147 configuration.SetEntrypoint("fixturesAreFunctionalMain");
3148
3149 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3150 ASSERT_TRUE(second_configuration.IsValid());
3151 second_configuration.SetEntrypoint("testCanLaunchSecondaryIsolate");
3152
3153 const std::string initial_route("/foo");
3154
3155 fml::AutoResetWaitableEvent main_latch;
3156 std::string last_entry_point;
3157 // Fulfill native function for the first Shell's entrypoint.
3158 AddNativeCallback(
3159 name: "SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) {
3160 last_entry_point = shell->GetEngine()->GetLastEntrypoint();
3161 main_latch.Signal();
3162 }));
3163 // Fulfill native function for the second Shell's entrypoint.
3164 fml::CountDownLatch second_latch(2);
3165 AddNativeCallback(
3166 // The Dart native function names aren't very consistent but this is
3167 // just the native function name of the second vm entrypoint in the
3168 // fixture.
3169 name: "NotifyNative",
3170 CREATE_NATIVE_ENTRY([&](auto args) { second_latch.CountDown(); }));
3171
3172 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3173 main_latch.Wait();
3174 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3175 // Check first Shell ran the first entrypoint.
3176 ASSERT_EQ("fixturesAreFunctionalMain", last_entry_point);
3177
3178 PostSync(
3179 task_runner: shell->GetTaskRunners().GetPlatformTaskRunner(),
3180 task: [this, &spawner = shell, &second_configuration, &second_latch,
3181 initial_route]() {
3182 MockPlatformViewDelegate platform_view_delegate;
3183 auto spawn = spawner->Spawn(
3184 run_configuration: std::move(second_configuration), initial_route,
3185 on_create_platform_view: [&platform_view_delegate](Shell& shell) {
3186 auto result = std::make_unique<MockPlatformView>(
3187 args&: platform_view_delegate, args: shell.GetTaskRunners());
3188 ON_CALL(*result, CreateRenderingSurface())
3189 .WillByDefault(action: ::testing::Invoke(
3190 function_impl: [] { return std::make_unique<MockSurface>(); }));
3191 return result;
3192 },
3193 on_create_rasterizer: [](Shell& shell) { return std::make_unique<Rasterizer>(args&: shell); });
3194 ASSERT_NE(nullptr, spawn.get());
3195 ASSERT_TRUE(ValidateShell(spawn.get()));
3196
3197 PostSync(task_runner: spawner->GetTaskRunners().GetUITaskRunner(), task: [&spawn, &spawner,
3198 initial_route] {
3199 // Check second shell ran the second entrypoint.
3200 ASSERT_EQ("testCanLaunchSecondaryIsolate",
3201 spawn->GetEngine()->GetLastEntrypoint());
3202 ASSERT_EQ(initial_route, spawn->GetEngine()->InitialRoute());
3203
3204 ASSERT_NE(spawner->GetEngine()
3205 ->GetRuntimeController()
3206 ->GetRootIsolateGroup(),
3207 0u);
3208 ASSERT_EQ(spawner->GetEngine()
3209 ->GetRuntimeController()
3210 ->GetRootIsolateGroup(),
3211 spawn->GetEngine()
3212 ->GetRuntimeController()
3213 ->GetRootIsolateGroup());
3214 auto spawner_snapshot_delegate = spawner->GetEngine()
3215 ->GetRuntimeController()
3216 ->GetSnapshotDelegate();
3217 auto spawn_snapshot_delegate =
3218 spawn->GetEngine()->GetRuntimeController()->GetSnapshotDelegate();
3219 PostSync(task_runner: spawner->GetTaskRunners().GetRasterTaskRunner(),
3220 task: [spawner_snapshot_delegate, spawn_snapshot_delegate] {
3221 ASSERT_NE(spawner_snapshot_delegate.get(),
3222 spawn_snapshot_delegate.get());
3223 });
3224 });
3225 PostSync(
3226 task_runner: spawner->GetTaskRunners().GetIOTaskRunner(), task: [&spawner, &spawn] {
3227 ASSERT_EQ(spawner->GetIOManager()->GetResourceContext().get(),
3228 spawn->GetIOManager()->GetResourceContext().get());
3229 });
3230
3231 // Before destroying the shell, wait for expectations of the spawned
3232 // isolate to be met.
3233 second_latch.Wait();
3234
3235 DestroyShell(shell: std::move(spawn));
3236 });
3237
3238 DestroyShell(shell: std::move(shell));
3239 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3240}
3241
3242TEST_F(ShellTest, SpawnWithDartEntrypointArgs) {
3243 auto settings = CreateSettingsForFixture();
3244 auto shell = CreateShell(settings);
3245 ASSERT_TRUE(ValidateShell(shell.get()));
3246
3247 auto configuration = RunConfiguration::InferFromSettings(settings);
3248 ASSERT_TRUE(configuration.IsValid());
3249 configuration.SetEntrypoint("canReceiveArgumentsWhenEngineRun");
3250 const std::vector<std::string> entrypoint_args{"foo", "bar"};
3251 configuration.SetEntrypointArgs(entrypoint_args);
3252
3253 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3254 ASSERT_TRUE(second_configuration.IsValid());
3255 second_configuration.SetEntrypoint("canReceiveArgumentsWhenEngineSpawn");
3256 const std::vector<std::string> second_entrypoint_args{"arg1", "arg2"};
3257 second_configuration.SetEntrypointArgs(second_entrypoint_args);
3258
3259 const std::string initial_route("/foo");
3260
3261 fml::AutoResetWaitableEvent main_latch;
3262 std::string last_entry_point;
3263 // Fulfill native function for the first Shell's entrypoint.
3264 AddNativeCallback(name: "NotifyNativeWhenEngineRun",
3265 CREATE_NATIVE_ENTRY(([&](Dart_NativeArguments args) {
3266 ASSERT_TRUE(tonic::DartConverter<bool>::FromDart(
3267 Dart_GetNativeArgument(args, 0)));
3268 last_entry_point =
3269 shell->GetEngine()->GetLastEntrypoint();
3270 main_latch.Signal();
3271 })));
3272
3273 fml::AutoResetWaitableEvent second_latch;
3274 // Fulfill native function for the second Shell's entrypoint.
3275 AddNativeCallback(name: "NotifyNativeWhenEngineSpawn",
3276 CREATE_NATIVE_ENTRY(([&](Dart_NativeArguments args) {
3277 ASSERT_TRUE(tonic::DartConverter<bool>::FromDart(
3278 Dart_GetNativeArgument(args, 0)));
3279 last_entry_point =
3280 shell->GetEngine()->GetLastEntrypoint();
3281 second_latch.Signal();
3282 })));
3283
3284 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3285 main_latch.Wait();
3286 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3287 // Check first Shell ran the first entrypoint.
3288 ASSERT_EQ("canReceiveArgumentsWhenEngineRun", last_entry_point);
3289
3290 PostSync(
3291 task_runner: shell->GetTaskRunners().GetPlatformTaskRunner(),
3292 task: [this, &spawner = shell, &second_configuration, &second_latch,
3293 initial_route]() {
3294 MockPlatformViewDelegate platform_view_delegate;
3295 auto spawn = spawner->Spawn(
3296 run_configuration: std::move(second_configuration), initial_route,
3297 on_create_platform_view: [&platform_view_delegate](Shell& shell) {
3298 auto result = std::make_unique<MockPlatformView>(
3299 args&: platform_view_delegate, args: shell.GetTaskRunners());
3300 ON_CALL(*result, CreateRenderingSurface())
3301 .WillByDefault(action: ::testing::Invoke(
3302 function_impl: [] { return std::make_unique<MockSurface>(); }));
3303 return result;
3304 },
3305 on_create_rasterizer: [](Shell& shell) { return std::make_unique<Rasterizer>(args&: shell); });
3306 ASSERT_NE(nullptr, spawn.get());
3307 ASSERT_TRUE(ValidateShell(spawn.get()));
3308
3309 PostSync(task_runner: spawner->GetTaskRunners().GetUITaskRunner(),
3310 task: [&spawn, &spawner, initial_route] {
3311 // Check second shell ran the second entrypoint.
3312 ASSERT_EQ("canReceiveArgumentsWhenEngineSpawn",
3313 spawn->GetEngine()->GetLastEntrypoint());
3314 ASSERT_EQ(initial_route, spawn->GetEngine()->InitialRoute());
3315
3316 ASSERT_NE(spawner->GetEngine()
3317 ->GetRuntimeController()
3318 ->GetRootIsolateGroup(),
3319 0u);
3320 ASSERT_EQ(spawner->GetEngine()
3321 ->GetRuntimeController()
3322 ->GetRootIsolateGroup(),
3323 spawn->GetEngine()
3324 ->GetRuntimeController()
3325 ->GetRootIsolateGroup());
3326 });
3327
3328 PostSync(
3329 task_runner: spawner->GetTaskRunners().GetIOTaskRunner(), task: [&spawner, &spawn] {
3330 ASSERT_EQ(spawner->GetIOManager()->GetResourceContext().get(),
3331 spawn->GetIOManager()->GetResourceContext().get());
3332 });
3333
3334 // Before destroying the shell, wait for expectations of the spawned
3335 // isolate to be met.
3336 second_latch.Wait();
3337
3338 DestroyShell(shell: std::move(spawn));
3339 });
3340
3341 DestroyShell(shell: std::move(shell));
3342 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3343}
3344
3345TEST_F(ShellTest, IOManagerIsSharedBetweenParentAndSpawnedShell) {
3346 auto settings = CreateSettingsForFixture();
3347 auto shell = CreateShell(settings);
3348 ASSERT_TRUE(ValidateShell(shell.get()));
3349
3350 PostSync(task_runner: shell->GetTaskRunners().GetPlatformTaskRunner(), task: [this,
3351 &spawner = shell,
3352 &settings] {
3353 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3354 ASSERT_TRUE(second_configuration.IsValid());
3355 second_configuration.SetEntrypoint("emptyMain");
3356 const std::string initial_route("/foo");
3357 MockPlatformViewDelegate platform_view_delegate;
3358 auto spawn = spawner->Spawn(
3359 run_configuration: std::move(second_configuration), initial_route,
3360 on_create_platform_view: [&platform_view_delegate](Shell& shell) {
3361 auto result = std::make_unique<MockPlatformView>(
3362 args&: platform_view_delegate, args: shell.GetTaskRunners());
3363 ON_CALL(*result, CreateRenderingSurface())
3364 .WillByDefault(action: ::testing::Invoke(
3365 function_impl: [] { return std::make_unique<MockSurface>(); }));
3366 return result;
3367 },
3368 on_create_rasterizer: [](Shell& shell) { return std::make_unique<Rasterizer>(args&: shell); });
3369 ASSERT_TRUE(ValidateShell(spawn.get()));
3370
3371 PostSync(task_runner: spawner->GetTaskRunners().GetIOTaskRunner(), task: [&spawner, &spawn] {
3372 ASSERT_NE(spawner->GetIOManager().get(), nullptr);
3373 ASSERT_EQ(spawner->GetIOManager().get(), spawn->GetIOManager().get());
3374 });
3375
3376 // Destroy the child shell.
3377 DestroyShell(shell: std::move(spawn));
3378 });
3379 // Destroy the parent shell.
3380 DestroyShell(shell: std::move(shell));
3381}
3382
3383TEST_F(ShellTest, IOManagerInSpawnedShellIsNotNullAfterParentShellDestroyed) {
3384 auto settings = CreateSettingsForFixture();
3385 auto shell = CreateShell(settings);
3386 ASSERT_TRUE(ValidateShell(shell.get()));
3387
3388 PostSync(task_runner: shell->GetTaskRunners().GetUITaskRunner(), task: [&shell] {
3389 // We must get engine on UI thread.
3390 auto runtime_controller = shell->GetEngine()->GetRuntimeController();
3391 PostSync(task_runner: shell->GetTaskRunners().GetIOTaskRunner(),
3392 task: [&shell, &runtime_controller] {
3393 // We must get io_manager on IO thread.
3394 auto io_manager = runtime_controller->GetIOManager();
3395 // Check io_manager existence.
3396 ASSERT_NE(io_manager.get(), nullptr);
3397 ASSERT_NE(io_manager->GetSkiaUnrefQueue().get(), nullptr);
3398 // Get io_manager directly from shell and check its existence.
3399 ASSERT_NE(shell->GetIOManager().get(), nullptr);
3400 });
3401 });
3402
3403 std::unique_ptr<Shell> spawn;
3404
3405 PostSync(task_runner: shell->GetTaskRunners().GetPlatformTaskRunner(), task: [&shell, &settings,
3406 &spawn] {
3407 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3408 ASSERT_TRUE(second_configuration.IsValid());
3409 second_configuration.SetEntrypoint("emptyMain");
3410 const std::string initial_route("/foo");
3411 MockPlatformViewDelegate platform_view_delegate;
3412 auto child = shell->Spawn(
3413 run_configuration: std::move(second_configuration), initial_route,
3414 on_create_platform_view: [&platform_view_delegate](Shell& shell) {
3415 auto result = std::make_unique<MockPlatformView>(
3416 args&: platform_view_delegate, args: shell.GetTaskRunners());
3417 ON_CALL(*result, CreateRenderingSurface())
3418 .WillByDefault(action: ::testing::Invoke(
3419 function_impl: [] { return std::make_unique<MockSurface>(); }));
3420 return result;
3421 },
3422 on_create_rasterizer: [](Shell& shell) { return std::make_unique<Rasterizer>(args&: shell); });
3423 spawn = std::move(child);
3424 });
3425 // Destroy the parent shell.
3426 DestroyShell(shell: std::move(shell));
3427
3428 PostSync(task_runner: spawn->GetTaskRunners().GetUITaskRunner(), task: [&spawn] {
3429 // We must get engine on UI thread.
3430 auto runtime_controller = spawn->GetEngine()->GetRuntimeController();
3431 PostSync(task_runner: spawn->GetTaskRunners().GetIOTaskRunner(),
3432 task: [&spawn, &runtime_controller] {
3433 // We must get io_manager on IO thread.
3434 auto io_manager = runtime_controller->GetIOManager();
3435 // Check io_manager existence here.
3436 ASSERT_NE(io_manager.get(), nullptr);
3437 ASSERT_NE(io_manager->GetSkiaUnrefQueue().get(), nullptr);
3438 // Get io_manager directly from shell and check its existence.
3439 ASSERT_NE(spawn->GetIOManager().get(), nullptr);
3440 });
3441 });
3442 // Destroy the child shell.
3443 DestroyShell(shell: std::move(spawn));
3444}
3445
3446TEST_F(ShellTest, ImageGeneratorRegistryNotNullAfterParentShellDestroyed) {
3447 auto settings = CreateSettingsForFixture();
3448 auto shell = CreateShell(settings);
3449 ASSERT_TRUE(ValidateShell(shell.get()));
3450
3451 std::unique_ptr<Shell> spawn;
3452
3453 PostSync(task_runner: shell->GetTaskRunners().GetPlatformTaskRunner(), task: [&shell, &settings,
3454 &spawn] {
3455 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3456 ASSERT_TRUE(second_configuration.IsValid());
3457 second_configuration.SetEntrypoint("emptyMain");
3458 const std::string initial_route("/foo");
3459 MockPlatformViewDelegate platform_view_delegate;
3460 auto child = shell->Spawn(
3461 run_configuration: std::move(second_configuration), initial_route,
3462 on_create_platform_view: [&platform_view_delegate](Shell& shell) {
3463 auto result = std::make_unique<MockPlatformView>(
3464 args&: platform_view_delegate, args: shell.GetTaskRunners());
3465 ON_CALL(*result, CreateRenderingSurface())
3466 .WillByDefault(action: ::testing::Invoke(
3467 function_impl: [] { return std::make_unique<MockSurface>(); }));
3468 return result;
3469 },
3470 on_create_rasterizer: [](Shell& shell) { return std::make_unique<Rasterizer>(args&: shell); });
3471 spawn = std::move(child);
3472 });
3473
3474 PostSync(task_runner: spawn->GetTaskRunners().GetUITaskRunner(), task: [&spawn] {
3475 std::shared_ptr<const DartIsolate> isolate =
3476 spawn->GetEngine()->GetRuntimeController()->GetRootIsolate().lock();
3477 ASSERT_TRUE(isolate);
3478 ASSERT_TRUE(isolate->GetImageGeneratorRegistry());
3479 });
3480
3481 // Destroy the parent shell.
3482 DestroyShell(shell: std::move(shell));
3483
3484 PostSync(task_runner: spawn->GetTaskRunners().GetUITaskRunner(), task: [&spawn] {
3485 std::shared_ptr<const DartIsolate> isolate =
3486 spawn->GetEngine()->GetRuntimeController()->GetRootIsolate().lock();
3487 ASSERT_TRUE(isolate);
3488 ASSERT_TRUE(isolate->GetImageGeneratorRegistry());
3489 });
3490 // Destroy the child shell.
3491 DestroyShell(shell: std::move(spawn));
3492}
3493
3494TEST_F(ShellTest, UpdateAssetResolverByTypeReplaces) {
3495 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3496 Settings settings = CreateSettingsForFixture();
3497
3498 fml::MessageLoop::EnsureInitializedForCurrentThread();
3499 auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
3500 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
3501 task_runner);
3502 auto shell = CreateShell(settings, task_runners);
3503 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3504 ASSERT_TRUE(ValidateShell(shell.get()));
3505
3506 auto configuration = RunConfiguration::InferFromSettings(settings);
3507 configuration.SetEntrypoint("emptyMain");
3508 auto asset_manager = configuration.GetAssetManager();
3509
3510 shell->RunEngine(run_configuration: std::move(configuration), result_callback: [&](auto result) {
3511 ASSERT_EQ(result, Engine::RunStatus::Success);
3512 });
3513
3514 auto platform_view =
3515 std::make_unique<PlatformView>(args&: *shell.get(), args&: task_runners);
3516
3517 auto old_resolver = std::make_unique<TestAssetResolver>(
3518 args: true, args: AssetResolver::AssetResolverType::kApkAssetProvider);
3519 ASSERT_TRUE(old_resolver->IsValid());
3520 asset_manager->PushBack(resolver: std::move(old_resolver));
3521
3522 auto updated_resolver = std::make_unique<TestAssetResolver>(
3523 args: false, args: AssetResolver::AssetResolverType::kApkAssetProvider);
3524 ASSERT_FALSE(updated_resolver->IsValidAfterAssetManagerChange());
3525 platform_view->UpdateAssetResolverByType(
3526 updated_asset_resolver: std::move(updated_resolver),
3527 type: AssetResolver::AssetResolverType::kApkAssetProvider);
3528
3529 auto resolvers = asset_manager->TakeResolvers();
3530 ASSERT_EQ(resolvers.size(), 2ull);
3531 ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
3532
3533 ASSERT_FALSE(resolvers[1]->IsValidAfterAssetManagerChange());
3534
3535 DestroyShell(shell: std::move(shell), task_runners);
3536 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3537}
3538
3539TEST_F(ShellTest, UpdateAssetResolverByTypeAppends) {
3540 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3541 Settings settings = CreateSettingsForFixture();
3542
3543 fml::MessageLoop::EnsureInitializedForCurrentThread();
3544 auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
3545 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
3546 task_runner);
3547 auto shell = CreateShell(settings, task_runners);
3548 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3549 ASSERT_TRUE(ValidateShell(shell.get()));
3550
3551 auto configuration = RunConfiguration::InferFromSettings(settings);
3552 configuration.SetEntrypoint("emptyMain");
3553 auto asset_manager = configuration.GetAssetManager();
3554
3555 shell->RunEngine(run_configuration: std::move(configuration), result_callback: [&](auto result) {
3556 ASSERT_EQ(result, Engine::RunStatus::Success);
3557 });
3558
3559 auto platform_view =
3560 std::make_unique<PlatformView>(args&: *shell.get(), args&: task_runners);
3561
3562 auto updated_resolver = std::make_unique<TestAssetResolver>(
3563 args: false, args: AssetResolver::AssetResolverType::kApkAssetProvider);
3564 ASSERT_FALSE(updated_resolver->IsValidAfterAssetManagerChange());
3565 platform_view->UpdateAssetResolverByType(
3566 updated_asset_resolver: std::move(updated_resolver),
3567 type: AssetResolver::AssetResolverType::kApkAssetProvider);
3568
3569 auto resolvers = asset_manager->TakeResolvers();
3570 ASSERT_EQ(resolvers.size(), 2ull);
3571 ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
3572
3573 ASSERT_FALSE(resolvers[1]->IsValidAfterAssetManagerChange());
3574
3575 DestroyShell(shell: std::move(shell), task_runners);
3576 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3577}
3578
3579TEST_F(ShellTest, UpdateAssetResolverByTypeNull) {
3580 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3581 Settings settings = CreateSettingsForFixture();
3582 ThreadHost thread_host(ThreadHost::ThreadHostConfig(
3583 "io.flutter.test." + GetCurrentTestName() + ".",
3584 ThreadHost::Type::Platform));
3585 auto task_runner = thread_host.platform_thread->GetTaskRunner();
3586 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
3587 task_runner);
3588 auto shell = CreateShell(settings, task_runners);
3589 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3590 ASSERT_TRUE(ValidateShell(shell.get()));
3591
3592 auto configuration = RunConfiguration::InferFromSettings(settings);
3593 configuration.SetEntrypoint("emptyMain");
3594 auto asset_manager = configuration.GetAssetManager();
3595 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3596
3597 auto platform_view =
3598 std::make_unique<PlatformView>(args&: *shell.get(), args&: task_runners);
3599
3600 auto old_resolver = std::make_unique<TestAssetResolver>(
3601 args: true, args: AssetResolver::AssetResolverType::kApkAssetProvider);
3602 ASSERT_TRUE(old_resolver->IsValid());
3603 asset_manager->PushBack(resolver: std::move(old_resolver));
3604
3605 platform_view->UpdateAssetResolverByType(
3606 updated_asset_resolver: nullptr, type: AssetResolver::AssetResolverType::kApkAssetProvider);
3607
3608 auto resolvers = asset_manager->TakeResolvers();
3609 ASSERT_EQ(resolvers.size(), 2ull);
3610 ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
3611 ASSERT_TRUE(resolvers[1]->IsValidAfterAssetManagerChange());
3612
3613 DestroyShell(shell: std::move(shell), task_runners);
3614 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3615}
3616
3617TEST_F(ShellTest, UpdateAssetResolverByTypeDoesNotReplaceMismatchType) {
3618 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3619 Settings settings = CreateSettingsForFixture();
3620
3621 fml::MessageLoop::EnsureInitializedForCurrentThread();
3622 auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
3623 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
3624 task_runner);
3625 auto shell = CreateShell(settings, task_runners);
3626 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3627 ASSERT_TRUE(ValidateShell(shell.get()));
3628
3629 auto configuration = RunConfiguration::InferFromSettings(settings);
3630 configuration.SetEntrypoint("emptyMain");
3631 auto asset_manager = configuration.GetAssetManager();
3632
3633 shell->RunEngine(run_configuration: std::move(configuration), result_callback: [&](auto result) {
3634 ASSERT_EQ(result, Engine::RunStatus::Success);
3635 });
3636
3637 auto platform_view =
3638 std::make_unique<PlatformView>(args&: *shell.get(), args&: task_runners);
3639
3640 auto old_resolver = std::make_unique<TestAssetResolver>(
3641 args: true, args: AssetResolver::AssetResolverType::kAssetManager);
3642 ASSERT_TRUE(old_resolver->IsValid());
3643 asset_manager->PushBack(resolver: std::move(old_resolver));
3644
3645 auto updated_resolver = std::make_unique<TestAssetResolver>(
3646 args: false, args: AssetResolver::AssetResolverType::kApkAssetProvider);
3647 ASSERT_FALSE(updated_resolver->IsValidAfterAssetManagerChange());
3648 platform_view->UpdateAssetResolverByType(
3649 updated_asset_resolver: std::move(updated_resolver),
3650 type: AssetResolver::AssetResolverType::kApkAssetProvider);
3651
3652 auto resolvers = asset_manager->TakeResolvers();
3653 ASSERT_EQ(resolvers.size(), 3ull);
3654 ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
3655
3656 ASSERT_TRUE(resolvers[1]->IsValidAfterAssetManagerChange());
3657
3658 ASSERT_FALSE(resolvers[2]->IsValidAfterAssetManagerChange());
3659
3660 DestroyShell(shell: std::move(shell), task_runners);
3661 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3662}
3663
3664TEST_F(ShellTest, CanCreateShellsWithGLBackend) {
3665#if !SHELL_ENABLE_GL
3666 // GL emulation does not exist on Fuchsia.
3667 GTEST_SKIP();
3668#endif // !SHELL_ENABLE_GL
3669 auto settings = CreateSettingsForFixture();
3670 std::unique_ptr<Shell> shell = CreateShell(config: {
3671 .settings = settings,
3672 .platform_view_create_callback = ShellTestPlatformViewBuilder({
3673 .rendering_backend = ShellTestPlatformView::BackendType::kGLBackend,
3674 }),
3675 });
3676 ASSERT_NE(shell, nullptr);
3677 ASSERT_TRUE(shell->IsSetup());
3678 auto configuration = RunConfiguration::InferFromSettings(settings);
3679 PlatformViewNotifyCreated(shell: shell.get());
3680 configuration.SetEntrypoint("emptyMain");
3681 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3682 PumpOneFrame(shell: shell.get());
3683 PlatformViewNotifyDestroyed(shell: shell.get());
3684 DestroyShell(shell: std::move(shell));
3685}
3686
3687TEST_F(ShellTest, CanCreateShellsWithVulkanBackend) {
3688#if !SHELL_ENABLE_VULKAN
3689 GTEST_SKIP();
3690#endif // !SHELL_ENABLE_VULKAN
3691 auto settings = CreateSettingsForFixture();
3692 std::unique_ptr<Shell> shell = CreateShell(config: {
3693 .settings = settings,
3694 .platform_view_create_callback = ShellTestPlatformViewBuilder({
3695 .rendering_backend =
3696 ShellTestPlatformView::BackendType::kVulkanBackend,
3697 }),
3698 });
3699 ASSERT_NE(shell, nullptr);
3700 ASSERT_TRUE(shell->IsSetup());
3701 auto configuration = RunConfiguration::InferFromSettings(settings);
3702 PlatformViewNotifyCreated(shell: shell.get());
3703 configuration.SetEntrypoint("emptyMain");
3704 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3705 PumpOneFrame(shell: shell.get());
3706 PlatformViewNotifyDestroyed(shell: shell.get());
3707 DestroyShell(shell: std::move(shell));
3708}
3709
3710TEST_F(ShellTest, CanCreateShellsWithMetalBackend) {
3711#if !SHELL_ENABLE_METAL
3712 GTEST_SKIP();
3713#endif // !SHELL_ENABLE_METAL
3714 auto settings = CreateSettingsForFixture();
3715 std::unique_ptr<Shell> shell = CreateShell(config: {
3716 .settings = settings,
3717 .platform_view_create_callback = ShellTestPlatformViewBuilder({
3718 .rendering_backend =
3719 ShellTestPlatformView::BackendType::kMetalBackend,
3720 }),
3721 });
3722 ASSERT_NE(shell, nullptr);
3723 ASSERT_TRUE(shell->IsSetup());
3724 auto configuration = RunConfiguration::InferFromSettings(settings);
3725 PlatformViewNotifyCreated(shell: shell.get());
3726 configuration.SetEntrypoint("emptyMain");
3727 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3728 PumpOneFrame(shell: shell.get());
3729 PlatformViewNotifyDestroyed(shell: shell.get());
3730 DestroyShell(shell: std::move(shell));
3731}
3732
3733TEST_F(ShellTest, UserTagSetOnStartup) {
3734 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3735 // Make sure the shell launch does not kick off the creation of the VM
3736 // instance by already creating one upfront.
3737 auto vm_settings = CreateSettingsForFixture();
3738 auto vm_ref = DartVMRef::Create(settings: vm_settings);
3739 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3740
3741 auto settings = vm_settings;
3742 fml::AutoResetWaitableEvent isolate_create_latch;
3743
3744 // ensure that "AppStartUpTag" is set during isolate creation.
3745 settings.root_isolate_create_callback = [&](const DartIsolate& isolate) {
3746 Dart_Handle current_tag = Dart_GetCurrentUserTag();
3747 Dart_Handle startup_tag = Dart_NewUserTag(label: "AppStartUp");
3748 EXPECT_TRUE(Dart_IdentityEquals(current_tag, startup_tag));
3749
3750 isolate_create_latch.Signal();
3751 };
3752
3753 auto shell = CreateShell(settings);
3754 ASSERT_TRUE(ValidateShell(shell.get()));
3755
3756 auto configuration = RunConfiguration::InferFromSettings(settings);
3757 ASSERT_TRUE(configuration.IsValid());
3758
3759 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3760 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3761
3762 DestroyShell(shell: std::move(shell));
3763 isolate_create_latch.Wait();
3764}
3765
3766TEST_F(ShellTest, PrefetchDefaultFontManager) {
3767 auto settings = CreateSettingsForFixture();
3768 settings.prefetched_default_font_manager = true;
3769 std::unique_ptr<Shell> shell;
3770
3771 auto get_font_manager_count = [&] {
3772 fml::AutoResetWaitableEvent latch;
3773 size_t font_manager_count;
3774 fml::TaskRunner::RunNowOrPostTask(
3775 runner: shell->GetTaskRunners().GetUITaskRunner(),
3776 task: [this, &shell, &latch, &font_manager_count]() {
3777 font_manager_count =
3778 GetFontCollection(shell: shell.get())->GetFontManagersCount();
3779 latch.Signal();
3780 });
3781 latch.Wait();
3782 return font_manager_count;
3783 };
3784 size_t initial_font_manager_count = 0;
3785 settings.root_isolate_create_callback = [&](const auto& isolate) {
3786 ASSERT_GT(initial_font_manager_count, 0ul);
3787 // Should not have fetched the default font manager yet, since the root
3788 // isolate was only just created.
3789 ASSERT_EQ(get_font_manager_count(), initial_font_manager_count);
3790 };
3791
3792 shell = CreateShell(settings);
3793
3794 initial_font_manager_count = get_font_manager_count();
3795
3796 auto configuration = RunConfiguration::InferFromSettings(settings);
3797 configuration.SetEntrypoint("emptyMain");
3798 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3799
3800 // If the prefetched_default_font_manager flag is set, then the default font
3801 // manager will not be added until the engine starts running.
3802 ASSERT_EQ(get_font_manager_count(), initial_font_manager_count + 1);
3803
3804 DestroyShell(shell: std::move(shell));
3805}
3806
3807TEST_F(ShellTest, OnPlatformViewCreatedWhenUIThreadIsBusy) {
3808 // This test will deadlock if the threading logic in
3809 // Shell::OnCreatePlatformView is wrong.
3810 auto settings = CreateSettingsForFixture();
3811 auto shell = CreateShell(settings);
3812
3813 fml::AutoResetWaitableEvent latch;
3814 fml::TaskRunner::RunNowOrPostTask(runner: shell->GetTaskRunners().GetUITaskRunner(),
3815 task: [&latch]() { latch.Wait(); });
3816
3817 ShellTest::PlatformViewNotifyCreated(shell: shell.get());
3818 latch.Signal();
3819
3820 DestroyShell(shell: std::move(shell));
3821}
3822
3823TEST_F(ShellTest, UIWorkAfterOnPlatformViewDestroyed) {
3824 auto settings = CreateSettingsForFixture();
3825 auto shell = CreateShell(settings);
3826 auto configuration = RunConfiguration::InferFromSettings(settings);
3827 configuration.SetEntrypoint("drawFrames");
3828
3829 fml::AutoResetWaitableEvent latch;
3830 fml::AutoResetWaitableEvent notify_native_latch;
3831 AddNativeCallback(name: "NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
3832 notify_native_latch.Signal();
3833 latch.Wait();
3834 }));
3835
3836 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3837 // Wait to make sure we get called back from Dart and thus have latched
3838 // the UI thread before we create/destroy the platform view.
3839 notify_native_latch.Wait();
3840
3841 ShellTest::PlatformViewNotifyCreated(shell: shell.get());
3842
3843 fml::AutoResetWaitableEvent destroy_latch;
3844 fml::TaskRunner::RunNowOrPostTask(
3845 runner: shell->GetTaskRunners().GetPlatformTaskRunner(),
3846 task: [&shell, &destroy_latch]() {
3847 shell->GetPlatformView()->NotifyDestroyed();
3848 destroy_latch.Signal();
3849 });
3850
3851 destroy_latch.Wait();
3852
3853 // Unlatch the UI thread and let it send us a scene to render.
3854 latch.Signal();
3855
3856 // Flush the UI task runner to make sure we process the render/scheduleFrame
3857 // request.
3858 fml::AutoResetWaitableEvent ui_flush_latch;
3859 fml::TaskRunner::RunNowOrPostTask(
3860 runner: shell->GetTaskRunners().GetUITaskRunner(),
3861 task: [&ui_flush_latch]() { ui_flush_latch.Signal(); });
3862 ui_flush_latch.Wait();
3863 DestroyShell(shell: std::move(shell));
3864}
3865
3866TEST_F(ShellTest, UsesPlatformMessageHandler) {
3867 TaskRunners task_runners = GetTaskRunnersForFixture();
3868 auto settings = CreateSettingsForFixture();
3869 MockPlatformViewDelegate platform_view_delegate;
3870 auto platform_message_handler =
3871 std::make_shared<MockPlatformMessageHandler>();
3872 int message_id = 1;
3873 EXPECT_CALL(*platform_message_handler, HandlePlatformMessage(_));
3874 EXPECT_CALL(*platform_message_handler,
3875 InvokePlatformMessageEmptyResponseCallback(message_id));
3876 Shell::CreateCallback<PlatformView> platform_view_create_callback =
3877 [&platform_view_delegate, task_runners,
3878 platform_message_handler](flutter::Shell& shell) {
3879 auto result = std::make_unique<MockPlatformView>(args&: platform_view_delegate,
3880 args: task_runners);
3881 EXPECT_CALL(*result, GetPlatformMessageHandler())
3882 .WillOnce(action: Return(value: platform_message_handler));
3883 return result;
3884 };
3885 auto shell = CreateShell(config: {
3886 .settings = settings,
3887 .task_runners = task_runners,
3888 .platform_view_create_callback = platform_view_create_callback,
3889 });
3890
3891 EXPECT_EQ(platform_message_handler, shell->GetPlatformMessageHandler());
3892 PostSync(task_runner: task_runners.GetUITaskRunner(), task: [&shell]() {
3893 size_t data_size = 4;
3894 fml::MallocMapping bytes =
3895 fml::MallocMapping(static_cast<uint8_t*>(malloc(size: data_size)), data_size);
3896 fml::RefPtr<MockPlatformMessageResponse> response =
3897 MockPlatformMessageResponse::Create();
3898 auto message = std::make_unique<PlatformMessage>(
3899 /*channel=*/args: "foo", /*data=*/args: std::move(bytes), /*response=*/args&: response);
3900 (static_cast<Engine::Delegate*>(shell.get()))
3901 ->OnEngineHandlePlatformMessage(message: std::move(message));
3902 });
3903 shell->GetPlatformMessageHandler()
3904 ->InvokePlatformMessageEmptyResponseCallback(response_id: message_id);
3905 DestroyShell(shell: std::move(shell));
3906}
3907
3908TEST_F(ShellTest, SpawnWorksWithOnError) {
3909 auto settings = CreateSettingsForFixture();
3910 auto shell = CreateShell(settings);
3911 ASSERT_TRUE(ValidateShell(shell.get()));
3912
3913 auto configuration = RunConfiguration::InferFromSettings(settings);
3914 ASSERT_TRUE(configuration.IsValid());
3915 configuration.SetEntrypoint("onErrorA");
3916
3917 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3918 ASSERT_TRUE(second_configuration.IsValid());
3919 second_configuration.SetEntrypoint("onErrorB");
3920
3921 fml::CountDownLatch latch(2);
3922
3923 AddNativeCallback(
3924 name: "NotifyErrorA", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
3925 auto string_handle = Dart_GetNativeArgument(args, 0);
3926 const char* c_str;
3927 Dart_StringToCString(string_handle, &c_str);
3928 EXPECT_STREQ(c_str, "Exception: I should be coming from A");
3929 latch.CountDown();
3930 }));
3931
3932 AddNativeCallback(
3933 name: "NotifyErrorB", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
3934 auto string_handle = Dart_GetNativeArgument(args, 0);
3935 const char* c_str;
3936 Dart_StringToCString(string_handle, &c_str);
3937 EXPECT_STREQ(c_str, "Exception: I should be coming from B");
3938 latch.CountDown();
3939 }));
3940
3941 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3942
3943 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3944
3945 PostSync(
3946 task_runner: shell->GetTaskRunners().GetPlatformTaskRunner(),
3947 task: [this, &spawner = shell, &second_configuration, &latch]() {
3948 ::testing::NiceMock<MockPlatformViewDelegate> platform_view_delegate;
3949 auto spawn = spawner->Spawn(
3950 run_configuration: std::move(second_configuration), initial_route: "",
3951 on_create_platform_view: [&platform_view_delegate](Shell& shell) {
3952 auto result =
3953 std::make_unique<::testing::NiceMock<MockPlatformView>>(
3954 args&: platform_view_delegate, args: shell.GetTaskRunners());
3955 ON_CALL(*result, CreateRenderingSurface())
3956 .WillByDefault(action: ::testing::Invoke(function_impl: [] {
3957 return std::make_unique<::testing::NiceMock<MockSurface>>();
3958 }));
3959 return result;
3960 },
3961 on_create_rasterizer: [](Shell& shell) { return std::make_unique<Rasterizer>(args&: shell); });
3962 ASSERT_NE(nullptr, spawn.get());
3963 ASSERT_TRUE(ValidateShell(spawn.get()));
3964
3965 // Before destroying the shell, wait for expectations of the spawned
3966 // isolate to be met.
3967 latch.Wait();
3968
3969 DestroyShell(shell: std::move(spawn));
3970 });
3971
3972 DestroyShell(shell: std::move(shell));
3973 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3974}
3975
3976TEST_F(ShellTest, ImmutableBufferLoadsAssetOnBackgroundThread) {
3977 Settings settings = CreateSettingsForFixture();
3978 auto task_runner = CreateNewThread();
3979 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
3980 task_runner);
3981 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
3982
3983 fml::CountDownLatch latch(1);
3984 AddNativeCallback(name: "NotifyNative",
3985 CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); }));
3986
3987 // Create the surface needed by rasterizer
3988 PlatformViewNotifyCreated(shell: shell.get());
3989
3990 auto configuration = RunConfiguration::InferFromSettings(settings);
3991 configuration.SetEntrypoint("testThatAssetLoadingHappensOnWorkerThread");
3992 auto asset_manager = configuration.GetAssetManager();
3993 auto test_resolver = std::make_unique<ThreadCheckingAssetResolver>(
3994 args: shell->GetDartVM()->GetConcurrentMessageLoop());
3995 auto leaked_resolver = test_resolver.get();
3996 asset_manager->PushBack(resolver: std::move(test_resolver));
3997
3998 RunEngine(shell: shell.get(), configuration: std::move(configuration));
3999 PumpOneFrame(shell: shell.get());
4000
4001 latch.Wait();
4002
4003 EXPECT_EQ(leaked_resolver->mapping_requests[0], "DoesNotExist");
4004
4005 PlatformViewNotifyDestroyed(shell: shell.get());
4006 DestroyShell(shell: std::move(shell), task_runners);
4007}
4008
4009TEST_F(ShellTest, PictureToImageSync) {
4010#if !SHELL_ENABLE_GL
4011 // This test uses the GL backend.
4012 GTEST_SKIP();
4013#endif // !SHELL_ENABLE_GL
4014 auto settings = CreateSettingsForFixture();
4015 std::unique_ptr<Shell> shell = CreateShell(config: {
4016 .settings = settings,
4017 .platform_view_create_callback = ShellTestPlatformViewBuilder({
4018 .rendering_backend = ShellTestPlatformView::BackendType::kGLBackend,
4019 }),
4020 });
4021
4022 AddNativeCallback(name: "NativeOnBeforeToImageSync",
4023 CREATE_NATIVE_ENTRY([&](auto args) {
4024 // nop
4025 }));
4026
4027 fml::CountDownLatch latch(2);
4028 AddNativeCallback(name: "NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
4029 // Teardown and set up rasterizer again.
4030 PlatformViewNotifyDestroyed(shell.get());
4031 PlatformViewNotifyCreated(shell.get());
4032 latch.CountDown();
4033 }));
4034
4035 ASSERT_NE(shell, nullptr);
4036 ASSERT_TRUE(shell->IsSetup());
4037 auto configuration = RunConfiguration::InferFromSettings(settings);
4038 PlatformViewNotifyCreated(shell: shell.get());
4039 configuration.SetEntrypoint("toImageSync");
4040 RunEngine(shell: shell.get(), configuration: std::move(configuration));
4041 PumpOneFrame(shell: shell.get());
4042
4043 latch.Wait();
4044
4045 PlatformViewNotifyDestroyed(shell: shell.get());
4046 DestroyShell(shell: std::move(shell));
4047}
4048
4049TEST_F(ShellTest, PictureToImageSyncImpellerNoSurface) {
4050#if !SHELL_ENABLE_METAL
4051 // This test uses the Metal backend.
4052 GTEST_SKIP();
4053#endif // !SHELL_ENABLE_METAL
4054 auto settings = CreateSettingsForFixture();
4055 settings.enable_impeller = true;
4056 std::unique_ptr<Shell> shell = CreateShell(config: {
4057 .settings = settings,
4058 .platform_view_create_callback = ShellTestPlatformViewBuilder({
4059 .rendering_backend =
4060 ShellTestPlatformView::BackendType::kMetalBackend,
4061 }),
4062 });
4063
4064 AddNativeCallback(name: "NativeOnBeforeToImageSync",
4065 CREATE_NATIVE_ENTRY([&](auto args) {
4066 // nop
4067 }));
4068
4069 fml::CountDownLatch latch(2);
4070 AddNativeCallback(name: "NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
4071 // Teardown and set up rasterizer again.
4072 PlatformViewNotifyDestroyed(shell.get());
4073 PlatformViewNotifyCreated(shell.get());
4074 latch.CountDown();
4075 }));
4076
4077 ASSERT_NE(shell, nullptr);
4078 ASSERT_TRUE(shell->IsSetup());
4079 auto configuration = RunConfiguration::InferFromSettings(settings);
4080
4081 // Important: Do not create the platform view yet!
4082 // This test is making sure that the rasterizer can create the texture
4083 // as expected without a surface.
4084
4085 configuration.SetEntrypoint("toImageSync");
4086 RunEngine(shell: shell.get(), configuration: std::move(configuration));
4087 PumpOneFrame(shell: shell.get());
4088
4089 latch.Wait();
4090
4091 PlatformViewNotifyDestroyed(shell: shell.get());
4092 DestroyShell(shell: std::move(shell));
4093}
4094
4095#if SHELL_ENABLE_GL
4096// This test uses the GL backend and refers to symbols in egl.h
4097TEST_F(ShellTest, PictureToImageSyncWithTrampledContext) {
4098 // make it easier to trample the GL context by running on a single task
4099 // runner.
4100 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4101 ThreadHost::Type::Platform);
4102 auto task_runner = thread_host.platform_thread->GetTaskRunner();
4103 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4104 task_runner);
4105
4106 auto settings = CreateSettingsForFixture();
4107 std::unique_ptr<Shell> shell = CreateShell(config: {
4108 .settings = settings,
4109 .task_runners = task_runners,
4110 .platform_view_create_callback = ShellTestPlatformViewBuilder({
4111 .rendering_backend = ShellTestPlatformView::BackendType::kGLBackend,
4112 }),
4113 });
4114
4115 AddNativeCallback(
4116 name: "NativeOnBeforeToImageSync", CREATE_NATIVE_ENTRY([&](auto args) {
4117 // Trample the GL context. If the rasterizer fails
4118 // to make the right one current again, test will
4119 // fail.
4120 ::eglMakeCurrent(::eglGetCurrentDisplay(), NULL, NULL, NULL);
4121 }));
4122
4123 fml::CountDownLatch latch(2);
4124 AddNativeCallback(name: "NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
4125 // Teardown and set up rasterizer again.
4126 PlatformViewNotifyDestroyed(shell.get());
4127 PlatformViewNotifyCreated(shell.get());
4128 latch.CountDown();
4129 }));
4130
4131 ASSERT_NE(shell, nullptr);
4132 ASSERT_TRUE(shell->IsSetup());
4133 auto configuration = RunConfiguration::InferFromSettings(settings);
4134 PlatformViewNotifyCreated(shell: shell.get());
4135 configuration.SetEntrypoint("toImageSync");
4136 RunEngine(shell: shell.get(), configuration: std::move(configuration));
4137 PumpOneFrame(shell: shell.get());
4138
4139 latch.Wait();
4140
4141 PlatformViewNotifyDestroyed(shell: shell.get());
4142 DestroyShell(shell: std::move(shell), task_runners);
4143}
4144#endif // SHELL_ENABLE_GL
4145
4146TEST_F(ShellTest, PluginUtilitiesCallbackHandleErrorHandling) {
4147 auto settings = CreateSettingsForFixture();
4148 std::unique_ptr<Shell> shell = CreateShell(settings);
4149
4150 fml::AutoResetWaitableEvent latch;
4151 bool test_passed;
4152 AddNativeCallback(name: "NotifyNativeBool", CREATE_NATIVE_ENTRY([&](auto args) {
4153 Dart_Handle exception = nullptr;
4154 test_passed = tonic::DartConverter<bool>::FromArguments(
4155 args, 0, exception);
4156 latch.Signal();
4157 }));
4158
4159 ASSERT_NE(shell, nullptr);
4160 ASSERT_TRUE(shell->IsSetup());
4161 auto configuration = RunConfiguration::InferFromSettings(settings);
4162 PlatformViewNotifyCreated(shell: shell.get());
4163 configuration.SetEntrypoint("testPluginUtilitiesCallbackHandle");
4164 RunEngine(shell: shell.get(), configuration: std::move(configuration));
4165 PumpOneFrame(shell: shell.get());
4166
4167 latch.Wait();
4168
4169 ASSERT_TRUE(test_passed);
4170
4171 PlatformViewNotifyDestroyed(shell: shell.get());
4172 DestroyShell(shell: std::move(shell));
4173}
4174
4175TEST_F(ShellTest, NotifyIdleRejectsPastAndNearFuture) {
4176 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4177 Settings settings = CreateSettingsForFixture();
4178 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4179 ThreadHost::Type::Platform | ThreadHost::UI |
4180 ThreadHost::IO | ThreadHost::RASTER);
4181 auto platform_task_runner = thread_host.platform_thread->GetTaskRunner();
4182 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
4183 thread_host.raster_thread->GetTaskRunner(),
4184 thread_host.ui_thread->GetTaskRunner(),
4185 thread_host.io_thread->GetTaskRunner());
4186 auto shell = CreateShell(settings, task_runners);
4187 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
4188 ASSERT_TRUE(ValidateShell(shell.get()));
4189
4190 fml::AutoResetWaitableEvent latch;
4191
4192 auto configuration = RunConfiguration::InferFromSettings(settings);
4193 configuration.SetEntrypoint("emptyMain");
4194 RunEngine(shell: shell.get(), configuration: std::move(configuration));
4195
4196 fml::TaskRunner::RunNowOrPostTask(
4197 runner: task_runners.GetUITaskRunner(), task: [&latch, &shell]() {
4198 auto runtime_controller = const_cast<RuntimeController*>(
4199 shell->GetEngine()->GetRuntimeController());
4200
4201 auto now = fml::TimeDelta::FromMicroseconds(micros: Dart_TimelineGetMicros());
4202
4203 EXPECT_FALSE(runtime_controller->NotifyIdle(
4204 now - fml::TimeDelta::FromMilliseconds(10)));
4205 EXPECT_FALSE(runtime_controller->NotifyIdle(now));
4206 EXPECT_FALSE(runtime_controller->NotifyIdle(
4207 now + fml::TimeDelta::FromNanoseconds(100)));
4208
4209 EXPECT_TRUE(runtime_controller->NotifyIdle(
4210 now + fml::TimeDelta::FromMilliseconds(100)));
4211 latch.Signal();
4212 });
4213
4214 latch.Wait();
4215
4216 DestroyShell(shell: std::move(shell), task_runners);
4217 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4218}
4219
4220TEST_F(ShellTest, NotifyIdleNotCalledInLatencyMode) {
4221 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4222 Settings settings = CreateSettingsForFixture();
4223 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4224 ThreadHost::Type::Platform | ThreadHost::UI |
4225 ThreadHost::IO | ThreadHost::RASTER);
4226 auto platform_task_runner = thread_host.platform_thread->GetTaskRunner();
4227 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
4228 thread_host.raster_thread->GetTaskRunner(),
4229 thread_host.ui_thread->GetTaskRunner(),
4230 thread_host.io_thread->GetTaskRunner());
4231 auto shell = CreateShell(settings, task_runners);
4232 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
4233 ASSERT_TRUE(ValidateShell(shell.get()));
4234
4235 // we start off in balanced mode, where we expect idle notifications to
4236 // succeed. After the first `NotifyNativeBool` we expect to be in latency
4237 // mode, where we expect idle notifications to fail.
4238 fml::CountDownLatch latch(2);
4239 AddNativeCallback(
4240 name: "NotifyNativeBool", CREATE_NATIVE_ENTRY([&](auto args) {
4241 Dart_Handle exception = nullptr;
4242 bool is_in_latency_mode =
4243 tonic::DartConverter<bool>::FromArguments(args, 0, exception);
4244 auto runtime_controller = const_cast<RuntimeController*>(
4245 shell->GetEngine()->GetRuntimeController());
4246 bool success =
4247 runtime_controller->NotifyIdle(fml::TimeDelta::FromMicroseconds(
4248 Dart_TimelineGetMicros() + 100000));
4249 EXPECT_EQ(success, !is_in_latency_mode);
4250 latch.CountDown();
4251 }));
4252
4253 auto configuration = RunConfiguration::InferFromSettings(settings);
4254 configuration.SetEntrypoint("performanceModeImpactsNotifyIdle");
4255 RunEngine(shell: shell.get(), configuration: std::move(configuration));
4256
4257 latch.Wait();
4258
4259 DestroyShell(shell: std::move(shell), task_runners);
4260 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4261}
4262
4263TEST_F(ShellTest, NotifyDestroyed) {
4264 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4265 Settings settings = CreateSettingsForFixture();
4266 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4267 ThreadHost::Type::Platform | ThreadHost::UI |
4268 ThreadHost::IO | ThreadHost::RASTER);
4269 auto platform_task_runner = thread_host.platform_thread->GetTaskRunner();
4270 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
4271 thread_host.raster_thread->GetTaskRunner(),
4272 thread_host.ui_thread->GetTaskRunner(),
4273 thread_host.io_thread->GetTaskRunner());
4274 auto shell = CreateShell(settings, task_runners);
4275 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
4276 ASSERT_TRUE(ValidateShell(shell.get()));
4277
4278 fml::CountDownLatch latch(1);
4279 AddNativeCallback(name: "NotifyDestroyed", CREATE_NATIVE_ENTRY([&](auto args) {
4280 auto runtime_controller = const_cast<RuntimeController*>(
4281 shell->GetEngine()->GetRuntimeController());
4282 bool success = runtime_controller->NotifyDestroyed();
4283 EXPECT_TRUE(success);
4284 latch.CountDown();
4285 }));
4286
4287 auto configuration = RunConfiguration::InferFromSettings(settings);
4288 configuration.SetEntrypoint("callNotifyDestroyed");
4289 RunEngine(shell: shell.get(), configuration: std::move(configuration));
4290
4291 latch.Wait();
4292
4293 DestroyShell(shell: std::move(shell), task_runners);
4294 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4295}
4296
4297TEST_F(ShellTest, PrintsErrorWhenPlatformMessageSentFromWrongThread) {
4298#if FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG || OS_FUCHSIA
4299 GTEST_SKIP() << "Test is for debug mode only on non-fuchsia targets.";
4300#endif
4301 Settings settings = CreateSettingsForFixture();
4302 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4303 ThreadHost::Type::Platform);
4304 auto task_runner = thread_host.platform_thread->GetTaskRunner();
4305 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4306 task_runner);
4307 auto shell = CreateShell(settings, task_runners);
4308
4309 {
4310 fml::testing::LogCapture log_capture;
4311
4312 // The next call will result in a thread checker violation.
4313 fml::ThreadChecker::DisableNextThreadCheckFailure();
4314 SendPlatformMessage(shell: shell.get(), message: std::make_unique<PlatformMessage>(
4315 args: "com.test.plugin", args: nullptr));
4316
4317 EXPECT_THAT(
4318 log_capture.str(),
4319 ::testing::EndsWith(
4320 "The 'com.test.plugin' channel sent a message from native to "
4321 "Flutter on a non-platform thread. Platform channel messages "
4322 "must be sent on the platform thread. Failure to do so may "
4323 "result in data loss or crashes, and must be fixed in the "
4324 "plugin or application code creating that channel.\nSee "
4325 "https://docs.flutter.dev/platform-integration/"
4326 "platform-channels#channels-and-platform-threading for more "
4327 "information.\n"));
4328 }
4329
4330 {
4331 fml::testing::LogCapture log_capture;
4332
4333 // The next call will result in a thread checker violation.
4334 fml::ThreadChecker::DisableNextThreadCheckFailure();
4335 SendPlatformMessage(shell: shell.get(), message: std::make_unique<PlatformMessage>(
4336 args: "com.test.plugin", args: nullptr));
4337
4338 EXPECT_EQ(log_capture.str(), "");
4339 }
4340
4341 DestroyShell(shell: std::move(shell), task_runners);
4342 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4343}
4344
4345TEST_F(ShellTest, DiesIfSoftwareRenderingAndImpellerAreEnabledDeathTest) {
4346#if defined(OS_FUCHSIA)
4347 GTEST_SKIP() << "Fuchsia";
4348#endif // OS_FUCHSIA
4349 ::testing::FLAGS_gtest_death_test_style = "threadsafe";
4350 Settings settings = CreateSettingsForFixture();
4351 settings.enable_impeller = true;
4352 settings.enable_software_rendering = true;
4353 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4354 ThreadHost::Type::Platform);
4355 auto task_runner = thread_host.platform_thread->GetTaskRunner();
4356 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4357 task_runner);
4358 EXPECT_DEATH_IF_SUPPORTED(
4359 CreateShell(settings, task_runners),
4360 "Software rendering is incompatible with Impeller.");
4361}
4362
4363} // namespace testing
4364} // namespace flutter
4365
4366// NOLINTEND(clang-analyzer-core.StackAddressEscape)
4367

source code of flutter_engine/flutter/shell/common/shell_unittests.cc