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