Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
shell_unittests.cc
Go to the documentation of this file.
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#define FML_USED_ON_EMBEDDER
6
7#include <algorithm>
8#include <ctime>
9#include <future>
10#include <memory>
11#include <thread>
12#include <utility>
13#include <vector>
14
15#if SHELL_ENABLE_GL
16#include <EGL/egl.h>
17#endif // SHELL_ENABLE_GL
18
47#include "fml/mapping.h"
48#include "gmock/gmock.h"
51#include "third_party/rapidjson/include/rapidjson/writer.h"
52#include "third_party/skia/include/codec/SkCodecAnimation.h"
53#include "third_party/skia/include/gpu/ganesh/mock/GrMockTypes.h"
55
56#ifdef SHELL_ENABLE_VULKAN
57#include "flutter/vulkan/vulkan_application.h" // nogncheck
58#endif
59
60// CREATE_NATIVE_ENTRY is leaky by design
61// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
62
63namespace flutter {
64namespace testing {
65
66constexpr int64_t kImplicitViewId = 0ll;
67
68using ::testing::_;
69using ::testing::Return;
70
71namespace {
72
73std::unique_ptr<PlatformMessage> MakePlatformMessage(
74 const std::string& channel,
75 const std::map<std::string, std::string>& values,
77 rapidjson::Document document;
78 auto& allocator = document.GetAllocator();
79 document.SetObject();
80
81 for (const auto& pair : values) {
82 rapidjson::Value key(pair.first.c_str(), strlen(pair.first.c_str()),
83 allocator);
84 rapidjson::Value value(pair.second.c_str(), strlen(pair.second.c_str()),
85 allocator);
86 document.AddMember(key, value, allocator);
87 }
88
89 rapidjson::StringBuffer buffer;
90 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
91 document.Accept(writer);
92 const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
93
94 std::unique_ptr<PlatformMessage> message = std::make_unique<PlatformMessage>(
95 channel, fml::MallocMapping::Copy(data, buffer.GetSize()), response);
96 return message;
97}
98
99class MockPlatformViewDelegate : public PlatformView::Delegate {
100 MOCK_METHOD(void,
101 OnPlatformViewCreated,
102 (std::unique_ptr<Surface> surface),
103 (override));
104
105 MOCK_METHOD(void, OnPlatformViewDestroyed, (), (override));
106
107 MOCK_METHOD(void, OnPlatformViewScheduleFrame, (), (override));
108
109 MOCK_METHOD(void,
110 OnPlatformViewAddView,
111 (int64_t view_id,
112 const ViewportMetrics& viewport_metrics,
113 AddViewCallback callback),
114 (override));
115
116 MOCK_METHOD(void,
117 OnPlatformViewRemoveView,
118 (int64_t view_id, RemoveViewCallback callback),
119 (override));
120
121 MOCK_METHOD(void,
122 OnPlatformViewSendViewFocusEvent,
123 (const ViewFocusEvent& event),
124 (override));
125
126 MOCK_METHOD(void,
127 OnPlatformViewSetNextFrameCallback,
128 (const fml::closure& closure),
129 (override));
130
131 MOCK_METHOD(void,
132 OnPlatformViewSetViewportMetrics,
133 (int64_t view_id, const ViewportMetrics& metrics),
134 (override));
135
136 MOCK_METHOD(void,
137 OnPlatformViewDispatchPlatformMessage,
138 (std::unique_ptr<PlatformMessage> message),
139 (override));
140
141 MOCK_METHOD(void,
142 OnPlatformViewDispatchPointerDataPacket,
143 (std::unique_ptr<PointerDataPacket> packet),
144 (override));
145
146 MOCK_METHOD(HitTestResponse,
147 OnPlatformViewHitTest,
148 (int64_t view_id, const flutter::PointData offset),
149 (override));
150
151 MOCK_METHOD(void,
152 OnPlatformViewDispatchSemanticsAction,
153 (int64_t view_id,
154 int32_t id,
157 (override));
158
159 MOCK_METHOD(void,
160 OnPlatformViewSetSemanticsEnabled,
161 (bool enabled),
162 (override));
163
164 MOCK_METHOD(void,
165 OnPlatformViewSetAccessibilityFeatures,
166 (int32_t flags),
167 (override));
168
169 MOCK_METHOD(void,
170 OnPlatformViewRegisterTexture,
171 (std::shared_ptr<Texture> texture),
172 (override));
173
174 MOCK_METHOD(void,
175 OnPlatformViewUnregisterTexture,
176 (int64_t texture_id),
177 (override));
178
179 MOCK_METHOD(void,
180 OnPlatformViewMarkTextureFrameAvailable,
181 (int64_t texture_id),
182 (override));
183
184 MOCK_METHOD(const Settings&,
185 OnPlatformViewGetSettings,
186 (),
187 (const, override));
188
189 MOCK_METHOD(void,
191 (intptr_t loading_unit_id,
192 std::unique_ptr<const fml::Mapping> snapshot_data,
193 std::unique_ptr<const fml::Mapping> snapshot_instructions),
194 (override));
195
196 MOCK_METHOD(void,
197 LoadDartDeferredLibraryError,
198 (intptr_t loading_unit_id,
199 const std::string error_message,
200 bool transient),
201 (override));
202
203 MOCK_METHOD(void,
204 UpdateAssetResolverByType,
205 (std::unique_ptr<AssetResolver> updated_asset_resolver,
207 (override));
208};
209
210class MockSurface : public Surface {
211 public:
212 MOCK_METHOD(bool, IsValid, (), (override));
213
214 MOCK_METHOD(std::unique_ptr<SurfaceFrame>,
215 AcquireFrame,
216 (const DlISize& size),
217 (override));
218
219 MOCK_METHOD(DlMatrix, GetRootTransformation, (), (const, override));
220
221 MOCK_METHOD(GrDirectContext*, GetContext, (), (override));
222
223 MOCK_METHOD(std::unique_ptr<GLContextResult>,
224 MakeRenderContextCurrent,
225 (),
226 (override));
227
228 MOCK_METHOD(bool, ClearRenderContext, (), (override));
229};
230
231class MockPlatformView : public PlatformView {
232 public:
233 MockPlatformView(MockPlatformViewDelegate& delegate,
234 const TaskRunners& task_runners)
235 : PlatformView(delegate, task_runners) {}
236 MOCK_METHOD(std::unique_ptr<Surface>, CreateRenderingSurface, (), (override));
237 MOCK_METHOD(std::shared_ptr<PlatformMessageHandler>,
238 GetPlatformMessageHandler,
239 (),
240 (const, override));
241};
242
243class TestPlatformView : public PlatformView {
244 public:
245 TestPlatformView(Shell& shell, const TaskRunners& task_runners)
246 : PlatformView(shell, task_runners) {}
247 MOCK_METHOD(std::unique_ptr<Surface>, CreateRenderingSurface, (), (override));
248 MOCK_METHOD(void, ReleaseResourceContext, (), (const, override));
249};
250
251class MockPlatformMessageHandler : public PlatformMessageHandler {
252 public:
253 MOCK_METHOD(void,
254 HandlePlatformMessage,
255 (std::unique_ptr<PlatformMessage> message),
256 (override));
257 MOCK_METHOD(bool,
258 DoesHandlePlatformMessageOnPlatformThread,
259 (),
260 (const, override));
261 MOCK_METHOD(void,
263 (int response_id, std::unique_ptr<fml::Mapping> mapping),
264 (override));
265 MOCK_METHOD(void,
267 (int response_id),
268 (override));
269};
270
271class MockPlatformMessageResponse : public PlatformMessageResponse {
272 public:
274 return fml::AdoptRef(new MockPlatformMessageResponse());
275 }
276 MOCK_METHOD(void, Complete, (std::unique_ptr<fml::Mapping> data), (override));
277 MOCK_METHOD(void, CompleteEmpty, (), (override));
278};
279} // namespace
280
282 public:
284 : valid_(valid), type_(type) {}
285
286 bool IsValid() const override { return true; }
287
288 // This is used to identify if replacement was made or not.
289 bool IsValidAfterAssetManagerChange() const override { return valid_; }
290
291 AssetResolver::AssetResolverType GetType() const override { return type_; }
292
293 std::unique_ptr<fml::Mapping> GetAsMapping(
294 const std::string& asset_name) const override {
295 return nullptr;
296 }
297
298 std::vector<std::unique_ptr<fml::Mapping>> GetAsMappings(
299 const std::string& asset_pattern,
300 const std::optional<std::string>& subdir) const override {
301 return {};
302 };
303
304 bool operator==(const AssetResolver& other) const override {
305 return this == &other;
306 }
307
308 private:
309 bool valid_;
311};
312
314 public:
316 std::shared_ptr<fml::ConcurrentMessageLoop> concurrent_loop)
317 : concurrent_loop_(std::move(concurrent_loop)) {}
318
319 // |AssetResolver|
320 bool IsValid() const override { return true; }
321
322 // |AssetResolver|
323 bool IsValidAfterAssetManagerChange() const override { return true; }
324
325 // |AssetResolver|
329
330 // |AssetResolver|
331 std::unique_ptr<fml::Mapping> GetAsMapping(
332 const std::string& asset_name) const override {
333 if (asset_name == "FontManifest.json" ||
334 asset_name == "NativeAssetsManifest.json") {
335 // This file is loaded directly by the engine.
336 return nullptr;
337 }
338 mapping_requests.push_back(asset_name);
339 EXPECT_TRUE(concurrent_loop_->RunsTasksOnCurrentThread())
341 return nullptr;
342 }
343
344 mutable std::vector<std::string> mapping_requests;
345
346 bool operator==(const AssetResolver& other) const override {
347 return this == &other;
348 }
349
350 private:
351 std::shared_ptr<fml::ConcurrentMessageLoop> concurrent_loop_;
352};
353
354static bool ValidateShell(Shell* shell) {
355 if (!shell) {
356 return false;
357 }
358
359 if (!shell->IsSetup()) {
360 return false;
361 }
362
364
365 {
368 shell->GetTaskRunners().GetPlatformTaskRunner(), [shell, &latch]() {
369 shell->GetPlatformView()->NotifyDestroyed();
370 latch.Signal();
371 });
372 latch.Wait();
373 }
374
375 return true;
376}
377
378static bool RasterizerIsTornDown(Shell* shell) {
380 bool is_torn_down = false;
383 [shell, &latch, &is_torn_down]() {
384 is_torn_down = shell->GetRasterizer()->IsTornDown();
385 latch.Signal();
386 });
387 latch.Wait();
388 return is_torn_down;
389}
390
392 ASSERT_TRUE(shell != nullptr);
393 ASSERT_TRUE(shell->IsSetup());
394
395 ASSERT_FALSE(RasterizerIsTornDown(shell));
397 ASSERT_TRUE(RasterizerIsTornDown(shell));
398}
399
400static std::string CreateFlagsString(std::vector<const char*>& flags) {
401 if (flags.empty()) {
402 return "";
403 }
404 std::string flags_string = flags[0];
405 for (size_t i = 1; i < flags.size(); ++i) {
406 flags_string += ",";
407 flags_string += flags[i];
408 }
409 return flags_string;
410}
411
412static void TestDartVmFlags(std::vector<const char*>& flags) {
413 std::string flags_string = CreateFlagsString(flags);
414 const std::vector<fml::CommandLine::Option> options = {
415 fml::CommandLine::Option("dart-flags", flags_string)};
416 fml::CommandLine command_line("", options, std::vector<std::string>());
418 EXPECT_EQ(settings.dart_flags.size(), flags.size());
419 for (size_t i = 0; i < flags.size(); ++i) {
420 EXPECT_EQ(settings.dart_flags[i], flags[i]);
421 }
422}
423
424static void PostSync(const fml::RefPtr<fml::TaskRunner>& task_runner,
425 const fml::closure& task) {
427 fml::TaskRunner::RunNowOrPostTask(task_runner, [&latch, &task] {
428 task();
429 latch.Signal();
430 });
431 latch.Wait();
432}
433
434static sk_sp<DisplayList> MakeSizedDisplayList(int width, int height) {
436 builder.DrawRect(DlRect::MakeXYWH(0, 0, width, height),
438 return builder.Build();
439}
440
441TEST_F(ShellTest, InitializeWithInvalidThreads) {
442 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
443 Settings settings = CreateSettingsForFixture();
444 TaskRunners task_runners("test", nullptr, nullptr, nullptr, nullptr);
445 auto shell = CreateShell(settings, task_runners);
446 ASSERT_FALSE(shell);
447 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
448}
449
450TEST_F(ShellTest, InitializeWithDifferentThreads) {
451 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
452 Settings settings = CreateSettingsForFixture();
453 std::string name_prefix = "io.flutter.test." + GetCurrentTestName() + ".";
457 ASSERT_EQ(thread_host.name_prefix, name_prefix);
458
459 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
460 thread_host.raster_thread->GetTaskRunner(),
461 thread_host.ui_thread->GetTaskRunner(),
462 thread_host.io_thread->GetTaskRunner());
463 auto shell = CreateShell(settings, task_runners);
464 ASSERT_TRUE(ValidateShell(shell.get()));
465 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
466 DestroyShell(std::move(shell), task_runners);
467 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
468}
469
470TEST_F(ShellTest, InitializeWithSingleThread) {
471 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
472 Settings settings = CreateSettingsForFixture();
473 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
475 auto task_runner = thread_host.platform_thread->GetTaskRunner();
476 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
477 task_runner);
478 auto shell = CreateShell(settings, task_runners);
479 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
480 ASSERT_TRUE(ValidateShell(shell.get()));
481 DestroyShell(std::move(shell), task_runners);
482 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
483}
484
485TEST_F(ShellTest, InitializeWithSingleThreadWhichIsTheCallingThread) {
486 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
487 Settings settings = CreateSettingsForFixture();
489 auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
490 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
491 task_runner);
492 auto shell = CreateShell(settings, task_runners);
493 ASSERT_TRUE(ValidateShell(shell.get()));
494 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
495 DestroyShell(std::move(shell), task_runners);
496 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
497}
498
500 InitializeWithMultipleThreadButCallingThreadAsPlatformThread) {
501 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
502 Settings settings = CreateSettingsForFixture();
503 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
507 TaskRunners task_runners("test",
508 fml::MessageLoop::GetCurrent().GetTaskRunner(),
509 thread_host.raster_thread->GetTaskRunner(),
510 thread_host.ui_thread->GetTaskRunner(),
511 thread_host.io_thread->GetTaskRunner());
512 auto shell = Shell::Create(
513 flutter::PlatformData(), task_runners, settings,
514 [](Shell& shell) {
515 // This is unused in the platform view as we are not using the simulated
516 // vsync mechanism. We should have better DI in the tests.
517 const auto vsync_clock = std::make_shared<ShellTestVsyncClock>();
520 shell.GetTaskRunners(), vsync_clock,
521 [task_runners = shell.GetTaskRunners()]() {
522 return static_cast<std::unique_ptr<VsyncWaiter>>(
523 std::make_unique<VsyncWaiterFallback>(task_runners));
524 },
525 nullptr, shell.GetIsGpuDisabledSyncSwitch());
526 },
527 [](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
528 ASSERT_TRUE(ValidateShell(shell.get()));
529 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
530 DestroyShell(std::move(shell), task_runners);
531 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
532}
533
534TEST_F(ShellTest, InitializeWithDisabledGpu) {
535 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
536 Settings settings = CreateSettingsForFixture();
537 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
539 auto task_runner = thread_host.platform_thread->GetTaskRunner();
540 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
541 task_runner);
542 auto shell = CreateShell({
543 .settings = settings,
544 .task_runners = task_runners,
545 .is_gpu_disabled = true,
546 });
547 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
548 ASSERT_TRUE(ValidateShell(shell.get()));
549
550 bool is_disabled = false;
551 shell->GetIsGpuDisabledSyncSwitch()->Execute(
552 fml::SyncSwitch::Handlers().SetIfTrue([&] { is_disabled = true; }));
553 ASSERT_TRUE(is_disabled);
554
555 DestroyShell(std::move(shell), task_runners);
556 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
557}
558
559TEST_F(ShellTest, InitializeWithGPUAndPlatformThreadsTheSame) {
560 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
561 Settings settings = CreateSettingsForFixture();
562 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
565 TaskRunners task_runners(
566 "test",
567 thread_host.platform_thread->GetTaskRunner(), // platform
568 thread_host.platform_thread->GetTaskRunner(), // raster
569 thread_host.ui_thread->GetTaskRunner(), // ui
570 thread_host.io_thread->GetTaskRunner() // io
571 );
572 auto shell = CreateShell(settings, task_runners);
573 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
574 ASSERT_TRUE(ValidateShell(shell.get()));
575 DestroyShell(std::move(shell), task_runners);
576 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
577}
578
579TEST_F(ShellTest, FixturesAreFunctional) {
580 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
581 auto settings = CreateSettingsForFixture();
582 auto shell = CreateShell(settings);
583 ASSERT_TRUE(ValidateShell(shell.get()));
584
585 auto configuration = RunConfiguration::InferFromSettings(settings);
586 ASSERT_TRUE(configuration.IsValid());
587 configuration.SetEntrypoint("fixturesAreFunctionalMain");
588
590 AddNativeCallback(
591 "SayHiFromFixturesAreFunctionalMain",
592 CREATE_NATIVE_ENTRY([&main_latch](auto args) { main_latch.Signal(); }));
593
594 RunEngine(shell.get(), std::move(configuration));
595 main_latch.Wait();
596 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
597 DestroyShell(std::move(shell));
598 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
599}
600
601TEST_F(ShellTest, HitTestInsidePlatformViewIsFunctional) {
602 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
603 auto settings = CreateSettingsForFixture();
604 auto task_runner = CreateNewThread();
605 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
606 task_runner);
607
608 auto shell = CreateShell(settings, task_runners);
609 ASSERT_TRUE(ValidateShell(shell.get()));
610
611 auto configuration = RunConfiguration::InferFromSettings(settings);
612 ASSERT_TRUE(configuration.IsValid());
613
614 configuration.SetEntrypoint("hitTestInsidePlatformViewMain");
615 RunEngine(shell.get(), std::move(configuration));
616
618
619 task_runner->PostTask([&shell, &latch]() {
621 metrics.physical_width = 100;
622 metrics.physical_height = 100;
623 metrics.device_pixel_ratio = 1.0;
624 shell->GetEngine()->SetViewportMetrics(0, metrics);
625
626 HitTestResponse response = shell->GetPlatformView()->HitTest(0, {0.0, 0.0});
627 EXPECT_TRUE(response.has_platform_view);
628 latch.Signal();
629 });
630
631 latch.Wait();
632 DestroyShell(std::move(shell), task_runners);
633}
634
635TEST_F(ShellTest, HitTestOutsidePlatformViewIsFunctional) {
636 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
637 auto settings = CreateSettingsForFixture();
638 auto task_runner = CreateNewThread();
639 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
640 task_runner);
641
642 auto shell = CreateShell(settings, task_runners);
643 ASSERT_TRUE(ValidateShell(shell.get()));
644
645 auto configuration = RunConfiguration::InferFromSettings(settings);
646 ASSERT_TRUE(configuration.IsValid());
647
648 configuration.SetEntrypoint("hitTestOutsidePlatformViewMain");
649 RunEngine(shell.get(), std::move(configuration));
650
652
653 task_runner->PostTask([&shell, &latch]() {
655 metrics.physical_width = 100;
656 metrics.physical_height = 100;
657 metrics.device_pixel_ratio = 1.0;
658 shell->GetEngine()->SetViewportMetrics(0, metrics);
659
660 HitTestResponse response = shell->GetPlatformView()->HitTest(0, {0.0, 0.0});
661 EXPECT_FALSE(response.has_platform_view);
662 latch.Signal();
663 });
664
665 latch.Wait();
666 DestroyShell(std::move(shell), task_runners);
667}
668
669TEST_F(ShellTest, SecondaryIsolateBindingsAreSetupViaShellSettings) {
670 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
671 auto settings = CreateSettingsForFixture();
672 auto shell = CreateShell(settings);
673 ASSERT_TRUE(ValidateShell(shell.get()));
674
675 auto configuration = RunConfiguration::InferFromSettings(settings);
676 ASSERT_TRUE(configuration.IsValid());
677 configuration.SetEntrypoint("testCanLaunchSecondaryIsolate");
678
679 fml::CountDownLatch latch(2);
680 AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) {
681 latch.CountDown();
682 }));
683
684 RunEngine(shell.get(), std::move(configuration));
685
686 latch.Wait();
687
688 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
689 DestroyShell(std::move(shell));
690 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
691}
692
693TEST_F(ShellTest, LastEntrypoint) {
694 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
695 auto settings = CreateSettingsForFixture();
696 auto shell = CreateShell(settings);
697 ASSERT_TRUE(ValidateShell(shell.get()));
698
699 auto configuration = RunConfiguration::InferFromSettings(settings);
700 ASSERT_TRUE(configuration.IsValid());
701 std::string entry_point = "fixturesAreFunctionalMain";
702 configuration.SetEntrypoint(entry_point);
703
705 std::string last_entry_point;
706 AddNativeCallback(
707 "SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) {
708 last_entry_point = shell->GetEngine()->GetLastEntrypoint();
709 main_latch.Signal();
710 }));
711
712 RunEngine(shell.get(), std::move(configuration));
713 main_latch.Wait();
714 EXPECT_EQ(entry_point, last_entry_point);
715 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
716 DestroyShell(std::move(shell));
717 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
718}
719
720TEST_F(ShellTest, LastEntrypointArgs) {
721 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
722 auto settings = CreateSettingsForFixture();
723 auto shell = CreateShell(settings);
724 ASSERT_TRUE(ValidateShell(shell.get()));
725
726 auto configuration = RunConfiguration::InferFromSettings(settings);
727 ASSERT_TRUE(configuration.IsValid());
728 std::string entry_point = "fixturesAreFunctionalMain";
729 std::vector<std::string> entry_point_args = {"arg1"};
730 configuration.SetEntrypoint(entry_point);
731 configuration.SetEntrypointArgs(entry_point_args);
732
734 std::vector<std::string> last_entry_point_args;
735 AddNativeCallback(
736 "SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) {
737 last_entry_point_args = shell->GetEngine()->GetLastEntrypointArgs();
738 main_latch.Signal();
739 }));
740
741 RunEngine(shell.get(), std::move(configuration));
742 main_latch.Wait();
743#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
744 EXPECT_EQ(last_entry_point_args, entry_point_args);
745#else
746 ASSERT_TRUE(last_entry_point_args.empty());
747#endif
748 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
749 DestroyShell(std::move(shell));
750 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
751}
752
753TEST_F(ShellTest, DisallowedDartVMFlag) {
754#if defined(OS_FUCHSIA)
755 GTEST_SKIP() << "This test flakes on Fuchsia. https://fxbug.dev/110006 ";
756#else
757
758 // Run this test in a thread-safe manner, otherwise gtest will complain.
759 ::testing::FLAGS_gtest_death_test_style = "threadsafe";
760
761 const std::vector<fml::CommandLine::Option> options = {
762 fml::CommandLine::Option("dart-flags", "--verify_after_gc")};
763 fml::CommandLine command_line("", options, std::vector<std::string>());
764
765 // Upon encountering a disallowed Dart flag the process terminates.
766 const char* expected =
767 "Encountered disallowed Dart VM flag: --verify_after_gc";
768 ASSERT_DEATH(flutter::SettingsFromCommandLine(command_line), expected);
769#endif // OS_FUCHSIA
770}
771
772TEST_F(ShellTest, AllowedDartVMFlag) {
773 std::vector<const char*> flags = {
774 "--enable-isolate-groups",
775 "--no-enable-isolate-groups",
776 };
777#if !FLUTTER_RELEASE
778 flags.push_back("--max_profile_depth 1");
779 flags.push_back("--random_seed 42");
780 flags.push_back("--max_subtype_cache_entries=22");
782 flags.push_back("--enable_mirrors");
783 }
784#endif
785
786 TestDartVmFlags(flags);
787}
788
789TEST_F(ShellTest, NoNeedToReportTimingsByDefault) {
790 auto settings = CreateSettingsForFixture();
791 std::unique_ptr<Shell> shell = CreateShell(settings);
792
793 // Create the surface needed by rasterizer
794 PlatformViewNotifyCreated(shell.get());
795
796 auto configuration = RunConfiguration::InferFromSettings(settings);
797 configuration.SetEntrypoint("emptyMain");
798
799 RunEngine(shell.get(), std::move(configuration));
800 PumpOneFrame(shell.get());
801 ASSERT_FALSE(GetNeedsReportTimings(shell.get()));
802
803 // This assertion may or may not be the direct result of needs_report_timings_
804 // being false. The count could be 0 simply because we just cleared
805 // unreported timings by reporting them. Hence this can't replace the
806 // ASSERT_FALSE(GetNeedsReportTimings(shell.get())) check. We added
807 // this assertion for an additional confidence that we're not pushing
808 // back to unreported timings unnecessarily.
809 //
810 // Conversely, do not assert UnreportedTimingsCount(shell.get()) to be
811 // positive in any tests. Otherwise those tests will be flaky as the clearing
812 // of unreported timings is unpredictive.
813 ASSERT_EQ(UnreportedTimingsCount(shell.get()), 0);
814 DestroyShell(std::move(shell));
815}
816
817TEST_F(ShellTest, NeedsReportTimingsIsSetWithCallback) {
818 auto settings = CreateSettingsForFixture();
819 std::unique_ptr<Shell> shell = CreateShell(settings);
820
821 // Create the surface needed by rasterizer
822 PlatformViewNotifyCreated(shell.get());
823
824 auto configuration = RunConfiguration::InferFromSettings(settings);
825 configuration.SetEntrypoint("dummyReportTimingsMain");
826
827 RunEngine(shell.get(), std::move(configuration));
828 PumpOneFrame(shell.get());
829 ASSERT_TRUE(GetNeedsReportTimings(shell.get()));
830 DestroyShell(std::move(shell));
831}
832
833static void CheckFrameTimings(const std::vector<FrameTiming>& timings,
834 fml::TimePoint start,
835 fml::TimePoint finish) {
836 fml::TimePoint last_frame_start;
837 for (size_t i = 0; i < timings.size(); i += 1) {
838 // Ensure that timings are sorted.
839 ASSERT_TRUE(timings[i].Get(FrameTiming::kPhases[0]) >= last_frame_start);
840 last_frame_start = timings[i].Get(FrameTiming::kPhases[0]);
841
842 fml::TimePoint last_phase_time;
843 for (auto phase : FrameTiming::kPhases) {
844 // raster finish wall time doesn't use the same clock base
845 // as rest of the frame timings.
847 continue;
848 }
849
850 ASSERT_TRUE(timings[i].Get(phase) >= start);
851 ASSERT_TRUE(timings[i].Get(phase) <= finish);
852
853 // phases should have weakly increasing time points
854 ASSERT_TRUE(last_phase_time <= timings[i].Get(phase));
855 last_phase_time = timings[i].Get(phase);
856 }
857 }
858}
859
860TEST_F(ShellTest, ReportTimingsIsCalled) {
861 auto settings = CreateSettingsForFixture();
862 std::unique_ptr<Shell> shell = CreateShell(settings);
863
864 // We MUST put |start| after |CreateShell| because the clock source will be
865 // reset through |TimePoint::SetClockSource()| in
866 // |DartVMInitializer::Initialize()| within |CreateShell()|.
868
869 // Create the surface needed by rasterizer
870 PlatformViewNotifyCreated(shell.get());
871
872 auto configuration = RunConfiguration::InferFromSettings(settings);
873 ASSERT_TRUE(configuration.IsValid());
874 configuration.SetEntrypoint("reportTimingsMain");
875 fml::AutoResetWaitableEvent reportLatch;
876 std::vector<int64_t> timestamps;
877 auto nativeTimingCallback = [&reportLatch,
878 &timestamps](Dart_NativeArguments args) {
879 Dart_Handle exception = nullptr;
880 ASSERT_EQ(timestamps.size(), 0ul);
881 timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
882 args, 0, exception);
883 reportLatch.Signal();
884 };
885 AddNativeCallback("NativeReportTimingsCallback",
886 CREATE_NATIVE_ENTRY(nativeTimingCallback));
887 RunEngine(shell.get(), std::move(configuration));
888
889 // Pump many frames so we can trigger the report quickly instead of waiting
890 // for the 1 second threshold.
891 for (int i = 0; i < 200; i += 1) {
892 PumpOneFrame(shell.get());
893 }
894
895 reportLatch.Wait();
896 DestroyShell(std::move(shell));
897
899 ASSERT_TRUE(!timestamps.empty());
900 ASSERT_TRUE(timestamps.size() % FrameTiming::kCount == 0);
901 std::vector<FrameTiming> timings(timestamps.size() / FrameTiming::kCount);
902
903 for (size_t i = 0; i * FrameTiming::kCount < timestamps.size(); i += 1) {
904 for (auto phase : FrameTiming::kPhases) {
905 timings[i].Set(
906 phase,
908 timestamps[i * FrameTiming::kCount + phase])));
909 }
910 }
911 CheckFrameTimings(timings, start, finish);
912}
913
914TEST_F(ShellTest, FrameRasterizedCallbackIsCalled) {
915 auto settings = CreateSettingsForFixture();
916
917 FrameTiming timing;
918 fml::AutoResetWaitableEvent timingLatch;
919 settings.frame_rasterized_callback = [&timing,
920 &timingLatch](const FrameTiming& t) {
921 timing = t;
922 timingLatch.Signal();
923 };
924
925 std::unique_ptr<Shell> shell = CreateShell(settings);
926
927 // Wait to make |start| bigger than zero
928 using namespace std::chrono_literals;
929 std::this_thread::sleep_for(1ms);
930
931 // We MUST put |start| after |CreateShell()| because the clock source will be
932 // reset through |TimePoint::SetClockSource()| in
933 // |DartVMInitializer::Initialize()| within |CreateShell()|.
935
936 for (auto phase : FrameTiming::kPhases) {
937 timing.Set(phase, fml::TimePoint());
938 // Check that the time points are initially smaller than start, so
939 // CheckFrameTimings will fail if they're not properly set later.
940 ASSERT_TRUE(timing.Get(phase) < start);
941 }
942
943 // Create the surface needed by rasterizer
944 PlatformViewNotifyCreated(shell.get());
945
946 auto configuration = RunConfiguration::InferFromSettings(settings);
947 configuration.SetEntrypoint("onBeginFrameMain");
948
949 int64_t frame_target_time;
950 auto nativeOnBeginFrame = [&frame_target_time](Dart_NativeArguments args) {
951 Dart_Handle exception = nullptr;
952 frame_target_time =
954 };
955 AddNativeCallback("NativeOnBeginFrame",
956 CREATE_NATIVE_ENTRY(nativeOnBeginFrame));
957
958 RunEngine(shell.get(), std::move(configuration));
959 PumpOneFrame(shell.get());
960
961 // Check that timing is properly set. This implies that
962 // settings.frame_rasterized_callback is called.
963 timingLatch.Wait();
965 std::vector<FrameTiming> timings = {timing};
966 CheckFrameTimings(timings, start, finish);
967
968 // Check that onBeginFrame, which is the frame_target_time, is after
969 // FrameTiming's build start
970 int64_t build_start =
972 ASSERT_GT(frame_target_time, build_start);
973 DestroyShell(std::move(shell));
974}
975
976TEST_F(ShellTest, ExternalEmbedderNoThreadMerger) {
977 auto settings = CreateSettingsForFixture();
978 fml::AutoResetWaitableEvent end_frame_latch;
979 bool end_frame_called = false;
980 auto end_frame_callback =
981 [&](bool should_resubmit_frame,
982 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
983 ASSERT_TRUE(raster_thread_merger.get() == nullptr);
984 ASSERT_FALSE(should_resubmit_frame);
985 end_frame_called = true;
986 end_frame_latch.Signal();
987 };
988 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
989 end_frame_callback, PostPrerollResult::kResubmitFrame, false);
990 auto shell = CreateShell({
991 .settings = settings,
992 .platform_view_create_callback = ShellTestPlatformViewBuilder({
993 .shell_test_external_view_embedder = external_view_embedder,
994 }),
995 });
996
997 // Create the surface needed by rasterizer
998 PlatformViewNotifyCreated(shell.get());
999
1000 auto configuration = RunConfiguration::InferFromSettings(settings);
1001 configuration.SetEntrypoint("emptyMain");
1002
1003 RunEngine(shell.get(), std::move(configuration));
1004
1005 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1006 auto display_list_layer = std::make_shared<DisplayListLayer>(
1007 DlPoint(10, 10), MakeSizedDisplayList(80, 80), false, false);
1008 root->Add(display_list_layer);
1009 };
1010
1011 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1012 end_frame_latch.Wait();
1013 ASSERT_TRUE(end_frame_called);
1014
1015 DestroyShell(std::move(shell));
1016}
1017
1018TEST_F(ShellTest, PushBackdropFilterToVisitedPlatformViews) {
1019#if defined(OS_FUCHSIA)
1020 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1021 "https://github.com/flutter/flutter/issues/59816 ";
1022#else
1023
1024 auto settings = CreateSettingsForFixture();
1025
1026 std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
1027
1028 fml::AutoResetWaitableEvent end_frame_latch;
1029 bool end_frame_called = false;
1030 std::vector<int64_t> visited_platform_views;
1031 MutatorsStack stack_50;
1032 MutatorsStack stack_75;
1033 auto end_frame_callback =
1034 [&](bool should_resubmit_frame,
1035 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1036 if (end_frame_called) {
1037 return;
1038 }
1039 ASSERT_TRUE(raster_thread_merger.get() == nullptr);
1040 ASSERT_FALSE(should_resubmit_frame);
1041 end_frame_called = true;
1042 visited_platform_views =
1043 external_view_embedder->GetVisitedPlatformViews();
1044 stack_50 = external_view_embedder->GetStack(50);
1045 stack_75 = external_view_embedder->GetStack(75);
1046 end_frame_latch.Signal();
1047 };
1048
1049 external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1050 end_frame_callback, PostPrerollResult::kResubmitFrame, false);
1051 auto shell = CreateShell({
1052 .settings = settings,
1053 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1054 .shell_test_external_view_embedder = external_view_embedder,
1055 }),
1056 });
1057
1058 // Create the surface needed by rasterizer
1059 PlatformViewNotifyCreated(shell.get());
1060
1061 auto configuration = RunConfiguration::InferFromSettings(settings);
1062 configuration.SetEntrypoint("emptyMain");
1063
1064 RunEngine(shell.get(), std::move(configuration));
1065
1066 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1067 auto platform_view_layer = std::make_shared<PlatformViewLayer>(
1068 DlPoint(10, 10), DlSize(10, 10), 50);
1069 root->Add(platform_view_layer);
1070 auto transform_layer = std::make_shared<TransformLayer>(
1071 DlMatrix::MakeTranslation({1.0f, 1.0f}));
1072 root->Add(transform_layer);
1073 auto clip_rect_layer = std::make_shared<ClipRectLayer>(
1074 DlRect::MakeLTRB(0, 0, 30, 30), Clip::kHardEdge);
1075 transform_layer->Add(clip_rect_layer);
1076 auto filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp);
1077 auto backdrop_filter_layer =
1078 std::make_shared<BackdropFilterLayer>(filter, DlBlendMode::kSrcOver);
1079 clip_rect_layer->Add(backdrop_filter_layer);
1080 auto platform_view_layer2 = std::make_shared<PlatformViewLayer>(
1081 DlPoint(10, 10), DlSize(10, 10), 75);
1082 backdrop_filter_layer->Add(platform_view_layer2);
1083 };
1084
1085 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1086 end_frame_latch.Wait();
1087 ASSERT_EQ(visited_platform_views, (std::vector<int64_t>{50, 75}));
1088 ASSERT_TRUE(stack_75.is_empty());
1089 ASSERT_FALSE(stack_50.is_empty());
1090
1091 auto filter = DlImageFilter::MakeBlur(5, 5, DlTileMode::kClamp);
1092 auto mutator = *stack_50.Begin();
1093 ASSERT_EQ(mutator->GetType(), MutatorType::kBackdropFilter);
1094 ASSERT_EQ(mutator->GetFilterMutation().GetFilter(), *filter);
1095 // Make sure the filterRect is in global coordinates (contains the (1,1)
1096 // translation).
1097 ASSERT_EQ(mutator->GetFilterMutation().GetFilterRect(),
1098 DlRect::MakeLTRB(1, 1, 31, 31));
1099
1100 DestroyShell(std::move(shell));
1101#endif // OS_FUCHSIA
1102}
1103
1104// TODO(https://github.com/flutter/flutter/issues/59816): Enable on fuchsia.
1106 ExternalEmbedderEndFrameIsCalledWhenPostPrerollResultIsResubmit) {
1107#if defined(OS_FUCHSIA)
1108 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1109 "https://github.com/flutter/flutter/issues/59816 ";
1110#else
1111
1112 auto settings = CreateSettingsForFixture();
1113 fml::AutoResetWaitableEvent end_frame_latch;
1114 bool end_frame_called = false;
1115 auto end_frame_callback =
1116 [&](bool should_resubmit_frame,
1117 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1118 ASSERT_TRUE(raster_thread_merger.get() != nullptr);
1119 ASSERT_TRUE(should_resubmit_frame);
1120 end_frame_called = true;
1121 end_frame_latch.Signal();
1122 };
1123 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1124 end_frame_callback, PostPrerollResult::kResubmitFrame, true);
1125 auto shell = CreateShell({
1126 .settings = settings,
1127 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1128 .shell_test_external_view_embedder = external_view_embedder,
1129 }),
1130 });
1131
1132 // Create the surface needed by rasterizer
1133 PlatformViewNotifyCreated(shell.get());
1134
1135 auto configuration = RunConfiguration::InferFromSettings(settings);
1136 configuration.SetEntrypoint("emptyMain");
1137
1138 RunEngine(shell.get(), std::move(configuration));
1139
1140 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1141 auto display_list_layer = std::make_shared<DisplayListLayer>(
1142 DlPoint(10, 10), MakeSizedDisplayList(80, 80), false, false);
1143 root->Add(display_list_layer);
1144 };
1145
1146 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1147 end_frame_latch.Wait();
1148
1149 ASSERT_TRUE(end_frame_called);
1150
1151 DestroyShell(std::move(shell));
1152#endif // OS_FUCHSIA
1153}
1154
1155TEST_F(ShellTest, OnPlatformViewDestroyDisablesThreadMerger) {
1156#if defined(OS_FUCHSIA)
1157 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1158 "https://github.com/flutter/flutter/issues/59816 ";
1159#else
1160
1161 auto settings = CreateSettingsForFixture();
1162 fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger;
1163 auto end_frame_callback =
1164 [&](bool should_resubmit_frame,
1166 raster_thread_merger = std::move(thread_merger);
1167 };
1168 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1169 end_frame_callback, PostPrerollResult::kSuccess, true);
1170
1171 auto shell = CreateShell({
1172 .settings = settings,
1173 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1174 .shell_test_external_view_embedder = external_view_embedder,
1175 }),
1176 });
1177
1178 // Create the surface needed by rasterizer
1179 PlatformViewNotifyCreated(shell.get());
1180
1181 auto configuration = RunConfiguration::InferFromSettings(settings);
1182 configuration.SetEntrypoint("emptyMain");
1183
1184 RunEngine(shell.get(), std::move(configuration));
1185
1186 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1187 auto display_list_layer = std::make_shared<DisplayListLayer>(
1188 DlPoint(10, 10), MakeSizedDisplayList(80, 80), false, false);
1189 root->Add(display_list_layer);
1190 };
1191
1192 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1193
1194 auto result = shell->WaitForFirstFrame(fml::TimeDelta::Max());
1195 // Wait for the rasterizer to process the frame. WaitForFirstFrame only waits
1196 // for the Animator, but end_frame_callback is called by the Rasterizer.
1197 PostSync(shell->GetTaskRunners().GetRasterTaskRunner(), [] {});
1198 ASSERT_TRUE(result.ok()) << "Result: " << static_cast<int>(result.code())
1199 << ": " << result.message();
1200
1201 ASSERT_TRUE(raster_thread_merger->IsEnabled());
1202
1203 ValidateDestroyPlatformView(shell.get());
1204 ASSERT_TRUE(raster_thread_merger->IsEnabled());
1205
1206 // Validate the platform view can be recreated and destroyed again
1207 ValidateShell(shell.get());
1208 ASSERT_TRUE(raster_thread_merger->IsEnabled());
1209 DestroyShell(std::move(shell));
1210#endif // OS_FUCHSIA
1211}
1212
1213TEST_F(ShellTest, OnPlatformViewDestroyAfterMergingThreads) {
1214#if defined(OS_FUCHSIA)
1215 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1216 "https://github.com/flutter/flutter/issues/59816 ";
1217#else
1218
1219 const int ThreadMergingLease = 10;
1220 auto settings = CreateSettingsForFixture();
1221 fml::AutoResetWaitableEvent end_frame_latch;
1222 std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
1223
1224 auto end_frame_callback =
1225 [&](bool should_resubmit_frame,
1226 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1227 if (should_resubmit_frame && !raster_thread_merger->IsMerged()) {
1228 raster_thread_merger->MergeWithLease(ThreadMergingLease);
1229
1230 ASSERT_TRUE(raster_thread_merger->IsMerged());
1231 external_view_embedder->UpdatePostPrerollResult(
1233 }
1234 end_frame_latch.Signal();
1235 };
1236 external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1237 end_frame_callback, PostPrerollResult::kSuccess, true);
1238 // Set resubmit once to trigger thread merging.
1239 external_view_embedder->UpdatePostPrerollResult(
1241 auto shell = CreateShell({
1242 .settings = settings,
1243 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1244 .shell_test_external_view_embedder = external_view_embedder,
1245 }),
1246 });
1247
1248 // Create the surface needed by rasterizer
1249 PlatformViewNotifyCreated(shell.get());
1250
1251 auto configuration = RunConfiguration::InferFromSettings(settings);
1252 configuration.SetEntrypoint("emptyMain");
1253
1254 RunEngine(shell.get(), std::move(configuration));
1255
1256 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1257 auto display_list_layer = std::make_shared<DisplayListLayer>(
1258 DlPoint(10, 10), MakeSizedDisplayList(80, 80), false, false);
1259 root->Add(display_list_layer);
1260 };
1261
1262 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1263 // Pump one frame to trigger thread merging.
1264 end_frame_latch.Wait();
1265 // Pump another frame to ensure threads are merged and a regular layer tree is
1266 // submitted.
1267 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1268 // Threads are merged here. PlatformViewNotifyDestroy should be executed
1269 // successfully.
1271 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1272 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1273 ValidateDestroyPlatformView(shell.get());
1274
1275 // Ensure threads are unmerged after platform view destroy
1277 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1278 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1279
1280 // Validate the platform view can be recreated and destroyed again
1281 ValidateShell(shell.get());
1282
1283 DestroyShell(std::move(shell));
1284#endif // OS_FUCHSIA
1285}
1286
1287TEST_F(ShellTest, OnPlatformViewDestroyWhenThreadsAreMerging) {
1288#if defined(OS_FUCHSIA)
1289 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1290 "https://github.com/flutter/flutter/issues/59816 ";
1291#else
1292
1293 const int kThreadMergingLease = 10;
1294 auto settings = CreateSettingsForFixture();
1295 fml::AutoResetWaitableEvent end_frame_latch;
1296 auto end_frame_callback =
1297 [&](bool should_resubmit_frame,
1298 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1299 if (should_resubmit_frame && !raster_thread_merger->IsMerged()) {
1300 raster_thread_merger->MergeWithLease(kThreadMergingLease);
1301 }
1302 end_frame_latch.Signal();
1303 };
1304 // Start with a regular layer tree with `PostPrerollResult::kSuccess` so we
1305 // can later check if the rasterizer is tore down using
1306 // |ValidateDestroyPlatformView|
1307 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1308 end_frame_callback, PostPrerollResult::kSuccess, true);
1309
1310 auto shell = CreateShell({
1311 .settings = settings,
1312 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1313 .shell_test_external_view_embedder = external_view_embedder,
1314 }),
1315 });
1316
1317 // Create the surface needed by rasterizer
1318 PlatformViewNotifyCreated(shell.get());
1319
1320 auto configuration = RunConfiguration::InferFromSettings(settings);
1321 configuration.SetEntrypoint("emptyMain");
1322
1323 RunEngine(shell.get(), std::move(configuration));
1324
1325 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1326 auto display_list_layer = std::make_shared<DisplayListLayer>(
1327 DlPoint(10, 10), MakeSizedDisplayList(80, 80), false, false);
1328 root->Add(display_list_layer);
1329 };
1330
1331 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1332 // Pump one frame and threads aren't merged
1333 end_frame_latch.Wait();
1335 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1336 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1337
1338 // Pump a frame with `PostPrerollResult::kResubmitFrame` to start merging
1339 // threads
1340 external_view_embedder->UpdatePostPrerollResult(
1342 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1343
1344 // Now destroy the platform view immediately.
1345 // Two things can happen here:
1346 // 1. Threads haven't merged. 2. Threads has already merged.
1347 // |Shell:OnPlatformViewDestroy| should be able to handle both cases.
1348 ValidateDestroyPlatformView(shell.get());
1349
1350 // Ensure threads are unmerged after platform view destroy
1352 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1353 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1354
1355 // Validate the platform view can be recreated and destroyed again
1356 ValidateShell(shell.get());
1357
1358 DestroyShell(std::move(shell));
1359#endif // OS_FUCHSIA
1360}
1361
1363 OnPlatformViewDestroyWithThreadMergerWhileThreadsAreUnmerged) {
1364#if defined(OS_FUCHSIA)
1365 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1366 "https://github.com/flutter/flutter/issues/59816 ";
1367#else
1368
1369 auto settings = CreateSettingsForFixture();
1370 fml::AutoResetWaitableEvent end_frame_latch;
1371 auto end_frame_callback =
1372 [&](bool should_resubmit_frame,
1373 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1374 end_frame_latch.Signal();
1375 };
1376 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1377 end_frame_callback, PostPrerollResult::kSuccess, true);
1378 auto shell = CreateShell({
1379 .settings = settings,
1380 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1381 .shell_test_external_view_embedder = external_view_embedder,
1382 }),
1383 });
1384
1385 // Create the surface needed by rasterizer
1386 PlatformViewNotifyCreated(shell.get());
1387
1388 auto configuration = RunConfiguration::InferFromSettings(settings);
1389 configuration.SetEntrypoint("emptyMain");
1390
1391 RunEngine(shell.get(), std::move(configuration));
1392
1393 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1394 auto display_list_layer = std::make_shared<DisplayListLayer>(
1395 DlPoint(10, 10), MakeSizedDisplayList(80, 80), false, false);
1396 root->Add(display_list_layer);
1397 };
1398 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1399 end_frame_latch.Wait();
1400
1401 // Threads should not be merged.
1403 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1404 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1405 ValidateDestroyPlatformView(shell.get());
1406
1407 // Ensure threads are unmerged after platform view destroy
1409 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1410 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1411
1412 // Validate the platform view can be recreated and destroyed again
1413 ValidateShell(shell.get());
1414
1415 DestroyShell(std::move(shell));
1416#endif // OS_FUCHSIA
1417}
1418
1419TEST_F(ShellTest, OnPlatformViewDestroyWithoutRasterThreadMerger) {
1420 auto settings = CreateSettingsForFixture();
1421
1422 auto shell = CreateShell(settings);
1423
1424 // Create the surface needed by rasterizer
1425 PlatformViewNotifyCreated(shell.get());
1426
1427 auto configuration = RunConfiguration::InferFromSettings(settings);
1428 configuration.SetEntrypoint("emptyMain");
1429
1430 RunEngine(shell.get(), std::move(configuration));
1431
1432 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1433 auto display_list_layer = std::make_shared<DisplayListLayer>(
1434 DlPoint(10, 10), MakeSizedDisplayList(80, 80), false, false);
1435 root->Add(display_list_layer);
1436 };
1437 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1438
1439 // Threads should not be merged.
1441 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1442 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1443 ValidateDestroyPlatformView(shell.get());
1444
1445 // Ensure threads are unmerged after platform view destroy
1447 shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
1448 shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
1449
1450 // Validate the platform view can be recreated and destroyed again
1451 ValidateShell(shell.get());
1452
1453 DestroyShell(std::move(shell));
1454}
1455
1456// TODO(https://github.com/flutter/flutter/issues/59816): Enable on fuchsia.
1457TEST_F(ShellTest, OnPlatformViewDestroyWithStaticThreadMerging) {
1458#if defined(OS_FUCHSIA)
1459 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1460 "https://github.com/flutter/flutter/issues/59816 ";
1461#else
1462
1463 auto settings = CreateSettingsForFixture();
1464 fml::AutoResetWaitableEvent end_frame_latch;
1465 auto end_frame_callback =
1466 [&](bool should_resubmit_frame,
1467 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1468 end_frame_latch.Signal();
1469 };
1470 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1471 end_frame_callback, PostPrerollResult::kSuccess, true);
1472 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
1475 TaskRunners task_runners(
1476 "test",
1477 thread_host.platform_thread->GetTaskRunner(), // platform
1478 thread_host.platform_thread->GetTaskRunner(), // raster
1479 thread_host.ui_thread->GetTaskRunner(), // ui
1480 thread_host.io_thread->GetTaskRunner() // io
1481 );
1482 auto shell = CreateShell({
1483 .settings = settings,
1484 .task_runners = task_runners,
1485 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1486 .shell_test_external_view_embedder = external_view_embedder,
1487 }),
1488 });
1489
1490 // Create the surface needed by rasterizer
1491 PlatformViewNotifyCreated(shell.get());
1492
1493 auto configuration = RunConfiguration::InferFromSettings(settings);
1494 configuration.SetEntrypoint("emptyMain");
1495
1496 RunEngine(shell.get(), std::move(configuration));
1497
1498 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1499 auto display_list_layer = std::make_shared<DisplayListLayer>(
1500 DlPoint(10, 10), MakeSizedDisplayList(80, 80), false, false);
1501 root->Add(display_list_layer);
1502 };
1503 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1504 end_frame_latch.Wait();
1505
1506 ValidateDestroyPlatformView(shell.get());
1507
1508 // Validate the platform view can be recreated and destroyed again
1509 ValidateShell(shell.get());
1510
1511 DestroyShell(std::move(shell), task_runners);
1512#endif // OS_FUCHSIA
1513}
1514
1515TEST_F(ShellTest, GetUsedThisFrameShouldBeSetBeforeEndFrame) {
1516 auto settings = CreateSettingsForFixture();
1517 fml::AutoResetWaitableEvent end_frame_latch;
1518 std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
1519 bool used_this_frame = true;
1520 auto end_frame_callback =
1521 [&](bool should_resubmit_frame,
1522 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1523 // We expect `used_this_frame` to be false.
1524 used_this_frame = external_view_embedder->GetUsedThisFrame();
1525 end_frame_latch.Signal();
1526 };
1527 external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1528 end_frame_callback, PostPrerollResult::kSuccess, true);
1529 auto shell = CreateShell({
1530 .settings = settings,
1531 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1532 .shell_test_external_view_embedder = external_view_embedder,
1533 }),
1534 });
1535
1536 // Create the surface needed by rasterizer
1537 PlatformViewNotifyCreated(shell.get());
1538
1539 auto configuration = RunConfiguration::InferFromSettings(settings);
1540 configuration.SetEntrypoint("emptyMain");
1541
1542 RunEngine(shell.get(), std::move(configuration));
1543
1544 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
1545 auto display_list_layer = std::make_shared<DisplayListLayer>(
1546 DlPoint(10, 10), MakeSizedDisplayList(80, 80), false, false);
1547 root->Add(display_list_layer);
1548 };
1549 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
1550 end_frame_latch.Wait();
1551 ASSERT_FALSE(used_this_frame);
1552
1553 // Validate the platform view can be recreated and destroyed again
1554 ValidateShell(shell.get());
1555
1556 DestroyShell(std::move(shell));
1557}
1558
1559// TODO(https://github.com/flutter/flutter/issues/66056): Deflake on all other
1560// platforms
1561TEST_F(ShellTest, DISABLED_SkipAndSubmitFrame) {
1562#if defined(OS_FUCHSIA)
1563 GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
1564 "https://github.com/flutter/flutter/issues/59816 ";
1565#else
1566
1567 auto settings = CreateSettingsForFixture();
1568 fml::AutoResetWaitableEvent end_frame_latch;
1569 std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
1570
1571 auto end_frame_callback =
1572 [&](bool should_resubmit_frame,
1573 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
1574 if (should_resubmit_frame && !raster_thread_merger->IsMerged()) {
1575 raster_thread_merger->MergeWithLease(10);
1576 external_view_embedder->UpdatePostPrerollResult(
1578 }
1579 end_frame_latch.Signal();
1580 };
1581 external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
1582 end_frame_callback, PostPrerollResult::kSkipAndRetryFrame, true);
1583
1584 auto shell = CreateShell({
1585 .settings = settings,
1586 .platform_view_create_callback = ShellTestPlatformViewBuilder({
1587 .shell_test_external_view_embedder = external_view_embedder,
1588 }),
1589 });
1590
1591 PlatformViewNotifyCreated(shell.get());
1592
1593 auto configuration = RunConfiguration::InferFromSettings(settings);
1594 configuration.SetEntrypoint("emptyMain");
1595 RunEngine(shell.get(), std::move(configuration));
1596
1597 ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
1598
1599 PumpOneFrame(shell.get());
1600
1601 // `EndFrame` changed the post preroll result to `kSuccess`.
1602 end_frame_latch.Wait();
1603
1604 // Let the resubmitted frame to run and `GetSubmittedFrameCount` should be
1605 // called.
1606 end_frame_latch.Wait();
1607 // 2 frames are submitted because `kSkipAndRetryFrame`, but only the 2nd frame
1608 // should be submitted with `external_view_embedder`, hence the below check.
1609 ASSERT_EQ(1, external_view_embedder->GetSubmittedFrameCount());
1610
1611 PlatformViewNotifyDestroyed(shell.get());
1612 DestroyShell(std::move(shell));
1613#endif // OS_FUCHSIA
1614}
1615
1616TEST(SettingsTest, FrameTimingSetsAndGetsProperly) {
1617 // Ensure that all phases are in kPhases.
1618 ASSERT_EQ(sizeof(FrameTiming::kPhases),
1620
1621 int lastPhaseIndex = -1;
1622 FrameTiming timing;
1623 for (auto phase : FrameTiming::kPhases) {
1624 ASSERT_TRUE(phase > lastPhaseIndex); // Ensure that kPhases are in order.
1625 lastPhaseIndex = phase;
1626 auto fake_time =
1628 timing.Set(phase, fake_time);
1629 ASSERT_TRUE(timing.Get(phase) == fake_time);
1630 }
1631}
1632
1633TEST_F(ShellTest, ReportTimingsIsCalledImmediatelyAfterTheFirstFrame) {
1634 auto settings = CreateSettingsForFixture();
1635 std::unique_ptr<Shell> shell = CreateShell(settings);
1636
1637 // Create the surface needed by rasterizer
1638 PlatformViewNotifyCreated(shell.get());
1639
1640 auto configuration = RunConfiguration::InferFromSettings(settings);
1641 ASSERT_TRUE(configuration.IsValid());
1642 configuration.SetEntrypoint("reportTimingsMain");
1643 fml::AutoResetWaitableEvent reportLatch;
1644 std::vector<int64_t> timestamps;
1645 auto nativeTimingCallback = [&reportLatch,
1646 &timestamps](Dart_NativeArguments args) {
1647 Dart_Handle exception = nullptr;
1648 ASSERT_EQ(timestamps.size(), 0ul);
1649 timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
1650 args, 0, exception);
1651 reportLatch.Signal();
1652 };
1653 AddNativeCallback("NativeReportTimingsCallback",
1654 CREATE_NATIVE_ENTRY(nativeTimingCallback));
1655 ASSERT_TRUE(configuration.IsValid());
1656 RunEngine(shell.get(), std::move(configuration));
1657
1658 for (int i = 0; i < 10; i += 1) {
1659 PumpOneFrame(shell.get());
1660 }
1661
1662 reportLatch.Wait();
1663 DestroyShell(std::move(shell));
1664
1665 // Check for the immediate callback of the first frame that doesn't wait for
1666 // the other 9 frames to be rasterized.
1667 ASSERT_EQ(timestamps.size(), FrameTiming::kCount);
1668}
1669
1670TEST_F(ShellTest, WaitForFirstFrame) {
1671 auto settings = CreateSettingsForFixture();
1672 std::unique_ptr<Shell> shell = CreateShell(settings);
1673
1674 // Create the surface needed by rasterizer
1675 PlatformViewNotifyCreated(shell.get());
1676
1677 auto configuration = RunConfiguration::InferFromSettings(settings);
1678 configuration.SetEntrypoint("emptyMain");
1679
1680 RunEngine(shell.get(), std::move(configuration));
1681 PumpOneFrame(shell.get());
1682 fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::Max());
1683 ASSERT_TRUE(result.ok());
1684
1685 DestroyShell(std::move(shell));
1686}
1687
1688TEST_F(ShellTest, WaitForFirstFrameZeroSizeFrame) {
1689 auto settings = CreateSettingsForFixture();
1690 std::unique_ptr<Shell> shell = CreateShell(settings);
1691
1692 // Create the surface needed by rasterizer
1693 PlatformViewNotifyCreated(shell.get());
1694
1695 auto configuration = RunConfiguration::InferFromSettings(settings);
1696 configuration.SetEntrypoint("emptyMain");
1697
1698 RunEngine(shell.get(), std::move(configuration));
1699 PumpOneFrame(shell.get(), ViewContent::DummyView({1.0, 0.0, 0.0, 22, 0}));
1700 fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::Zero());
1701 EXPECT_FALSE(result.ok());
1702 EXPECT_EQ(result.message(), "timeout");
1703 EXPECT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded);
1704
1705 DestroyShell(std::move(shell));
1706}
1707
1708TEST_F(ShellTest, WaitForFirstFrameTimeout) {
1709 auto settings = CreateSettingsForFixture();
1710 std::unique_ptr<Shell> shell = CreateShell(settings);
1711
1712 // Create the surface needed by rasterizer
1713 PlatformViewNotifyCreated(shell.get());
1714
1715 auto configuration = RunConfiguration::InferFromSettings(settings);
1716 configuration.SetEntrypoint("emptyMain");
1717
1718 RunEngine(shell.get(), std::move(configuration));
1719 fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::Zero());
1720 ASSERT_FALSE(result.ok());
1721 ASSERT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded);
1722
1723 DestroyShell(std::move(shell));
1724}
1725
1726TEST_F(ShellTest, WaitForFirstFrameMultiple) {
1727 auto settings = CreateSettingsForFixture();
1728 std::unique_ptr<Shell> shell = CreateShell(settings);
1729
1730 // Create the surface needed by rasterizer
1731 PlatformViewNotifyCreated(shell.get());
1732
1733 auto configuration = RunConfiguration::InferFromSettings(settings);
1734 configuration.SetEntrypoint("emptyMain");
1735
1736 RunEngine(shell.get(), std::move(configuration));
1737 PumpOneFrame(shell.get());
1738 fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::Max());
1739 ASSERT_TRUE(result.ok());
1740 for (int i = 0; i < 100; ++i) {
1741 result = shell->WaitForFirstFrame(fml::TimeDelta::Zero());
1742 ASSERT_TRUE(result.ok());
1743 }
1744
1745 DestroyShell(std::move(shell));
1746}
1747
1748/// Makes sure that WaitForFirstFrame works if we rendered a frame with the
1749/// single-thread setup.
1750TEST_F(ShellTest, WaitForFirstFrameInlined) {
1751 Settings settings = CreateSettingsForFixture();
1752 auto task_runner = CreateNewThread();
1753 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
1754 task_runner);
1755 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
1756
1757 // Create the surface needed by rasterizer
1758 PlatformViewNotifyCreated(shell.get());
1759
1760 auto configuration = RunConfiguration::InferFromSettings(settings);
1761 configuration.SetEntrypoint("emptyMain");
1762
1763 RunEngine(shell.get(), std::move(configuration));
1764 PumpOneFrame(shell.get());
1766 task_runner->PostTask([&shell, &event] {
1767 fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::Max());
1768 ASSERT_FALSE(result.ok());
1769 ASSERT_EQ(result.code(), fml::StatusCode::kFailedPrecondition);
1770 event.Signal();
1771 });
1772 ASSERT_FALSE(event.WaitWithTimeout(fml::TimeDelta::Max()));
1773
1774 DestroyShell(std::move(shell), task_runners);
1775}
1776
1777static size_t GetRasterizerResourceCacheBytesSync(const Shell& shell) {
1778 size_t bytes = 0;
1781 shell.GetTaskRunners().GetRasterTaskRunner(), [&]() {
1782 if (auto rasterizer = shell.GetRasterizer()) {
1783 bytes = rasterizer->GetResourceCacheMaxBytes().value_or(0U);
1784 }
1785 latch.Signal();
1786 });
1787 latch.Wait();
1788 return bytes;
1789}
1790
1791TEST_F(ShellTest, MultipleFluttersSetResourceCacheBytes) {
1792 TaskRunners task_runners = GetTaskRunnersForFixture();
1793 auto settings = CreateSettingsForFixture();
1794 settings.resource_cache_max_bytes_threshold = 4000000U;
1795 GrMockOptions main_context_options;
1796 sk_sp<GrDirectContext> main_context =
1797 GrDirectContext::MakeMock(&main_context_options);
1798 Shell::CreateCallback<PlatformView> platform_view_create_callback =
1799 [task_runners, main_context](flutter::Shell& shell) {
1800 auto result = std::make_unique<TestPlatformView>(shell, task_runners);
1801 ON_CALL(*result, CreateRenderingSurface())
1802 .WillByDefault([main_context] {
1803 auto surface = std::make_unique<MockSurface>();
1804 ON_CALL(*surface, GetContext())
1805 .WillByDefault(Return(main_context.get()));
1806 ON_CALL(*surface, IsValid()).WillByDefault(Return(true));
1807 ON_CALL(*surface, MakeRenderContextCurrent()).WillByDefault([] {
1808 return std::make_unique<GLContextDefaultResult>(true);
1809 });
1810 return surface;
1811 });
1812 return result;
1813 };
1814
1815 auto shell = CreateShell({
1816 .settings = settings,
1817 .task_runners = task_runners,
1818 .platform_view_create_callback = platform_view_create_callback,
1819 });
1820
1821 // Create the surface needed by rasterizer
1822 PlatformViewNotifyCreated(shell.get());
1823
1824 auto configuration = RunConfiguration::InferFromSettings(settings);
1825 configuration.SetEntrypoint("emptyMain");
1826
1827 RunEngine(shell.get(), std::move(configuration));
1828 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
1829 shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
1830 {1.0, 100, 100, 22, 0});
1831 });
1832
1833 // first cache bytes
1834 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1835 static_cast<size_t>(480000U));
1836
1837 auto shell_spawn_callback = [&]() {
1838 std::unique_ptr<Shell> spawn;
1839 PostSync(
1840 shell->GetTaskRunners().GetPlatformTaskRunner(),
1841 [this, &spawn, &spawner = shell, platform_view_create_callback]() {
1842 auto configuration =
1843 RunConfiguration::InferFromSettings(CreateSettingsForFixture());
1844 configuration.SetEntrypoint("emptyMain");
1845 spawn = spawner->Spawn(
1846 std::move(configuration), "", platform_view_create_callback,
1847 [](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
1848 ASSERT_NE(nullptr, spawn.get());
1849 ASSERT_TRUE(ValidateShell(spawn.get()));
1850 });
1851 return spawn;
1852 };
1853
1854 std::unique_ptr<Shell> second_shell = shell_spawn_callback();
1855 PlatformViewNotifyCreated(second_shell.get());
1856 PostSync(second_shell->GetTaskRunners().GetPlatformTaskRunner(),
1857 [&second_shell]() {
1858 second_shell->GetPlatformView()->SetViewportMetrics(
1859 kImplicitViewId, {1.0, 100, 100, 22, 0});
1860 });
1861 // first cache bytes + second cache bytes
1862 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1863 static_cast<size_t>(960000U));
1864
1865 PostSync(second_shell->GetTaskRunners().GetPlatformTaskRunner(),
1866 [&second_shell]() {
1867 second_shell->GetPlatformView()->SetViewportMetrics(
1868 kImplicitViewId, {1.0, 100, 300, 22, 0});
1869 });
1870 // first cache bytes + second cache bytes
1871 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1872 static_cast<size_t>(1920000U));
1873
1874 std::unique_ptr<Shell> third_shell = shell_spawn_callback();
1875 PlatformViewNotifyCreated(third_shell.get());
1876 PostSync(third_shell->GetTaskRunners().GetPlatformTaskRunner(),
1877 [&third_shell]() {
1878 third_shell->GetPlatformView()->SetViewportMetrics(
1879 kImplicitViewId, {1.0, 400, 100, 22, 0});
1880 });
1881 // first cache bytes + second cache bytes + third cache bytes
1882 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1883 static_cast<size_t>(3840000U));
1884
1885 PostSync(third_shell->GetTaskRunners().GetPlatformTaskRunner(),
1886 [&third_shell]() {
1887 third_shell->GetPlatformView()->SetViewportMetrics(
1888 kImplicitViewId, {1.0, 800, 100, 22, 0});
1889 });
1890 // max bytes threshold
1891 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1892 static_cast<size_t>(4000000U));
1893 DestroyShell(std::move(third_shell), task_runners);
1894 // max bytes threshold
1895 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1896 static_cast<size_t>(4000000U));
1897
1898 PostSync(second_shell->GetTaskRunners().GetPlatformTaskRunner(),
1899 [&second_shell]() {
1900 second_shell->GetPlatformView()->SetViewportMetrics(
1901 kImplicitViewId, {1.0, 100, 100, 22, 0});
1902 });
1903 // first cache bytes + second cache bytes
1904 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1905 static_cast<size_t>(960000U));
1906
1907 DestroyShell(std::move(second_shell), task_runners);
1908 DestroyShell(std::move(shell), task_runners);
1909}
1910
1911TEST_F(ShellTest, SetResourceCacheSize) {
1912 Settings settings = CreateSettingsForFixture();
1913 auto task_runner = CreateNewThread();
1914 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
1915 task_runner);
1916 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
1917
1918 // Create the surface needed by rasterizer
1919 PlatformViewNotifyCreated(shell.get());
1920
1921 auto configuration = RunConfiguration::InferFromSettings(settings);
1922 configuration.SetEntrypoint("emptyMain");
1923
1924 RunEngine(shell.get(), std::move(configuration));
1925 PumpOneFrame(shell.get());
1926
1927 // The Vulkan and GL backends set different default values for the resource
1928 // cache size. The default backend (specified by the default param of
1929 // `CreateShell` in this test) will only resolve to Vulkan (in
1930 // `ShellTestPlatformView::Create`) if GL is disabled. This situation arises
1931 // when targeting the Fuchsia Emulator.
1932#if defined(SHELL_ENABLE_VULKAN) && !defined(SHELL_ENABLE_GL)
1933 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1935#elif defined(SHELL_ENABLE_METAL)
1936 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1937 static_cast<size_t>(256 * (1 << 20)));
1938#else
1939 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1940 static_cast<size_t>(24 * (1 << 20)));
1941#endif
1942
1944 shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
1945 shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
1946 {1.0, 400, 200, 22, 0});
1947 });
1948 PumpOneFrame(shell.get());
1949
1950 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 3840000U);
1951
1952 std::string request_json = R"json({
1953 "method": "Skia.setResourceCacheMaxBytes",
1954 "args": 10000
1955 })json";
1956 auto data =
1957 fml::MallocMapping::Copy(request_json.c_str(), request_json.length());
1958 auto platform_message = std::make_unique<PlatformMessage>(
1959 "flutter/skia", std::move(data), nullptr);
1960 SendEnginePlatformMessage(shell.get(), std::move(platform_message));
1961 PumpOneFrame(shell.get());
1962 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U);
1963
1965 shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
1966 shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
1967 {1.0, 800, 400, 22, 0});
1968 });
1969 PumpOneFrame(shell.get());
1970
1971 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U);
1972 DestroyShell(std::move(shell), task_runners);
1973}
1974
1975TEST_F(ShellTest, SetResourceCacheSizeEarly) {
1976 Settings settings = CreateSettingsForFixture();
1977 auto task_runner = CreateNewThread();
1978 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
1979 task_runner);
1980 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
1981
1983 shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
1984 shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
1985 {1.0, 400, 200, 22, 0});
1986 });
1987 PumpOneFrame(shell.get());
1988
1989 // Create the surface needed by rasterizer
1990 PlatformViewNotifyCreated(shell.get());
1991
1992 auto configuration = RunConfiguration::InferFromSettings(settings);
1993 configuration.SetEntrypoint("emptyMain");
1994
1995 RunEngine(shell.get(), std::move(configuration));
1996 PumpOneFrame(shell.get());
1997
1998 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
1999 static_cast<size_t>(3840000U));
2000 DestroyShell(std::move(shell), task_runners);
2001}
2002
2003TEST_F(ShellTest, SetResourceCacheSizeNotifiesDart) {
2004 Settings settings = CreateSettingsForFixture();
2005 auto task_runner = CreateNewThread();
2006 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
2007 task_runner);
2008 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2009
2011 shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
2012 shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
2013 {1.0, 400, 200, 22, 0});
2014 });
2015 PumpOneFrame(shell.get());
2016
2017 // Create the surface needed by rasterizer
2018 PlatformViewNotifyCreated(shell.get());
2019
2020 auto configuration = RunConfiguration::InferFromSettings(settings);
2021 configuration.SetEntrypoint("testSkiaResourceCacheSendsResponse");
2022
2023 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
2024 static_cast<size_t>(3840000U));
2025
2027 AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) {
2028 latch.Signal();
2029 }));
2030
2031 RunEngine(shell.get(), std::move(configuration));
2032 PumpOneFrame(shell.get());
2033
2034 latch.Wait();
2035
2036 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
2037 static_cast<size_t>(10000U));
2038 DestroyShell(std::move(shell), task_runners);
2039}
2040
2041TEST_F(ShellTest, CanCreateImagefromDecompressedBytes) {
2042 Settings settings = CreateSettingsForFixture();
2043 auto task_runner = CreateNewThread();
2044
2045 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
2046 task_runner);
2047
2048 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2049
2050 // Create the surface needed by rasterizer
2051 PlatformViewNotifyCreated(shell.get());
2052
2053 auto configuration = RunConfiguration::InferFromSettings(settings);
2054 configuration.SetEntrypoint("canCreateImageFromDecompressedData");
2055
2057 AddNativeCallback("NotifyWidthHeight",
2058 CREATE_NATIVE_ENTRY([&latch](auto args) {
2060 Dart_GetNativeArgument(args, 0));
2062 Dart_GetNativeArgument(args, 1));
2063 ASSERT_EQ(width, 10);
2064 ASSERT_EQ(height, 10);
2065 latch.Signal();
2066 }));
2067
2068 RunEngine(shell.get(), std::move(configuration));
2069
2070 latch.Wait();
2071 DestroyShell(std::move(shell), task_runners);
2072}
2073
2074class MockTexture : public Texture {
2075 public:
2076 MockTexture(int64_t textureId,
2077 std::shared_ptr<fml::AutoResetWaitableEvent> latch)
2078 : Texture(textureId), latch_(std::move(latch)) {}
2079
2080 ~MockTexture() override = default;
2081
2082 // Called from raster thread.
2083 void Paint(PaintContext& context,
2084 const DlRect& bounds,
2085 bool freeze,
2086 const DlImageSampling) override {}
2087
2088 void OnGrContextCreated() override {}
2089
2090 void OnGrContextDestroyed() override {}
2091
2092 void MarkNewFrameAvailable() override {
2093 frames_available_++;
2094 latch_->Signal();
2095 }
2096
2097 void OnTextureUnregistered() override {
2098 unregistered_ = true;
2099 latch_->Signal();
2100 }
2101
2102 bool unregistered() { return unregistered_; }
2103 int frames_available() { return frames_available_; }
2104
2105 private:
2106 bool unregistered_ = false;
2107 int frames_available_ = 0;
2108 std::shared_ptr<fml::AutoResetWaitableEvent> latch_;
2109};
2110
2111TEST_F(ShellTest, TextureFrameMarkedAvailableAndUnregister) {
2112 Settings settings = CreateSettingsForFixture();
2113 auto configuration = RunConfiguration::InferFromSettings(settings);
2114 auto task_runner = CreateNewThread();
2115 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
2116 task_runner);
2117 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2118
2119 ASSERT_TRUE(ValidateShell(shell.get()));
2120 PlatformViewNotifyCreated(shell.get());
2121
2122 RunEngine(shell.get(), std::move(configuration));
2123
2124 std::shared_ptr<fml::AutoResetWaitableEvent> latch =
2125 std::make_shared<fml::AutoResetWaitableEvent>();
2126
2127 std::shared_ptr<MockTexture> mockTexture =
2128 std::make_shared<MockTexture>(0, latch);
2129
2131 shell->GetTaskRunners().GetRasterTaskRunner(), [&]() {
2132 shell->GetPlatformView()->RegisterTexture(mockTexture);
2133 shell->GetPlatformView()->MarkTextureFrameAvailable(0);
2134 });
2135 latch->Wait();
2136
2137 EXPECT_EQ(mockTexture->frames_available(), 1);
2138
2140 shell->GetTaskRunners().GetRasterTaskRunner(),
2141 [&]() { shell->GetPlatformView()->UnregisterTexture(0); });
2142 latch->Wait();
2143
2144 EXPECT_EQ(mockTexture->unregistered(), true);
2145 DestroyShell(std::move(shell), task_runners);
2146}
2147
2148TEST_F(ShellTest, IsolateCanAccessPersistentIsolateData) {
2149 const std::string message = "dummy isolate launch data.";
2150
2151 Settings settings = CreateSettingsForFixture();
2152 settings.persistent_isolate_data =
2153 std::make_shared<fml::DataMapping>(message);
2154 TaskRunners task_runners("test", // label
2155 GetCurrentTaskRunner(), // platform
2156 CreateNewThread(), // raster
2157 CreateNewThread(), // ui
2158 CreateNewThread() // io
2159 );
2160
2161 fml::AutoResetWaitableEvent message_latch;
2162 AddNativeCallback("NotifyMessage",
2163 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
2164 const auto message_from_dart =
2166 Dart_GetNativeArgument(args, 0));
2167 ASSERT_EQ(message, message_from_dart);
2168 message_latch.Signal();
2169 }));
2170
2171 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2172
2173 ASSERT_TRUE(shell->IsSetup());
2174 auto configuration = RunConfiguration::InferFromSettings(settings);
2175 configuration.SetEntrypoint("canAccessIsolateLaunchData");
2176
2178 shell->RunEngine(std::move(configuration), [&](auto result) {
2179 ASSERT_EQ(result, Engine::RunStatus::Success);
2180 });
2181
2182 message_latch.Wait();
2183 DestroyShell(std::move(shell), task_runners);
2184}
2185
2186TEST_F(ShellTest, CanScheduleFrameFromPlatform) {
2187 Settings settings = CreateSettingsForFixture();
2188 TaskRunners task_runners = GetTaskRunnersForFixture();
2190 AddNativeCallback(
2191 "NotifyNative",
2192 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }));
2193 fml::AutoResetWaitableEvent check_latch;
2194 AddNativeCallback("NativeOnBeginFrame",
2195 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
2196 check_latch.Signal();
2197 }));
2198 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2199 ASSERT_TRUE(shell->IsSetup());
2200
2201 auto configuration = RunConfiguration::InferFromSettings(settings);
2202 configuration.SetEntrypoint("onBeginFrameWithNotifyNativeMain");
2203 RunEngine(shell.get(), std::move(configuration));
2204
2205 // Wait for the application to attach the listener.
2206 latch.Wait();
2207
2209 shell->GetTaskRunners().GetPlatformTaskRunner(),
2210 [&shell]() { shell->GetPlatformView()->ScheduleFrame(); });
2211 check_latch.Wait();
2212 DestroyShell(std::move(shell), task_runners);
2213}
2214
2215TEST_F(ShellTest, SecondaryVsyncCallbackShouldBeCalledAfterVsyncCallback) {
2216 bool is_on_begin_frame_called = false;
2217 bool is_secondary_callback_called = false;
2218 bool test_started = false;
2219 Settings settings = CreateSettingsForFixture();
2220 TaskRunners task_runners = GetTaskRunnersForFixture();
2222 AddNativeCallback(
2223 "NotifyNative",
2224 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }));
2225 fml::CountDownLatch count_down_latch(2);
2226 AddNativeCallback("NativeOnBeginFrame",
2227 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
2228 if (!test_started) {
2229 return;
2230 }
2231 EXPECT_FALSE(is_on_begin_frame_called);
2232 EXPECT_FALSE(is_secondary_callback_called);
2233 is_on_begin_frame_called = true;
2234 count_down_latch.CountDown();
2235 }));
2236 std::unique_ptr<Shell> shell = CreateShell({
2237 .settings = settings,
2238 .task_runners = task_runners,
2239 });
2240 ASSERT_TRUE(shell->IsSetup());
2241
2242 auto configuration = RunConfiguration::InferFromSettings(settings);
2243 configuration.SetEntrypoint("onBeginFrameWithNotifyNativeMain");
2244 RunEngine(shell.get(), std::move(configuration));
2245
2246 // Wait for the application to attach the listener.
2247 latch.Wait();
2248
2250 shell->GetTaskRunners().GetUITaskRunner(), [&]() {
2251 shell->GetEngine()->ScheduleSecondaryVsyncCallback(0, [&]() {
2252 if (!test_started) {
2253 return;
2254 }
2255 EXPECT_TRUE(is_on_begin_frame_called);
2256 EXPECT_FALSE(is_secondary_callback_called);
2257 is_secondary_callback_called = true;
2258 count_down_latch.CountDown();
2259 });
2260 shell->GetEngine()->ScheduleFrame();
2261 test_started = true;
2262 });
2263 count_down_latch.Wait();
2264 EXPECT_TRUE(is_on_begin_frame_called);
2265 EXPECT_TRUE(is_secondary_callback_called);
2266 DestroyShell(std::move(shell), task_runners);
2267}
2268
2269static void LogSkData(const sk_sp<SkData>& data, const char* title) {
2270 FML_LOG(ERROR) << "---------- " << title;
2271 std::ostringstream ostr;
2272 for (size_t i = 0; i < data->size();) {
2273 ostr << std::hex << std::setfill('0') << std::setw(2)
2274 << static_cast<int>(data->bytes()[i]) << " ";
2275 i++;
2276 if (i % 16 == 0 || i == data->size()) {
2277 FML_LOG(ERROR) << ostr.str();
2278 ostr.str("");
2279 ostr.clear();
2280 }
2281 }
2282}
2283
2284TEST_F(ShellTest, Screenshot) {
2285 auto settings = CreateSettingsForFixture();
2286 fml::AutoResetWaitableEvent firstFrameLatch;
2287 settings.frame_rasterized_callback =
2288 [&firstFrameLatch](const FrameTiming& t) { firstFrameLatch.Signal(); };
2289
2290 std::unique_ptr<Shell> shell = CreateShell(settings);
2291
2292 // Create the surface needed by rasterizer
2293 PlatformViewNotifyCreated(shell.get());
2294
2295 auto configuration = RunConfiguration::InferFromSettings(settings);
2296 configuration.SetEntrypoint("emptyMain");
2297
2298 RunEngine(shell.get(), std::move(configuration));
2299
2300 LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
2301 auto display_list_layer = std::make_shared<DisplayListLayer>(
2302 DlPoint(10, 10), MakeSizedDisplayList(80, 80), false, false);
2303 root->Add(display_list_layer);
2304 };
2305
2306 PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
2307 firstFrameLatch.Wait();
2308
2309 std::promise<Rasterizer::Screenshot> screenshot_promise;
2310 auto screenshot_future = screenshot_promise.get_future();
2311
2313 shell->GetTaskRunners().GetRasterTaskRunner(),
2314 [&screenshot_promise, &shell]() {
2315 auto rasterizer = shell->GetRasterizer();
2316 screenshot_promise.set_value(rasterizer->ScreenshotLastLayerTree(
2317 Rasterizer::ScreenshotType::CompressedImage, false));
2318 });
2319
2320 auto fixtures_dir =
2322
2323 auto reference_png = fml::FileMapping::CreateReadOnly(
2324 fixtures_dir, "shelltest_screenshot.png");
2325
2326 // Use MakeWithoutCopy instead of MakeWithCString because we don't want to
2327 // encode the null sentinel
2328 sk_sp<SkData> reference_data = SkData::MakeWithoutCopy(
2329 reference_png->GetMapping(), reference_png->GetSize());
2330
2331 sk_sp<SkData> screenshot_data = screenshot_future.get().data;
2332 if (!reference_data->equals(screenshot_data.get())) {
2333 LogSkData(reference_data, "reference");
2334 LogSkData(screenshot_data, "screenshot");
2335 ASSERT_TRUE(false);
2336 }
2337
2338 DestroyShell(std::move(shell));
2339}
2340
2341// Compares local times as seen by the dart isolate and as seen by this test
2342// fixture, to a resolution of 1 hour.
2343//
2344// This verifies that (1) the isolate is able to get a timezone (doesn't lock
2345// up for example), and (2) that the host and the isolate agree on what the
2346// timezone is.
2347TEST_F(ShellTest, LocaltimesMatch) {
2349 std::string dart_isolate_time_str;
2350
2351 // See fixtures/shell_test.dart, the callback NotifyLocalTime is declared
2352 // there.
2353 AddNativeCallback("NotifyLocalTime", CREATE_NATIVE_ENTRY([&](auto args) {
2354 dart_isolate_time_str =
2356 Dart_GetNativeArgument(args, 0));
2357 latch.Signal();
2358 }));
2359
2360 auto settings = CreateSettingsForFixture();
2361 auto configuration = RunConfiguration::InferFromSettings(settings);
2362 configuration.SetEntrypoint("localtimesMatch");
2363 std::unique_ptr<Shell> shell = CreateShell(settings);
2364 ASSERT_NE(shell.get(), nullptr);
2365 RunEngine(shell.get(), std::move(configuration));
2366 latch.Wait();
2367
2368 char timestr[200];
2369 const time_t timestamp = time(nullptr);
2370 const struct tm* local_time = localtime(&timestamp);
2371 ASSERT_NE(local_time, nullptr)
2372 << "Could not get local time: errno=" << errno << ": " << strerror(errno);
2373 // Example: "2020-02-26 14" for 2pm on February 26, 2020.
2374 const size_t format_size =
2375 strftime(timestr, sizeof(timestr), "%Y-%m-%d %H", local_time);
2376 ASSERT_NE(format_size, 0UL)
2377 << "strftime failed: host time: " << std::string(timestr)
2378 << " dart isolate time: " << dart_isolate_time_str;
2379
2380 const std::string host_local_time_str = timestr;
2381
2382 ASSERT_EQ(dart_isolate_time_str, host_local_time_str)
2383 << "Local times in the dart isolate and the local time seen by the test "
2384 << "differ by more than 1 hour, but are expected to be about equal";
2385
2386 DestroyShell(std::move(shell));
2387}
2388
2389/// An image generator that always creates a 1x1 single-frame green image.
2391 public:
2393 : info_(SkImageInfo::MakeN32(1, 1, SkAlphaType::kOpaque_SkAlphaType)) {};
2395 const SkImageInfo& GetInfo() { return info_; }
2396
2397 unsigned int GetFrameCount() const { return 1; }
2398
2399 unsigned int GetPlayCount() const { return 1; }
2400
2401 const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index) {
2402 return {std::nullopt, 0, SkCodecAnimation::DisposalMethod::kKeep};
2403 }
2404
2405 SkISize GetScaledDimensions(float scale) {
2406 return SkISize::Make(info_.width(), info_.height());
2407 }
2408
2409 bool GetPixels(const SkImageInfo& info,
2410 void* pixels,
2411 size_t row_bytes,
2412 unsigned int frame_index,
2413 std::optional<unsigned int> prior_frame) {
2414 assert(info.width() == 1);
2415 assert(info.height() == 1);
2416 assert(row_bytes == 4);
2417
2418 reinterpret_cast<uint32_t*>(pixels)[0] = 0x00ff00ff;
2419 return true;
2420 };
2421
2422 private:
2423 SkImageInfo info_;
2424};
2425
2426TEST_F(ShellTest, CanRegisterImageDecoders) {
2428 AddNativeCallback("NotifyWidthHeight", CREATE_NATIVE_ENTRY([&](auto args) {
2430 Dart_GetNativeArgument(args, 0));
2432 Dart_GetNativeArgument(args, 1));
2433 ASSERT_EQ(width, 1);
2434 ASSERT_EQ(height, 1);
2435 latch.Signal();
2436 }));
2437
2438 auto settings = CreateSettingsForFixture();
2439 auto configuration = RunConfiguration::InferFromSettings(settings);
2440 configuration.SetEntrypoint("canRegisterImageDecoders");
2441 std::unique_ptr<Shell> shell = CreateShell(settings);
2442 ASSERT_NE(shell.get(), nullptr);
2443
2445 shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
2446 shell->RegisterImageDecoder(
2447 [](const sk_sp<SkData>& buffer) {
2448 return std::make_unique<SinglePixelImageGenerator>();
2449 },
2450 100);
2451 });
2452
2453 RunEngine(shell.get(), std::move(configuration));
2454 latch.Wait();
2455 DestroyShell(std::move(shell));
2456}
2457
2458TEST_F(ShellTest, OnServiceProtocolGetSkSLsWorks) {
2460 ASSERT_TRUE(base_dir.fd().is_valid());
2461 PersistentCache::SetCacheDirectoryPath(base_dir.path());
2462 PersistentCache::ResetCacheForProcess();
2463
2464 // Create 2 dummy SkSL cache file IE (base32 encoding of A), II (base32
2465 // encoding of B) with content x and y.
2466 std::vector<std::string> components = {
2467 "flutter_engine", GetFlutterEngineVersion(), "skia", GetSkiaVersion(),
2468 PersistentCache::kSkSLSubdirName};
2469 auto sksl_dir = fml::CreateDirectory(base_dir.fd(), components,
2471 const std::string x_key_str = "A";
2472 const std::string x_value_str = "x";
2473 sk_sp<SkData> x_key =
2474 SkData::MakeWithCopy(x_key_str.data(), x_key_str.size());
2475 sk_sp<SkData> x_value =
2476 SkData::MakeWithCopy(x_value_str.data(), x_value_str.size());
2477 auto x_data = PersistentCache::BuildCacheObject(*x_key, *x_value);
2478
2479 const std::string y_key_str = "B";
2480 const std::string y_value_str = "y";
2481 sk_sp<SkData> y_key =
2482 SkData::MakeWithCopy(y_key_str.data(), y_key_str.size());
2483 sk_sp<SkData> y_value =
2484 SkData::MakeWithCopy(y_value_str.data(), y_value_str.size());
2485 auto y_data = PersistentCache::BuildCacheObject(*y_key, *y_value);
2486
2487 ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "x_cache", *x_data));
2488 ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "y_cache", *y_data));
2489
2490 Settings settings = CreateSettingsForFixture();
2491 std::unique_ptr<Shell> shell = CreateShell(settings);
2493 rapidjson::Document document;
2494 OnServiceProtocol(shell.get(), ServiceProtocolEnum::kGetSkSLs,
2495 shell->GetTaskRunners().GetIOTaskRunner(), empty_params,
2496 &document);
2497 rapidjson::StringBuffer buffer;
2498 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
2499 document.Accept(writer);
2500 DestroyShell(std::move(shell));
2501
2502 const std::string expected_json1 =
2503 "{\"type\":\"GetSkSLs\",\"SkSLs\":{\"II\":\"eQ==\",\"IE\":\"eA==\"}}";
2504 const std::string expected_json2 =
2505 "{\"type\":\"GetSkSLs\",\"SkSLs\":{\"IE\":\"eA==\",\"II\":\"eQ==\"}}";
2506 bool json_is_expected = (expected_json1 == buffer.GetString()) ||
2507 (expected_json2 == buffer.GetString());
2508 ASSERT_TRUE(json_is_expected) << buffer.GetString() << " is not equal to "
2509 << expected_json1 << " or " << expected_json2;
2510}
2511
2512TEST_F(ShellTest, RasterizerScreenshot) {
2513 Settings settings = CreateSettingsForFixture();
2514 auto configuration = RunConfiguration::InferFromSettings(settings);
2515 auto task_runner = CreateNewThread();
2516 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
2517 task_runner);
2518 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2519
2520 ASSERT_TRUE(ValidateShell(shell.get()));
2521 PlatformViewNotifyCreated(shell.get());
2522
2523 RunEngine(shell.get(), std::move(configuration));
2524
2525 auto latch = std::make_shared<fml::AutoResetWaitableEvent>();
2526
2527 PumpOneFrame(shell.get());
2528
2530 shell->GetTaskRunners().GetRasterTaskRunner(), [&shell, &latch]() {
2531 Rasterizer::Screenshot screenshot =
2532 shell->GetRasterizer()->ScreenshotLastLayerTree(
2533 Rasterizer::ScreenshotType::CompressedImage, true);
2534 EXPECT_NE(screenshot.data, nullptr);
2535
2536 latch->Signal();
2537 });
2538 latch->Wait();
2539 DestroyShell(std::move(shell), task_runners);
2540}
2541
2542TEST_F(ShellTest, RasterizerMakeSkiaSnapshot) {
2543 Settings settings = CreateSettingsForFixture();
2544 auto configuration = RunConfiguration::InferFromSettings(settings);
2545 auto task_runner = CreateNewThread();
2546 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
2547 task_runner);
2548 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2549
2550 ASSERT_TRUE(ValidateShell(shell.get()));
2551 PlatformViewNotifyCreated(shell.get());
2552
2553 RunEngine(shell.get(), std::move(configuration));
2554
2555 auto latch = std::make_shared<fml::AutoResetWaitableEvent>();
2556
2557 PumpOneFrame(shell.get());
2558
2560 shell->GetTaskRunners().GetRasterTaskRunner(), [&shell, &latch]() {
2561 SnapshotDelegate* delegate =
2562 reinterpret_cast<Rasterizer*>(shell->GetRasterizer().get());
2563 sk_sp<SkImage> image = delegate->MakeSkiaSnapshotSync(
2564 MakeSizedDisplayList(50, 50), DlISize(50, 50),
2565 SnapshotPixelFormat::kDontCare);
2566 EXPECT_NE(image, nullptr);
2567
2568 latch->Signal();
2569 });
2570 latch->Wait();
2571 DestroyShell(std::move(shell), task_runners);
2572}
2573
2574TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) {
2575 Settings settings = CreateSettingsForFixture();
2576 std::unique_ptr<Shell> shell = CreateShell(settings);
2577
2578 // 1. Construct a picture and a picture layer to be raster cached.
2579 sk_sp<DisplayList> display_list = MakeSizedDisplayList(10, 10);
2580 auto display_list_layer = std::make_shared<DisplayListLayer>(
2581 DlPoint(0, 0), MakeSizedDisplayList(100, 100), false, false);
2582 display_list_layer->set_paint_bounds(DlRect::MakeWH(100, 100));
2583
2584 // 2. Rasterize the picture and the picture layer in the raster cache.
2585 std::promise<bool> rasterized;
2586
2587 shell->GetTaskRunners().GetRasterTaskRunner()->PostTask(
2588 [&shell, &rasterized, &display_list, &display_list_layer] {
2589 std::vector<RasterCacheItem*> raster_cache_items;
2590 auto* compositor_context = shell->GetRasterizer()->compositor_context();
2591 auto& raster_cache = compositor_context->raster_cache();
2592
2593 LayerStateStack state_stack;
2594 FixedRefreshRateStopwatch raster_time;
2596 PaintContext paint_context = {
2597 // clang-format off
2598 .state_stack = state_stack,
2599 .canvas = nullptr,
2600 .gr_context = nullptr,
2601 .dst_color_space = nullptr,
2602 .view_embedder = nullptr,
2603 .raster_time = raster_time,
2604 .ui_time = ui_time,
2605 .texture_registry = nullptr,
2606 .raster_cache = &raster_cache,
2607 // clang-format on
2608 };
2609
2610 PrerollContext preroll_context = {
2611 // clang-format off
2612 .raster_cache = &raster_cache,
2613 .gr_context = nullptr,
2614 .view_embedder = nullptr,
2615 .state_stack = state_stack,
2616 .dst_color_space = nullptr,
2617 .surface_needs_readback = false,
2618 .raster_time = raster_time,
2619 .ui_time = ui_time,
2620 .texture_registry = nullptr,
2621 .has_platform_view = false,
2622 .has_texture_layer = false,
2623 .raster_cached_entries = &raster_cache_items,
2624 // clang-format on
2625 };
2626
2627 // 2.1. Rasterize the picture. Call Draw multiple times to pass the
2628 // access threshold (default to 3) so a cache can be generated.
2629 DisplayListBuilder dummy_canvas;
2630 DlPaint paint;
2631 bool picture_cache_generated;
2632 DisplayListRasterCacheItem display_list_raster_cache_item(
2633 display_list, SkPoint(), true, false);
2634 for (int i = 0; i < 4; i += 1) {
2635 DlMatrix matrix;
2636 state_stack.set_preroll_delegate(matrix);
2637 display_list_raster_cache_item.PrerollSetup(&preroll_context, matrix);
2638 display_list_raster_cache_item.PrerollFinalize(&preroll_context,
2639 matrix);
2640 picture_cache_generated =
2641 display_list_raster_cache_item.need_caching();
2642 state_stack.set_delegate(&dummy_canvas);
2643 display_list_raster_cache_item.TryToPrepareRasterCache(paint_context);
2644 display_list_raster_cache_item.Draw(paint_context, &dummy_canvas,
2645 &paint);
2646 }
2647 ASSERT_TRUE(picture_cache_generated);
2648
2649 // 2.2. Rasterize the picture layer.
2650 LayerRasterCacheItem layer_raster_cache_item(display_list_layer.get());
2651 state_stack.set_preroll_delegate(DlMatrix());
2652 layer_raster_cache_item.PrerollSetup(&preroll_context, DlMatrix());
2653 layer_raster_cache_item.PrerollFinalize(&preroll_context, DlMatrix());
2654 state_stack.set_delegate(&dummy_canvas);
2655 layer_raster_cache_item.TryToPrepareRasterCache(paint_context);
2656 layer_raster_cache_item.Draw(paint_context, &dummy_canvas, &paint);
2657 rasterized.set_value(true);
2658 });
2659 rasterized.get_future().wait();
2660
2661 // 3. Call the service protocol and check its output.
2663 rapidjson::Document document;
2664 OnServiceProtocol(
2665 shell.get(), ServiceProtocolEnum::kEstimateRasterCacheMemory,
2666 shell->GetTaskRunners().GetRasterTaskRunner(), empty_params, &document);
2667 rapidjson::StringBuffer buffer;
2668 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
2669 document.Accept(writer);
2670 std::string expected_json =
2671 "{\"type\":\"EstimateRasterCacheMemory\",\"layerBytes\":40024,\"picture"
2672 "Bytes\":424}";
2673 std::string actual_json = buffer.GetString();
2674 ASSERT_EQ(actual_json, expected_json);
2675
2676 DestroyShell(std::move(shell));
2677}
2678
2679// TODO(https://github.com/flutter/flutter/issues/100273): Disabled due to
2680// flakiness.
2681// TODO(https://github.com/flutter/flutter/issues/100299): Fix it when
2682// re-enabling.
2683TEST_F(ShellTest, DISABLED_DiscardLayerTreeOnResize) {
2684 auto settings = CreateSettingsForFixture();
2685
2686 DlISize wrong_size = DlISize(400, 100);
2687 DlISize expected_size = DlISize(400, 200);
2688
2689 fml::AutoResetWaitableEvent end_frame_latch;
2690 auto end_frame_callback =
2691 [&](bool should_merge_thread,
2692 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
2693 end_frame_latch.Signal();
2694 };
2695 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
2696 std::move(end_frame_callback), PostPrerollResult::kSuccess, false);
2697 std::unique_ptr<Shell> shell = CreateShell({
2698 .settings = settings,
2699 .platform_view_create_callback = ShellTestPlatformViewBuilder({
2700 .shell_test_external_view_embedder = external_view_embedder,
2701 }),
2702 });
2703
2704 // Create the surface needed by rasterizer
2705 PlatformViewNotifyCreated(shell.get());
2706
2708 shell->GetTaskRunners().GetPlatformTaskRunner(),
2709 [&shell, &expected_size]() {
2710 shell->GetPlatformView()->SetViewportMetrics(
2711 kImplicitViewId,
2712 {1.0, static_cast<double>(expected_size.width),
2713 static_cast<double>(expected_size.height), 22, 0});
2714 });
2715
2716 auto configuration = RunConfiguration::InferFromSettings(settings);
2717 configuration.SetEntrypoint("emptyMain");
2718
2719 RunEngine(shell.get(), std::move(configuration));
2720
2721 PumpOneFrame(shell.get(),
2722 ViewContent::DummyView(static_cast<double>(wrong_size.width),
2723 static_cast<double>(wrong_size.height)));
2724 end_frame_latch.Wait();
2725 // Wrong size, no frames are submitted.
2726 ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
2727
2728 PumpOneFrame(shell.get(), ViewContent::DummyView(
2729 static_cast<double>(expected_size.width),
2730 static_cast<double>(expected_size.height)));
2731 end_frame_latch.Wait();
2732 // Expected size, 1 frame submitted.
2733 ASSERT_EQ(1, external_view_embedder->GetSubmittedFrameCount());
2734 ASSERT_EQ(expected_size, external_view_embedder->GetLastSubmittedFrameSize());
2735
2736 PlatformViewNotifyDestroyed(shell.get());
2737 DestroyShell(std::move(shell));
2738}
2739
2740// TODO(https://github.com/flutter/flutter/issues/100273): Disabled due to
2741// flakiness.
2742// TODO(https://github.com/flutter/flutter/issues/100299): Fix it when
2743// re-enabling.
2744TEST_F(ShellTest, DISABLED_DiscardResubmittedLayerTreeOnResize) {
2745 auto settings = CreateSettingsForFixture();
2746
2747 DlISize origin_size = DlISize(400, 100);
2748 DlISize new_size = DlISize(400, 200);
2749
2750 fml::AutoResetWaitableEvent end_frame_latch;
2751
2752 fml::AutoResetWaitableEvent resize_latch;
2753
2754 std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
2755 fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger_ref;
2756 auto end_frame_callback =
2757 [&](bool should_merge_thread,
2758 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
2759 if (!raster_thread_merger_ref) {
2760 raster_thread_merger_ref = raster_thread_merger;
2761 }
2762 if (should_merge_thread) {
2763 raster_thread_merger->MergeWithLease(10);
2764 external_view_embedder->UpdatePostPrerollResult(
2765 PostPrerollResult::kSuccess);
2766 }
2767 end_frame_latch.Signal();
2768
2769 if (should_merge_thread) {
2770 resize_latch.Wait();
2771 }
2772 };
2773
2774 external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
2775 std::move(end_frame_callback), PostPrerollResult::kResubmitFrame, true);
2776
2777 std::unique_ptr<Shell> shell = CreateShell({
2778 .settings = settings,
2779 .platform_view_create_callback = ShellTestPlatformViewBuilder({
2780 .shell_test_external_view_embedder = external_view_embedder,
2781 }),
2782 });
2783
2784 // Create the surface needed by rasterizer
2785 PlatformViewNotifyCreated(shell.get());
2786
2788 shell->GetTaskRunners().GetPlatformTaskRunner(),
2789 [&shell, &origin_size]() {
2790 shell->GetPlatformView()->SetViewportMetrics(
2791 kImplicitViewId, {1.0, static_cast<double>(origin_size.width),
2792 static_cast<double>(origin_size.height), 22, 0});
2793 });
2794
2795 auto configuration = RunConfiguration::InferFromSettings(settings);
2796 configuration.SetEntrypoint("emptyMain");
2797
2798 RunEngine(shell.get(), std::move(configuration));
2799
2800 PumpOneFrame(shell.get(),
2801 ViewContent::DummyView(static_cast<double>(origin_size.width),
2802 static_cast<double>(origin_size.height)));
2803
2804 end_frame_latch.Wait();
2805 ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
2806
2808 shell->GetTaskRunners().GetPlatformTaskRunner(),
2809 [&shell, &new_size, &resize_latch]() {
2810 shell->GetPlatformView()->SetViewportMetrics(
2811 kImplicitViewId, {1.0, static_cast<double>(new_size.width),
2812 static_cast<double>(new_size.height), 22, 0});
2813 resize_latch.Signal();
2814 });
2815
2816 end_frame_latch.Wait();
2817
2818 // The frame resubmitted with origin size should be discarded after the
2819 // viewport metrics changed.
2820 ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
2821
2822 // Threads will be merged at the end of this frame.
2823 PumpOneFrame(shell.get(),
2824 ViewContent::DummyView(static_cast<double>(new_size.width),
2825 static_cast<double>(new_size.height)));
2826
2827 end_frame_latch.Wait();
2828 ASSERT_TRUE(raster_thread_merger_ref->IsMerged());
2829 ASSERT_EQ(1, external_view_embedder->GetSubmittedFrameCount());
2830 ASSERT_EQ(new_size, external_view_embedder->GetLastSubmittedFrameSize());
2831
2832 PlatformViewNotifyDestroyed(shell.get());
2833 DestroyShell(std::move(shell));
2834}
2835
2836TEST_F(ShellTest, IgnoresInvalidMetrics) {
2838 double last_device_pixel_ratio;
2839 double last_width;
2840 double last_height;
2841 auto native_report_device_pixel_ratio = [&](Dart_NativeArguments args) {
2842 auto dpr_handle = Dart_GetNativeArgument(args, 0);
2843 ASSERT_TRUE(Dart_IsDouble(dpr_handle));
2844 Dart_DoubleValue(dpr_handle, &last_device_pixel_ratio);
2845 ASSERT_FALSE(last_device_pixel_ratio == 0.0);
2846
2847 auto width_handle = Dart_GetNativeArgument(args, 1);
2848 ASSERT_TRUE(Dart_IsDouble(width_handle));
2849 Dart_DoubleValue(width_handle, &last_width);
2850 ASSERT_FALSE(last_width == 0.0);
2851
2852 auto height_handle = Dart_GetNativeArgument(args, 2);
2853 ASSERT_TRUE(Dart_IsDouble(height_handle));
2854 Dart_DoubleValue(height_handle, &last_height);
2855 ASSERT_FALSE(last_height == 0.0);
2856
2857 latch.Signal();
2858 };
2859
2860 Settings settings = CreateSettingsForFixture();
2861 auto task_runner = CreateNewThread();
2862 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
2863 task_runner);
2864
2865 AddNativeCallback("ReportMetrics",
2866 CREATE_NATIVE_ENTRY(native_report_device_pixel_ratio));
2867
2868 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2869
2870 auto configuration = RunConfiguration::InferFromSettings(settings);
2871 configuration.SetEntrypoint("reportMetrics");
2872
2873 RunEngine(shell.get(), std::move(configuration));
2874
2875 task_runner->PostTask([&]() {
2876 // This one is invalid for having 0 pixel ratio.
2877 shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
2878 {0.0, 400, 200, 22, 0});
2879 task_runner->PostTask([&]() {
2880 // This one is invalid for having 0 width.
2881 shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
2882 {0.8, 0.0, 200, 22, 0});
2883 task_runner->PostTask([&]() {
2884 // This one is invalid for having 0 height.
2885 shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
2886 {0.8, 400, 0.0, 22, 0});
2887 task_runner->PostTask([&]() {
2888 // This one makes it through.
2889 shell->GetPlatformView()->SetViewportMetrics(
2890 kImplicitViewId, {0.8, 400, 200.0, 22, 0});
2891 });
2892 });
2893 });
2894 });
2895 latch.Wait();
2896 ASSERT_EQ(last_device_pixel_ratio, 0.8);
2897 ASSERT_EQ(last_width, 400.0);
2898 ASSERT_EQ(last_height, 200.0);
2899 latch.Reset();
2900
2901 task_runner->PostTask([&]() {
2902 shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
2903 {1.2, 600, 300, 22, 0});
2904 });
2905 latch.Wait();
2906 ASSERT_EQ(last_device_pixel_ratio, 1.2);
2907 ASSERT_EQ(last_width, 600.0);
2908 ASSERT_EQ(last_height, 300.0);
2909
2910 DestroyShell(std::move(shell), task_runners);
2911}
2912
2913TEST_F(ShellTest, IgnoresMetricsUpdateToInvalidView) {
2915 double last_device_pixel_ratio;
2916 // This callback will be called whenever any view's metrics change.
2917 auto native_report_device_pixel_ratio = [&](Dart_NativeArguments args) {
2918 // The correct call will have a DPR of 3.
2919 auto dpr_handle = Dart_GetNativeArgument(args, 0);
2920 ASSERT_TRUE(Dart_IsDouble(dpr_handle));
2921 Dart_DoubleValue(dpr_handle, &last_device_pixel_ratio);
2922 ASSERT_TRUE(last_device_pixel_ratio > 2.5);
2923
2924 latch.Signal();
2925 };
2926
2927 Settings settings = CreateSettingsForFixture();
2928 auto task_runner = CreateNewThread();
2929 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
2930 task_runner);
2931
2932 AddNativeCallback("ReportMetrics",
2933 CREATE_NATIVE_ENTRY(native_report_device_pixel_ratio));
2934
2935 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
2936
2937 auto configuration = RunConfiguration::InferFromSettings(settings);
2938 configuration.SetEntrypoint("reportMetrics");
2939
2940 RunEngine(shell.get(), std::move(configuration));
2941
2942 task_runner->PostTask([&]() {
2943 // This one is invalid for having an nonexistent view ID.
2944 // Also, it has a DPR of 2.0 for detection.
2945 shell->GetPlatformView()->SetViewportMetrics(2, {2.0, 400, 200, 22, 0});
2946 task_runner->PostTask([&]() {
2947 // This one is valid with DPR 3.0.
2948 shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
2949 {3.0, 400, 200, 22, 0});
2950 });
2951 });
2952 latch.Wait();
2953 ASSERT_EQ(last_device_pixel_ratio, 3.0);
2954 latch.Reset();
2955
2956 DestroyShell(std::move(shell), task_runners);
2957}
2958
2959TEST_F(ShellTest, OnServiceProtocolSetAssetBundlePathWorks) {
2960 Settings settings = CreateSettingsForFixture();
2961 std::unique_ptr<Shell> shell = CreateShell(settings);
2962 RunConfiguration configuration =
2963 RunConfiguration::InferFromSettings(settings);
2964 configuration.SetEntrypoint("canAccessResourceFromAssetDir");
2965
2966 // Verify isolate can load a known resource with the
2967 // default asset directory - kernel_blob.bin
2969
2970 // Callback used to signal whether the resource was loaded successfully.
2971 bool can_access_resource = false;
2972 auto native_can_access_resource = [&can_access_resource,
2973 &latch](Dart_NativeArguments args) {
2974 Dart_Handle exception = nullptr;
2975 can_access_resource =
2977 latch.Signal();
2978 };
2979 AddNativeCallback("NotifyCanAccessResource",
2980 CREATE_NATIVE_ENTRY(native_can_access_resource));
2981
2982 // Callback used to delay the asset load until after the service
2983 // protocol method has finished.
2984 auto native_notify_set_asset_bundle_path =
2985 [&shell](Dart_NativeArguments args) {
2986 // Update the asset directory to a bonus path.
2988 params["assetDirectory"] = "assetDirectory";
2989 rapidjson::Document document;
2990 OnServiceProtocol(shell.get(), ServiceProtocolEnum::kSetAssetBundlePath,
2991 shell->GetTaskRunners().GetUITaskRunner(), params,
2992 &document);
2993 rapidjson::StringBuffer buffer;
2994 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
2995 document.Accept(writer);
2996 };
2997 AddNativeCallback("NotifySetAssetBundlePath",
2998 CREATE_NATIVE_ENTRY(native_notify_set_asset_bundle_path));
2999
3000 RunEngine(shell.get(), std::move(configuration));
3001
3002 latch.Wait();
3003 ASSERT_TRUE(can_access_resource);
3004
3005 DestroyShell(std::move(shell));
3006}
3007
3008TEST_F(ShellTest, EngineRootIsolateLaunchesDontTakeVMDataSettings) {
3009 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3010 // Make sure the shell launch does not kick off the creation of the VM
3011 // instance by already creating one upfront.
3012 auto vm_settings = CreateSettingsForFixture();
3013 auto vm_ref = DartVMRef::Create(vm_settings);
3014 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3015
3016 auto settings = vm_settings;
3017 fml::AutoResetWaitableEvent isolate_create_latch;
3018 settings.root_isolate_create_callback = [&](const auto& isolate) {
3019 isolate_create_latch.Signal();
3020 };
3021 auto shell = CreateShell(settings);
3022 ASSERT_TRUE(ValidateShell(shell.get()));
3023 auto configuration = RunConfiguration::InferFromSettings(settings);
3024 ASSERT_TRUE(configuration.IsValid());
3025 RunEngine(shell.get(), std::move(configuration));
3026 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3027 DestroyShell(std::move(shell));
3028 isolate_create_latch.Wait();
3029}
3030
3031TEST_F(ShellTest, AssetManagerSingle) {
3033 fml::UniqueFD asset_dir_fd = fml::OpenDirectory(
3034 asset_dir.path().c_str(), false, fml::FilePermission::kRead);
3035
3036 std::string filename = "test_name";
3037 std::string content = "test_content";
3038
3039 bool success = fml::WriteAtomically(asset_dir_fd, filename.c_str(),
3041 ASSERT_TRUE(success);
3042
3043 AssetManager asset_manager;
3044 asset_manager.PushBack(
3045 std::make_unique<DirectoryAssetBundle>(std::move(asset_dir_fd), false));
3046
3047 auto mapping = asset_manager.GetAsMapping(filename);
3048 ASSERT_TRUE(mapping != nullptr);
3049
3050 std::string result(reinterpret_cast<const char*>(mapping->GetMapping()),
3051 mapping->GetSize());
3052
3053 ASSERT_TRUE(result == content);
3054}
3055
3056TEST_F(ShellTest, AssetManagerMulti) {
3058 fml::UniqueFD asset_dir_fd = fml::OpenDirectory(
3059 asset_dir.path().c_str(), false, fml::FilePermission::kRead);
3060
3061 std::vector<std::string> filenames = {
3062 "good0",
3063 "bad0",
3064 "good1",
3065 "bad1",
3066 };
3067
3068 for (const auto& filename : filenames) {
3069 bool success = fml::WriteAtomically(asset_dir_fd, filename.c_str(),
3070 fml::DataMapping(filename));
3071 ASSERT_TRUE(success);
3072 }
3073
3074 AssetManager asset_manager;
3075 asset_manager.PushBack(
3076 std::make_unique<DirectoryAssetBundle>(std::move(asset_dir_fd), false));
3077
3078 auto mappings = asset_manager.GetAsMappings("(.*)", std::nullopt);
3079 EXPECT_EQ(mappings.size(), 4u);
3080
3081 std::vector<std::string> expected_results = {
3082 "good0",
3083 "good1",
3084 };
3085
3086 mappings = asset_manager.GetAsMappings("(.*)good(.*)", std::nullopt);
3087 ASSERT_EQ(mappings.size(), expected_results.size());
3088
3089 for (auto& mapping : mappings) {
3090 std::string result(reinterpret_cast<const char*>(mapping->GetMapping()),
3091 mapping->GetSize());
3092 EXPECT_NE(
3093 std::find(expected_results.begin(), expected_results.end(), result),
3094 expected_results.end());
3095 }
3096}
3097
3098#if defined(OS_FUCHSIA)
3099TEST_F(ShellTest, AssetManagerMultiSubdir) {
3100 std::string subdir_path = "subdir";
3101
3103 fml::UniqueFD asset_dir_fd = fml::OpenDirectory(
3104 asset_dir.path().c_str(), false, fml::FilePermission::kRead);
3105 fml::UniqueFD subdir_fd =
3106 fml::OpenDirectory((asset_dir.path() + "/" + subdir_path).c_str(), true,
3108
3109 std::vector<std::string> filenames = {
3110 "bad0",
3111 "notgood", // this is to make sure the pattern (.*)good(.*) only
3112 // matches things in the subdirectory
3113 };
3114
3115 std::vector<std::string> subdir_filenames = {
3116 "good0",
3117 "good1",
3118 "bad1",
3119 };
3120
3121 for (auto filename : filenames) {
3122 bool success = fml::WriteAtomically(asset_dir_fd, filename.c_str(),
3123 fml::DataMapping(filename));
3124 ASSERT_TRUE(success);
3125 }
3126
3127 for (auto filename : subdir_filenames) {
3128 bool success = fml::WriteAtomically(subdir_fd, filename.c_str(),
3129 fml::DataMapping(filename));
3130 ASSERT_TRUE(success);
3131 }
3132
3133 AssetManager asset_manager;
3134 asset_manager.PushBack(
3135 std::make_unique<DirectoryAssetBundle>(std::move(asset_dir_fd), false));
3136
3137 auto mappings = asset_manager.GetAsMappings("(.*)", std::nullopt);
3138 EXPECT_EQ(mappings.size(), 5u);
3139
3140 mappings = asset_manager.GetAsMappings("(.*)", subdir_path);
3141 EXPECT_EQ(mappings.size(), 3u);
3142
3143 std::vector<std::string> expected_results = {
3144 "good0",
3145 "good1",
3146 };
3147
3148 mappings = asset_manager.GetAsMappings("(.*)good(.*)", subdir_path);
3149 ASSERT_EQ(mappings.size(), expected_results.size());
3150
3151 for (auto& mapping : mappings) {
3152 std::string result(reinterpret_cast<const char*>(mapping->GetMapping()),
3153 mapping->GetSize());
3154 ASSERT_NE(
3155 std::find(expected_results.begin(), expected_results.end(), result),
3156 expected_results.end());
3157 }
3158}
3159#endif // OS_FUCHSIA
3160
3162 auto settings = CreateSettingsForFixture();
3163 auto shell = CreateShell(settings);
3164 ASSERT_TRUE(ValidateShell(shell.get()));
3165
3166 auto configuration = RunConfiguration::InferFromSettings(settings);
3167 ASSERT_TRUE(configuration.IsValid());
3168 configuration.SetEntrypoint("fixturesAreFunctionalMain");
3169
3170 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3171 ASSERT_TRUE(second_configuration.IsValid());
3172 second_configuration.SetEntrypoint("testCanLaunchSecondaryIsolate");
3173
3174 const std::string initial_route("/foo");
3175
3176 fml::AutoResetWaitableEvent main_latch;
3177 std::string last_entry_point;
3178 // Fulfill native function for the first Shell's entrypoint.
3179 AddNativeCallback(
3180 "SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) {
3181 last_entry_point = shell->GetEngine()->GetLastEntrypoint();
3182 main_latch.Signal();
3183 }));
3184 // Fulfill native function for the second Shell's entrypoint.
3185 fml::CountDownLatch second_latch(2);
3186 AddNativeCallback(
3187 // The Dart native function names aren't very consistent but this is
3188 // just the native function name of the second vm entrypoint in the
3189 // fixture.
3190 "NotifyNative",
3191 CREATE_NATIVE_ENTRY([&](auto args) { second_latch.CountDown(); }));
3192
3193 RunEngine(shell.get(), std::move(configuration));
3194 main_latch.Wait();
3195 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3196 // Check first Shell ran the first entrypoint.
3197 ASSERT_EQ("fixturesAreFunctionalMain", last_entry_point);
3198
3199 PostSync(
3200 shell->GetTaskRunners().GetPlatformTaskRunner(),
3201 [this, &spawner = shell, &second_configuration, &second_latch,
3202 initial_route]() {
3203 MockPlatformViewDelegate platform_view_delegate;
3204 auto spawn = spawner->Spawn(
3205 std::move(second_configuration), initial_route,
3206 [&platform_view_delegate](Shell& shell) {
3207 auto result = std::make_unique<MockPlatformView>(
3208 platform_view_delegate, shell.GetTaskRunners());
3209 ON_CALL(*result, CreateRenderingSurface()).WillByDefault([] {
3210 return std::make_unique<MockSurface>();
3211 });
3212 return result;
3213 },
3214 [](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
3215 ASSERT_NE(nullptr, spawn.get());
3216 ASSERT_TRUE(ValidateShell(spawn.get()));
3217
3218 PostSync(spawner->GetTaskRunners().GetUITaskRunner(), [&spawn, &spawner,
3219 initial_route] {
3220 // Check second shell ran the second entrypoint.
3221 ASSERT_EQ("testCanLaunchSecondaryIsolate",
3222 spawn->GetEngine()->GetLastEntrypoint());
3223 ASSERT_EQ(initial_route, spawn->GetEngine()->InitialRoute());
3224
3225 ASSERT_NE(spawner->GetEngine()
3226 ->GetRuntimeController()
3227 ->GetRootIsolateGroup(),
3228 0u);
3229 ASSERT_EQ(spawner->GetEngine()
3230 ->GetRuntimeController()
3231 ->GetRootIsolateGroup(),
3232 spawn->GetEngine()
3233 ->GetRuntimeController()
3234 ->GetRootIsolateGroup());
3235 auto spawner_snapshot_delegate = spawner->GetEngine()
3236 ->GetRuntimeController()
3237 ->GetSnapshotDelegate();
3238 auto spawn_snapshot_delegate =
3239 spawn->GetEngine()->GetRuntimeController()->GetSnapshotDelegate();
3240 PostSync(spawner->GetTaskRunners().GetRasterTaskRunner(),
3241 [spawner_snapshot_delegate, spawn_snapshot_delegate] {
3242 ASSERT_NE(spawner_snapshot_delegate.get(),
3243 spawn_snapshot_delegate.get());
3244 });
3245 });
3246 PostSync(
3247 spawner->GetTaskRunners().GetIOTaskRunner(), [&spawner, &spawn] {
3248 ASSERT_EQ(spawner->GetIOManager()->GetResourceContext().get(),
3249 spawn->GetIOManager()->GetResourceContext().get());
3250 });
3251
3252 // Before destroying the shell, wait for expectations of the spawned
3253 // isolate to be met.
3254 second_latch.Wait();
3255
3256 DestroyShell(std::move(spawn));
3257 });
3258
3259 DestroyShell(std::move(shell));
3260 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3261}
3262
3263TEST_F(ShellTest, SpawnWithDartEntrypointArgs) {
3264 auto settings = CreateSettingsForFixture();
3265 auto shell = CreateShell(settings);
3266 ASSERT_TRUE(ValidateShell(shell.get()));
3267
3268 auto configuration = RunConfiguration::InferFromSettings(settings);
3269 ASSERT_TRUE(configuration.IsValid());
3270 configuration.SetEntrypoint("canReceiveArgumentsWhenEngineRun");
3271 const std::vector<std::string> entrypoint_args{"foo", "bar"};
3272 configuration.SetEntrypointArgs(entrypoint_args);
3273
3274 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3275 ASSERT_TRUE(second_configuration.IsValid());
3276 second_configuration.SetEntrypoint("canReceiveArgumentsWhenEngineSpawn");
3277 const std::vector<std::string> second_entrypoint_args{"arg1", "arg2"};
3278 second_configuration.SetEntrypointArgs(second_entrypoint_args);
3279
3280 const std::string initial_route("/foo");
3281
3282 fml::AutoResetWaitableEvent main_latch;
3283 std::string last_entry_point;
3284 // Fulfill native function for the first Shell's entrypoint.
3285 AddNativeCallback("NotifyNativeWhenEngineRun",
3286 CREATE_NATIVE_ENTRY(([&](Dart_NativeArguments args) {
3288 Dart_GetNativeArgument(args, 0)));
3289 last_entry_point =
3290 shell->GetEngine()->GetLastEntrypoint();
3291 main_latch.Signal();
3292 })));
3293
3294 fml::AutoResetWaitableEvent second_latch;
3295 // Fulfill native function for the second Shell's entrypoint.
3296 AddNativeCallback("NotifyNativeWhenEngineSpawn",
3297 CREATE_NATIVE_ENTRY(([&](Dart_NativeArguments args) {
3299 Dart_GetNativeArgument(args, 0)));
3300 last_entry_point =
3301 shell->GetEngine()->GetLastEntrypoint();
3302 second_latch.Signal();
3303 })));
3304
3305 RunEngine(shell.get(), std::move(configuration));
3306 main_latch.Wait();
3307 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3308 // Check first Shell ran the first entrypoint.
3309 ASSERT_EQ("canReceiveArgumentsWhenEngineRun", last_entry_point);
3310
3311 PostSync(
3312 shell->GetTaskRunners().GetPlatformTaskRunner(),
3313 [this, &spawner = shell, &second_configuration, &second_latch,
3314 initial_route]() {
3315 MockPlatformViewDelegate platform_view_delegate;
3316 auto spawn = spawner->Spawn(
3317 std::move(second_configuration), initial_route,
3318 [&platform_view_delegate](Shell& shell) {
3319 auto result = std::make_unique<MockPlatformView>(
3320 platform_view_delegate, shell.GetTaskRunners());
3321 ON_CALL(*result, CreateRenderingSurface()).WillByDefault([] {
3322 return std::make_unique<MockSurface>();
3323 });
3324 return result;
3325 },
3326 [](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
3327 ASSERT_NE(nullptr, spawn.get());
3328 ASSERT_TRUE(ValidateShell(spawn.get()));
3329
3330 PostSync(spawner->GetTaskRunners().GetUITaskRunner(),
3331 [&spawn, &spawner, initial_route] {
3332 // Check second shell ran the second entrypoint.
3333 ASSERT_EQ("canReceiveArgumentsWhenEngineSpawn",
3334 spawn->GetEngine()->GetLastEntrypoint());
3335 ASSERT_EQ(initial_route, spawn->GetEngine()->InitialRoute());
3336
3337 ASSERT_NE(spawner->GetEngine()
3338 ->GetRuntimeController()
3339 ->GetRootIsolateGroup(),
3340 0u);
3341 ASSERT_EQ(spawner->GetEngine()
3342 ->GetRuntimeController()
3343 ->GetRootIsolateGroup(),
3344 spawn->GetEngine()
3345 ->GetRuntimeController()
3346 ->GetRootIsolateGroup());
3347 });
3348
3349 PostSync(
3350 spawner->GetTaskRunners().GetIOTaskRunner(), [&spawner, &spawn] {
3351 ASSERT_EQ(spawner->GetIOManager()->GetResourceContext().get(),
3352 spawn->GetIOManager()->GetResourceContext().get());
3353 });
3354
3355 // Before destroying the shell, wait for expectations of the spawned
3356 // isolate to be met.
3357 second_latch.Wait();
3358
3359 DestroyShell(std::move(spawn));
3360 });
3361
3362 DestroyShell(std::move(shell));
3363 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3364}
3365
3366TEST_F(ShellTest, IOManagerIsSharedBetweenParentAndSpawnedShell) {
3367 auto settings = CreateSettingsForFixture();
3368 auto shell = CreateShell(settings);
3369 ASSERT_TRUE(ValidateShell(shell.get()));
3370
3371 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [this,
3372 &spawner = shell,
3373 &settings] {
3374 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3375 ASSERT_TRUE(second_configuration.IsValid());
3376 second_configuration.SetEntrypoint("emptyMain");
3377 const std::string initial_route("/foo");
3378 MockPlatformViewDelegate platform_view_delegate;
3379 auto spawn = spawner->Spawn(
3380 std::move(second_configuration), initial_route,
3381 [&platform_view_delegate](Shell& shell) {
3382 auto result = std::make_unique<MockPlatformView>(
3383 platform_view_delegate, shell.GetTaskRunners());
3384 ON_CALL(*result, CreateRenderingSurface()).WillByDefault([] {
3385 return std::make_unique<MockSurface>();
3386 });
3387 return result;
3388 },
3389 [](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
3390 ASSERT_TRUE(ValidateShell(spawn.get()));
3391
3392 PostSync(spawner->GetTaskRunners().GetIOTaskRunner(), [&spawner, &spawn] {
3393 ASSERT_NE(spawner->GetIOManager().get(), nullptr);
3394 ASSERT_EQ(spawner->GetIOManager().get(), spawn->GetIOManager().get());
3395 });
3396
3397 // Destroy the child shell.
3398 DestroyShell(std::move(spawn));
3399 });
3400 // Destroy the parent shell.
3401 DestroyShell(std::move(shell));
3402}
3403
3404TEST_F(ShellTest, IOManagerInSpawnedShellIsNotNullAfterParentShellDestroyed) {
3405 auto settings = CreateSettingsForFixture();
3406 auto shell = CreateShell(settings);
3407 ASSERT_TRUE(ValidateShell(shell.get()));
3408
3409 PostSync(shell->GetTaskRunners().GetUITaskRunner(), [&shell] {
3410 // We must get engine on UI thread.
3411 auto runtime_controller = shell->GetEngine()->GetRuntimeController();
3412 PostSync(shell->GetTaskRunners().GetIOTaskRunner(),
3413 [&shell, &runtime_controller] {
3414 // We must get io_manager on IO thread.
3415 auto io_manager = runtime_controller->GetIOManager();
3416 // Check io_manager existence.
3417 ASSERT_NE(io_manager.get(), nullptr);
3418 ASSERT_NE(io_manager->GetSkiaUnrefQueue().get(), nullptr);
3419 // Get io_manager directly from shell and check its existence.
3420 ASSERT_NE(shell->GetIOManager().get(), nullptr);
3421 });
3422 });
3423
3424 std::unique_ptr<Shell> spawn;
3425
3426 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell, &settings,
3427 &spawn] {
3428 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3429 ASSERT_TRUE(second_configuration.IsValid());
3430 second_configuration.SetEntrypoint("emptyMain");
3431 const std::string initial_route("/foo");
3432 MockPlatformViewDelegate platform_view_delegate;
3433 auto child = shell->Spawn(
3434 std::move(second_configuration), initial_route,
3435 [&platform_view_delegate](Shell& shell) {
3436 auto result = std::make_unique<MockPlatformView>(
3437 platform_view_delegate, shell.GetTaskRunners());
3438 ON_CALL(*result, CreateRenderingSurface()).WillByDefault([] {
3439 return std::make_unique<MockSurface>();
3440 });
3441 return result;
3442 },
3443 [](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
3444 spawn = std::move(child);
3445 });
3446 // Destroy the parent shell.
3447 DestroyShell(std::move(shell));
3448
3449 PostSync(spawn->GetTaskRunners().GetUITaskRunner(), [&spawn] {
3450 // We must get engine on UI thread.
3451 auto runtime_controller = spawn->GetEngine()->GetRuntimeController();
3452 PostSync(spawn->GetTaskRunners().GetIOTaskRunner(),
3453 [&spawn, &runtime_controller] {
3454 // We must get io_manager on IO thread.
3455 auto io_manager = runtime_controller->GetIOManager();
3456 // Check io_manager existence here.
3457 ASSERT_NE(io_manager.get(), nullptr);
3458 ASSERT_NE(io_manager->GetSkiaUnrefQueue().get(), nullptr);
3459 // Get io_manager directly from shell and check its existence.
3460 ASSERT_NE(spawn->GetIOManager().get(), nullptr);
3461 });
3462 });
3463 // Destroy the child shell.
3464 DestroyShell(std::move(spawn));
3465}
3466
3467TEST_F(ShellTest, ImageGeneratorRegistryNotNullAfterParentShellDestroyed) {
3468 auto settings = CreateSettingsForFixture();
3469 auto shell = CreateShell(settings);
3470 ASSERT_TRUE(ValidateShell(shell.get()));
3471
3472 std::unique_ptr<Shell> spawn;
3473
3474 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell, &settings,
3475 &spawn] {
3476 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3477 ASSERT_TRUE(second_configuration.IsValid());
3478 second_configuration.SetEntrypoint("emptyMain");
3479 const std::string initial_route("/foo");
3480 MockPlatformViewDelegate platform_view_delegate;
3481 auto child = shell->Spawn(
3482 std::move(second_configuration), initial_route,
3483 [&platform_view_delegate](Shell& shell) {
3484 auto result = std::make_unique<MockPlatformView>(
3485 platform_view_delegate, shell.GetTaskRunners());
3486 ON_CALL(*result, CreateRenderingSurface()).WillByDefault([] {
3487 return std::make_unique<MockSurface>();
3488 });
3489 return result;
3490 },
3491 [](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
3492 spawn = std::move(child);
3493 });
3494
3495 PostSync(spawn->GetTaskRunners().GetUITaskRunner(), [&spawn] {
3496 std::shared_ptr<const DartIsolate> isolate =
3497 spawn->GetEngine()->GetRuntimeController()->GetRootIsolate().lock();
3498 ASSERT_TRUE(isolate);
3499 ASSERT_TRUE(isolate->GetImageGeneratorRegistry());
3500 });
3501
3502 // Destroy the parent shell.
3503 DestroyShell(std::move(shell));
3504
3505 PostSync(spawn->GetTaskRunners().GetUITaskRunner(), [&spawn] {
3506 std::shared_ptr<const DartIsolate> isolate =
3507 spawn->GetEngine()->GetRuntimeController()->GetRootIsolate().lock();
3508 ASSERT_TRUE(isolate);
3509 ASSERT_TRUE(isolate->GetImageGeneratorRegistry());
3510 });
3511 // Destroy the child shell.
3512 DestroyShell(std::move(spawn));
3513}
3514
3515TEST_F(ShellTest, UpdateAssetResolverByTypeReplaces) {
3516 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3517 Settings settings = CreateSettingsForFixture();
3518
3520 auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
3521 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
3522 task_runner);
3523 auto shell = CreateShell(settings, task_runners);
3524 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3525 ASSERT_TRUE(ValidateShell(shell.get()));
3526
3527 auto configuration = RunConfiguration::InferFromSettings(settings);
3528 configuration.SetEntrypoint("emptyMain");
3529 auto asset_manager = configuration.GetAssetManager();
3530
3531 shell->RunEngine(std::move(configuration), [&](auto result) {
3532 ASSERT_EQ(result, Engine::RunStatus::Success);
3533 });
3534
3535 auto platform_view =
3536 std::make_unique<PlatformView>(*shell.get(), task_runners);
3537
3538 auto old_resolver = std::make_unique<TestAssetResolver>(
3539 true, AssetResolver::AssetResolverType::kApkAssetProvider);
3540 ASSERT_TRUE(old_resolver->IsValid());
3541 asset_manager->PushBack(std::move(old_resolver));
3542
3543 auto updated_resolver = std::make_unique<TestAssetResolver>(
3544 false, AssetResolver::AssetResolverType::kApkAssetProvider);
3545 ASSERT_FALSE(updated_resolver->IsValidAfterAssetManagerChange());
3546 platform_view->UpdateAssetResolverByType(
3547 std::move(updated_resolver),
3548 AssetResolver::AssetResolverType::kApkAssetProvider);
3549
3550 auto resolvers = asset_manager->TakeResolvers();
3551 ASSERT_EQ(resolvers.size(), 2ull);
3552 ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
3553
3554 ASSERT_FALSE(resolvers[1]->IsValidAfterAssetManagerChange());
3555
3556 DestroyShell(std::move(shell), task_runners);
3557 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3558}
3559
3560TEST_F(ShellTest, UpdateAssetResolverByTypeAppends) {
3561 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3562 Settings settings = CreateSettingsForFixture();
3563
3565 auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
3566 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
3567 task_runner);
3568 auto shell = CreateShell(settings, task_runners);
3569 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3570 ASSERT_TRUE(ValidateShell(shell.get()));
3571
3572 auto configuration = RunConfiguration::InferFromSettings(settings);
3573 configuration.SetEntrypoint("emptyMain");
3574 auto asset_manager = configuration.GetAssetManager();
3575
3576 shell->RunEngine(std::move(configuration), [&](auto result) {
3577 ASSERT_EQ(result, Engine::RunStatus::Success);
3578 });
3579
3580 auto platform_view =
3581 std::make_unique<PlatformView>(*shell.get(), task_runners);
3582
3583 auto updated_resolver = std::make_unique<TestAssetResolver>(
3584 false, AssetResolver::AssetResolverType::kApkAssetProvider);
3585 ASSERT_FALSE(updated_resolver->IsValidAfterAssetManagerChange());
3586 platform_view->UpdateAssetResolverByType(
3587 std::move(updated_resolver),
3588 AssetResolver::AssetResolverType::kApkAssetProvider);
3589
3590 auto resolvers = asset_manager->TakeResolvers();
3591 ASSERT_EQ(resolvers.size(), 2ull);
3592 ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
3593
3594 ASSERT_FALSE(resolvers[1]->IsValidAfterAssetManagerChange());
3595
3596 DestroyShell(std::move(shell), task_runners);
3597 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3598}
3599
3600TEST_F(ShellTest, UpdateAssetResolverByTypeNull) {
3601 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3602 Settings settings = CreateSettingsForFixture();
3604 "io.flutter.test." + GetCurrentTestName() + ".",
3605 ThreadHost::Type::kPlatform));
3606 auto task_runner = thread_host.platform_thread->GetTaskRunner();
3607 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
3608 task_runner);
3609 auto shell = CreateShell(settings, task_runners);
3610 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3611 ASSERT_TRUE(ValidateShell(shell.get()));
3612
3613 auto configuration = RunConfiguration::InferFromSettings(settings);
3614 configuration.SetEntrypoint("emptyMain");
3615 auto asset_manager = configuration.GetAssetManager();
3616 RunEngine(shell.get(), std::move(configuration));
3617
3618 auto platform_view =
3619 std::make_unique<PlatformView>(*shell.get(), task_runners);
3620
3621 auto old_resolver = std::make_unique<TestAssetResolver>(
3622 true, AssetResolver::AssetResolverType::kApkAssetProvider);
3623 ASSERT_TRUE(old_resolver->IsValid());
3624 asset_manager->PushBack(std::move(old_resolver));
3625
3626 platform_view->UpdateAssetResolverByType(
3627 nullptr, AssetResolver::AssetResolverType::kApkAssetProvider);
3628
3629 auto resolvers = asset_manager->TakeResolvers();
3630 ASSERT_EQ(resolvers.size(), 2ull);
3631 ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
3632 ASSERT_TRUE(resolvers[1]->IsValidAfterAssetManagerChange());
3633
3634 DestroyShell(std::move(shell), task_runners);
3635 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3636}
3637
3638TEST_F(ShellTest, UpdateAssetResolverByTypeDoesNotReplaceMismatchType) {
3639 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3640 Settings settings = CreateSettingsForFixture();
3641
3643 auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
3644 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
3645 task_runner);
3646 auto shell = CreateShell(settings, task_runners);
3647 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3648 ASSERT_TRUE(ValidateShell(shell.get()));
3649
3650 auto configuration = RunConfiguration::InferFromSettings(settings);
3651 configuration.SetEntrypoint("emptyMain");
3652 auto asset_manager = configuration.GetAssetManager();
3653
3654 shell->RunEngine(std::move(configuration), [&](auto result) {
3655 ASSERT_EQ(result, Engine::RunStatus::Success);
3656 });
3657
3658 auto platform_view =
3659 std::make_unique<PlatformView>(*shell.get(), task_runners);
3660
3661 auto old_resolver = std::make_unique<TestAssetResolver>(
3662 true, AssetResolver::AssetResolverType::kAssetManager);
3663 ASSERT_TRUE(old_resolver->IsValid());
3664 asset_manager->PushBack(std::move(old_resolver));
3665
3666 auto updated_resolver = std::make_unique<TestAssetResolver>(
3667 false, AssetResolver::AssetResolverType::kApkAssetProvider);
3668 ASSERT_FALSE(updated_resolver->IsValidAfterAssetManagerChange());
3669 platform_view->UpdateAssetResolverByType(
3670 std::move(updated_resolver),
3671 AssetResolver::AssetResolverType::kApkAssetProvider);
3672
3673 auto resolvers = asset_manager->TakeResolvers();
3674 ASSERT_EQ(resolvers.size(), 3ull);
3675 ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
3676
3677 ASSERT_TRUE(resolvers[1]->IsValidAfterAssetManagerChange());
3678
3679 ASSERT_FALSE(resolvers[2]->IsValidAfterAssetManagerChange());
3680
3681 DestroyShell(std::move(shell), task_runners);
3682 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3683}
3684
3685TEST_F(ShellTest, CanCreateShellsWithGLBackend) {
3686#if !SHELL_ENABLE_GL
3687 // GL emulation does not exist on Fuchsia.
3688 GTEST_SKIP();
3689#else
3690 auto settings = CreateSettingsForFixture();
3691 std::unique_ptr<Shell> shell = CreateShell({
3692 .settings = settings,
3693 .platform_view_create_callback = ShellTestPlatformViewBuilder({
3694 .rendering_backend = ShellTestPlatformView::BackendType::kGLBackend,
3695 }),
3696 });
3697 ASSERT_NE(shell, nullptr);
3698 ASSERT_TRUE(shell->IsSetup());
3699 auto configuration = RunConfiguration::InferFromSettings(settings);
3700 PlatformViewNotifyCreated(shell.get());
3701 configuration.SetEntrypoint("emptyMain");
3702 RunEngine(shell.get(), std::move(configuration));
3703 PumpOneFrame(shell.get());
3704 PlatformViewNotifyDestroyed(shell.get());
3705 DestroyShell(std::move(shell));
3706#endif // !SHELL_ENABLE_GL
3707}
3708
3709TEST_F(ShellTest, CanCreateShellsWithVulkanBackend) {
3710#if !SHELL_ENABLE_VULKAN
3711 GTEST_SKIP();
3712#else
3713 auto settings = CreateSettingsForFixture();
3714 std::unique_ptr<Shell> shell = CreateShell({
3715 .settings = settings,
3716 .platform_view_create_callback = ShellTestPlatformViewBuilder({
3717 .rendering_backend =
3718 ShellTestPlatformView::BackendType::kVulkanBackend,
3719 }),
3720 });
3721 ASSERT_NE(shell, nullptr);
3722 ASSERT_TRUE(shell->IsSetup());
3723 auto configuration = RunConfiguration::InferFromSettings(settings);
3724 PlatformViewNotifyCreated(shell.get());
3725 configuration.SetEntrypoint("emptyMain");
3726 RunEngine(shell.get(), std::move(configuration));
3727 PumpOneFrame(shell.get());
3728 PlatformViewNotifyDestroyed(shell.get());
3729 DestroyShell(std::move(shell));
3730#endif // !SHELL_ENABLE_VULKAN
3731}
3732
3733TEST_F(ShellTest, CanCreateShellsWithMetalBackend) {
3734#if !SHELL_ENABLE_METAL
3735 GTEST_SKIP();
3736#else
3737 auto settings = CreateSettingsForFixture();
3738 std::unique_ptr<Shell> shell = CreateShell({
3739 .settings = settings,
3740 .platform_view_create_callback = ShellTestPlatformViewBuilder({
3741 .rendering_backend =
3742 ShellTestPlatformView::BackendType::kMetalBackend,
3743 }),
3744 });
3745 ASSERT_NE(shell, nullptr);
3746 ASSERT_TRUE(shell->IsSetup());
3747 auto configuration = RunConfiguration::InferFromSettings(settings);
3748 PlatformViewNotifyCreated(shell.get());
3749 configuration.SetEntrypoint("emptyMain");
3750 RunEngine(shell.get(), std::move(configuration));
3751 PumpOneFrame(shell.get());
3752 PlatformViewNotifyDestroyed(shell.get());
3753 DestroyShell(std::move(shell));
3754#endif // !SHELL_ENABLE_METAL
3755}
3756
3757TEST_F(ShellTest, UserTagSetOnStartup) {
3758 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3759 // Make sure the shell launch does not kick off the creation of the VM
3760 // instance by already creating one upfront.
3761 auto vm_settings = CreateSettingsForFixture();
3762 auto vm_ref = DartVMRef::Create(vm_settings);
3763 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3764
3765 auto settings = vm_settings;
3766 fml::AutoResetWaitableEvent isolate_create_latch;
3767
3768 // ensure that "AppStartUpTag" is set during isolate creation.
3769 settings.root_isolate_create_callback = [&](const DartIsolate& isolate) {
3770 Dart_Handle current_tag = Dart_GetCurrentUserTag();
3771 Dart_Handle startup_tag = Dart_NewUserTag("AppStartUp");
3772 EXPECT_TRUE(Dart_IdentityEquals(current_tag, startup_tag));
3773
3774 isolate_create_latch.Signal();
3775 };
3776
3777 auto shell = CreateShell(settings);
3778 ASSERT_TRUE(ValidateShell(shell.get()));
3779
3780 auto configuration = RunConfiguration::InferFromSettings(settings);
3781 ASSERT_TRUE(configuration.IsValid());
3782
3783 RunEngine(shell.get(), std::move(configuration));
3784 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3785
3786 DestroyShell(std::move(shell));
3787 isolate_create_latch.Wait();
3788}
3789
3790TEST_F(ShellTest, PrefetchDefaultFontManager) {
3791 auto settings = CreateSettingsForFixture();
3792 settings.prefetched_default_font_manager = true;
3793 std::unique_ptr<Shell> shell;
3794
3795 auto get_font_manager_count = [&] {
3797 size_t font_manager_count;
3799 shell->GetTaskRunners().GetUITaskRunner(),
3800 [this, &shell, &latch, &font_manager_count]() {
3801 font_manager_count =
3802 GetFontCollection(shell.get())->GetFontManagersCount();
3803 latch.Signal();
3804 });
3805 latch.Wait();
3806 return font_manager_count;
3807 };
3808 size_t initial_font_manager_count = 0;
3809 settings.root_isolate_create_callback = [&](const auto& isolate) {
3810 ASSERT_GT(initial_font_manager_count, 0ul);
3811 // Should not have fetched the default font manager yet, since the root
3812 // isolate was only just created.
3813 ASSERT_EQ(get_font_manager_count(), initial_font_manager_count);
3814 };
3815
3816 shell = CreateShell(settings);
3817
3818 initial_font_manager_count = get_font_manager_count();
3819
3820 auto configuration = RunConfiguration::InferFromSettings(settings);
3821 configuration.SetEntrypoint("emptyMain");
3822 RunEngine(shell.get(), std::move(configuration));
3823
3824 // If the prefetched_default_font_manager flag is set, then the default font
3825 // manager will not be added until the engine starts running.
3826 ASSERT_EQ(get_font_manager_count(), initial_font_manager_count + 1);
3827
3828 DestroyShell(std::move(shell));
3829}
3830
3831TEST_F(ShellTest, OnPlatformViewCreatedWhenUIThreadIsBusy) {
3832 // This test will deadlock if the threading logic in
3833 // Shell::OnCreatePlatformView is wrong.
3834 auto settings = CreateSettingsForFixture();
3835 auto shell = CreateShell(settings);
3836
3838 fml::TaskRunner::RunNowOrPostTask(shell->GetTaskRunners().GetUITaskRunner(),
3839 [&latch]() { latch.Wait(); });
3840
3841 ShellTest::PlatformViewNotifyCreated(shell.get());
3842 latch.Signal();
3843
3844 DestroyShell(std::move(shell));
3845}
3846
3847TEST_F(ShellTest, UIWorkAfterOnPlatformViewDestroyed) {
3848 auto settings = CreateSettingsForFixture();
3849 auto shell = CreateShell(settings);
3850 auto configuration = RunConfiguration::InferFromSettings(settings);
3851 configuration.SetEntrypoint("drawFrames");
3852
3854 fml::AutoResetWaitableEvent notify_native_latch;
3855 AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
3856 notify_native_latch.Signal();
3857 latch.Wait();
3858 }));
3859
3860 RunEngine(shell.get(), std::move(configuration));
3861 // Wait to make sure we get called back from Dart and thus have latched
3862 // the UI thread before we create/destroy the platform view.
3863 notify_native_latch.Wait();
3864
3865 ShellTest::PlatformViewNotifyCreated(shell.get());
3866
3867 fml::AutoResetWaitableEvent destroy_latch;
3869 shell->GetTaskRunners().GetPlatformTaskRunner(),
3870 [&shell, &destroy_latch]() {
3871 shell->GetPlatformView()->NotifyDestroyed();
3872 destroy_latch.Signal();
3873 });
3874
3875 destroy_latch.Wait();
3876
3877 // Unlatch the UI thread and let it send us a scene to render.
3878 latch.Signal();
3879
3880 // Flush the UI task runner to make sure we process the render/scheduleFrame
3881 // request.
3882 fml::AutoResetWaitableEvent ui_flush_latch;
3884 shell->GetTaskRunners().GetUITaskRunner(),
3885 [&ui_flush_latch]() { ui_flush_latch.Signal(); });
3886 ui_flush_latch.Wait();
3887 DestroyShell(std::move(shell));
3888}
3889
3890TEST_F(ShellTest, UsesPlatformMessageHandler) {
3891 TaskRunners task_runners = GetTaskRunnersForFixture();
3892 auto settings = CreateSettingsForFixture();
3893 MockPlatformViewDelegate platform_view_delegate;
3894 auto platform_message_handler =
3895 std::make_shared<MockPlatformMessageHandler>();
3896 int message_id = 1;
3897 EXPECT_CALL(*platform_message_handler, HandlePlatformMessage(_));
3898 EXPECT_CALL(*platform_message_handler,
3899 InvokePlatformMessageEmptyResponseCallback(message_id));
3900 Shell::CreateCallback<PlatformView> platform_view_create_callback =
3901 [&platform_view_delegate, task_runners,
3902 platform_message_handler](flutter::Shell& shell) {
3903 auto result = std::make_unique<MockPlatformView>(platform_view_delegate,
3904 task_runners);
3905 EXPECT_CALL(*result, GetPlatformMessageHandler())
3906 .WillOnce(Return(platform_message_handler));
3907 return result;
3908 };
3909 auto shell = CreateShell({
3910 .settings = settings,
3911 .task_runners = task_runners,
3912 .platform_view_create_callback = platform_view_create_callback,
3913 });
3914
3915 EXPECT_EQ(platform_message_handler, shell->GetPlatformMessageHandler());
3916 PostSync(task_runners.GetUITaskRunner(), [&shell]() {
3917 size_t data_size = 4;
3918 fml::MallocMapping bytes =
3919 fml::MallocMapping(static_cast<uint8_t*>(malloc(data_size)), data_size);
3920 fml::RefPtr<MockPlatformMessageResponse> response =
3921 MockPlatformMessageResponse::Create();
3922 auto message = std::make_unique<PlatformMessage>(
3923 /*channel=*/"foo", /*data=*/std::move(bytes), /*response=*/response);
3924 (static_cast<Engine::Delegate*>(shell.get()))
3925 ->OnEngineHandlePlatformMessage(std::move(message));
3926 });
3927 shell->GetPlatformMessageHandler()
3928 ->InvokePlatformMessageEmptyResponseCallback(message_id);
3929 DestroyShell(std::move(shell));
3930}
3931
3932TEST_F(ShellTest, SpawnWorksWithOnError) {
3933 auto settings = CreateSettingsForFixture();
3934 auto shell = CreateShell(settings);
3935 ASSERT_TRUE(ValidateShell(shell.get()));
3936
3937 auto configuration = RunConfiguration::InferFromSettings(settings);
3938 ASSERT_TRUE(configuration.IsValid());
3939 configuration.SetEntrypoint("onErrorA");
3940
3941 auto second_configuration = RunConfiguration::InferFromSettings(settings);
3942 ASSERT_TRUE(second_configuration.IsValid());
3943 second_configuration.SetEntrypoint("onErrorB");
3944
3945 fml::CountDownLatch latch(2);
3946
3947 AddNativeCallback(
3948 "NotifyErrorA", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
3949 auto string_handle = Dart_GetNativeArgument(args, 0);
3950 const char* c_str;
3951 Dart_StringToCString(string_handle, &c_str);
3952 EXPECT_STREQ(c_str, "Exception: I should be coming from A");
3953 latch.CountDown();
3954 }));
3955
3956 AddNativeCallback(
3957 "NotifyErrorB", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
3958 auto string_handle = Dart_GetNativeArgument(args, 0);
3959 const char* c_str;
3960 Dart_StringToCString(string_handle, &c_str);
3961 EXPECT_STREQ(c_str, "Exception: I should be coming from B");
3962 latch.CountDown();
3963 }));
3964
3965 RunEngine(shell.get(), std::move(configuration));
3966
3967 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
3968
3969 PostSync(
3970 shell->GetTaskRunners().GetPlatformTaskRunner(),
3971 [this, &spawner = shell, &second_configuration, &latch]() {
3972 ::testing::NiceMock<MockPlatformViewDelegate> platform_view_delegate;
3973 auto spawn = spawner->Spawn(
3974 std::move(second_configuration), "",
3975 [&platform_view_delegate](Shell& shell) {
3976 auto result =
3977 std::make_unique<::testing::NiceMock<MockPlatformView>>(
3978 platform_view_delegate, shell.GetTaskRunners());
3979 ON_CALL(*result, CreateRenderingSurface()).WillByDefault([] {
3980 return std::make_unique<::testing::NiceMock<MockSurface>>();
3981 });
3982 return result;
3983 },
3984 [](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
3985 ASSERT_NE(nullptr, spawn.get());
3986 ASSERT_TRUE(ValidateShell(spawn.get()));
3987
3988 // Before destroying the shell, wait for expectations of the spawned
3989 // isolate to be met.
3990 latch.Wait();
3991
3992 DestroyShell(std::move(spawn));
3993 });
3994
3995 DestroyShell(std::move(shell));
3996 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
3997}
3998
3999TEST_F(ShellTest, ImmutableBufferLoadsAssetOnBackgroundThread) {
4000 Settings settings = CreateSettingsForFixture();
4001 auto task_runner = CreateNewThread();
4002 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4003 task_runner);
4004 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
4005
4006 fml::CountDownLatch latch(1);
4007 AddNativeCallback("NotifyNative",
4008 CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); }));
4009
4010 // Create the surface needed by rasterizer
4011 PlatformViewNotifyCreated(shell.get());
4012
4013 auto configuration = RunConfiguration::InferFromSettings(settings);
4014 configuration.SetEntrypoint("testThatAssetLoadingHappensOnWorkerThread");
4015 auto asset_manager = configuration.GetAssetManager();
4016 auto test_resolver = std::make_unique<ThreadCheckingAssetResolver>(
4017 shell->GetDartVM()->GetConcurrentMessageLoop());
4018 auto leaked_resolver = test_resolver.get();
4019 asset_manager->PushBack(std::move(test_resolver));
4020
4021 RunEngine(shell.get(), std::move(configuration));
4022 PumpOneFrame(shell.get());
4023
4024 latch.Wait();
4025
4026 EXPECT_EQ(leaked_resolver->mapping_requests[0], "DoesNotExist");
4027
4028 PlatformViewNotifyDestroyed(shell.get());
4029 DestroyShell(std::move(shell), task_runners);
4030}
4031
4032TEST_F(ShellTest, PictureToImageSync) {
4033#if !SHELL_ENABLE_GL
4034 // This test uses the GL backend.
4035 GTEST_SKIP();
4036#else
4037 auto settings = CreateSettingsForFixture();
4038 std::unique_ptr<Shell> shell = CreateShell({
4039 .settings = settings,
4040 .platform_view_create_callback = ShellTestPlatformViewBuilder({
4041 .rendering_backend = ShellTestPlatformView::BackendType::kGLBackend,
4042 }),
4043 });
4044
4045 AddNativeCallback("NativeOnBeforeToImageSync",
4046 CREATE_NATIVE_ENTRY([&](auto args) {
4047 // nop
4048 }));
4049
4050 fml::CountDownLatch latch(2);
4051 AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
4052 // Teardown and set up rasterizer again.
4053 PlatformViewNotifyDestroyed(shell.get());
4054 PlatformViewNotifyCreated(shell.get());
4055 latch.CountDown();
4056 }));
4057
4058 ASSERT_NE(shell, nullptr);
4059 ASSERT_TRUE(shell->IsSetup());
4060 auto configuration = RunConfiguration::InferFromSettings(settings);
4061 PlatformViewNotifyCreated(shell.get());
4062 configuration.SetEntrypoint("toImageSync");
4063 RunEngine(shell.get(), std::move(configuration));
4064 PumpOneFrame(shell.get());
4065
4066 latch.Wait();
4067
4068 PlatformViewNotifyDestroyed(shell.get());
4069 DestroyShell(std::move(shell));
4070#endif // !SHELL_ENABLE_GL
4071}
4072
4073TEST_F(ShellTest, PictureToImageSyncImpellerNoSurface) {
4074#if !SHELL_ENABLE_METAL
4075 // This test uses the Metal backend.
4076 GTEST_SKIP();
4077#else
4078 auto settings = CreateSettingsForFixture();
4079 settings.enable_impeller = true;
4080 std::unique_ptr<Shell> shell = CreateShell({
4081 .settings = settings,
4082 .platform_view_create_callback = ShellTestPlatformViewBuilder({
4083 .rendering_backend =
4084 ShellTestPlatformView::BackendType::kMetalBackend,
4085 }),
4086 });
4087
4088 AddNativeCallback("NativeOnBeforeToImageSync",
4089 CREATE_NATIVE_ENTRY([&](auto args) {
4090 // nop
4091 }));
4092
4093 fml::CountDownLatch latch(2);
4094 AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
4095 // Teardown and set up rasterizer again.
4096 PlatformViewNotifyDestroyed(shell.get());
4097 PlatformViewNotifyCreated(shell.get());
4098 latch.CountDown();
4099 }));
4100
4101 ASSERT_NE(shell, nullptr);
4102 ASSERT_TRUE(shell->IsSetup());
4103 auto configuration = RunConfiguration::InferFromSettings(settings);
4104
4105 // Important: Do not create the platform view yet!
4106 // This test is making sure that the rasterizer can create the texture
4107 // as expected without a surface.
4108
4109 configuration.SetEntrypoint("toImageSync");
4110 RunEngine(shell.get(), std::move(configuration));
4111 PumpOneFrame(shell.get());
4112
4113 latch.Wait();
4114
4115 PlatformViewNotifyDestroyed(shell.get());
4116 DestroyShell(std::move(shell));
4117#endif // !SHELL_ENABLE_METAL
4118}
4119
4120#if SHELL_ENABLE_GL
4121// This test uses the GL backend and refers to symbols in egl.h
4122TEST_F(ShellTest, PictureToImageSyncWithTrampledContext) {
4123 // make it easier to trample the GL context by running on a single task
4124 // runner.
4125 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4126 ThreadHost::Type::kPlatform);
4127 auto task_runner = thread_host.platform_thread->GetTaskRunner();
4128 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4129 task_runner);
4130
4131 auto settings = CreateSettingsForFixture();
4132 std::unique_ptr<Shell> shell = CreateShell({
4133 .settings = settings,
4134 .task_runners = task_runners,
4135 .platform_view_create_callback = ShellTestPlatformViewBuilder({
4136 .rendering_backend = ShellTestPlatformView::BackendType::kGLBackend,
4137 }),
4138 });
4139
4140 AddNativeCallback(
4141 "NativeOnBeforeToImageSync", CREATE_NATIVE_ENTRY([&](auto args) {
4142 // Trample the GL context. If the rasterizer fails
4143 // to make the right one current again, test will
4144 // fail.
4145 ::eglMakeCurrent(::eglGetCurrentDisplay(), NULL, NULL, NULL);
4146 }));
4147
4148 fml::CountDownLatch latch(2);
4149 AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
4150 // Teardown and set up rasterizer again.
4151 PlatformViewNotifyDestroyed(shell.get());
4152 PlatformViewNotifyCreated(shell.get());
4153 latch.CountDown();
4154 }));
4155
4156 ASSERT_NE(shell, nullptr);
4157 ASSERT_TRUE(shell->IsSetup());
4158 auto configuration = RunConfiguration::InferFromSettings(settings);
4159 PlatformViewNotifyCreated(shell.get());
4160 configuration.SetEntrypoint("toImageSync");
4161 RunEngine(shell.get(), std::move(configuration));
4162 PumpOneFrame(shell.get());
4163
4164 latch.Wait();
4165
4166 PlatformViewNotifyDestroyed(shell.get());
4167 DestroyShell(std::move(shell), task_runners);
4168}
4169#endif // SHELL_ENABLE_GL
4170
4171TEST_F(ShellTest, PluginUtilitiesCallbackHandleErrorHandling) {
4172 auto settings = CreateSettingsForFixture();
4173 std::unique_ptr<Shell> shell = CreateShell(settings);
4174
4176 bool test_passed;
4177 AddNativeCallback("NotifyNativeBool", CREATE_NATIVE_ENTRY([&](auto args) {
4178 Dart_Handle exception = nullptr;
4180 args, 0, exception);
4181 latch.Signal();
4182 }));
4183
4184 ASSERT_NE(shell, nullptr);
4185 ASSERT_TRUE(shell->IsSetup());
4186 auto configuration = RunConfiguration::InferFromSettings(settings);
4187 PlatformViewNotifyCreated(shell.get());
4188 configuration.SetEntrypoint("testPluginUtilitiesCallbackHandle");
4189 RunEngine(shell.get(), std::move(configuration));
4190 PumpOneFrame(shell.get());
4191
4192 latch.Wait();
4193
4194 ASSERT_TRUE(test_passed);
4195
4196 PlatformViewNotifyDestroyed(shell.get());
4197 DestroyShell(std::move(shell));
4198}
4199
4200TEST_F(ShellTest, NotifyIdleRejectsPastAndNearFuture) {
4201 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4202 Settings settings = CreateSettingsForFixture();
4203 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4204 ThreadHost::Type::kPlatform | ThreadHost::kUi |
4205 ThreadHost::kIo | ThreadHost::kRaster);
4206 auto platform_task_runner = thread_host.platform_thread->GetTaskRunner();
4207 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
4208 thread_host.raster_thread->GetTaskRunner(),
4209 thread_host.ui_thread->GetTaskRunner(),
4210 thread_host.io_thread->GetTaskRunner());
4211 auto shell = CreateShell(settings, task_runners);
4212 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
4213 ASSERT_TRUE(ValidateShell(shell.get()));
4214
4216
4217 auto configuration = RunConfiguration::InferFromSettings(settings);
4218 configuration.SetEntrypoint("emptyMain");
4219 RunEngine(shell.get(), std::move(configuration));
4220
4222 task_runners.GetUITaskRunner(), [&latch, &shell]() {
4223 auto runtime_controller = const_cast<RuntimeController*>(
4224 shell->GetEngine()->GetRuntimeController());
4225
4226 auto now = fml::TimeDelta::FromMicroseconds(Dart_TimelineGetMicros());
4227
4228 EXPECT_FALSE(runtime_controller->NotifyIdle(
4229 now - fml::TimeDelta::FromMilliseconds(10)));
4230 EXPECT_FALSE(runtime_controller->NotifyIdle(now));
4231 EXPECT_FALSE(runtime_controller->NotifyIdle(
4232 now + fml::TimeDelta::FromNanoseconds(100)));
4233
4234 EXPECT_TRUE(runtime_controller->NotifyIdle(
4235 now + fml::TimeDelta::FromMilliseconds(100)));
4236 latch.Signal();
4237 });
4238
4239 latch.Wait();
4240
4241 DestroyShell(std::move(shell), task_runners);
4242 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4243}
4244
4245TEST_F(ShellTest, NotifyIdleNotCalledInLatencyMode) {
4246 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4247 Settings settings = CreateSettingsForFixture();
4248 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4249 ThreadHost::Type::kPlatform | ThreadHost::kUi |
4250 ThreadHost::kIo | ThreadHost::kRaster);
4251 auto platform_task_runner = thread_host.platform_thread->GetTaskRunner();
4252 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
4253 thread_host.raster_thread->GetTaskRunner(),
4254 thread_host.ui_thread->GetTaskRunner(),
4255 thread_host.io_thread->GetTaskRunner());
4256 auto shell = CreateShell(settings, task_runners);
4257 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
4258 ASSERT_TRUE(ValidateShell(shell.get()));
4259
4260 // we start off in balanced mode, where we expect idle notifications to
4261 // succeed. After the first `NotifyNativeBool` we expect to be in latency
4262 // mode, where we expect idle notifications to fail.
4263 fml::CountDownLatch latch(2);
4264 AddNativeCallback(
4265 "NotifyNativeBool", CREATE_NATIVE_ENTRY([&](auto args) {
4266 Dart_Handle exception = nullptr;
4267 bool is_in_latency_mode =
4269 auto runtime_controller = const_cast<RuntimeController*>(
4270 shell->GetEngine()->GetRuntimeController());
4271 bool success =
4272 runtime_controller->NotifyIdle(fml::TimeDelta::FromMicroseconds(
4273 Dart_TimelineGetMicros() + 100000));
4274 EXPECT_EQ(success, !is_in_latency_mode);
4275 latch.CountDown();
4276 }));
4277
4278 auto configuration = RunConfiguration::InferFromSettings(settings);
4279 configuration.SetEntrypoint("performanceModeImpactsNotifyIdle");
4280 RunEngine(shell.get(), std::move(configuration));
4281
4282 latch.Wait();
4283
4284 DestroyShell(std::move(shell), task_runners);
4285 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4286}
4287
4288TEST_F(ShellTest, PrintsErrorWhenPlatformMessageSentFromWrongThread) {
4289#if FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG || OS_FUCHSIA
4290 GTEST_SKIP() << "Test is for debug mode only on non-fuchsia targets.";
4291#else
4292 Settings settings = CreateSettingsForFixture();
4293 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4294 ThreadHost::Type::kPlatform);
4295 auto task_runner = thread_host.platform_thread->GetTaskRunner();
4296 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4297 task_runner);
4298 auto shell = CreateShell(settings, task_runners);
4299
4300 {
4301 fml::testing::LogCapture log_capture;
4302
4303 // The next call will result in a thread checker violation.
4305 SendPlatformMessage(shell.get(), std::make_unique<PlatformMessage>(
4306 "com.test.plugin", nullptr));
4307
4308 EXPECT_THAT(
4309 log_capture.str(),
4310 ::testing::EndsWith(
4311 "The 'com.test.plugin' channel sent a message from native to "
4312 "Flutter on a non-platform thread. Platform channel messages "
4313 "must be sent on the platform thread. Failure to do so may "
4314 "result in data loss or crashes, and must be fixed in the "
4315 "plugin or application code creating that channel.\nSee "
4316 "https://docs.flutter.dev/platform-integration/"
4317 "platform-channels#channels-and-platform-threading for more "
4318 "information.\n"));
4319 }
4320
4321 {
4322 fml::testing::LogCapture log_capture;
4323
4324 // The next call will result in a thread checker violation.
4326 SendPlatformMessage(shell.get(), std::make_unique<PlatformMessage>(
4327 "com.test.plugin", nullptr));
4328
4329 EXPECT_EQ(log_capture.str(), "");
4330 }
4331
4332 DestroyShell(std::move(shell), task_runners);
4333 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4334#endif
4335}
4336
4337TEST_F(ShellTest, NavigationMessageDispachedImmediately) {
4338 Settings settings = CreateSettingsForFixture();
4339 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4340 ThreadHost::Type::kPlatform);
4341 auto task_runner = thread_host.platform_thread->GetTaskRunner();
4342 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4343 task_runner);
4344 auto shell = CreateShell(settings, task_runners);
4345
4346 auto latch = std::make_shared<fml::CountDownLatch>(1u);
4347 task_runner->PostTask([&]() {
4348 auto message = MakePlatformMessage(
4349 "flutter/navigation",
4350 {{"method", "setInitialRoute"}, {"args", "/testo"}}, nullptr);
4351 SendPlatformMessage(shell.get(), std::move(message));
4352 EXPECT_EQ(shell->GetEngine()->InitialRoute(), "/testo");
4353
4354 latch->CountDown();
4355 });
4356 latch->Wait();
4357
4358 DestroyShell(std::move(shell), task_runners);
4359 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4360}
4361
4362// Verifies a semantics Action will flush the dart event loop.
4363TEST_F(ShellTest, SemanticsActionsFlushMessageLoop) {
4364 Settings settings = CreateSettingsForFixture();
4365 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4366 ThreadHost::Type::kPlatform);
4367 auto task_runner = thread_host.platform_thread->GetTaskRunner();
4368 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4369 task_runner);
4370
4371 EXPECT_EQ(task_runners.GetPlatformTaskRunner(),
4372 task_runners.GetUITaskRunner());
4373 auto shell = CreateShell(settings, task_runners);
4374 auto configuration = RunConfiguration::InferFromSettings(settings);
4375 configuration.SetEntrypoint("testSemanticsActions");
4376
4377 RunEngine(shell.get(), std::move(configuration));
4378 fml::CountDownLatch latch(1);
4379 AddNativeCallback(
4380 // The Dart native function names aren't very consistent but this is
4381 // just the native function name of the second vm entrypoint in the
4382 // fixture.
4383 "NotifyNative",
4384 CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); }));
4385
4386 task_runners.GetPlatformTaskRunner()->PostTask([&] {
4387 SendSemanticsAction(shell.get(), 456, 0, SemanticsAction::kTap,
4388 fml::MallocMapping(nullptr, 0));
4389 });
4390 latch.Wait();
4391
4392 DestroyShell(std::move(shell), task_runners);
4393 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4394}
4395
4396// Verifies a pointer event will flush the dart event loop.
4397TEST_F(ShellTest, PointerPacketFlushMessageLoop) {
4398 Settings settings = CreateSettingsForFixture();
4399 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4400 ThreadHost::Type::kPlatform);
4401 auto task_runner = thread_host.platform_thread->GetTaskRunner();
4402 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4403 task_runner);
4404
4405 EXPECT_EQ(task_runners.GetPlatformTaskRunner(),
4406 task_runners.GetUITaskRunner());
4407 auto shell = CreateShell(settings, task_runners);
4408 auto configuration = RunConfiguration::InferFromSettings(settings);
4409 configuration.SetEntrypoint("testPointerActions");
4410
4411 RunEngine(shell.get(), std::move(configuration));
4412 fml::CountDownLatch latch(1);
4413 AddNativeCallback(
4414 // The Dart native function names aren't very consistent but this is
4415 // just the native function name of the second vm entrypoint in the
4416 // fixture.
4417 "NotifyNative",
4418 CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); }));
4419
4420 DispatchFakePointerData(shell.get(), 23);
4421 latch.Wait();
4422
4423 DestroyShell(std::move(shell), task_runners);
4424 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4425}
4426
4427// Verifies a pointer event will flush the dart event loop.
4428TEST_F(ShellTest, DISABLED_PointerPacketsAreDispatchedWithTask) {
4429 Settings settings = CreateSettingsForFixture();
4430 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4431 ThreadHost::Type::kPlatform);
4432 auto task_runner = thread_host.platform_thread->GetTaskRunner();
4433 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4434 task_runner);
4435
4436 EXPECT_EQ(task_runners.GetPlatformTaskRunner(),
4437 task_runners.GetUITaskRunner());
4438 auto shell = CreateShell(settings, task_runners);
4439 auto configuration = RunConfiguration::InferFromSettings(settings);
4440 configuration.SetEntrypoint("testDispatchEvents");
4441
4442 RunEngine(shell.get(), std::move(configuration));
4443 fml::CountDownLatch latch(1);
4444 bool did_invoke_callback = false;
4445 AddNativeCallback(
4446 // The Dart native function names aren't very consistent but this is
4447 // just the native function name of the second vm entrypoint in the
4448 // fixture.
4449 "NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
4450 did_invoke_callback = true;
4451 latch.CountDown();
4452 }));
4453
4454 DispatchFakePointerData(shell.get(), 23);
4455 EXPECT_FALSE(did_invoke_callback);
4456 latch.Wait();
4457 EXPECT_TRUE(did_invoke_callback);
4458
4459 DestroyShell(std::move(shell), task_runners);
4460 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4461}
4462
4463TEST_F(ShellTest, DiesIfSoftwareRenderingAndImpellerAreEnabledDeathTest) {
4464#if defined(OS_FUCHSIA)
4465 GTEST_SKIP() << "Fuchsia";
4466#else
4467 ::testing::FLAGS_gtest_death_test_style = "threadsafe";
4468 Settings settings = CreateSettingsForFixture();
4469 settings.enable_impeller = true;
4470 settings.enable_software_rendering = true;
4471 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4472 ThreadHost::Type::kPlatform);
4473 auto task_runner = thread_host.platform_thread->GetTaskRunner();
4474 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4475 task_runner);
4476 EXPECT_DEATH_IF_SUPPORTED(
4477 CreateShell(settings, task_runners),
4478 "Software rendering is incompatible with Impeller.");
4479#endif // OS_FUCHSIA
4480}
4481
4482// Parse the arguments of NativeReportViewIdsCallback and
4483// store them in hasImplicitView and viewIds.
4484static void ParseViewIdsCallback(const Dart_NativeArguments& args,
4485 bool* hasImplicitView,
4486 std::vector<int64_t>* viewIds) {
4487 Dart_Handle exception = nullptr;
4488 viewIds->clear();
4489 *hasImplicitView =
4491 ASSERT_EQ(exception, nullptr);
4492 *viewIds = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
4493 args, 1, exception);
4494 ASSERT_EQ(exception, nullptr);
4495}
4496
4497TEST_F(ShellTest, ShellStartsWithImplicitView) {
4498 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4499 Settings settings = CreateSettingsForFixture();
4500 auto task_runner = CreateNewThread();
4501 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4502 task_runner);
4503 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
4504 ASSERT_TRUE(shell);
4505
4506 bool hasImplicitView;
4507 std::vector<int64_t> viewIds;
4508 fml::AutoResetWaitableEvent reportLatch;
4509 auto nativeViewIdsCallback = [&reportLatch, &hasImplicitView,
4510 &viewIds](Dart_NativeArguments args) {
4511 ParseViewIdsCallback(args, &hasImplicitView, &viewIds);
4512 reportLatch.Signal();
4513 };
4514 AddNativeCallback("NativeReportViewIdsCallback",
4515 CREATE_NATIVE_ENTRY(nativeViewIdsCallback));
4516
4517 PlatformViewNotifyCreated(shell.get());
4518 auto configuration = RunConfiguration::InferFromSettings(settings);
4519 configuration.SetEntrypoint("testReportViewIds");
4520 RunEngine(shell.get(), std::move(configuration));
4521 reportLatch.Wait();
4522
4523 ASSERT_TRUE(hasImplicitView);
4524 ASSERT_EQ(viewIds.size(), 1u);
4525 ASSERT_EQ(viewIds[0], 0ll);
4526
4527 PlatformViewNotifyDestroyed(shell.get());
4528 DestroyShell(std::move(shell), task_runners);
4529}
4530
4531// Tests that Shell::AddView and Shell::RemoveView works.
4532TEST_F(ShellTest, ShellCanAddViewOrRemoveView) {
4533 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4534 Settings settings = CreateSettingsForFixture();
4536 "io.flutter.test." + GetCurrentTestName() + ".",
4537 ThreadHost::Type::kPlatform | ThreadHost::Type::kRaster |
4538 ThreadHost::Type::kIo | ThreadHost::Type::kUi));
4539 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
4540 thread_host.raster_thread->GetTaskRunner(),
4541 thread_host.ui_thread->GetTaskRunner(),
4542 thread_host.io_thread->GetTaskRunner());
4543 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
4544 ASSERT_TRUE(shell);
4545
4546 bool hasImplicitView;
4547 std::vector<int64_t> viewIds;
4548 fml::AutoResetWaitableEvent reportLatch;
4549 auto nativeViewIdsCallback = [&reportLatch, &hasImplicitView,
4550 &viewIds](Dart_NativeArguments args) {
4551 ParseViewIdsCallback(args, &hasImplicitView, &viewIds);
4552 reportLatch.Signal();
4553 };
4554 AddNativeCallback("NativeReportViewIdsCallback",
4555 CREATE_NATIVE_ENTRY(nativeViewIdsCallback));
4556
4557 PlatformViewNotifyCreated(shell.get());
4558 auto configuration = RunConfiguration::InferFromSettings(settings);
4559 configuration.SetEntrypoint("testReportViewIds");
4560 RunEngine(shell.get(), std::move(configuration));
4561
4562 reportLatch.Wait();
4563 ASSERT_TRUE(hasImplicitView);
4564 ASSERT_EQ(viewIds.size(), 1u);
4565 ASSERT_EQ(viewIds[0], 0ll);
4566
4567 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] {
4568 shell->GetPlatformView()->AddView(2, ViewportMetrics{},
4569 [](bool added) { EXPECT_TRUE(added); });
4570 });
4571 reportLatch.Wait();
4572 ASSERT_TRUE(hasImplicitView);
4573 ASSERT_EQ(viewIds.size(), 2u);
4574 ASSERT_EQ(viewIds[1], 2ll);
4575
4576 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] {
4577 shell->GetPlatformView()->RemoveView(
4578 2, [](bool removed) { ASSERT_TRUE(removed); });
4579 });
4580 reportLatch.Wait();
4581 ASSERT_TRUE(hasImplicitView);
4582 ASSERT_EQ(viewIds.size(), 1u);
4583 ASSERT_EQ(viewIds[0], 0ll);
4584
4585 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] {
4586 shell->GetPlatformView()->AddView(4, ViewportMetrics{},
4587 [](bool added) { EXPECT_TRUE(added); });
4588 });
4589 reportLatch.Wait();
4590 ASSERT_TRUE(hasImplicitView);
4591 ASSERT_EQ(viewIds.size(), 2u);
4592 ASSERT_EQ(viewIds[1], 4ll);
4593
4594 PlatformViewNotifyDestroyed(shell.get());
4595 DestroyShell(std::move(shell), task_runners);
4596}
4597
4598// Test that add view fails if the view ID already exists.
4599TEST_F(ShellTest, ShellCannotAddDuplicateViewId) {
4600 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4601 Settings settings = CreateSettingsForFixture();
4603 "io.flutter.test." + GetCurrentTestName() + ".",
4604 ThreadHost::Type::kPlatform | ThreadHost::Type::kRaster |
4605 ThreadHost::Type::kIo | ThreadHost::Type::kUi));
4606 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
4607 thread_host.raster_thread->GetTaskRunner(),
4608 thread_host.ui_thread->GetTaskRunner(),
4609 thread_host.io_thread->GetTaskRunner());
4610 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
4611 ASSERT_TRUE(shell);
4612
4613 bool has_implicit_view;
4614 std::vector<int64_t> view_ids;
4615 fml::AutoResetWaitableEvent report_latch;
4616 AddNativeCallback("NativeReportViewIdsCallback",
4617 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
4618 ParseViewIdsCallback(args, &has_implicit_view, &view_ids);
4619 report_latch.Signal();
4620 }));
4621
4622 PlatformViewNotifyCreated(shell.get());
4623 auto configuration = RunConfiguration::InferFromSettings(settings);
4624 configuration.SetEntrypoint("testReportViewIds");
4625 RunEngine(shell.get(), std::move(configuration));
4626
4627 report_latch.Wait();
4628 ASSERT_TRUE(has_implicit_view);
4629 ASSERT_EQ(view_ids.size(), 1u);
4630 ASSERT_EQ(view_ids[0], kImplicitViewId);
4631
4632 // Add view 123.
4634 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell,
4635 &add_latch] {
4636 shell->GetPlatformView()->AddView(123, ViewportMetrics{}, [&](bool added) {
4637 EXPECT_TRUE(added);
4638 add_latch.Signal();
4639 });
4640 });
4641
4642 add_latch.Wait();
4643
4644 report_latch.Wait();
4645 ASSERT_EQ(view_ids.size(), 2u);
4646 ASSERT_EQ(view_ids[0], kImplicitViewId);
4647 ASSERT_EQ(view_ids[1], 123);
4648
4649 // Attempt to add duplicate view ID 123. This should fail.
4650 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell,
4651 &add_latch] {
4652 shell->GetPlatformView()->AddView(123, ViewportMetrics{}, [&](bool added) {
4653 EXPECT_FALSE(added);
4654 add_latch.Signal();
4655 });
4656 });
4657
4658 add_latch.Wait();
4659
4660 PlatformViewNotifyDestroyed(shell.get());
4661 DestroyShell(std::move(shell), task_runners);
4662}
4663
4664// Test that remove view fails if the view ID does not exist.
4665TEST_F(ShellTest, ShellCannotRemoveNonexistentId) {
4666 // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Assign)
4667 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4668 Settings settings = CreateSettingsForFixture();
4670 "io.flutter.test." + GetCurrentTestName() + ".",
4671 ThreadHost::Type::kPlatform | ThreadHost::Type::kRaster |
4672 ThreadHost::Type::kIo | ThreadHost::Type::kUi));
4673 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
4674 thread_host.raster_thread->GetTaskRunner(),
4675 thread_host.ui_thread->GetTaskRunner(),
4676 thread_host.io_thread->GetTaskRunner());
4677 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
4678 ASSERT_TRUE(shell);
4679
4680 bool has_implicit_view;
4681 std::vector<int64_t> view_ids;
4682 fml::AutoResetWaitableEvent report_latch;
4683 AddNativeCallback("NativeReportViewIdsCallback",
4684 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
4685 ParseViewIdsCallback(args, &has_implicit_view, &view_ids);
4686 report_latch.Signal();
4687 }));
4688
4689 PlatformViewNotifyCreated(shell.get());
4690 auto configuration = RunConfiguration::InferFromSettings(settings);
4691 configuration.SetEntrypoint("testReportViewIds");
4692 RunEngine(shell.get(), std::move(configuration));
4693
4694 report_latch.Wait();
4695 ASSERT_TRUE(has_implicit_view);
4696 ASSERT_EQ(view_ids.size(), 1u);
4697 ASSERT_EQ(view_ids[0], kImplicitViewId);
4698
4699 // Remove view 123. This should fail as this view doesn't exist.
4700 fml::AutoResetWaitableEvent remove_latch;
4701 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(),
4702 [&shell, &remove_latch] {
4703 shell->GetPlatformView()->RemoveView(123, [&](bool removed) {
4704 EXPECT_FALSE(removed);
4705 remove_latch.Signal();
4706 });
4707 });
4708
4709 remove_latch.Wait();
4710
4711 PlatformViewNotifyDestroyed(shell.get());
4712 DestroyShell(std::move(shell), task_runners);
4713}
4714
4715// Parse the arguments of NativeReportViewWidthsCallback and
4716// store them in viewWidths.
4717static void ParseViewWidthsCallback(const Dart_NativeArguments& args,
4718 std::map<int64_t, int64_t>* viewWidths) {
4719 Dart_Handle exception = nullptr;
4720 viewWidths->clear();
4721 std::vector<int64_t> viewWidthPacket =
4723 exception);
4724 ASSERT_EQ(exception, nullptr);
4725 ASSERT_EQ(viewWidthPacket.size() % 2, 0ul);
4726 for (size_t packetIndex = 0; packetIndex < viewWidthPacket.size();
4727 packetIndex += 2) {
4728 (*viewWidths)[viewWidthPacket[packetIndex]] =
4729 viewWidthPacket[packetIndex + 1];
4730 }
4731}
4732
4733// Ensure that PlatformView::SetViewportMetrics and Shell::AddView that were
4734// dispatched before the isolate is run have been flushed to the Dart VM when
4735// the main function starts.
4736TEST_F(ShellTest, ShellFlushesPlatformStatesByMain) {
4737 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4738 Settings settings = CreateSettingsForFixture();
4740 "io.flutter.test." + GetCurrentTestName() + ".",
4741 ThreadHost::Type::kPlatform | ThreadHost::Type::kRaster |
4742 ThreadHost::Type::kIo | ThreadHost::Type::kUi));
4743 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
4744 thread_host.raster_thread->GetTaskRunner(),
4745 thread_host.ui_thread->GetTaskRunner(),
4746 thread_host.io_thread->GetTaskRunner());
4747 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
4748 ASSERT_TRUE(shell);
4749
4750 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] {
4751 auto platform_view = shell->GetPlatformView();
4752 // The construtor for ViewportMetrics{_, width, _, _, _} (only the 2nd
4753 // argument matters in this test).
4754 platform_view->SetViewportMetrics(0, ViewportMetrics{1, 10, 1, 0, 0});
4755 shell->GetPlatformView()->AddView(1, ViewportMetrics{1, 30, 1, 0, 0},
4756 [](bool added) { ASSERT_TRUE(added); });
4757 platform_view->SetViewportMetrics(0, ViewportMetrics{1, 20, 1, 0, 0});
4758 });
4759
4760 bool first_report = true;
4761 std::map<int64_t, int64_t> viewWidths;
4762 fml::AutoResetWaitableEvent reportLatch;
4763 auto nativeViewWidthsCallback = [&reportLatch, &viewWidths,
4764 &first_report](Dart_NativeArguments args) {
4765 EXPECT_TRUE(first_report);
4766 first_report = false;
4767 ParseViewWidthsCallback(args, &viewWidths);
4768 reportLatch.Signal();
4769 };
4770 AddNativeCallback("NativeReportViewWidthsCallback",
4771 CREATE_NATIVE_ENTRY(nativeViewWidthsCallback));
4772
4773 PlatformViewNotifyCreated(shell.get());
4774 auto configuration = RunConfiguration::InferFromSettings(settings);
4775 configuration.SetEntrypoint("testReportViewWidths");
4776 RunEngine(shell.get(), std::move(configuration));
4777
4778 reportLatch.Wait();
4779 EXPECT_EQ(viewWidths.size(), 2u);
4780 EXPECT_EQ(viewWidths[0], 20ll);
4781 EXPECT_EQ(viewWidths[1], 30ll);
4782
4783 PlatformViewNotifyDestroyed(shell.get());
4784 DestroyShell(std::move(shell), task_runners);
4785}
4786
4787// A view can be added and removed before the Dart isolate is launched.
4788TEST_F(ShellTest, CanRemoveViewBeforeLaunchingIsolate) {
4789 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4790 Settings settings = CreateSettingsForFixture();
4792 "io.flutter.test." + GetCurrentTestName() + ".",
4793 ThreadHost::Type::kPlatform | ThreadHost::Type::kRaster |
4794 ThreadHost::Type::kIo | ThreadHost::Type::kUi));
4795 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
4796 thread_host.raster_thread->GetTaskRunner(),
4797 thread_host.ui_thread->GetTaskRunner(),
4798 thread_host.io_thread->GetTaskRunner());
4799 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
4800 ASSERT_TRUE(shell);
4801
4802 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] {
4803 auto platform_view = shell->GetPlatformView();
4804
4805 // A view can be added and removed all before the isolate launches.
4806 // The pending add view operation is cancelled, the view is never
4807 // added to the Dart isolate.
4808 shell->GetPlatformView()->AddView(123, ViewportMetrics{1, 30, 1, 0, 0},
4809 [](bool added) { ASSERT_FALSE(added); });
4810 shell->GetPlatformView()->RemoveView(
4811 123, [](bool removed) { ASSERT_FALSE(removed); });
4812 });
4813
4814 bool first_report = true;
4815 std::map<int64_t, int64_t> view_widths;
4816 fml::AutoResetWaitableEvent report_latch;
4817 AddNativeCallback("NativeReportViewWidthsCallback",
4818 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
4819 EXPECT_TRUE(first_report);
4820 first_report = false;
4821 ParseViewWidthsCallback(args, &view_widths);
4822 report_latch.Signal();
4823 }));
4824
4825 PlatformViewNotifyCreated(shell.get());
4826 auto configuration = RunConfiguration::InferFromSettings(settings);
4827 configuration.SetEntrypoint("testReportViewWidths");
4828 RunEngine(shell.get(), std::move(configuration));
4829
4830 report_latch.Wait();
4831 EXPECT_EQ(view_widths.size(), 1u);
4832
4833 PlatformViewNotifyDestroyed(shell.get());
4834 DestroyShell(std::move(shell), task_runners);
4835}
4836
4837// Ensure pending "add views" failures are properly flushed when the Dart
4838// isolate is launched.
4839TEST_F(ShellTest, IgnoresBadAddViewsBeforeLaunchingIsolate) {
4840 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4841 Settings settings = CreateSettingsForFixture();
4843 "io.flutter.test." + GetCurrentTestName() + ".",
4844 ThreadHost::Type::kPlatform | ThreadHost::Type::kRaster |
4845 ThreadHost::Type::kIo | ThreadHost::Type::kUi));
4846 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
4847 thread_host.raster_thread->GetTaskRunner(),
4848 thread_host.ui_thread->GetTaskRunner(),
4849 thread_host.io_thread->GetTaskRunner());
4850 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
4851 ASSERT_TRUE(shell);
4852
4853 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] {
4854 auto platform_view = shell->GetPlatformView();
4855
4856 // Add the same view twice. The second time should fail.
4857 shell->GetPlatformView()->AddView(123, ViewportMetrics{1, 100, 1, 0, 0},
4858 [](bool added) { ASSERT_TRUE(added); });
4859
4860 shell->GetPlatformView()->AddView(123, ViewportMetrics{1, 200, 1, 0, 0},
4861 [](bool added) { ASSERT_FALSE(added); });
4862
4863 // Add another view. Previous failures should not affect this.
4864 shell->GetPlatformView()->AddView(456, ViewportMetrics{1, 300, 1, 0, 0},
4865 [](bool added) { ASSERT_TRUE(added); });
4866 });
4867
4868 bool first_report = true;
4869 std::map<int64_t, int64_t> view_widths;
4870 fml::AutoResetWaitableEvent report_latch;
4871 AddNativeCallback("NativeReportViewWidthsCallback",
4872 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
4873 EXPECT_TRUE(first_report);
4874 first_report = false;
4875 ParseViewWidthsCallback(args, &view_widths);
4876 report_latch.Signal();
4877 }));
4878
4879 PlatformViewNotifyCreated(shell.get());
4880 auto configuration = RunConfiguration::InferFromSettings(settings);
4881 configuration.SetEntrypoint("testReportViewWidths");
4882 RunEngine(shell.get(), std::move(configuration));
4883
4884 report_latch.Wait();
4885 EXPECT_EQ(view_widths.size(), 3u);
4886 EXPECT_EQ(view_widths[0], 0);
4887 EXPECT_EQ(view_widths[123], 100);
4888 EXPECT_EQ(view_widths[456], 300);
4889
4890 PlatformViewNotifyDestroyed(shell.get());
4891 DestroyShell(std::move(shell), task_runners);
4892}
4893
4894TEST_F(ShellTest, WillLogWarningWhenImpellerIsOptedOut) {
4895#if !IMPELLER_SUPPORTS_RENDERING
4896 GTEST_SKIP() << "This platform doesn't support Impeller.";
4897#endif
4898 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4899 Settings settings = CreateSettingsForFixture();
4900 settings.enable_impeller = false;
4901 settings.warn_on_impeller_opt_out = true;
4902 // Log captures are thread specific. Just put the shell in single threaded
4903 // configuration.
4904 const auto& runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
4905 TaskRunners task_runners("test", runner, runner, runner, runner);
4906 std::ostringstream stream;
4908 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
4909 ASSERT_TRUE(
4910 stream.str().find("[Action Required]: Impeller opt-out deprecated.") !=
4911 std::string::npos);
4912 ASSERT_TRUE(shell);
4913 DestroyShell(std::move(shell), task_runners);
4914}
4915
4916TEST_F(ShellTest, SendViewFocusEvent) {
4917 Settings settings = CreateSettingsForFixture();
4918 TaskRunners task_runners = GetTaskRunnersForFixture();
4920 std::string last_event;
4921
4922 AddNativeCallback(
4923 "NotifyNative",
4924 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }));
4925
4926 AddNativeCallback("NotifyMessage",
4927 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
4928 const auto message_from_dart =
4930 Dart_GetNativeArgument(args, 0));
4931 last_event = message_from_dart;
4932 latch.Signal();
4933 }));
4934 fml::AutoResetWaitableEvent check_latch;
4935
4936 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
4937 ASSERT_TRUE(shell->IsSetup());
4938
4939 auto configuration = RunConfiguration::InferFromSettings(settings);
4940
4941 configuration.SetEntrypoint("testSendViewFocusEvent");
4942 RunEngine(shell.get(), std::move(configuration));
4943 latch.Wait();
4944 latch.Reset();
4945
4946 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
4947 shell->GetPlatformView()->SendViewFocusEvent(ViewFocusEvent(
4948 1, ViewFocusState::kFocused, ViewFocusDirection::kUndefined));
4949 });
4950 latch.Wait();
4951 ASSERT_EQ(last_event,
4952 "1 ViewFocusState.focused ViewFocusDirection.undefined");
4953
4954 latch.Reset();
4955 PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
4956 shell->GetPlatformView()->SendViewFocusEvent(ViewFocusEvent(
4957 2, ViewFocusState::kUnfocused, ViewFocusDirection::kBackward));
4958 });
4959 latch.Wait();
4960 ASSERT_EQ(last_event,
4961 "2 ViewFocusState.unfocused ViewFocusDirection.backward");
4962 DestroyShell(std::move(shell), task_runners);
4963}
4964
4965TEST_F(ShellTest, ProvidesEngineId) {
4966 Settings settings = CreateSettingsForFixture();
4967 TaskRunners task_runners = GetTaskRunnersForFixture();
4969
4970 std::optional<int> reported_handle = std::nullopt;
4971
4972 AddNativeCallback(
4973 "ReportEngineId", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
4974 Dart_Handle arg = Dart_GetNativeArgument(args, 0);
4975 if (Dart_IsNull(arg)) {
4976 reported_handle = std::nullopt;
4977 } else {
4978 reported_handle = tonic::DartConverter<int64_t>::FromDart(arg);
4979 }
4980 latch.Signal();
4981 }));
4982 fml::AutoResetWaitableEvent check_latch;
4983
4984 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
4985 ASSERT_TRUE(shell->IsSetup());
4986
4987 auto configuration = RunConfiguration::InferFromSettings(settings);
4988 configuration.SetEngineId(99);
4989 configuration.SetEntrypoint("providesEngineId");
4990 RunEngine(shell.get(), std::move(configuration));
4991
4992 latch.Wait();
4993 ASSERT_EQ(reported_handle, 99);
4994
4995 latch.Reset();
4996
4998 shell->GetTaskRunners().GetUITaskRunner(), [&]() {
4999 ASSERT_EQ(shell->GetEngine()->GetLastEngineId(), 99);
5000 latch.Signal();
5001 });
5002 latch.Wait();
5003 DestroyShell(std::move(shell), task_runners);
5004}
5005
5006TEST_F(ShellTest, ProvidesNullEngineId) {
5007 Settings settings = CreateSettingsForFixture();
5008 TaskRunners task_runners = GetTaskRunnersForFixture();
5010
5011 std::optional<int> reported_handle = std::nullopt;
5012
5013 AddNativeCallback(
5014 "ReportEngineId", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
5015 Dart_Handle arg = Dart_GetNativeArgument(args, 0);
5016 if (Dart_IsNull(arg)) {
5017 reported_handle = std::nullopt;
5018 } else {
5019 reported_handle = tonic::DartConverter<int64_t>::FromDart(arg);
5020 }
5021 latch.Signal();
5022 }));
5023 fml::AutoResetWaitableEvent check_latch;
5024
5025 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
5026 ASSERT_TRUE(shell->IsSetup());
5027
5028 auto configuration = RunConfiguration::InferFromSettings(settings);
5029 configuration.SetEntrypoint("providesEngineId");
5030 RunEngine(shell.get(), std::move(configuration));
5031
5032 latch.Wait();
5033 ASSERT_EQ(reported_handle, std::nullopt);
5034 DestroyShell(std::move(shell), task_runners);
5035}
5036
5037TEST_F(ShellTest, MergeUIAndPlatformThreadsAfterLaunch) {
5038 Settings settings = CreateSettingsForFixture();
5039 settings.merged_platform_ui_thread =
5040 Settings::MergedPlatformUIThread::kMergeAfterLaunch;
5042 "io.flutter.test." + GetCurrentTestName() + ".",
5043 ThreadHost::Type::kPlatform | ThreadHost::Type::kRaster |
5044 ThreadHost::Type::kIo | ThreadHost::Type::kUi));
5045 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
5046 thread_host.raster_thread->GetTaskRunner(),
5047 thread_host.ui_thread->GetTaskRunner(),
5048 thread_host.io_thread->GetTaskRunner());
5049
5050 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
5051 ASSERT_TRUE(shell);
5052
5054 task_runners.GetUITaskRunner()->GetTaskQueueId(),
5055 task_runners.GetPlatformTaskRunner()->GetTaskQueueId()));
5056
5058 AddNativeCallback(
5059 "NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
5060 ASSERT_TRUE(
5062 latch.Signal();
5063 }));
5064
5065 auto configuration = RunConfiguration::InferFromSettings(settings);
5066 configuration.SetEntrypoint("mainNotifyNative");
5067 RunEngine(shell.get(), std::move(configuration));
5068
5069 latch.Wait();
5070
5072 task_runners.GetUITaskRunner()->GetTaskQueueId(),
5073 task_runners.GetPlatformTaskRunner()->GetTaskQueueId()));
5074
5075 DestroyShell(std::move(shell), task_runners);
5076}
5077
5078TEST_F(ShellTest, ReleaseResourceContextWhenIOManagerIsDeleted) {
5079 TaskRunners task_runners = GetTaskRunnersForFixture();
5080 auto settings = CreateSettingsForFixture();
5081 bool called_release_resource_context = false;
5082 Shell::CreateCallback<PlatformView> platform_view_create_callback =
5083 [task_runners, &called_release_resource_context](flutter::Shell& shell) {
5084 auto result = std::make_unique<::testing::NiceMock<TestPlatformView>>(
5085 shell, task_runners);
5086 ON_CALL(*result, ReleaseResourceContext())
5087 .WillByDefault(
5088 ::testing::Assign(&called_release_resource_context, true));
5089 return result;
5090 };
5091
5092 auto parent_shell = CreateShell({
5093 .settings = settings,
5094 .task_runners = task_runners,
5095 .platform_view_create_callback = platform_view_create_callback,
5096 });
5097
5098 std::unique_ptr<Shell> child_shell;
5099 PostSync(
5100 parent_shell->GetTaskRunners().GetPlatformTaskRunner(),
5101 [&parent_shell, &settings, &child_shell, platform_view_create_callback] {
5102 auto configuration = RunConfiguration::InferFromSettings(settings);
5103 configuration.SetEntrypoint("emptyMain");
5104 auto child = parent_shell->Spawn(
5105 std::move(configuration), "", platform_view_create_callback,
5106 [](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
5107 child_shell = std::move(child);
5108 });
5109
5110 DestroyShell(std::move(parent_shell), task_runners);
5111 ASSERT_FALSE(called_release_resource_context);
5112
5113 DestroyShell(std::move(child_shell), task_runners);
5114 ASSERT_TRUE(called_release_resource_context);
5115}
5116
5117TEST_F(ShellTest, ShoulDiscardLayerTreeIfFrameIsSizedIncorrectly) {
5118 Settings settings = CreateSettingsForFixture();
5119 auto task_runner = CreateNewThread();
5120 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
5121 task_runner);
5122 std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
5123
5125 shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
5126 shell->GetPlatformView()->SetViewportMetrics(
5127 kImplicitViewId,
5128 {
5129 1.0, // p_device_pixel_ratio
5130 500, // p_physical_width
5131 800, // p_physical_height
5132 1, // p_min_width_constraint,
5133 1000, // p_max_width_constraint,
5134 1, // p_min_height_constraint,
5135 1000, // p_max_height_constraint,
5136 0, // p_physical_padding_top
5137 0, // p_physical_padding_right
5138 0, // p_physical_padding_bottom
5139 0, // p_physical_padding_left
5140 0, // p_physical_view_inset_top,
5141 0, // p_physical_view_inset_right,
5142 0, // p_physical_view_inset_bottom,
5143 0, // p_physical_view_inset_left,
5144 0, // p_physical_system_gesture_inset_top,
5145 0, // p_physical_system_gesture_inset_right,
5146 0, // p_physical_system_gesture_inset_bottom,
5147 0, // p_physical_system_gesture_inset_left,
5148 22, // p_physical_touch_slop,
5149 {}, // p_physical_display_features_bounds,
5150 {}, // p_physical_display_features_type,
5151 {}, // p_physical_display_features_state,
5152 0, // p_display_id
5153 0, // p_physical_display_corner_radius_top_left
5154 0, // p_physical_display_corner_radius_top_right
5155 0, // p_physical_display_corner_radius_bottom_right
5156 0, // p_physical_display_corner_radius_bottom_left
5157 });
5158 });
5159 PumpOneFrame(shell.get());
5160
5161 auto layer_tree =
5162 std::make_unique<LayerTree>(/*root_layer=*/nullptr,
5163 /*frame_size=*/DlISize(100, 100));
5164 ASSERT_FALSE(ShellTest::ShouldDiscardLayerTree(shell.get(), kImplicitViewId,
5165 *layer_tree));
5166 auto over_width =
5167 std::make_unique<LayerTree>(/*root_layer=*/nullptr,
5168 /*frame_size=*/DlISize(1010, 100));
5169 ASSERT_TRUE(ShellTest::ShouldDiscardLayerTree(shell.get(), kImplicitViewId,
5170 *over_width));
5171 auto over_height =
5172 std::make_unique<LayerTree>(/*root_layer=*/nullptr,
5173 /*frame_size=*/DlISize(100, 1010));
5174 ASSERT_TRUE(ShellTest::ShouldDiscardLayerTree(shell.get(), kImplicitViewId,
5175 *over_height));
5176 auto min_width = std::make_unique<LayerTree>(/*root_layer=*/nullptr,
5177 /*frame_size=*/DlISize(0, 100));
5178 ASSERT_TRUE(ShellTest::ShouldDiscardLayerTree(shell.get(), kImplicitViewId,
5179 *min_width));
5180 auto min_height = std::make_unique<LayerTree>(/*root_layer=*/nullptr,
5181 /*frame_size=*/DlISize(100, 0));
5182 ASSERT_TRUE(ShellTest::ShouldDiscardLayerTree(shell.get(), kImplicitViewId,
5183 *min_height));
5184 DestroyShell(std::move(shell), task_runners);
5185}
5186
5187} // namespace testing
5188} // namespace flutter
5189
5190// NOLINTEND(clang-analyzer-core.StackAddressEscape)
std::unique_ptr< flutter::PlatformViewIOS > platform_view
std::vector< std::unique_ptr< fml::Mapping > > GetAsMappings(const std::string &asset_pattern, const std::optional< std::string > &subdir) const override
Same as GetAsMapping() but returns mappings for all files who's name matches a given pattern....
std::unique_ptr< fml::Mapping > GetAsMapping(const std::string &asset_name) const override
bool PushBack(std::unique_ptr< AssetResolver > resolver)
Adds an asset resolver to the end of the resolver queue. Assets would be loaded from this resolver af...
AssetResolverType
Identifies the type of AssetResolver an instance is.
Represents an instance of a live isolate. An isolate is a separate Dart execution context....
static bool IsRunningPrecompiledCode()
Checks if VM instances in the process can run precompiled code. This call can be made at any time and...
Definition dart_vm.cc:177
static bool IsInstanceRunning()
sk_sp< DisplayList > Build()
void DrawRect(const DlRect &rect, const DlPaint &paint) override
void PrerollSetup(PrerollContext *context, const DlMatrix &matrix) override
void PrerollFinalize(PrerollContext *context, const DlMatrix &matrix) override
bool TryToPrepareRasterCache(const PaintContext &context, bool parent_cached=false) const override
bool Draw(const PaintContext &context, const DlPaint *paint) const override
static std::shared_ptr< DlImageFilter > MakeBlur(DlScalar sigma_x, DlScalar sigma_y, DlTileMode tile_mode)
Used for fixed refresh rate cases.
Definition stopwatch.h:80
static constexpr Phase kPhases[kCount]
Definition settings.h:37
fml::TimePoint Set(Phase phase, fml::TimePoint value)
Definition settings.h:44
fml::TimePoint Get(Phase phase) const
Definition settings.h:43
The minimal interface necessary for defining a decoder that can be used for both single and multi-fra...
void PrerollSetup(PrerollContext *context, const DlMatrix &matrix) override
void PrerollFinalize(PrerollContext *context, const DlMatrix &matrix) override
bool Draw(const PaintContext &context, const DlPaint *paint) const override
bool TryToPrepareRasterCache(const PaintContext &context, bool parent_cached=false) const override
void set_delegate(DlCanvas *canvas)
void set_preroll_delegate(const DlRect &cull_rect, const DlMatrix &matrix)
const std::vector< std::shared_ptr< Mutator > >::const_iterator Begin() const
Specifies all the configuration required by the runtime library to launch the root isolate....
void SetEntrypoint(std::string entrypoint)
Updates the main application entrypoint. If this is not set, the "main" method is used as the entrypo...
static RunConfiguration InferFromSettings(const Settings &settings, const fml::RefPtr< fml::TaskRunner > &io_worker=nullptr, IsolateLaunchType launch_type=IsolateLaunchType::kNewGroup)
Attempts to infer a run configuration from the settings object. This tries to create a run configurat...
virtual bool NotifyIdle(fml::TimeDelta deadline)
Notify the Dart VM that no frame workloads are expected on the UI task runner till the specified dead...
std::map< std::string_view, std::string_view > ServiceProtocolMap
static std::unique_ptr< Shell > Create(const PlatformData &platform_data, const TaskRunners &task_runners, Settings settings, const CreateCallback< PlatformView > &on_create_platform_view, const CreateCallback< Rasterizer > &on_create_rasterizer, bool is_gpu_disabled=false)
Creates a shell instance using the provided settings. The callbacks to create the various shell subco...
Definition shell.cc:222
const TaskRunners & GetTaskRunners() const override
If callers wish to interact directly with any shell subcomponents, they must (on the platform thread)...
Definition shell.cc:916
std::shared_ptr< const fml::SyncSwitch > GetIsGpuDisabledSyncSwitch() const override
Accessor for the disable GPU SyncSwitch.
Definition shell.cc:2400
std::function< std::unique_ptr< T >(Shell &)> CreateCallback
Definition shell.h:121
bool IsSetup() const
Used by embedders to check if all shell subcomponents are initialized. It is the embedder's responsib...
Definition shell.cc:839
fml::RefPtr< fml::TaskRunner > GetRasterTaskRunner() const
fml::RefPtr< fml::TaskRunner > GetUITaskRunner() const
fml::RefPtr< fml::TaskRunner > GetPlatformTaskRunner() const
~MockTexture() override=default
MockTexture(int64_t textureId, std::shared_ptr< fml::AutoResetWaitableEvent > latch)
void Paint(PaintContext &context, const DlRect &bounds, bool freeze, const DlImageSampling) override
static void PlatformViewNotifyDestroyed(Shell *shell)
static void PlatformViewNotifyCreated(Shell *shell)
Definition shell_test.cc:93
static std::unique_ptr< ShellTestPlatformView > Create(BackendType backend, PlatformView::Delegate &delegate, const TaskRunners &task_runners, const std::shared_ptr< ShellTestVsyncClock > &vsync_clock, const CreateVsyncWaiter &create_vsync_waiter, const std::shared_ptr< ShellTestExternalViewEmbedder > &shell_test_external_view_embedder, const std::shared_ptr< const fml::SyncSwitch > &is_gpu_disabled_sync_switch)
An image generator that always creates a 1x1 single-frame green image.
unsigned int GetFrameCount() const
Get the number of frames that the encoded image stores. This method is always expected to be called b...
const SkImageInfo & GetInfo()
Returns basic information about the contents of the encoded image. This information can almost always...
const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index)
Get information about a single frame in the context of a multi-frame image, useful for animation and ...
bool GetPixels(const SkImageInfo &info, void *pixels, size_t row_bytes, unsigned int frame_index, std::optional< unsigned int > prior_frame)
Decode the image into a given buffer. This method is currently always used for sub-pixel image decodi...
SkISize GetScaledDimensions(float scale)
Given a scale value, find the closest image size that can be used for efficiently decoding the image....
unsigned int GetPlayCount() const
The number of times an animated image should play through before playback stops.
TestAssetResolver(bool valid, AssetResolver::AssetResolverType type)
bool IsValidAfterAssetManagerChange() const override
Certain asset resolvers are still valid after the asset manager is replaced before a hot reload,...
std::unique_ptr< fml::Mapping > GetAsMapping(const std::string &asset_name) const override
bool operator==(const AssetResolver &other) const override
AssetResolver::AssetResolverType GetType() const override
Gets the type of AssetResolver this is. Types are defined in AssetResolverType.
std::vector< std::unique_ptr< fml::Mapping > > GetAsMappings(const std::string &asset_pattern, const std::optional< std::string > &subdir) const override
Same as GetAsMapping() but returns mappings for all files who's name matches a given pattern....
std::unique_ptr< fml::Mapping > GetAsMapping(const std::string &asset_name) const override
bool operator==(const AssetResolver &other) const override
bool IsValidAfterAssetManagerChange() const override
Certain asset resolvers are still valid after the asset manager is replaced before a hot reload,...
ThreadCheckingAssetResolver(std::shared_ptr< fml::ConcurrentMessageLoop > concurrent_loop)
AssetResolverType GetType() const
Gets the type of AssetResolver this is. Types are defined in AssetResolverType.
bool WaitWithTimeout(TimeDelta timeout)
static std::unique_ptr< FileMapping > CreateReadOnly(const std::string &path)
Definition mapping.cc:20
static void CaptureNextLog(std::ostringstream *stream)
Definition logging.cc:109
A Mapping like NonOwnedMapping, but uses Free as its release proc.
Definition mapping.h:144
static MallocMapping Copy(const T *begin, const T *end)
Definition mapping.h:162
static void EnsureInitializedForCurrentThread()
fml::RefPtr< fml::TaskRunner > GetTaskRunner() const
static FML_EMBEDDER_ONLY MessageLoop & GetCurrent()
const UniqueFD & fd()
Definition file.h:147
const std::string & path() const
Definition file.h:146
bool ok() const
Definition status.h:71
fml::StatusCode code() const
Definition status.h:63
std::string_view message() const
Definition status.h:75
static bool RunsOnTheSameThread(TaskQueueId queue_a, TaskQueueId queue_b)
static void RunNowOrPostTask(const fml::RefPtr< fml::TaskRunner > &runner, const fml::closure &task)
virtual void PostTask(const fml::closure &task) override
virtual bool RunsTasksOnCurrentThread()
virtual TaskQueueId GetTaskQueueId()
static void DisableNextThreadCheckFailure()
constexpr int64_t ToMicroseconds() const
Definition time_delta.h:62
static constexpr TimeDelta Max()
Definition time_delta.h:37
static constexpr TimeDelta FromMicroseconds(int64_t micros)
Definition time_delta.h:43
static constexpr TimeDelta Zero()
Definition time_delta.h:33
constexpr TimeDelta ToEpochDelta() const
Definition time_point.h:52
static TimePoint Now()
Definition time_point.cc:49
static constexpr TimePoint FromEpochDelta(TimeDelta ticks)
Definition time_point.h:43
bool is_valid() const
int32_t value
const EmbeddedViewParams * params
VkSurfaceKHR surface
Definition main.cc:65
const char * message
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
const gchar * channel
G_BEGIN_DECLS FlutterViewId view_id
FlutterDesktopBinaryReply callback
#define FML_LOG(severity)
Definition logging.h:101
static constexpr FlutterViewId kImplicitViewId
std::shared_ptr< ImpellerAllocator > allocator
union flutter::testing::@2883::KeyboardChange::@77 content
FlTexture * texture
static void LogSkData(const sk_sp< SkData > &data, const char *title)
std::string GetCurrentTestName()
Gets the name of the currently running test. This is useful in generating logs or assets based on tes...
Definition testing.cc:14
TEST_F(DisplayListTest, Defaults)
const char * GetFixturesPath()
Returns the directory containing the test fixture for the target if this target has fixtures configur...
static void ParseViewIdsCallback(const Dart_NativeArguments &args, bool *hasImplicitView, std::vector< int64_t > *viewIds)
static void TestDartVmFlags(std::vector< const char * > &flags)
static sk_sp< DisplayList > MakeSizedDisplayList(int width, int height)
std::function< void(std::shared_ptr< ContainerLayer > root)> LayerTreeBuilder
Definition shell_test.h:33
static void PostSync(const fml::RefPtr< fml::TaskRunner > &task_runner, const fml::closure &task)
static void ParseViewWidthsCallback(const Dart_NativeArguments &args, std::map< int64_t, int64_t > *viewWidths)
static bool ValidateShell(Shell *shell)
static std::string CreateFlagsString(std::vector< const char * > &flags)
TEST(NativeAssetsManagerTest, NoAvailableAssets)
static void CheckFrameTimings(const std::vector< FrameTiming > &timings, fml::TimePoint start, fml::TimePoint finish)
static size_t GetRasterizerResourceCacheBytesSync(const Shell &shell)
static void ValidateDestroyPlatformView(Shell *shell)
static bool RasterizerIsTornDown(Shell *shell)
constexpr int64_t kImplicitViewId
const char * GetSkiaVersion()
Definition version.cc:19
static void InvokePlatformMessageResponseCallback(JNIEnv *env, jobject jcaller, jlong shell_holder, jint responseId, jobject message, jint position)
@ kHardEdge
Definition layer.h:43
static void InvokePlatformMessageEmptyResponseCallback(JNIEnv *env, jobject jcaller, jlong shell_holder, jint responseId)
impeller::Matrix DlMatrix
impeller::ISize32 DlISize
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
impeller::Size DlSize
const char * GetFlutterEngineVersion()
Definition version.cc:11
static void LoadDartDeferredLibrary(JNIEnv *env, jobject obj, jlong shell_holder, jint jLoadingUnitId, jobjectArray jSearchPaths)
static void SetViewportMetrics(JNIEnv *env, jobject jcaller, jlong shell_holder, jfloat devicePixelRatio, jint physicalWidth, jint physicalHeight, jint physicalPaddingTop, jint physicalPaddingRight, jint physicalPaddingBottom, jint physicalPaddingLeft, jint physicalViewInsetTop, jint physicalViewInsetRight, jint physicalViewInsetBottom, jint physicalViewInsetLeft, jint systemGestureInsetTop, jint systemGestureInsetRight, jint systemGestureInsetBottom, jint systemGestureInsetLeft, jint physicalTouchSlop, jintArray javaDisplayFeaturesBounds, jintArray javaDisplayFeaturesType, jintArray javaDisplayFeaturesState, jint physicalMinWidth, jint physicalMaxWidth, jint physicalMinHeight, jint physicalMaxHeight, jint physicalDisplayCornerRadiusTopLeft, jint physicalDisplayCornerRadiusTopRight, jint physicalDisplayCornerRadiusBottomRight, jint physicalDisplayCornerRadiusBottomLeft)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switch_defs.h:36
Settings SettingsFromCommandLine(const fml::CommandLine &command_line, bool require_merged_platform_ui_thread)
Definition switches.cc:230
TEST_F(EngineAnimatorTest, AnimatorAcceptsMultipleRenders)
impeller::Point DlPoint
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set profile Make the profiler discard new samples once the profiler sample buffer is full When this flag is not the profiler sample buffer is used as a ring buffer
Definition switch_defs.h:98
bool WriteAtomically(const fml::UniqueFD &base_directory, const char *file_name, const Mapping &mapping)
std::string BacktraceHere(size_t offset)
Definition backtrace.cc:43
fml::UniqueFD OpenDirectory(const char *path, bool create_if_necessary, FilePermission permission)
Definition file_posix.cc:97
RefPtr< T > AdoptRef(T *ptr)
Definition ref_ptr.h:227
static fml::UniqueFD CreateDirectory(const fml::UniqueFD &base_directory, const std::vector< std::string > &components, FilePermission permission, size_t index)
Definition file.cc:12
std::function< void()> closure
Definition closure.h:14
Definition ref_ptr.h:261
static const size_t kGrCacheMaxByteSize
impeller::ShaderType type
int32_t height
int32_t width
static constexpr DlColor kRed()
Definition dl_color.h:71
Info about a single frame in the context of a multi-frame image, useful for animation and blending.
LayerStateStack & state_stack
Definition layer.h:91
GrDirectContext * gr_context
Definition layer.h:47
bool enable_software_rendering
Definition settings.h:320
MergedPlatformUIThread merged_platform_ui_thread
Definition settings.h:382
std::shared_ptr< const fml::Mapping > persistent_isolate_data
Definition settings.h:348
std::vector< std::string > dart_flags
Definition settings.h:145
bool warn_on_impeller_opt_out
Definition settings.h:248
The collection of all the threads used by the engine.
Definition thread_host.h:21
std::unique_ptr< fml::Thread > io_thread
Definition thread_host.h:86
std::unique_ptr< fml::Thread > platform_thread
Definition thread_host.h:83
std::string name_prefix
Definition thread_host.h:82
std::unique_ptr< fml::Thread > raster_thread
Definition thread_host.h:85
std::unique_ptr< fml::Thread > ui_thread
Definition thread_host.h:84
static FrameContent ImplicitView(double width, double height, LayerTreeBuilder builder)
Definition shell_test.cc:47
static FrameContent DummyView(double width=1, double height=1)
Definition shell_test.cc:29
Represents the 2 code paths available when calling |SyncSwitchExecute|.
Definition sync_switch.h:35
std::string str() const
Definition logging.cc:102
A 4x4 matrix using column-major storage.
Definition matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition matrix.h:95
static constexpr TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition rect.h:136
static constexpr TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition rect.h:129
const size_t start
#define CREATE_NATIVE_ENTRY(native_entry)
int64_t texture_id