auto-submit[bot] 24debb363e Reverts "[Impeller] Disable layer tree screenshots." (flutter/engine#47356)
Reverts flutter/engine#46171
Initiated by: jonahwilliams
This change reverts the following previous change:
Original Description:
The "rasterizer screenshot" VM service API uses the Skia software backend, even when running with impeller. At best, this leads to confusing half working screenshots that are missing images.

See https://github.com/flutter/flutter/issues/135052

We should disable this functionality when running with impeller. Perhaps this could be reworked to use picture toImage instead of the offscreen so that it uses the "real" backend regardless of the platform it is running on.

Fixes https://github.com/flutter/flutter/issues/135052
2023-10-26 20:34:17 +00:00

4673 lines
177 KiB
C++

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <strstream>
#define FML_USED_ON_EMBEDDER
#include <algorithm>
#include <chrono>
#include <ctime>
#include <future>
#include <memory>
#include <thread>
#include <utility>
#include <vector>
#if SHELL_ENABLE_GL
#include <EGL/egl.h>
#endif // SHELL_ENABLE_GL
#include "assets/directory_asset_bundle.h"
#include "common/graphics/persistent_cache.h"
#include "flutter/flow/layers/backdrop_filter_layer.h"
#include "flutter/flow/layers/clip_rect_layer.h"
#include "flutter/flow/layers/display_list_layer.h"
#include "flutter/flow/layers/layer_raster_cache_item.h"
#include "flutter/flow/layers/platform_view_layer.h"
#include "flutter/flow/layers/transform_layer.h"
#include "flutter/fml/backtrace.h"
#include "flutter/fml/command_line.h"
#include "flutter/fml/dart/dart_converter.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/message_loop.h"
#include "flutter/fml/synchronization/count_down_latch.h"
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/runtime/dart_vm.h"
#include "flutter/shell/common/platform_view.h"
#include "flutter/shell/common/rasterizer.h"
#include "flutter/shell/common/shell_test.h"
#include "flutter/shell/common/shell_test_external_view_embedder.h"
#include "flutter/shell/common/shell_test_platform_view.h"
#include "flutter/shell/common/switches.h"
#include "flutter/shell/common/thread_host.h"
#include "flutter/shell/common/vsync_waiter_fallback.h"
#include "flutter/shell/common/vsync_waiters_test.h"
#include "flutter/shell/version/version.h"
#include "flutter/testing/mock_canvas.h"
#include "flutter/testing/testing.h"
#include "gmock/gmock.h"
#include "third_party/rapidjson/include/rapidjson/writer.h"
#include "third_party/skia/include/codec/SkCodecAnimation.h"
#include "third_party/tonic/converter/dart_converter.h"
#ifdef SHELL_ENABLE_VULKAN
#include "flutter/vulkan/vulkan_application.h" // nogncheck
#endif
// CREATE_NATIVE_ENTRY is leaky by design
// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
namespace flutter {
namespace testing {
constexpr int64_t kImplicitViewId = 0ll;
using ::testing::_;
using ::testing::Return;
namespace {
class MockPlatformViewDelegate : public PlatformView::Delegate {
MOCK_METHOD(void,
OnPlatformViewCreated,
(std::unique_ptr<Surface> surface),
(override));
MOCK_METHOD(void, OnPlatformViewDestroyed, (), (override));
MOCK_METHOD(void, OnPlatformViewScheduleFrame, (), (override));
MOCK_METHOD(void,
OnPlatformViewSetNextFrameCallback,
(const fml::closure& closure),
(override));
MOCK_METHOD(void,
OnPlatformViewSetViewportMetrics,
(int64_t view_id, const ViewportMetrics& metrics),
(override));
MOCK_METHOD(void,
OnPlatformViewDispatchPlatformMessage,
(std::unique_ptr<PlatformMessage> message),
(override));
MOCK_METHOD(void,
OnPlatformViewDispatchPointerDataPacket,
(std::unique_ptr<PointerDataPacket> packet),
(override));
MOCK_METHOD(void,
OnPlatformViewDispatchSemanticsAction,
(int32_t id, SemanticsAction action, fml::MallocMapping args),
(override));
MOCK_METHOD(void,
OnPlatformViewSetSemanticsEnabled,
(bool enabled),
(override));
MOCK_METHOD(void,
OnPlatformViewSetAccessibilityFeatures,
(int32_t flags),
(override));
MOCK_METHOD(void,
OnPlatformViewRegisterTexture,
(std::shared_ptr<Texture> texture),
(override));
MOCK_METHOD(void,
OnPlatformViewUnregisterTexture,
(int64_t texture_id),
(override));
MOCK_METHOD(void,
OnPlatformViewMarkTextureFrameAvailable,
(int64_t texture_id),
(override));
MOCK_METHOD(const Settings&,
OnPlatformViewGetSettings,
(),
(const, override));
MOCK_METHOD(void,
LoadDartDeferredLibrary,
(intptr_t loading_unit_id,
std::unique_ptr<const fml::Mapping> snapshot_data,
std::unique_ptr<const fml::Mapping> snapshot_instructions),
(override));
MOCK_METHOD(void,
LoadDartDeferredLibraryError,
(intptr_t loading_unit_id,
const std::string error_message,
bool transient),
(override));
MOCK_METHOD(void,
UpdateAssetResolverByType,
(std::unique_ptr<AssetResolver> updated_asset_resolver,
AssetResolver::AssetResolverType type),
(override));
};
class MockSurface : public Surface {
public:
MOCK_METHOD(bool, IsValid, (), (override));
MOCK_METHOD(std::unique_ptr<SurfaceFrame>,
AcquireFrame,
(const SkISize& size),
(override));
MOCK_METHOD(SkMatrix, GetRootTransformation, (), (const, override));
MOCK_METHOD(GrDirectContext*, GetContext, (), (override));
MOCK_METHOD(std::unique_ptr<GLContextResult>,
MakeRenderContextCurrent,
(),
(override));
MOCK_METHOD(bool, ClearRenderContext, (), (override));
};
class MockPlatformView : public PlatformView {
public:
MockPlatformView(MockPlatformViewDelegate& delegate,
const TaskRunners& task_runners)
: PlatformView(delegate, task_runners) {}
MOCK_METHOD(std::unique_ptr<Surface>, CreateRenderingSurface, (), (override));
MOCK_METHOD(std::shared_ptr<PlatformMessageHandler>,
GetPlatformMessageHandler,
(),
(const, override));
};
class TestPlatformView : public PlatformView {
public:
TestPlatformView(Shell& shell, const TaskRunners& task_runners)
: PlatformView(shell, task_runners) {}
MOCK_METHOD(std::unique_ptr<Surface>, CreateRenderingSurface, (), (override));
};
class MockPlatformMessageHandler : public PlatformMessageHandler {
public:
MOCK_METHOD(void,
HandlePlatformMessage,
(std::unique_ptr<PlatformMessage> message),
(override));
MOCK_METHOD(bool,
DoesHandlePlatformMessageOnPlatformThread,
(),
(const, override));
MOCK_METHOD(void,
InvokePlatformMessageResponseCallback,
(int response_id, std::unique_ptr<fml::Mapping> mapping),
(override));
MOCK_METHOD(void,
InvokePlatformMessageEmptyResponseCallback,
(int response_id),
(override));
};
class MockPlatformMessageResponse : public PlatformMessageResponse {
public:
static fml::RefPtr<MockPlatformMessageResponse> Create() {
return fml::AdoptRef(new MockPlatformMessageResponse());
}
MOCK_METHOD(void, Complete, (std::unique_ptr<fml::Mapping> data), (override));
MOCK_METHOD(void, CompleteEmpty, (), (override));
};
} // namespace
class TestAssetResolver : public AssetResolver {
public:
TestAssetResolver(bool valid, AssetResolver::AssetResolverType type)
: valid_(valid), type_(type) {}
bool IsValid() const override { return true; }
// This is used to identify if replacement was made or not.
bool IsValidAfterAssetManagerChange() const override { return valid_; }
AssetResolver::AssetResolverType GetType() const override { return type_; }
std::unique_ptr<fml::Mapping> GetAsMapping(
const std::string& asset_name) const override {
return nullptr;
}
std::vector<std::unique_ptr<fml::Mapping>> GetAsMappings(
const std::string& asset_pattern,
const std::optional<std::string>& subdir) const override {
return {};
};
private:
bool valid_;
AssetResolver::AssetResolverType type_;
};
class ThreadCheckingAssetResolver : public AssetResolver {
public:
explicit ThreadCheckingAssetResolver(
std::shared_ptr<fml::ConcurrentMessageLoop> concurrent_loop)
: concurrent_loop_(std::move(concurrent_loop)) {}
// |AssetResolver|
bool IsValid() const override { return true; }
// |AssetResolver|
bool IsValidAfterAssetManagerChange() const override { return true; }
// |AssetResolver|
AssetResolverType GetType() const {
return AssetResolverType::kApkAssetProvider;
}
// |AssetResolver|
std::unique_ptr<fml::Mapping> GetAsMapping(
const std::string& asset_name) const override {
if (asset_name == "FontManifest.json") {
// This file is loaded directly by the engine.
return nullptr;
}
mapping_requests.push_back(asset_name);
EXPECT_TRUE(concurrent_loop_->RunsTasksOnCurrentThread())
<< fml::BacktraceHere();
return nullptr;
}
mutable std::vector<std::string> mapping_requests;
private:
std::shared_ptr<fml::ConcurrentMessageLoop> concurrent_loop_;
};
static bool ValidateShell(Shell* shell) {
if (!shell) {
return false;
}
if (!shell->IsSetup()) {
return false;
}
ShellTest::PlatformViewNotifyCreated(shell);
{
fml::AutoResetWaitableEvent latch;
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(), [shell, &latch]() {
shell->GetPlatformView()->NotifyDestroyed();
latch.Signal();
});
latch.Wait();
}
return true;
}
static bool RasterizerIsTornDown(Shell* shell) {
fml::AutoResetWaitableEvent latch;
bool is_torn_down = false;
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetRasterTaskRunner(),
[shell, &latch, &is_torn_down]() {
is_torn_down = shell->GetRasterizer()->IsTornDown();
latch.Signal();
});
latch.Wait();
return is_torn_down;
}
static void ValidateDestroyPlatformView(Shell* shell) {
ASSERT_TRUE(shell != nullptr);
ASSERT_TRUE(shell->IsSetup());
ASSERT_FALSE(RasterizerIsTornDown(shell));
ShellTest::PlatformViewNotifyDestroyed(shell);
ASSERT_TRUE(RasterizerIsTornDown(shell));
}
static std::string CreateFlagsString(std::vector<const char*>& flags) {
if (flags.empty()) {
return "";
}
std::string flags_string = flags[0];
for (size_t i = 1; i < flags.size(); ++i) {
flags_string += ",";
flags_string += flags[i];
}
return flags_string;
}
static void TestDartVmFlags(std::vector<const char*>& flags) {
std::string flags_string = CreateFlagsString(flags);
const std::vector<fml::CommandLine::Option> options = {
fml::CommandLine::Option("dart-flags", flags_string)};
fml::CommandLine command_line("", options, std::vector<std::string>());
flutter::Settings settings = flutter::SettingsFromCommandLine(command_line);
EXPECT_EQ(settings.dart_flags.size(), flags.size());
for (size_t i = 0; i < flags.size(); ++i) {
EXPECT_EQ(settings.dart_flags[i], flags[i]);
}
}
static void PostSync(const fml::RefPtr<fml::TaskRunner>& task_runner,
const fml::closure& task) {
fml::AutoResetWaitableEvent latch;
fml::TaskRunner::RunNowOrPostTask(task_runner, [&latch, &task] {
task();
latch.Signal();
});
latch.Wait();
}
static sk_sp<DisplayList> MakeSizedDisplayList(int width, int height) {
DisplayListBuilder builder(SkRect::MakeXYWH(0, 0, width, height));
builder.DrawRect(SkRect::MakeXYWH(0, 0, width, height),
DlPaint(DlColor::kRed()));
return builder.Build();
}
TEST_F(ShellTest, InitializeWithInvalidThreads) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
TaskRunners task_runners("test", nullptr, nullptr, nullptr, nullptr);
auto shell = CreateShell(settings, task_runners);
ASSERT_FALSE(shell);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, InitializeWithDifferentThreads) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
std::string name_prefix = "io.flutter.test." + GetCurrentTestName() + ".";
ThreadHost thread_host(ThreadHost::ThreadHostConfig(
name_prefix, ThreadHost::Type::Platform | ThreadHost::Type::RASTER |
ThreadHost::Type::IO | ThreadHost::Type::UI));
ASSERT_EQ(thread_host.name_prefix, name_prefix);
TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
thread_host.raster_thread->GetTaskRunner(),
thread_host.ui_thread->GetTaskRunner(),
thread_host.io_thread->GetTaskRunner());
auto shell = CreateShell(settings, task_runners);
ASSERT_TRUE(ValidateShell(shell.get()));
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, InitializeWithSingleThread) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform);
auto task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto shell = CreateShell(settings, task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, InitializeWithSingleThreadWhichIsTheCallingThread) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto shell = CreateShell(settings, task_runners);
ASSERT_TRUE(ValidateShell(shell.get()));
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest,
InitializeWithMultipleThreadButCallingThreadAsPlatformThread) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host(
"io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::RASTER | ThreadHost::Type::IO | ThreadHost::Type::UI);
fml::MessageLoop::EnsureInitializedForCurrentThread();
TaskRunners task_runners("test",
fml::MessageLoop::GetCurrent().GetTaskRunner(),
thread_host.raster_thread->GetTaskRunner(),
thread_host.ui_thread->GetTaskRunner(),
thread_host.io_thread->GetTaskRunner());
auto shell = Shell::Create(
flutter::PlatformData(), task_runners, settings,
[](Shell& shell) {
// This is unused in the platform view as we are not using the simulated
// vsync mechanism. We should have better DI in the tests.
const auto vsync_clock = std::make_shared<ShellTestVsyncClock>();
return ShellTestPlatformView::Create(
shell, shell.GetTaskRunners(), vsync_clock,
[task_runners = shell.GetTaskRunners()]() {
return static_cast<std::unique_ptr<VsyncWaiter>>(
std::make_unique<VsyncWaiterFallback>(task_runners));
},
ShellTestPlatformView::BackendType::kDefaultBackend, nullptr,
shell.GetIsGpuDisabledSyncSwitch());
},
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
ASSERT_TRUE(ValidateShell(shell.get()));
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, InitializeWithDisabledGpu) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform);
auto task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto shell = CreateShell({
.settings = settings,
.task_runners = task_runners,
.is_gpu_disabled = true,
});
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));
bool is_disabled = false;
shell->GetIsGpuDisabledSyncSwitch()->Execute(
fml::SyncSwitch::Handlers().SetIfTrue([&] { is_disabled = true; }));
ASSERT_TRUE(is_disabled);
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, InitializeWithGPUAndPlatformThreadsTheSame) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host(
"io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform | ThreadHost::Type::IO | ThreadHost::Type::UI);
TaskRunners task_runners(
"test",
thread_host.platform_thread->GetTaskRunner(), // platform
thread_host.platform_thread->GetTaskRunner(), // raster
thread_host.ui_thread->GetTaskRunner(), // ui
thread_host.io_thread->GetTaskRunner() // io
);
auto shell = CreateShell(settings, task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, FixturesAreFunctional) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
configuration.SetEntrypoint("fixturesAreFunctionalMain");
fml::AutoResetWaitableEvent main_latch;
AddNativeCallback(
"SayHiFromFixturesAreFunctionalMain",
CREATE_NATIVE_ENTRY([&main_latch](auto args) { main_latch.Signal(); }));
RunEngine(shell.get(), std::move(configuration));
main_latch.Wait();
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
DestroyShell(std::move(shell));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, SecondaryIsolateBindingsAreSetupViaShellSettings) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
configuration.SetEntrypoint("testCanLaunchSecondaryIsolate");
fml::CountDownLatch latch(2);
AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) {
latch.CountDown();
}));
RunEngine(shell.get(), std::move(configuration));
latch.Wait();
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
DestroyShell(std::move(shell));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, LastEntrypoint) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
std::string entry_point = "fixturesAreFunctionalMain";
configuration.SetEntrypoint(entry_point);
fml::AutoResetWaitableEvent main_latch;
std::string last_entry_point;
AddNativeCallback(
"SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) {
last_entry_point = shell->GetEngine()->GetLastEntrypoint();
main_latch.Signal();
}));
RunEngine(shell.get(), std::move(configuration));
main_latch.Wait();
EXPECT_EQ(entry_point, last_entry_point);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
DestroyShell(std::move(shell));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, LastEntrypointArgs) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
std::string entry_point = "fixturesAreFunctionalMain";
std::vector<std::string> entry_point_args = {"arg1"};
configuration.SetEntrypoint(entry_point);
configuration.SetEntrypointArgs(entry_point_args);
fml::AutoResetWaitableEvent main_latch;
std::vector<std::string> last_entry_point_args;
AddNativeCallback(
"SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) {
last_entry_point_args = shell->GetEngine()->GetLastEntrypointArgs();
main_latch.Signal();
}));
RunEngine(shell.get(), std::move(configuration));
main_latch.Wait();
#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
EXPECT_EQ(last_entry_point_args, entry_point_args);
#else
ASSERT_TRUE(last_entry_point_args.empty());
#endif
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
DestroyShell(std::move(shell));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, DisallowedDartVMFlag) {
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "This test flakes on Fuchsia. https://fxbug.dev/110006 ";
#else
// Run this test in a thread-safe manner, otherwise gtest will complain.
::testing::FLAGS_gtest_death_test_style = "threadsafe";
const std::vector<fml::CommandLine::Option> options = {
fml::CommandLine::Option("dart-flags", "--verify_after_gc")};
fml::CommandLine command_line("", options, std::vector<std::string>());
// Upon encountering a disallowed Dart flag the process terminates.
const char* expected =
"Encountered disallowed Dart VM flag: --verify_after_gc";
ASSERT_DEATH(flutter::SettingsFromCommandLine(command_line), expected);
#endif // OS_FUCHSIA
}
TEST_F(ShellTest, AllowedDartVMFlag) {
std::vector<const char*> flags = {
"--enable-isolate-groups",
"--no-enable-isolate-groups",
};
#if !FLUTTER_RELEASE
flags.push_back("--max_profile_depth 1");
flags.push_back("--random_seed 42");
flags.push_back("--max_subtype_cache_entries=22");
if (!DartVM::IsRunningPrecompiledCode()) {
flags.push_back("--enable_mirrors");
}
#endif
TestDartVmFlags(flags);
}
TEST_F(ShellTest, NoNeedToReportTimingsByDefault) {
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
ASSERT_FALSE(GetNeedsReportTimings(shell.get()));
// This assertion may or may not be the direct result of needs_report_timings_
// being false. The count could be 0 simply because we just cleared
// unreported timings by reporting them. Hence this can't replace the
// ASSERT_FALSE(GetNeedsReportTimings(shell.get())) check. We added
// this assertion for an additional confidence that we're not pushing
// back to unreported timings unnecessarily.
//
// Conversely, do not assert UnreportedTimingsCount(shell.get()) to be
// positive in any tests. Otherwise those tests will be flaky as the clearing
// of unreported timings is unpredictive.
ASSERT_EQ(UnreportedTimingsCount(shell.get()), 0);
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, NeedsReportTimingsIsSetWithCallback) {
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("dummyReportTimingsMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
ASSERT_TRUE(GetNeedsReportTimings(shell.get()));
DestroyShell(std::move(shell));
}
static void CheckFrameTimings(const std::vector<FrameTiming>& timings,
fml::TimePoint start,
fml::TimePoint finish) {
fml::TimePoint last_frame_start;
for (size_t i = 0; i < timings.size(); i += 1) {
// Ensure that timings are sorted.
ASSERT_TRUE(timings[i].Get(FrameTiming::kPhases[0]) >= last_frame_start);
last_frame_start = timings[i].Get(FrameTiming::kPhases[0]);
fml::TimePoint last_phase_time;
for (auto phase : FrameTiming::kPhases) {
// raster finish wall time doesn't use the same clock base
// as rest of the frame timings.
if (phase == FrameTiming::kRasterFinishWallTime) {
continue;
}
ASSERT_TRUE(timings[i].Get(phase) >= start);
ASSERT_TRUE(timings[i].Get(phase) <= finish);
// phases should have weakly increasing time points
ASSERT_TRUE(last_phase_time <= timings[i].Get(phase));
last_phase_time = timings[i].Get(phase);
}
}
}
TEST_F(ShellTest, ReportTimingsIsCalled) {
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
// We MUST put |start| after |CreateShell| because the clock source will be
// reset through |TimePoint::SetClockSource()| in
// |DartVMInitializer::Initialize()| within |CreateShell()|.
fml::TimePoint start = fml::TimePoint::Now();
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
configuration.SetEntrypoint("reportTimingsMain");
fml::AutoResetWaitableEvent reportLatch;
std::vector<int64_t> timestamps;
auto nativeTimingCallback = [&reportLatch,
&timestamps](Dart_NativeArguments args) {
Dart_Handle exception = nullptr;
ASSERT_EQ(timestamps.size(), 0ul);
timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
args, 0, exception);
reportLatch.Signal();
};
AddNativeCallback("NativeReportTimingsCallback",
CREATE_NATIVE_ENTRY(nativeTimingCallback));
RunEngine(shell.get(), std::move(configuration));
// Pump many frames so we can trigger the report quickly instead of waiting
// for the 1 second threshold.
for (int i = 0; i < 200; i += 1) {
PumpOneFrame(shell.get());
}
reportLatch.Wait();
DestroyShell(std::move(shell));
fml::TimePoint finish = fml::TimePoint::Now();
ASSERT_TRUE(!timestamps.empty());
ASSERT_TRUE(timestamps.size() % FrameTiming::kCount == 0);
std::vector<FrameTiming> timings(timestamps.size() / FrameTiming::kCount);
for (size_t i = 0; i * FrameTiming::kCount < timestamps.size(); i += 1) {
for (auto phase : FrameTiming::kPhases) {
timings[i].Set(
phase,
fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMicroseconds(
timestamps[i * FrameTiming::kCount + phase])));
}
}
CheckFrameTimings(timings, start, finish);
}
TEST_F(ShellTest, FrameRasterizedCallbackIsCalled) {
auto settings = CreateSettingsForFixture();
FrameTiming timing;
fml::AutoResetWaitableEvent timingLatch;
settings.frame_rasterized_callback = [&timing,
&timingLatch](const FrameTiming& t) {
timing = t;
timingLatch.Signal();
};
std::unique_ptr<Shell> shell = CreateShell(settings);
// Wait to make |start| bigger than zero
using namespace std::chrono_literals;
std::this_thread::sleep_for(1ms);
// We MUST put |start| after |CreateShell()| because the clock source will be
// reset through |TimePoint::SetClockSource()| in
// |DartVMInitializer::Initialize()| within |CreateShell()|.
fml::TimePoint start = fml::TimePoint::Now();
for (auto phase : FrameTiming::kPhases) {
timing.Set(phase, fml::TimePoint());
// Check that the time points are initially smaller than start, so
// CheckFrameTimings will fail if they're not properly set later.
ASSERT_TRUE(timing.Get(phase) < start);
}
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("onBeginFrameMain");
int64_t frame_target_time;
auto nativeOnBeginFrame = [&frame_target_time](Dart_NativeArguments args) {
Dart_Handle exception = nullptr;
frame_target_time =
tonic::DartConverter<int64_t>::FromArguments(args, 0, exception);
};
AddNativeCallback("NativeOnBeginFrame",
CREATE_NATIVE_ENTRY(nativeOnBeginFrame));
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
// Check that timing is properly set. This implies that
// settings.frame_rasterized_callback is called.
timingLatch.Wait();
fml::TimePoint finish = fml::TimePoint::Now();
std::vector<FrameTiming> timings = {timing};
CheckFrameTimings(timings, start, finish);
// Check that onBeginFrame, which is the frame_target_time, is after
// FrameTiming's build start
int64_t build_start =
timing.Get(FrameTiming::kBuildStart).ToEpochDelta().ToMicroseconds();
ASSERT_GT(frame_target_time, build_start);
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, ExternalEmbedderNoThreadMerger) {
auto settings = CreateSettingsForFixture();
fml::AutoResetWaitableEvent end_frame_latch;
bool end_frame_called = false;
auto end_frame_callback =
[&](bool should_resubmit_frame,
const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
ASSERT_TRUE(raster_thread_merger.get() == nullptr);
ASSERT_FALSE(should_resubmit_frame);
end_frame_called = true;
end_frame_latch.Signal();
};
auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
end_frame_callback, PostPrerollResult::kResubmitFrame, false);
auto shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
auto display_list_layer = std::make_shared<DisplayListLayer>(
SkPoint::Make(10, 10), MakeSizedDisplayList(80, 80), false, false);
root->Add(display_list_layer);
};
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
end_frame_latch.Wait();
ASSERT_TRUE(end_frame_called);
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, PushBackdropFilterToVisitedPlatformViews) {
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
"https://github.com/flutter/flutter/issues/59816 ";
#else
auto settings = CreateSettingsForFixture();
std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
fml::AutoResetWaitableEvent end_frame_latch;
bool end_frame_called = false;
std::vector<int64_t> visited_platform_views;
MutatorsStack stack_50;
MutatorsStack stack_75;
auto end_frame_callback =
[&](bool should_resubmit_frame,
const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
if (end_frame_called) {
return;
}
ASSERT_TRUE(raster_thread_merger.get() == nullptr);
ASSERT_FALSE(should_resubmit_frame);
end_frame_called = true;
visited_platform_views =
external_view_embedder->GetVisitedPlatformViews();
stack_50 = external_view_embedder->GetStack(50);
stack_75 = external_view_embedder->GetStack(75);
end_frame_latch.Signal();
};
external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
end_frame_callback, PostPrerollResult::kResubmitFrame, false);
auto shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
auto platform_view_layer = std::make_shared<PlatformViewLayer>(
SkPoint::Make(10, 10), SkSize::Make(10, 10), 50);
root->Add(platform_view_layer);
auto transform_layer =
std::make_shared<TransformLayer>(SkMatrix::Translate(1, 1));
root->Add(transform_layer);
auto clip_rect_layer = std::make_shared<ClipRectLayer>(
SkRect::MakeLTRB(0, 0, 30, 30), Clip::hardEdge);
transform_layer->Add(clip_rect_layer);
auto filter = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
auto backdrop_filter_layer =
std::make_shared<BackdropFilterLayer>(filter, DlBlendMode::kSrcOver);
clip_rect_layer->Add(backdrop_filter_layer);
auto platform_view_layer2 = std::make_shared<PlatformViewLayer>(
SkPoint::Make(10, 10), SkSize::Make(10, 10), 75);
backdrop_filter_layer->Add(platform_view_layer2);
};
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
end_frame_latch.Wait();
ASSERT_EQ(visited_platform_views, (std::vector<int64_t>{50, 75}));
ASSERT_TRUE(stack_75.is_empty());
ASSERT_FALSE(stack_50.is_empty());
auto filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp);
auto mutator = *stack_50.Begin();
ASSERT_EQ(mutator->GetType(), MutatorType::kBackdropFilter);
ASSERT_EQ(mutator->GetFilterMutation().GetFilter(), filter);
// Make sure the filterRect is in global coordinates (contains the (1,1)
// translation).
ASSERT_EQ(mutator->GetFilterMutation().GetFilterRect(),
SkRect::MakeLTRB(1, 1, 31, 31));
DestroyShell(std::move(shell));
#endif // OS_FUCHSIA
}
// TODO(https://github.com/flutter/flutter/issues/59816): Enable on fuchsia.
TEST_F(ShellTest,
ExternalEmbedderEndFrameIsCalledWhenPostPrerollResultIsResubmit) {
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
"https://github.com/flutter/flutter/issues/59816 ";
#else
auto settings = CreateSettingsForFixture();
fml::AutoResetWaitableEvent end_frame_latch;
bool end_frame_called = false;
auto end_frame_callback =
[&](bool should_resubmit_frame,
const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
ASSERT_TRUE(raster_thread_merger.get() != nullptr);
ASSERT_TRUE(should_resubmit_frame);
end_frame_called = true;
end_frame_latch.Signal();
};
auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
end_frame_callback, PostPrerollResult::kResubmitFrame, true);
auto shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
auto display_list_layer = std::make_shared<DisplayListLayer>(
SkPoint::Make(10, 10), MakeSizedDisplayList(80, 80), false, false);
root->Add(display_list_layer);
};
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
end_frame_latch.Wait();
ASSERT_TRUE(end_frame_called);
DestroyShell(std::move(shell));
#endif // OS_FUCHSIA
}
TEST_F(ShellTest, OnPlatformViewDestroyDisablesThreadMerger) {
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
"https://github.com/flutter/flutter/issues/59816 ";
#else
auto settings = CreateSettingsForFixture();
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger;
auto end_frame_callback =
[&](bool should_resubmit_frame,
fml::RefPtr<fml::RasterThreadMerger> thread_merger) {
raster_thread_merger = std::move(thread_merger);
};
auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
end_frame_callback, PostPrerollResult::kSuccess, true);
auto shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
auto display_list_layer = std::make_shared<DisplayListLayer>(
SkPoint::Make(10, 10), MakeSizedDisplayList(80, 80), false, false);
root->Add(display_list_layer);
};
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
auto result = shell->WaitForFirstFrame(fml::TimeDelta::Max());
// Wait for the rasterizer to process the frame. WaitForFirstFrame only waits
// for the Animator, but end_frame_callback is called by the Rasterizer.
PostSync(shell->GetTaskRunners().GetRasterTaskRunner(), [] {});
ASSERT_TRUE(result.ok()) << "Result: " << static_cast<int>(result.code())
<< ": " << result.message();
ASSERT_TRUE(raster_thread_merger->IsEnabled());
ValidateDestroyPlatformView(shell.get());
ASSERT_TRUE(raster_thread_merger->IsEnabled());
// Validate the platform view can be recreated and destroyed again
ValidateShell(shell.get());
ASSERT_TRUE(raster_thread_merger->IsEnabled());
DestroyShell(std::move(shell));
#endif // OS_FUCHSIA
}
TEST_F(ShellTest, OnPlatformViewDestroyAfterMergingThreads) {
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
"https://github.com/flutter/flutter/issues/59816 ";
#else
const int ThreadMergingLease = 10;
auto settings = CreateSettingsForFixture();
fml::AutoResetWaitableEvent end_frame_latch;
std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
auto end_frame_callback =
[&](bool should_resubmit_frame,
const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
if (should_resubmit_frame && !raster_thread_merger->IsMerged()) {
raster_thread_merger->MergeWithLease(ThreadMergingLease);
ASSERT_TRUE(raster_thread_merger->IsMerged());
external_view_embedder->UpdatePostPrerollResult(
PostPrerollResult::kSuccess);
}
end_frame_latch.Signal();
};
external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
end_frame_callback, PostPrerollResult::kSuccess, true);
// Set resubmit once to trigger thread merging.
external_view_embedder->UpdatePostPrerollResult(
PostPrerollResult::kResubmitFrame);
auto shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
auto display_list_layer = std::make_shared<DisplayListLayer>(
SkPoint::Make(10, 10), MakeSizedDisplayList(80, 80), false, false);
root->Add(display_list_layer);
};
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
// Pump one frame to trigger thread merging.
end_frame_latch.Wait();
// Pump another frame to ensure threads are merged and a regular layer tree is
// submitted.
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
// Threads are merged here. PlatformViewNotifyDestroy should be executed
// successfully.
ASSERT_TRUE(fml::TaskRunnerChecker::RunsOnTheSameThread(
shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
ValidateDestroyPlatformView(shell.get());
// Ensure threads are unmerged after platform view destroy
ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
// Validate the platform view can be recreated and destroyed again
ValidateShell(shell.get());
DestroyShell(std::move(shell));
#endif // OS_FUCHSIA
}
TEST_F(ShellTest, OnPlatformViewDestroyWhenThreadsAreMerging) {
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
"https://github.com/flutter/flutter/issues/59816 ";
#else
const int kThreadMergingLease = 10;
auto settings = CreateSettingsForFixture();
fml::AutoResetWaitableEvent end_frame_latch;
auto end_frame_callback =
[&](bool should_resubmit_frame,
const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
if (should_resubmit_frame && !raster_thread_merger->IsMerged()) {
raster_thread_merger->MergeWithLease(kThreadMergingLease);
}
end_frame_latch.Signal();
};
// Start with a regular layer tree with `PostPrerollResult::kSuccess` so we
// can later check if the rasterizer is tore down using
// |ValidateDestroyPlatformView|
auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
end_frame_callback, PostPrerollResult::kSuccess, true);
auto shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
auto display_list_layer = std::make_shared<DisplayListLayer>(
SkPoint::Make(10, 10), MakeSizedDisplayList(80, 80), false, false);
root->Add(display_list_layer);
};
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
// Pump one frame and threads aren't merged
end_frame_latch.Wait();
ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
// Pump a frame with `PostPrerollResult::kResubmitFrame` to start merging
// threads
external_view_embedder->UpdatePostPrerollResult(
PostPrerollResult::kResubmitFrame);
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
// Now destroy the platform view immediately.
// Two things can happen here:
// 1. Threads haven't merged. 2. Threads has already merged.
// |Shell:OnPlatformViewDestroy| should be able to handle both cases.
ValidateDestroyPlatformView(shell.get());
// Ensure threads are unmerged after platform view destroy
ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
// Validate the platform view can be recreated and destroyed again
ValidateShell(shell.get());
DestroyShell(std::move(shell));
#endif // OS_FUCHSIA
}
TEST_F(ShellTest,
OnPlatformViewDestroyWithThreadMergerWhileThreadsAreUnmerged) {
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
"https://github.com/flutter/flutter/issues/59816 ";
#else
auto settings = CreateSettingsForFixture();
fml::AutoResetWaitableEvent end_frame_latch;
auto end_frame_callback =
[&](bool should_resubmit_frame,
const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
end_frame_latch.Signal();
};
auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
end_frame_callback, PostPrerollResult::kSuccess, true);
auto shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
auto display_list_layer = std::make_shared<DisplayListLayer>(
SkPoint::Make(10, 10), MakeSizedDisplayList(80, 80), false, false);
root->Add(display_list_layer);
};
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
end_frame_latch.Wait();
// Threads should not be merged.
ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
ValidateDestroyPlatformView(shell.get());
// Ensure threads are unmerged after platform view destroy
ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
// Validate the platform view can be recreated and destroyed again
ValidateShell(shell.get());
DestroyShell(std::move(shell));
#endif // OS_FUCHSIA
}
TEST_F(ShellTest, OnPlatformViewDestroyWithoutRasterThreadMerger) {
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
auto display_list_layer = std::make_shared<DisplayListLayer>(
SkPoint::Make(10, 10), MakeSizedDisplayList(80, 80), false, false);
root->Add(display_list_layer);
};
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
// Threads should not be merged.
ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
ValidateDestroyPlatformView(shell.get());
// Ensure threads are unmerged after platform view destroy
ASSERT_FALSE(fml::TaskRunnerChecker::RunsOnTheSameThread(
shell->GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId(),
shell->GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId()));
// Validate the platform view can be recreated and destroyed again
ValidateShell(shell.get());
DestroyShell(std::move(shell));
}
// TODO(https://github.com/flutter/flutter/issues/59816): Enable on fuchsia.
TEST_F(ShellTest, OnPlatformViewDestroyWithStaticThreadMerging) {
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
"https://github.com/flutter/flutter/issues/59816 ";
#else
auto settings = CreateSettingsForFixture();
fml::AutoResetWaitableEvent end_frame_latch;
auto end_frame_callback =
[&](bool should_resubmit_frame,
const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
end_frame_latch.Signal();
};
auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
end_frame_callback, PostPrerollResult::kSuccess, true);
ThreadHost thread_host(
"io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform | ThreadHost::Type::IO | ThreadHost::Type::UI);
TaskRunners task_runners(
"test",
thread_host.platform_thread->GetTaskRunner(), // platform
thread_host.platform_thread->GetTaskRunner(), // raster
thread_host.ui_thread->GetTaskRunner(), // ui
thread_host.io_thread->GetTaskRunner() // io
);
auto shell = CreateShell({
.settings = settings,
.task_runners = task_runners,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
auto display_list_layer = std::make_shared<DisplayListLayer>(
SkPoint::Make(10, 10), MakeSizedDisplayList(80, 80), false, false);
root->Add(display_list_layer);
};
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
end_frame_latch.Wait();
ValidateDestroyPlatformView(shell.get());
// Validate the platform view can be recreated and destroyed again
ValidateShell(shell.get());
DestroyShell(std::move(shell), task_runners);
#endif // OS_FUCHSIA
}
TEST_F(ShellTest, GetUsedThisFrameShouldBeSetBeforeEndFrame) {
auto settings = CreateSettingsForFixture();
fml::AutoResetWaitableEvent end_frame_latch;
std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
bool used_this_frame = true;
auto end_frame_callback =
[&](bool should_resubmit_frame,
const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
// We expect `used_this_frame` to be false.
used_this_frame = external_view_embedder->GetUsedThisFrame();
end_frame_latch.Signal();
};
external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
end_frame_callback, PostPrerollResult::kSuccess, true);
auto shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
auto display_list_layer = std::make_shared<DisplayListLayer>(
SkPoint::Make(10, 10), MakeSizedDisplayList(80, 80), false, false);
root->Add(display_list_layer);
};
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
end_frame_latch.Wait();
ASSERT_FALSE(used_this_frame);
// Validate the platform view can be recreated and destroyed again
ValidateShell(shell.get());
DestroyShell(std::move(shell));
}
// TODO(https://github.com/flutter/flutter/issues/66056): Deflake on all other
// platforms
TEST_F(ShellTest, DISABLED_SkipAndSubmitFrame) {
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "RasterThreadMerger flakes on Fuchsia. "
"https://github.com/flutter/flutter/issues/59816 ";
#else
auto settings = CreateSettingsForFixture();
fml::AutoResetWaitableEvent end_frame_latch;
std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
auto end_frame_callback =
[&](bool should_resubmit_frame,
const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
if (should_resubmit_frame && !raster_thread_merger->IsMerged()) {
raster_thread_merger->MergeWithLease(10);
external_view_embedder->UpdatePostPrerollResult(
PostPrerollResult::kSuccess);
}
end_frame_latch.Signal();
};
external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
end_frame_callback, PostPrerollResult::kSkipAndRetryFrame, true);
auto shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
PumpOneFrame(shell.get());
// `EndFrame` changed the post preroll result to `kSuccess`.
end_frame_latch.Wait();
// Let the resubmitted frame to run and `GetSubmittedFrameCount` should be
// called.
end_frame_latch.Wait();
// 2 frames are submitted because `kSkipAndRetryFrame`, but only the 2nd frame
// should be submitted with `external_view_embedder`, hence the below check.
ASSERT_EQ(1, external_view_embedder->GetSubmittedFrameCount());
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
#endif // OS_FUCHSIA
}
TEST(SettingsTest, FrameTimingSetsAndGetsProperly) {
// Ensure that all phases are in kPhases.
ASSERT_EQ(sizeof(FrameTiming::kPhases),
FrameTiming::kCount * sizeof(FrameTiming::Phase));
int lastPhaseIndex = -1;
FrameTiming timing;
for (auto phase : FrameTiming::kPhases) {
ASSERT_TRUE(phase > lastPhaseIndex); // Ensure that kPhases are in order.
lastPhaseIndex = phase;
auto fake_time =
fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMicroseconds(phase));
timing.Set(phase, fake_time);
ASSERT_TRUE(timing.Get(phase) == fake_time);
}
}
TEST_F(ShellTest, ReportTimingsIsCalledImmediatelyAfterTheFirstFrame) {
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
configuration.SetEntrypoint("reportTimingsMain");
fml::AutoResetWaitableEvent reportLatch;
std::vector<int64_t> timestamps;
auto nativeTimingCallback = [&reportLatch,
&timestamps](Dart_NativeArguments args) {
Dart_Handle exception = nullptr;
ASSERT_EQ(timestamps.size(), 0ul);
timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
args, 0, exception);
reportLatch.Signal();
};
AddNativeCallback("NativeReportTimingsCallback",
CREATE_NATIVE_ENTRY(nativeTimingCallback));
ASSERT_TRUE(configuration.IsValid());
RunEngine(shell.get(), std::move(configuration));
for (int i = 0; i < 10; i += 1) {
PumpOneFrame(shell.get());
}
reportLatch.Wait();
DestroyShell(std::move(shell));
// Check for the immediate callback of the first frame that doesn't wait for
// the other 9 frames to be rasterized.
ASSERT_EQ(timestamps.size(), FrameTiming::kCount);
}
TEST_F(ShellTest, WaitForFirstFrame) {
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::Max());
ASSERT_TRUE(result.ok());
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, WaitForFirstFrameZeroSizeFrame) {
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get(), ViewContent::DummyView({1.0, 0.0, 0.0, 22, 0}));
fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::Zero());
EXPECT_FALSE(result.ok());
EXPECT_EQ(result.message(), "timeout");
EXPECT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded);
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, WaitForFirstFrameTimeout) {
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::Zero());
ASSERT_FALSE(result.ok());
ASSERT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded);
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, WaitForFirstFrameMultiple) {
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::Max());
ASSERT_TRUE(result.ok());
for (int i = 0; i < 100; ++i) {
result = shell->WaitForFirstFrame(fml::TimeDelta::Zero());
ASSERT_TRUE(result.ok());
}
DestroyShell(std::move(shell));
}
/// Makes sure that WaitForFirstFrame works if we rendered a frame with the
/// single-thread setup.
TEST_F(ShellTest, WaitForFirstFrameInlined) {
Settings settings = CreateSettingsForFixture();
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
fml::AutoResetWaitableEvent event;
task_runner->PostTask([&shell, &event] {
fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::Max());
ASSERT_FALSE(result.ok());
ASSERT_EQ(result.code(), fml::StatusCode::kFailedPrecondition);
event.Signal();
});
ASSERT_FALSE(event.WaitWithTimeout(fml::TimeDelta::Max()));
DestroyShell(std::move(shell), task_runners);
}
static size_t GetRasterizerResourceCacheBytesSync(const Shell& shell) {
size_t bytes = 0;
fml::AutoResetWaitableEvent latch;
fml::TaskRunner::RunNowOrPostTask(
shell.GetTaskRunners().GetRasterTaskRunner(), [&]() {
if (auto rasterizer = shell.GetRasterizer()) {
bytes = rasterizer->GetResourceCacheMaxBytes().value_or(0U);
}
latch.Signal();
});
latch.Wait();
return bytes;
}
TEST_F(ShellTest, MultipleFluttersSetResourceCacheBytes) {
TaskRunners task_runners = GetTaskRunnersForFixture();
auto settings = CreateSettingsForFixture();
settings.resource_cache_max_bytes_threshold = 4000000U;
GrMockOptions main_context_options;
sk_sp<GrDirectContext> main_context =
GrDirectContext::MakeMock(&main_context_options);
Shell::CreateCallback<PlatformView> platform_view_create_callback =
[task_runners, main_context](flutter::Shell& shell) {
auto result = std::make_unique<TestPlatformView>(shell, task_runners);
ON_CALL(*result, CreateRenderingSurface())
.WillByDefault(::testing::Invoke([main_context] {
auto surface = std::make_unique<MockSurface>();
ON_CALL(*surface, GetContext())
.WillByDefault(Return(main_context.get()));
ON_CALL(*surface, IsValid()).WillByDefault(Return(true));
ON_CALL(*surface, MakeRenderContextCurrent())
.WillByDefault(::testing::Invoke([] {
return std::make_unique<GLContextDefaultResult>(true);
}));
return surface;
}));
return result;
};
auto shell = CreateShell({
.settings = settings,
.task_runners = task_runners,
.platform_view_create_callback = platform_view_create_callback,
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
{1.0, 100, 100, 22, 0});
});
// first cache bytes
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(480000U));
auto shell_spawn_callback = [&]() {
std::unique_ptr<Shell> spawn;
PostSync(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[this, &spawn, &spawner = shell, platform_view_create_callback]() {
auto configuration =
RunConfiguration::InferFromSettings(CreateSettingsForFixture());
configuration.SetEntrypoint("emptyMain");
spawn = spawner->Spawn(
std::move(configuration), "", platform_view_create_callback,
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
ASSERT_NE(nullptr, spawn.get());
ASSERT_TRUE(ValidateShell(spawn.get()));
});
return spawn;
};
std::unique_ptr<Shell> second_shell = shell_spawn_callback();
PlatformViewNotifyCreated(second_shell.get());
PostSync(second_shell->GetTaskRunners().GetPlatformTaskRunner(),
[&second_shell]() {
second_shell->GetPlatformView()->SetViewportMetrics(
kImplicitViewId, {1.0, 100, 100, 22, 0});
});
// first cache bytes + second cache bytes
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(960000U));
PostSync(second_shell->GetTaskRunners().GetPlatformTaskRunner(),
[&second_shell]() {
second_shell->GetPlatformView()->SetViewportMetrics(
kImplicitViewId, {1.0, 100, 300, 22, 0});
});
// first cache bytes + second cache bytes
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(1920000U));
std::unique_ptr<Shell> third_shell = shell_spawn_callback();
PlatformViewNotifyCreated(third_shell.get());
PostSync(third_shell->GetTaskRunners().GetPlatformTaskRunner(),
[&third_shell]() {
third_shell->GetPlatformView()->SetViewportMetrics(
kImplicitViewId, {1.0, 400, 100, 22, 0});
});
// first cache bytes + second cache bytes + third cache bytes
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(3840000U));
PostSync(third_shell->GetTaskRunners().GetPlatformTaskRunner(),
[&third_shell]() {
third_shell->GetPlatformView()->SetViewportMetrics(
kImplicitViewId, {1.0, 800, 100, 22, 0});
});
// max bytes threshold
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(4000000U));
DestroyShell(std::move(third_shell), task_runners);
// max bytes threshold
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(4000000U));
PostSync(second_shell->GetTaskRunners().GetPlatformTaskRunner(),
[&second_shell]() {
second_shell->GetPlatformView()->SetViewportMetrics(
kImplicitViewId, {1.0, 100, 100, 22, 0});
});
// first cache bytes + second cache bytes
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(960000U));
DestroyShell(std::move(second_shell), task_runners);
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, SetResourceCacheSize) {
Settings settings = CreateSettingsForFixture();
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
// The Vulkan and GL backends set different default values for the resource
// cache size. The default backend (specified by the default param of
// `CreateShell` in this test) will only resolve to Vulkan (in
// `ShellTestPlatformView::Create`) if GL is disabled. This situation arises
// when targeting the Fuchsia Emulator.
#if defined(SHELL_ENABLE_VULKAN) && !defined(SHELL_ENABLE_GL)
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
vulkan::kGrCacheMaxByteSize);
#elif defined(SHELL_ENABLE_METAL)
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(256 * (1 << 20)));
#else
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(24 * (1 << 20)));
#endif
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
{1.0, 400, 200, 22, 0});
});
PumpOneFrame(shell.get());
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 3840000U);
std::string request_json = R"json({
"method": "Skia.setResourceCacheMaxBytes",
"args": 10000
})json";
auto data =
fml::MallocMapping::Copy(request_json.c_str(), request_json.length());
auto platform_message = std::make_unique<PlatformMessage>(
"flutter/skia", std::move(data), nullptr);
SendEnginePlatformMessage(shell.get(), std::move(platform_message));
PumpOneFrame(shell.get());
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U);
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
{1.0, 800, 400, 22, 0});
});
PumpOneFrame(shell.get());
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U);
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, SetResourceCacheSizeEarly) {
Settings settings = CreateSettingsForFixture();
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
{1.0, 400, 200, 22, 0});
});
PumpOneFrame(shell.get());
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(3840000U));
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, SetResourceCacheSizeNotifiesDart) {
Settings settings = CreateSettingsForFixture();
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
{1.0, 400, 200, 22, 0});
});
PumpOneFrame(shell.get());
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("testSkiaResourceCacheSendsResponse");
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(3840000U));
fml::AutoResetWaitableEvent latch;
AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) {
latch.Signal();
}));
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
latch.Wait();
EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
static_cast<size_t>(10000U));
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, CanCreateImagefromDecompressedBytes) {
Settings settings = CreateSettingsForFixture();
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("canCreateImageFromDecompressedData");
fml::AutoResetWaitableEvent latch;
AddNativeCallback("NotifyWidthHeight",
CREATE_NATIVE_ENTRY([&latch](auto args) {
auto width = tonic::DartConverter<int>::FromDart(
Dart_GetNativeArgument(args, 0));
auto height = tonic::DartConverter<int>::FromDart(
Dart_GetNativeArgument(args, 1));
ASSERT_EQ(width, 10);
ASSERT_EQ(height, 10);
latch.Signal();
}));
RunEngine(shell.get(), std::move(configuration));
latch.Wait();
DestroyShell(std::move(shell), task_runners);
}
class MockTexture : public Texture {
public:
MockTexture(int64_t textureId,
std::shared_ptr<fml::AutoResetWaitableEvent> latch)
: Texture(textureId), latch_(std::move(latch)) {}
~MockTexture() override = default;
// Called from raster thread.
void Paint(PaintContext& context,
const SkRect& bounds,
bool freeze,
const DlImageSampling) override {}
void OnGrContextCreated() override {}
void OnGrContextDestroyed() override {}
void MarkNewFrameAvailable() override {
frames_available_++;
latch_->Signal();
}
void OnTextureUnregistered() override {
unregistered_ = true;
latch_->Signal();
}
bool unregistered() { return unregistered_; }
int frames_available() { return frames_available_; }
private:
bool unregistered_ = false;
int frames_available_ = 0;
std::shared_ptr<fml::AutoResetWaitableEvent> latch_;
};
TEST_F(ShellTest, TextureFrameMarkedAvailableAndUnregister) {
Settings settings = CreateSettingsForFixture();
auto configuration = RunConfiguration::InferFromSettings(settings);
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
ASSERT_TRUE(ValidateShell(shell.get()));
PlatformViewNotifyCreated(shell.get());
RunEngine(shell.get(), std::move(configuration));
std::shared_ptr<fml::AutoResetWaitableEvent> latch =
std::make_shared<fml::AutoResetWaitableEvent>();
std::shared_ptr<MockTexture> mockTexture =
std::make_shared<MockTexture>(0, latch);
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetRasterTaskRunner(), [&]() {
shell->GetPlatformView()->RegisterTexture(mockTexture);
shell->GetPlatformView()->MarkTextureFrameAvailable(0);
});
latch->Wait();
EXPECT_EQ(mockTexture->frames_available(), 1);
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetRasterTaskRunner(),
[&]() { shell->GetPlatformView()->UnregisterTexture(0); });
latch->Wait();
EXPECT_EQ(mockTexture->unregistered(), true);
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, IsolateCanAccessPersistentIsolateData) {
const std::string message = "dummy isolate launch data.";
Settings settings = CreateSettingsForFixture();
settings.persistent_isolate_data =
std::make_shared<fml::DataMapping>(message);
TaskRunners task_runners("test", // label
GetCurrentTaskRunner(), // platform
CreateNewThread(), // raster
CreateNewThread(), // ui
CreateNewThread() // io
);
fml::AutoResetWaitableEvent message_latch;
AddNativeCallback("NotifyMessage",
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
const auto message_from_dart =
tonic::DartConverter<std::string>::FromDart(
Dart_GetNativeArgument(args, 0));
ASSERT_EQ(message, message_from_dart);
message_latch.Signal();
}));
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("canAccessIsolateLaunchData");
fml::AutoResetWaitableEvent event;
shell->RunEngine(std::move(configuration), [&](auto result) {
ASSERT_EQ(result, Engine::RunStatus::Success);
});
message_latch.Wait();
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, CanScheduleFrameFromPlatform) {
Settings settings = CreateSettingsForFixture();
TaskRunners task_runners = GetTaskRunnersForFixture();
fml::AutoResetWaitableEvent latch;
AddNativeCallback(
"NotifyNative",
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }));
fml::AutoResetWaitableEvent check_latch;
AddNativeCallback("NativeOnBeginFrame",
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
check_latch.Signal();
}));
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("onBeginFrameWithNotifyNativeMain");
RunEngine(shell.get(), std::move(configuration));
// Wait for the application to attach the listener.
latch.Wait();
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[&shell]() { shell->GetPlatformView()->ScheduleFrame(); });
check_latch.Wait();
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, SecondaryVsyncCallbackShouldBeCalledAfterVsyncCallback) {
bool is_on_begin_frame_called = false;
bool is_secondary_callback_called = false;
bool test_started = false;
Settings settings = CreateSettingsForFixture();
TaskRunners task_runners = GetTaskRunnersForFixture();
fml::AutoResetWaitableEvent latch;
AddNativeCallback(
"NotifyNative",
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }));
fml::CountDownLatch count_down_latch(2);
AddNativeCallback("NativeOnBeginFrame",
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
if (!test_started) {
return;
}
EXPECT_FALSE(is_on_begin_frame_called);
EXPECT_FALSE(is_secondary_callback_called);
is_on_begin_frame_called = true;
count_down_latch.CountDown();
}));
std::unique_ptr<Shell> shell = CreateShell({
.settings = settings,
.task_runners = task_runners,
});
ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("onBeginFrameWithNotifyNativeMain");
RunEngine(shell.get(), std::move(configuration));
// Wait for the application to attach the listener.
latch.Wait();
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetUITaskRunner(), [&]() {
shell->GetEngine()->ScheduleSecondaryVsyncCallback(0, [&]() {
if (!test_started) {
return;
}
EXPECT_TRUE(is_on_begin_frame_called);
EXPECT_FALSE(is_secondary_callback_called);
is_secondary_callback_called = true;
count_down_latch.CountDown();
});
shell->GetEngine()->ScheduleFrame();
test_started = true;
});
count_down_latch.Wait();
EXPECT_TRUE(is_on_begin_frame_called);
EXPECT_TRUE(is_secondary_callback_called);
DestroyShell(std::move(shell), task_runners);
}
static void LogSkData(const sk_sp<SkData>& data, const char* title) {
FML_LOG(ERROR) << "---------- " << title;
std::ostringstream ostr;
for (size_t i = 0; i < data->size();) {
ostr << std::hex << std::setfill('0') << std::setw(2)
<< static_cast<int>(data->bytes()[i]) << " ";
i++;
if (i % 16 == 0 || i == data->size()) {
FML_LOG(ERROR) << ostr.str();
ostr.str("");
ostr.clear();
}
}
}
TEST_F(ShellTest, Screenshot) {
auto settings = CreateSettingsForFixture();
fml::AutoResetWaitableEvent firstFrameLatch;
settings.frame_rasterized_callback =
[&firstFrameLatch](const FrameTiming& t) { firstFrameLatch.Signal(); };
std::unique_ptr<Shell> shell = CreateShell(settings);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
LayerTreeBuilder builder = [&](const std::shared_ptr<ContainerLayer>& root) {
auto display_list_layer = std::make_shared<DisplayListLayer>(
SkPoint::Make(10, 10), MakeSizedDisplayList(80, 80), false, false);
root->Add(display_list_layer);
};
PumpOneFrame(shell.get(), ViewContent::ImplicitView(100, 100, builder));
firstFrameLatch.Wait();
std::promise<Rasterizer::Screenshot> screenshot_promise;
auto screenshot_future = screenshot_promise.get_future();
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetRasterTaskRunner(),
[&screenshot_promise, &shell]() {
auto rasterizer = shell->GetRasterizer();
screenshot_promise.set_value(rasterizer->ScreenshotLastLayerTree(
Rasterizer::ScreenshotType::CompressedImage, false));
});
auto fixtures_dir =
fml::OpenDirectory(GetFixturesPath(), false, fml::FilePermission::kRead);
auto reference_png = fml::FileMapping::CreateReadOnly(
fixtures_dir, "shelltest_screenshot.png");
// Use MakeWithoutCopy instead of MakeWithCString because we don't want to
// encode the null sentinel
sk_sp<SkData> reference_data = SkData::MakeWithoutCopy(
reference_png->GetMapping(), reference_png->GetSize());
sk_sp<SkData> screenshot_data = screenshot_future.get().data;
if (!reference_data->equals(screenshot_data.get())) {
LogSkData(reference_data, "reference");
LogSkData(screenshot_data, "screenshot");
ASSERT_TRUE(false);
}
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, CanConvertToAndFromMappings) {
const size_t buffer_size = 2 << 20;
uint8_t* buffer = static_cast<uint8_t*>(::malloc(buffer_size));
// NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
ASSERT_TRUE(buffer != nullptr);
ASSERT_TRUE(MemsetPatternSetOrCheck(
buffer, buffer_size, MemsetPatternOp::kMemsetPatternOpSetBuffer));
std::unique_ptr<fml::Mapping> mapping =
std::make_unique<fml::MallocMapping>(buffer, buffer_size);
ASSERT_EQ(mapping->GetSize(), buffer_size);
fml::AutoResetWaitableEvent latch;
AddNativeCallback(
"SendFixtureMapping", CREATE_NATIVE_ENTRY([&](auto args) {
auto mapping_from_dart =
tonic::DartConverter<std::unique_ptr<fml::Mapping>>::FromDart(
Dart_GetNativeArgument(args, 0));
ASSERT_NE(mapping_from_dart, nullptr);
ASSERT_EQ(mapping_from_dart->GetSize(), buffer_size);
ASSERT_TRUE(MemsetPatternSetOrCheck(
const_cast<uint8_t*>(mapping_from_dart->GetMapping()), // buffer
mapping_from_dart->GetSize(), // size
MemsetPatternOp::kMemsetPatternOpCheckBuffer // op
));
latch.Signal();
}));
AddNativeCallback(
"GetFixtureMapping", CREATE_NATIVE_ENTRY([&](auto args) {
tonic::DartConverter<tonic::DartConverterMapping>::SetReturnValue(
args, mapping);
}));
auto settings = CreateSettingsForFixture();
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("canConvertMappings");
std::unique_ptr<Shell> shell = CreateShell(settings);
ASSERT_NE(shell.get(), nullptr);
RunEngine(shell.get(), std::move(configuration));
latch.Wait();
DestroyShell(std::move(shell));
}
// Compares local times as seen by the dart isolate and as seen by this test
// fixture, to a resolution of 1 hour.
//
// This verifies that (1) the isolate is able to get a timezone (doesn't lock
// up for example), and (2) that the host and the isolate agree on what the
// timezone is.
TEST_F(ShellTest, LocaltimesMatch) {
fml::AutoResetWaitableEvent latch;
std::string dart_isolate_time_str;
// See fixtures/shell_test.dart, the callback NotifyLocalTime is declared
// there.
AddNativeCallback("NotifyLocalTime", CREATE_NATIVE_ENTRY([&](auto args) {
dart_isolate_time_str =
tonic::DartConverter<std::string>::FromDart(
Dart_GetNativeArgument(args, 0));
latch.Signal();
}));
auto settings = CreateSettingsForFixture();
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("localtimesMatch");
std::unique_ptr<Shell> shell = CreateShell(settings);
ASSERT_NE(shell.get(), nullptr);
RunEngine(shell.get(), std::move(configuration));
latch.Wait();
char timestr[200];
const time_t timestamp = time(nullptr);
const struct tm* local_time = localtime(&timestamp);
ASSERT_NE(local_time, nullptr)
<< "Could not get local time: errno=" << errno << ": " << strerror(errno);
// Example: "2020-02-26 14" for 2pm on February 26, 2020.
const size_t format_size =
strftime(timestr, sizeof(timestr), "%Y-%m-%d %H", local_time);
ASSERT_NE(format_size, 0UL)
<< "strftime failed: host time: " << std::string(timestr)
<< " dart isolate time: " << dart_isolate_time_str;
const std::string host_local_time_str = timestr;
ASSERT_EQ(dart_isolate_time_str, host_local_time_str)
<< "Local times in the dart isolate and the local time seen by the test "
<< "differ by more than 1 hour, but are expected to be about equal";
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, CanDecompressImageFromAsset) {
fml::AutoResetWaitableEvent latch;
AddNativeCallback("NotifyWidthHeight", CREATE_NATIVE_ENTRY([&](auto args) {
auto width = tonic::DartConverter<int>::FromDart(
Dart_GetNativeArgument(args, 0));
auto height = tonic::DartConverter<int>::FromDart(
Dart_GetNativeArgument(args, 1));
ASSERT_EQ(width, 100);
ASSERT_EQ(height, 100);
latch.Signal();
}));
AddNativeCallback(
"GetFixtureImage", CREATE_NATIVE_ENTRY([](auto args) {
auto fixture = OpenFixtureAsMapping("shelltest_screenshot.png");
tonic::DartConverter<tonic::DartConverterMapping>::SetReturnValue(
args, fixture);
}));
auto settings = CreateSettingsForFixture();
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("canDecompressImageFromAsset");
std::unique_ptr<Shell> shell = CreateShell(settings);
ASSERT_NE(shell.get(), nullptr);
RunEngine(shell.get(), std::move(configuration));
latch.Wait();
DestroyShell(std::move(shell));
}
/// An image generator that always creates a 1x1 single-frame green image.
class SinglePixelImageGenerator : public ImageGenerator {
public:
SinglePixelImageGenerator()
: info_(SkImageInfo::MakeN32(1, 1, SkAlphaType::kOpaque_SkAlphaType)){};
~SinglePixelImageGenerator() = default;
const SkImageInfo& GetInfo() { return info_; }
unsigned int GetFrameCount() const { return 1; }
unsigned int GetPlayCount() const { return 1; }
const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index) {
return {std::nullopt, 0, SkCodecAnimation::DisposalMethod::kKeep};
}
SkISize GetScaledDimensions(float scale) {
return SkISize::Make(info_.width(), info_.height());
}
bool GetPixels(const SkImageInfo& info,
void* pixels,
size_t row_bytes,
unsigned int frame_index,
std::optional<unsigned int> prior_frame) {
assert(info.width() == 1);
assert(info.height() == 1);
assert(row_bytes == 4);
reinterpret_cast<uint32_t*>(pixels)[0] = 0x00ff00ff;
return true;
};
private:
SkImageInfo info_;
};
TEST_F(ShellTest, CanRegisterImageDecoders) {
fml::AutoResetWaitableEvent latch;
AddNativeCallback("NotifyWidthHeight", CREATE_NATIVE_ENTRY([&](auto args) {
auto width = tonic::DartConverter<int>::FromDart(
Dart_GetNativeArgument(args, 0));
auto height = tonic::DartConverter<int>::FromDart(
Dart_GetNativeArgument(args, 1));
ASSERT_EQ(width, 1);
ASSERT_EQ(height, 1);
latch.Signal();
}));
auto settings = CreateSettingsForFixture();
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("canRegisterImageDecoders");
std::unique_ptr<Shell> shell = CreateShell(settings);
ASSERT_NE(shell.get(), nullptr);
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
shell->RegisterImageDecoder(
[](const sk_sp<SkData>& buffer) {
return std::make_unique<SinglePixelImageGenerator>();
},
100);
});
RunEngine(shell.get(), std::move(configuration));
latch.Wait();
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, OnServiceProtocolGetSkSLsWorks) {
fml::ScopedTemporaryDirectory base_dir;
ASSERT_TRUE(base_dir.fd().is_valid());
PersistentCache::SetCacheDirectoryPath(base_dir.path());
PersistentCache::ResetCacheForProcess();
// Create 2 dummy SkSL cache file IE (base32 encoding of A), II (base32
// encoding of B) with content x and y.
std::vector<std::string> components = {
"flutter_engine", GetFlutterEngineVersion(), "skia", GetSkiaVersion(),
PersistentCache::kSkSLSubdirName};
auto sksl_dir = fml::CreateDirectory(base_dir.fd(), components,
fml::FilePermission::kReadWrite);
const std::string x_key_str = "A";
const std::string x_value_str = "x";
sk_sp<SkData> x_key =
SkData::MakeWithCopy(x_key_str.data(), x_key_str.size());
sk_sp<SkData> x_value =
SkData::MakeWithCopy(x_value_str.data(), x_value_str.size());
auto x_data = PersistentCache::BuildCacheObject(*x_key, *x_value);
const std::string y_key_str = "B";
const std::string y_value_str = "y";
sk_sp<SkData> y_key =
SkData::MakeWithCopy(y_key_str.data(), y_key_str.size());
sk_sp<SkData> y_value =
SkData::MakeWithCopy(y_value_str.data(), y_value_str.size());
auto y_data = PersistentCache::BuildCacheObject(*y_key, *y_value);
ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "x_cache", *x_data));
ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "y_cache", *y_data));
Settings settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
ServiceProtocol::Handler::ServiceProtocolMap empty_params;
rapidjson::Document document;
OnServiceProtocol(shell.get(), ServiceProtocolEnum::kGetSkSLs,
shell->GetTaskRunners().GetIOTaskRunner(), empty_params,
&document);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document.Accept(writer);
DestroyShell(std::move(shell));
const std::string expected_json1 =
"{\"type\":\"GetSkSLs\",\"SkSLs\":{\"II\":\"eQ==\",\"IE\":\"eA==\"}}";
const std::string expected_json2 =
"{\"type\":\"GetSkSLs\",\"SkSLs\":{\"IE\":\"eA==\",\"II\":\"eQ==\"}}";
bool json_is_expected = (expected_json1 == buffer.GetString()) ||
(expected_json2 == buffer.GetString());
ASSERT_TRUE(json_is_expected) << buffer.GetString() << " is not equal to "
<< expected_json1 << " or " << expected_json2;
}
TEST_F(ShellTest, RasterizerScreenshot) {
Settings settings = CreateSettingsForFixture();
auto configuration = RunConfiguration::InferFromSettings(settings);
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
ASSERT_TRUE(ValidateShell(shell.get()));
PlatformViewNotifyCreated(shell.get());
RunEngine(shell.get(), std::move(configuration));
auto latch = std::make_shared<fml::AutoResetWaitableEvent>();
PumpOneFrame(shell.get());
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetRasterTaskRunner(), [&shell, &latch]() {
Rasterizer::Screenshot screenshot =
shell->GetRasterizer()->ScreenshotLastLayerTree(
Rasterizer::ScreenshotType::CompressedImage, true);
EXPECT_NE(screenshot.data, nullptr);
latch->Signal();
});
latch->Wait();
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, RasterizerMakeRasterSnapshot) {
Settings settings = CreateSettingsForFixture();
auto configuration = RunConfiguration::InferFromSettings(settings);
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
ASSERT_TRUE(ValidateShell(shell.get()));
PlatformViewNotifyCreated(shell.get());
RunEngine(shell.get(), std::move(configuration));
auto latch = std::make_shared<fml::AutoResetWaitableEvent>();
PumpOneFrame(shell.get());
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetRasterTaskRunner(), [&shell, &latch]() {
SnapshotDelegate* delegate =
reinterpret_cast<Rasterizer*>(shell->GetRasterizer().get());
sk_sp<DlImage> image = delegate->MakeRasterSnapshot(
MakeSizedDisplayList(50, 50), SkISize::Make(50, 50));
EXPECT_NE(image, nullptr);
latch->Signal();
});
latch->Wait();
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) {
Settings settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
// 1. Construct a picture and a picture layer to be raster cached.
sk_sp<DisplayList> display_list = MakeSizedDisplayList(10, 10);
auto display_list_layer = std::make_shared<DisplayListLayer>(
SkPoint::Make(0, 0), MakeSizedDisplayList(100, 100), false, false);
display_list_layer->set_paint_bounds(SkRect::MakeWH(100, 100));
// 2. Rasterize the picture and the picture layer in the raster cache.
std::promise<bool> rasterized;
shell->GetTaskRunners().GetRasterTaskRunner()->PostTask(
[&shell, &rasterized, &display_list, &display_list_layer] {
std::vector<RasterCacheItem*> raster_cache_items;
auto* compositor_context = shell->GetRasterizer()->compositor_context();
auto& raster_cache = compositor_context->raster_cache();
LayerStateStack state_stack;
FixedRefreshRateStopwatch raster_time;
FixedRefreshRateStopwatch ui_time;
PaintContext paint_context = {
// clang-format off
.state_stack = state_stack,
.canvas = nullptr,
.gr_context = nullptr,
.dst_color_space = nullptr,
.view_embedder = nullptr,
.raster_time = raster_time,
.ui_time = ui_time,
.texture_registry = nullptr,
.raster_cache = &raster_cache,
// clang-format on
};
PrerollContext preroll_context = {
// clang-format off
.raster_cache = &raster_cache,
.gr_context = nullptr,
.view_embedder = nullptr,
.state_stack = state_stack,
.dst_color_space = nullptr,
.surface_needs_readback = false,
.raster_time = raster_time,
.ui_time = ui_time,
.texture_registry = nullptr,
.has_platform_view = false,
.has_texture_layer = false,
.raster_cached_entries = &raster_cache_items,
// clang-format on
};
// 2.1. Rasterize the picture. Call Draw multiple times to pass the
// access threshold (default to 3) so a cache can be generated.
MockCanvas dummy_canvas;
DlPaint paint;
bool picture_cache_generated;
DisplayListRasterCacheItem display_list_raster_cache_item(
display_list, SkPoint(), true, false);
for (int i = 0; i < 4; i += 1) {
SkMatrix matrix = SkMatrix::I();
state_stack.set_preroll_delegate(matrix);
display_list_raster_cache_item.PrerollSetup(&preroll_context, matrix);
display_list_raster_cache_item.PrerollFinalize(&preroll_context,
matrix);
picture_cache_generated =
display_list_raster_cache_item.need_caching();
state_stack.set_delegate(&dummy_canvas);
display_list_raster_cache_item.TryToPrepareRasterCache(paint_context);
display_list_raster_cache_item.Draw(paint_context, &dummy_canvas,
&paint);
}
ASSERT_TRUE(picture_cache_generated);
// 2.2. Rasterize the picture layer.
LayerRasterCacheItem layer_raster_cache_item(display_list_layer.get());
state_stack.set_preroll_delegate(SkMatrix::I());
layer_raster_cache_item.PrerollSetup(&preroll_context, SkMatrix::I());
layer_raster_cache_item.PrerollFinalize(&preroll_context,
SkMatrix::I());
state_stack.set_delegate(&dummy_canvas);
layer_raster_cache_item.TryToPrepareRasterCache(paint_context);
layer_raster_cache_item.Draw(paint_context, &dummy_canvas, &paint);
rasterized.set_value(true);
});
rasterized.get_future().wait();
// 3. Call the service protocol and check its output.
ServiceProtocol::Handler::ServiceProtocolMap empty_params;
rapidjson::Document document;
OnServiceProtocol(
shell.get(), ServiceProtocolEnum::kEstimateRasterCacheMemory,
shell->GetTaskRunners().GetRasterTaskRunner(), empty_params, &document);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document.Accept(writer);
std::string expected_json =
"{\"type\":\"EstimateRasterCacheMemory\",\"layerBytes\":40024,\"picture"
"Bytes\":424}";
std::string actual_json = buffer.GetString();
ASSERT_EQ(actual_json, expected_json);
DestroyShell(std::move(shell));
}
// ktz
TEST_F(ShellTest, OnServiceProtocolRenderFrameWithRasterStatsWorks) {
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("scene_with_red_box");
RunEngine(shell.get(), std::move(configuration));
// Set a non-zero viewport metrics, otherwise the scene would be discarded.
PostSync(shell->GetTaskRunners().GetUITaskRunner(),
[engine = shell->GetEngine()]() {
engine->SetViewportMetrics(kImplicitViewId,
ViewportMetrics{1, 1, 1, 22, 0});
});
PumpOneFrame(shell.get(), ViewContent::NoViews());
ServiceProtocol::Handler::ServiceProtocolMap empty_params;
rapidjson::Document document;
OnServiceProtocol(
shell.get(), ServiceProtocolEnum::kRenderFrameWithRasterStats,
shell->GetTaskRunners().GetRasterTaskRunner(), empty_params, &document);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document.Accept(writer);
// It would be better to parse out the json and check for the validity of
// fields. Below checks approximate what needs to be checked, this can not be
// an exact check since duration will not exactly match.
#ifdef SHELL_ENABLE_METAL
std::string expected_json =
"\"snapshot\":[137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,3,32,0,"
"0,2,88,8,6,0,0,0,154,118,130,112,0,0,0,1,115,82,71,66,0,174,206,28,233,"
"0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,7,103,73,68,65,84,120,"
"156,237,206,65,13,192,48,0,3,177,211,248,115,78,73,172,234,199,70,224,"
"86,91,45,0,0,128,203,190,215,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"
"0,224,47,7,195,182,3,255,101,111,186,90,0,0,0,0,73,69,78,68,174,66,96,"
"130]";
#else
std::string expected_json =
"\"snapshot\":[137,80,78,71,13,10,26,10,0,"
"0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,6,0,0,0,31,21,196,137,0,0,0,1,115,"
"82,71,66,0,174,206,28,233,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,"
"0,0,13,73,68,65,84,8,153,99,248,207,192,240,31,0,5,0,1,255,171,206,54,"
"137,0,0,0,0,73,69,78,68,174,66,96,130]";
#endif
std::string actual_json = buffer.GetString();
EXPECT_THAT(actual_json, ::testing::HasSubstr(expected_json));
EXPECT_THAT(actual_json,
::testing::HasSubstr("{\"type\":\"RenderFrameWithRasterStats\""));
EXPECT_THAT(actual_json, ::testing::HasSubstr("\"duration_micros\""));
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
}
#if defined(FML_OS_MACOSX)
TEST_F(ShellTest, OnServiceProtocolRenderFrameWithRasterStatsDisableImpeller) {
auto settings = CreateSettingsForFixture();
settings.enable_impeller = true;
std::unique_ptr<Shell> shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.rendering_backend =
ShellTestPlatformView::BackendType::kMetalBackend,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("scene_with_red_box");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get(), ViewContent::NoViews());
ServiceProtocol::Handler::ServiceProtocolMap empty_params;
rapidjson::Document document;
OnServiceProtocol(
shell.get(), ServiceProtocolEnum::kRenderFrameWithRasterStats,
shell->GetTaskRunners().GetRasterTaskRunner(), empty_params, &document);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document.Accept(writer);
std::string actual_json = buffer.GetString();
std::string expected_json =
"{\"code\":-32000,\"message\":\"Raster status not supported on Impeller "
"backend.\"}";
ASSERT_EQ(actual_json, expected_json);
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
}
#endif // FML_OS_MACOSX
// TODO(https://github.com/flutter/flutter/issues/100273): Disabled due to
// flakiness.
// TODO(https://github.com/flutter/flutter/issues/100299): Fix it when
// re-enabling.
TEST_F(ShellTest, DISABLED_DiscardLayerTreeOnResize) {
auto settings = CreateSettingsForFixture();
SkISize wrong_size = SkISize::Make(400, 100);
SkISize expected_size = SkISize::Make(400, 200);
fml::AutoResetWaitableEvent end_frame_latch;
auto end_frame_callback =
[&](bool should_merge_thread,
const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
end_frame_latch.Signal();
};
auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
std::move(end_frame_callback), PostPrerollResult::kSuccess, false);
std::unique_ptr<Shell> shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[&shell, &expected_size]() {
shell->GetPlatformView()->SetViewportMetrics(
kImplicitViewId,
{1.0, static_cast<double>(expected_size.width()),
static_cast<double>(expected_size.height()), 22, 0});
});
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get(), ViewContent::DummyView(
static_cast<double>(wrong_size.width()),
static_cast<double>(wrong_size.height())));
end_frame_latch.Wait();
// Wrong size, no frames are submitted.
ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
PumpOneFrame(shell.get(), ViewContent::DummyView(
static_cast<double>(expected_size.width()),
static_cast<double>(expected_size.height())));
end_frame_latch.Wait();
// Expected size, 1 frame submitted.
ASSERT_EQ(1, external_view_embedder->GetSubmittedFrameCount());
ASSERT_EQ(expected_size, external_view_embedder->GetLastSubmittedFrameSize());
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
}
// TODO(https://github.com/flutter/flutter/issues/100273): Disabled due to
// flakiness.
// TODO(https://github.com/flutter/flutter/issues/100299): Fix it when
// re-enabling.
TEST_F(ShellTest, DISABLED_DiscardResubmittedLayerTreeOnResize) {
auto settings = CreateSettingsForFixture();
SkISize origin_size = SkISize::Make(400, 100);
SkISize new_size = SkISize::Make(400, 200);
fml::AutoResetWaitableEvent end_frame_latch;
fml::AutoResetWaitableEvent resize_latch;
std::shared_ptr<ShellTestExternalViewEmbedder> external_view_embedder;
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger_ref;
auto end_frame_callback =
[&](bool should_merge_thread,
const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
if (!raster_thread_merger_ref) {
raster_thread_merger_ref = raster_thread_merger;
}
if (should_merge_thread) {
raster_thread_merger->MergeWithLease(10);
external_view_embedder->UpdatePostPrerollResult(
PostPrerollResult::kSuccess);
}
end_frame_latch.Signal();
if (should_merge_thread) {
resize_latch.Wait();
}
};
external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
std::move(end_frame_callback), PostPrerollResult::kResubmitFrame, true);
std::unique_ptr<Shell> shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.shell_test_external_view_embedder = external_view_embedder,
}),
});
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[&shell, &origin_size]() {
shell->GetPlatformView()->SetViewportMetrics(
kImplicitViewId,
{1.0, static_cast<double>(origin_size.width()),
static_cast<double>(origin_size.height()), 22, 0});
});
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get(), ViewContent::DummyView(
static_cast<double>(origin_size.width()),
static_cast<double>(origin_size.height())));
end_frame_latch.Wait();
ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[&shell, &new_size, &resize_latch]() {
shell->GetPlatformView()->SetViewportMetrics(
kImplicitViewId, {1.0, static_cast<double>(new_size.width()),
static_cast<double>(new_size.height()), 22, 0});
resize_latch.Signal();
});
end_frame_latch.Wait();
// The frame resubmitted with origin size should be discarded after the
// viewport metrics changed.
ASSERT_EQ(0, external_view_embedder->GetSubmittedFrameCount());
// Threads will be merged at the end of this frame.
PumpOneFrame(shell.get(),
ViewContent::DummyView(static_cast<double>(new_size.width()),
static_cast<double>(new_size.height())));
end_frame_latch.Wait();
ASSERT_TRUE(raster_thread_merger_ref->IsMerged());
ASSERT_EQ(1, external_view_embedder->GetSubmittedFrameCount());
ASSERT_EQ(new_size, external_view_embedder->GetLastSubmittedFrameSize());
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, IgnoresInvalidMetrics) {
fml::AutoResetWaitableEvent latch;
double last_device_pixel_ratio;
double last_width;
double last_height;
auto native_report_device_pixel_ratio = [&](Dart_NativeArguments args) {
auto dpr_handle = Dart_GetNativeArgument(args, 0);
ASSERT_TRUE(Dart_IsDouble(dpr_handle));
Dart_DoubleValue(dpr_handle, &last_device_pixel_ratio);
ASSERT_FALSE(last_device_pixel_ratio == 0.0);
auto width_handle = Dart_GetNativeArgument(args, 1);
ASSERT_TRUE(Dart_IsDouble(width_handle));
Dart_DoubleValue(width_handle, &last_width);
ASSERT_FALSE(last_width == 0.0);
auto height_handle = Dart_GetNativeArgument(args, 2);
ASSERT_TRUE(Dart_IsDouble(height_handle));
Dart_DoubleValue(height_handle, &last_height);
ASSERT_FALSE(last_height == 0.0);
latch.Signal();
};
Settings settings = CreateSettingsForFixture();
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
AddNativeCallback("ReportMetrics",
CREATE_NATIVE_ENTRY(native_report_device_pixel_ratio));
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("reportMetrics");
RunEngine(shell.get(), std::move(configuration));
task_runner->PostTask([&]() {
// This one is invalid for having 0 pixel ratio.
shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
{0.0, 400, 200, 22, 0});
task_runner->PostTask([&]() {
// This one is invalid for having 0 width.
shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
{0.8, 0.0, 200, 22, 0});
task_runner->PostTask([&]() {
// This one is invalid for having 0 height.
shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
{0.8, 400, 0.0, 22, 0});
task_runner->PostTask([&]() {
// This one makes it through.
shell->GetPlatformView()->SetViewportMetrics(
kImplicitViewId, {0.8, 400, 200.0, 22, 0});
});
});
});
});
latch.Wait();
ASSERT_EQ(last_device_pixel_ratio, 0.8);
ASSERT_EQ(last_width, 400.0);
ASSERT_EQ(last_height, 200.0);
latch.Reset();
task_runner->PostTask([&]() {
shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
{1.2, 600, 300, 22, 0});
});
latch.Wait();
ASSERT_EQ(last_device_pixel_ratio, 1.2);
ASSERT_EQ(last_width, 600.0);
ASSERT_EQ(last_height, 300.0);
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, IgnoresMetricsUpdateToInvalidView) {
fml::AutoResetWaitableEvent latch;
double last_device_pixel_ratio;
// This callback will be called whenever any view's metrics change.
auto native_report_device_pixel_ratio = [&](Dart_NativeArguments args) {
// The correct call will have a DPR of 3.
auto dpr_handle = Dart_GetNativeArgument(args, 0);
ASSERT_TRUE(Dart_IsDouble(dpr_handle));
Dart_DoubleValue(dpr_handle, &last_device_pixel_ratio);
ASSERT_TRUE(last_device_pixel_ratio > 2.5);
latch.Signal();
};
Settings settings = CreateSettingsForFixture();
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
AddNativeCallback("ReportMetrics",
CREATE_NATIVE_ENTRY(native_report_device_pixel_ratio));
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("reportMetrics");
RunEngine(shell.get(), std::move(configuration));
task_runner->PostTask([&]() {
// This one is invalid for having an nonexistent view ID.
// Also, it has a DPR of 2.0 for detection.
shell->GetPlatformView()->SetViewportMetrics(2, {2.0, 400, 200, 22, 0});
task_runner->PostTask([&]() {
// This one is valid with DPR 3.0.
shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId,
{3.0, 400, 200, 22, 0});
});
});
latch.Wait();
ASSERT_EQ(last_device_pixel_ratio, 3.0);
latch.Reset();
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, OnServiceProtocolSetAssetBundlePathWorks) {
Settings settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
RunConfiguration configuration =
RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("canAccessResourceFromAssetDir");
// Verify isolate can load a known resource with the
// default asset directory - kernel_blob.bin
fml::AutoResetWaitableEvent latch;
// Callback used to signal whether the resource was loaded successfully.
bool can_access_resource = false;
auto native_can_access_resource = [&can_access_resource,
&latch](Dart_NativeArguments args) {
Dart_Handle exception = nullptr;
can_access_resource =
tonic::DartConverter<bool>::FromArguments(args, 0, exception);
latch.Signal();
};
AddNativeCallback("NotifyCanAccessResource",
CREATE_NATIVE_ENTRY(native_can_access_resource));
// Callback used to delay the asset load until after the service
// protocol method has finished.
auto native_notify_set_asset_bundle_path =
[&shell](Dart_NativeArguments args) {
// Update the asset directory to a bonus path.
ServiceProtocol::Handler::ServiceProtocolMap params;
params["assetDirectory"] = "assetDirectory";
rapidjson::Document document;
OnServiceProtocol(shell.get(), ServiceProtocolEnum::kSetAssetBundlePath,
shell->GetTaskRunners().GetUITaskRunner(), params,
&document);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document.Accept(writer);
};
AddNativeCallback("NotifySetAssetBundlePath",
CREATE_NATIVE_ENTRY(native_notify_set_asset_bundle_path));
RunEngine(shell.get(), std::move(configuration));
latch.Wait();
ASSERT_TRUE(can_access_resource);
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, EngineRootIsolateLaunchesDontTakeVMDataSettings) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
// Make sure the shell launch does not kick off the creation of the VM
// instance by already creating one upfront.
auto vm_settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(vm_settings);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
auto settings = vm_settings;
fml::AutoResetWaitableEvent isolate_create_latch;
settings.root_isolate_create_callback = [&](const auto& isolate) {
isolate_create_latch.Signal();
};
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
RunEngine(shell.get(), std::move(configuration));
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
DestroyShell(std::move(shell));
isolate_create_latch.Wait();
}
TEST_F(ShellTest, AssetManagerSingle) {
fml::ScopedTemporaryDirectory asset_dir;
fml::UniqueFD asset_dir_fd = fml::OpenDirectory(
asset_dir.path().c_str(), false, fml::FilePermission::kRead);
std::string filename = "test_name";
std::string content = "test_content";
bool success = fml::WriteAtomically(asset_dir_fd, filename.c_str(),
fml::DataMapping(content));
ASSERT_TRUE(success);
AssetManager asset_manager;
asset_manager.PushBack(
std::make_unique<DirectoryAssetBundle>(std::move(asset_dir_fd), false));
auto mapping = asset_manager.GetAsMapping(filename);
ASSERT_TRUE(mapping != nullptr);
std::string result(reinterpret_cast<const char*>(mapping->GetMapping()),
mapping->GetSize());
ASSERT_TRUE(result == content);
}
TEST_F(ShellTest, AssetManagerMulti) {
fml::ScopedTemporaryDirectory asset_dir;
fml::UniqueFD asset_dir_fd = fml::OpenDirectory(
asset_dir.path().c_str(), false, fml::FilePermission::kRead);
std::vector<std::string> filenames = {
"good0",
"bad0",
"good1",
"bad1",
};
for (auto filename : filenames) {
bool success = fml::WriteAtomically(asset_dir_fd, filename.c_str(),
fml::DataMapping(filename));
ASSERT_TRUE(success);
}
AssetManager asset_manager;
asset_manager.PushBack(
std::make_unique<DirectoryAssetBundle>(std::move(asset_dir_fd), false));
auto mappings = asset_manager.GetAsMappings("(.*)", std::nullopt);
EXPECT_EQ(mappings.size(), 4u);
std::vector<std::string> expected_results = {
"good0",
"good1",
};
mappings = asset_manager.GetAsMappings("(.*)good(.*)", std::nullopt);
ASSERT_EQ(mappings.size(), expected_results.size());
for (auto& mapping : mappings) {
std::string result(reinterpret_cast<const char*>(mapping->GetMapping()),
mapping->GetSize());
EXPECT_NE(
std::find(expected_results.begin(), expected_results.end(), result),
expected_results.end());
}
}
#if defined(OS_FUCHSIA)
TEST_F(ShellTest, AssetManagerMultiSubdir) {
std::string subdir_path = "subdir";
fml::ScopedTemporaryDirectory asset_dir;
fml::UniqueFD asset_dir_fd = fml::OpenDirectory(
asset_dir.path().c_str(), false, fml::FilePermission::kRead);
fml::UniqueFD subdir_fd =
fml::OpenDirectory((asset_dir.path() + "/" + subdir_path).c_str(), true,
fml::FilePermission::kReadWrite);
std::vector<std::string> filenames = {
"bad0",
"notgood", // this is to make sure the pattern (.*)good(.*) only
// matches things in the subdirectory
};
std::vector<std::string> subdir_filenames = {
"good0",
"good1",
"bad1",
};
for (auto filename : filenames) {
bool success = fml::WriteAtomically(asset_dir_fd, filename.c_str(),
fml::DataMapping(filename));
ASSERT_TRUE(success);
}
for (auto filename : subdir_filenames) {
bool success = fml::WriteAtomically(subdir_fd, filename.c_str(),
fml::DataMapping(filename));
ASSERT_TRUE(success);
}
AssetManager asset_manager;
asset_manager.PushBack(
std::make_unique<DirectoryAssetBundle>(std::move(asset_dir_fd), false));
auto mappings = asset_manager.GetAsMappings("(.*)", std::nullopt);
EXPECT_EQ(mappings.size(), 5u);
mappings = asset_manager.GetAsMappings("(.*)", subdir_path);
EXPECT_EQ(mappings.size(), 3u);
std::vector<std::string> expected_results = {
"good0",
"good1",
};
mappings = asset_manager.GetAsMappings("(.*)good(.*)", subdir_path);
ASSERT_EQ(mappings.size(), expected_results.size());
for (auto& mapping : mappings) {
std::string result(reinterpret_cast<const char*>(mapping->GetMapping()),
mapping->GetSize());
ASSERT_NE(
std::find(expected_results.begin(), expected_results.end(), result),
expected_results.end());
}
}
#endif // OS_FUCHSIA
TEST_F(ShellTest, Spawn) {
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
configuration.SetEntrypoint("fixturesAreFunctionalMain");
auto second_configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(second_configuration.IsValid());
second_configuration.SetEntrypoint("testCanLaunchSecondaryIsolate");
const std::string initial_route("/foo");
fml::AutoResetWaitableEvent main_latch;
std::string last_entry_point;
// Fulfill native function for the first Shell's entrypoint.
AddNativeCallback(
"SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) {
last_entry_point = shell->GetEngine()->GetLastEntrypoint();
main_latch.Signal();
}));
// Fulfill native function for the second Shell's entrypoint.
fml::CountDownLatch second_latch(2);
AddNativeCallback(
// The Dart native function names aren't very consistent but this is
// just the native function name of the second vm entrypoint in the
// fixture.
"NotifyNative",
CREATE_NATIVE_ENTRY([&](auto args) { second_latch.CountDown(); }));
RunEngine(shell.get(), std::move(configuration));
main_latch.Wait();
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
// Check first Shell ran the first entrypoint.
ASSERT_EQ("fixturesAreFunctionalMain", last_entry_point);
PostSync(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[this, &spawner = shell, &second_configuration, &second_latch,
initial_route]() {
MockPlatformViewDelegate platform_view_delegate;
auto spawn = spawner->Spawn(
std::move(second_configuration), initial_route,
[&platform_view_delegate](Shell& shell) {
auto result = std::make_unique<MockPlatformView>(
platform_view_delegate, shell.GetTaskRunners());
ON_CALL(*result, CreateRenderingSurface())
.WillByDefault(::testing::Invoke(
[] { return std::make_unique<MockSurface>(); }));
return result;
},
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
ASSERT_NE(nullptr, spawn.get());
ASSERT_TRUE(ValidateShell(spawn.get()));
PostSync(spawner->GetTaskRunners().GetUITaskRunner(), [&spawn, &spawner,
initial_route] {
// Check second shell ran the second entrypoint.
ASSERT_EQ("testCanLaunchSecondaryIsolate",
spawn->GetEngine()->GetLastEntrypoint());
ASSERT_EQ(initial_route, spawn->GetEngine()->InitialRoute());
ASSERT_NE(spawner->GetEngine()
->GetRuntimeController()
->GetRootIsolateGroup(),
0u);
ASSERT_EQ(spawner->GetEngine()
->GetRuntimeController()
->GetRootIsolateGroup(),
spawn->GetEngine()
->GetRuntimeController()
->GetRootIsolateGroup());
auto spawner_snapshot_delegate = spawner->GetEngine()
->GetRuntimeController()
->GetSnapshotDelegate();
auto spawn_snapshot_delegate =
spawn->GetEngine()->GetRuntimeController()->GetSnapshotDelegate();
PostSync(spawner->GetTaskRunners().GetRasterTaskRunner(),
[spawner_snapshot_delegate, spawn_snapshot_delegate] {
ASSERT_NE(spawner_snapshot_delegate.get(),
spawn_snapshot_delegate.get());
});
});
PostSync(
spawner->GetTaskRunners().GetIOTaskRunner(), [&spawner, &spawn] {
ASSERT_EQ(spawner->GetIOManager()->GetResourceContext().get(),
spawn->GetIOManager()->GetResourceContext().get());
});
// Before destroying the shell, wait for expectations of the spawned
// isolate to be met.
second_latch.Wait();
DestroyShell(std::move(spawn));
});
DestroyShell(std::move(shell));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, SpawnWithDartEntrypointArgs) {
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
configuration.SetEntrypoint("canReceiveArgumentsWhenEngineRun");
const std::vector<std::string> entrypoint_args{"foo", "bar"};
configuration.SetEntrypointArgs(entrypoint_args);
auto second_configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(second_configuration.IsValid());
second_configuration.SetEntrypoint("canReceiveArgumentsWhenEngineSpawn");
const std::vector<std::string> second_entrypoint_args{"arg1", "arg2"};
second_configuration.SetEntrypointArgs(second_entrypoint_args);
const std::string initial_route("/foo");
fml::AutoResetWaitableEvent main_latch;
std::string last_entry_point;
// Fulfill native function for the first Shell's entrypoint.
AddNativeCallback("NotifyNativeWhenEngineRun",
CREATE_NATIVE_ENTRY(([&](Dart_NativeArguments args) {
ASSERT_TRUE(tonic::DartConverter<bool>::FromDart(
Dart_GetNativeArgument(args, 0)));
last_entry_point =
shell->GetEngine()->GetLastEntrypoint();
main_latch.Signal();
})));
fml::AutoResetWaitableEvent second_latch;
// Fulfill native function for the second Shell's entrypoint.
AddNativeCallback("NotifyNativeWhenEngineSpawn",
CREATE_NATIVE_ENTRY(([&](Dart_NativeArguments args) {
ASSERT_TRUE(tonic::DartConverter<bool>::FromDart(
Dart_GetNativeArgument(args, 0)));
last_entry_point =
shell->GetEngine()->GetLastEntrypoint();
second_latch.Signal();
})));
RunEngine(shell.get(), std::move(configuration));
main_latch.Wait();
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
// Check first Shell ran the first entrypoint.
ASSERT_EQ("canReceiveArgumentsWhenEngineRun", last_entry_point);
PostSync(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[this, &spawner = shell, &second_configuration, &second_latch,
initial_route]() {
MockPlatformViewDelegate platform_view_delegate;
auto spawn = spawner->Spawn(
std::move(second_configuration), initial_route,
[&platform_view_delegate](Shell& shell) {
auto result = std::make_unique<MockPlatformView>(
platform_view_delegate, shell.GetTaskRunners());
ON_CALL(*result, CreateRenderingSurface())
.WillByDefault(::testing::Invoke(
[] { return std::make_unique<MockSurface>(); }));
return result;
},
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
ASSERT_NE(nullptr, spawn.get());
ASSERT_TRUE(ValidateShell(spawn.get()));
PostSync(spawner->GetTaskRunners().GetUITaskRunner(),
[&spawn, &spawner, initial_route] {
// Check second shell ran the second entrypoint.
ASSERT_EQ("canReceiveArgumentsWhenEngineSpawn",
spawn->GetEngine()->GetLastEntrypoint());
ASSERT_EQ(initial_route, spawn->GetEngine()->InitialRoute());
ASSERT_NE(spawner->GetEngine()
->GetRuntimeController()
->GetRootIsolateGroup(),
0u);
ASSERT_EQ(spawner->GetEngine()
->GetRuntimeController()
->GetRootIsolateGroup(),
spawn->GetEngine()
->GetRuntimeController()
->GetRootIsolateGroup());
});
PostSync(
spawner->GetTaskRunners().GetIOTaskRunner(), [&spawner, &spawn] {
ASSERT_EQ(spawner->GetIOManager()->GetResourceContext().get(),
spawn->GetIOManager()->GetResourceContext().get());
});
// Before destroying the shell, wait for expectations of the spawned
// isolate to be met.
second_latch.Wait();
DestroyShell(std::move(spawn));
});
DestroyShell(std::move(shell));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, IOManagerIsSharedBetweenParentAndSpawnedShell) {
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [this,
&spawner = shell,
&settings] {
auto second_configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(second_configuration.IsValid());
second_configuration.SetEntrypoint("emptyMain");
const std::string initial_route("/foo");
MockPlatformViewDelegate platform_view_delegate;
auto spawn = spawner->Spawn(
std::move(second_configuration), initial_route,
[&platform_view_delegate](Shell& shell) {
auto result = std::make_unique<MockPlatformView>(
platform_view_delegate, shell.GetTaskRunners());
ON_CALL(*result, CreateRenderingSurface())
.WillByDefault(::testing::Invoke(
[] { return std::make_unique<MockSurface>(); }));
return result;
},
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
ASSERT_TRUE(ValidateShell(spawn.get()));
PostSync(spawner->GetTaskRunners().GetIOTaskRunner(), [&spawner, &spawn] {
ASSERT_NE(spawner->GetIOManager().get(), nullptr);
ASSERT_EQ(spawner->GetIOManager().get(), spawn->GetIOManager().get());
});
// Destroy the child shell.
DestroyShell(std::move(spawn));
});
// Destroy the parent shell.
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, IOManagerInSpawnedShellIsNotNullAfterParentShellDestroyed) {
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
PostSync(shell->GetTaskRunners().GetUITaskRunner(), [&shell] {
// We must get engine on UI thread.
auto runtime_controller = shell->GetEngine()->GetRuntimeController();
PostSync(shell->GetTaskRunners().GetIOTaskRunner(),
[&shell, &runtime_controller] {
// We must get io_manager on IO thread.
auto io_manager = runtime_controller->GetIOManager();
// Check io_manager existence.
ASSERT_NE(io_manager.get(), nullptr);
ASSERT_NE(io_manager->GetSkiaUnrefQueue().get(), nullptr);
// Get io_manager directly from shell and check its existence.
ASSERT_NE(shell->GetIOManager().get(), nullptr);
});
});
std::unique_ptr<Shell> spawn;
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell, &settings,
&spawn] {
auto second_configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(second_configuration.IsValid());
second_configuration.SetEntrypoint("emptyMain");
const std::string initial_route("/foo");
MockPlatformViewDelegate platform_view_delegate;
auto child = shell->Spawn(
std::move(second_configuration), initial_route,
[&platform_view_delegate](Shell& shell) {
auto result = std::make_unique<MockPlatformView>(
platform_view_delegate, shell.GetTaskRunners());
ON_CALL(*result, CreateRenderingSurface())
.WillByDefault(::testing::Invoke(
[] { return std::make_unique<MockSurface>(); }));
return result;
},
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
spawn = std::move(child);
});
// Destroy the parent shell.
DestroyShell(std::move(shell));
PostSync(spawn->GetTaskRunners().GetUITaskRunner(), [&spawn] {
// We must get engine on UI thread.
auto runtime_controller = spawn->GetEngine()->GetRuntimeController();
PostSync(spawn->GetTaskRunners().GetIOTaskRunner(),
[&spawn, &runtime_controller] {
// We must get io_manager on IO thread.
auto io_manager = runtime_controller->GetIOManager();
// Check io_manager existence here.
ASSERT_NE(io_manager.get(), nullptr);
ASSERT_NE(io_manager->GetSkiaUnrefQueue().get(), nullptr);
// Get io_manager directly from shell and check its existence.
ASSERT_NE(spawn->GetIOManager().get(), nullptr);
});
});
// Destroy the child shell.
DestroyShell(std::move(spawn));
}
TEST_F(ShellTest, ImageGeneratorRegistryNotNullAfterParentShellDestroyed) {
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
std::unique_ptr<Shell> spawn;
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell, &settings,
&spawn] {
auto second_configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(second_configuration.IsValid());
second_configuration.SetEntrypoint("emptyMain");
const std::string initial_route("/foo");
MockPlatformViewDelegate platform_view_delegate;
auto child = shell->Spawn(
std::move(second_configuration), initial_route,
[&platform_view_delegate](Shell& shell) {
auto result = std::make_unique<MockPlatformView>(
platform_view_delegate, shell.GetTaskRunners());
ON_CALL(*result, CreateRenderingSurface())
.WillByDefault(::testing::Invoke(
[] { return std::make_unique<MockSurface>(); }));
return result;
},
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
spawn = std::move(child);
});
PostSync(spawn->GetTaskRunners().GetUITaskRunner(), [&spawn] {
std::shared_ptr<const DartIsolate> isolate =
spawn->GetEngine()->GetRuntimeController()->GetRootIsolate().lock();
ASSERT_TRUE(isolate);
ASSERT_TRUE(isolate->GetImageGeneratorRegistry());
});
// Destroy the parent shell.
DestroyShell(std::move(shell));
PostSync(spawn->GetTaskRunners().GetUITaskRunner(), [&spawn] {
std::shared_ptr<const DartIsolate> isolate =
spawn->GetEngine()->GetRuntimeController()->GetRootIsolate().lock();
ASSERT_TRUE(isolate);
ASSERT_TRUE(isolate->GetImageGeneratorRegistry());
});
// Destroy the child shell.
DestroyShell(std::move(spawn));
}
TEST_F(ShellTest, UpdateAssetResolverByTypeReplaces) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto shell = CreateShell(settings, task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
auto asset_manager = configuration.GetAssetManager();
shell->RunEngine(std::move(configuration), [&](auto result) {
ASSERT_EQ(result, Engine::RunStatus::Success);
});
auto platform_view =
std::make_unique<PlatformView>(*shell.get(), task_runners);
auto old_resolver = std::make_unique<TestAssetResolver>(
true, AssetResolver::AssetResolverType::kApkAssetProvider);
ASSERT_TRUE(old_resolver->IsValid());
asset_manager->PushBack(std::move(old_resolver));
auto updated_resolver = std::make_unique<TestAssetResolver>(
false, AssetResolver::AssetResolverType::kApkAssetProvider);
ASSERT_FALSE(updated_resolver->IsValidAfterAssetManagerChange());
platform_view->UpdateAssetResolverByType(
std::move(updated_resolver),
AssetResolver::AssetResolverType::kApkAssetProvider);
auto resolvers = asset_manager->TakeResolvers();
ASSERT_EQ(resolvers.size(), 2ull);
ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
ASSERT_FALSE(resolvers[1]->IsValidAfterAssetManagerChange());
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, UpdateAssetResolverByTypeAppends) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto shell = CreateShell(settings, task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
auto asset_manager = configuration.GetAssetManager();
shell->RunEngine(std::move(configuration), [&](auto result) {
ASSERT_EQ(result, Engine::RunStatus::Success);
});
auto platform_view =
std::make_unique<PlatformView>(*shell.get(), task_runners);
auto updated_resolver = std::make_unique<TestAssetResolver>(
false, AssetResolver::AssetResolverType::kApkAssetProvider);
ASSERT_FALSE(updated_resolver->IsValidAfterAssetManagerChange());
platform_view->UpdateAssetResolverByType(
std::move(updated_resolver),
AssetResolver::AssetResolverType::kApkAssetProvider);
auto resolvers = asset_manager->TakeResolvers();
ASSERT_EQ(resolvers.size(), 2ull);
ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
ASSERT_FALSE(resolvers[1]->IsValidAfterAssetManagerChange());
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, UpdateAssetResolverByTypeNull) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host(ThreadHost::ThreadHostConfig(
"io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform));
auto task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto shell = CreateShell(settings, task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
auto asset_manager = configuration.GetAssetManager();
RunEngine(shell.get(), std::move(configuration));
auto platform_view =
std::make_unique<PlatformView>(*shell.get(), task_runners);
auto old_resolver = std::make_unique<TestAssetResolver>(
true, AssetResolver::AssetResolverType::kApkAssetProvider);
ASSERT_TRUE(old_resolver->IsValid());
asset_manager->PushBack(std::move(old_resolver));
platform_view->UpdateAssetResolverByType(
nullptr, AssetResolver::AssetResolverType::kApkAssetProvider);
auto resolvers = asset_manager->TakeResolvers();
ASSERT_EQ(resolvers.size(), 2ull);
ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
ASSERT_TRUE(resolvers[1]->IsValidAfterAssetManagerChange());
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, UpdateAssetResolverByTypeDoesNotReplaceMismatchType) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
fml::MessageLoop::EnsureInitializedForCurrentThread();
auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto shell = CreateShell(settings, task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
auto asset_manager = configuration.GetAssetManager();
shell->RunEngine(std::move(configuration), [&](auto result) {
ASSERT_EQ(result, Engine::RunStatus::Success);
});
auto platform_view =
std::make_unique<PlatformView>(*shell.get(), task_runners);
auto old_resolver = std::make_unique<TestAssetResolver>(
true, AssetResolver::AssetResolverType::kAssetManager);
ASSERT_TRUE(old_resolver->IsValid());
asset_manager->PushBack(std::move(old_resolver));
auto updated_resolver = std::make_unique<TestAssetResolver>(
false, AssetResolver::AssetResolverType::kApkAssetProvider);
ASSERT_FALSE(updated_resolver->IsValidAfterAssetManagerChange());
platform_view->UpdateAssetResolverByType(
std::move(updated_resolver),
AssetResolver::AssetResolverType::kApkAssetProvider);
auto resolvers = asset_manager->TakeResolvers();
ASSERT_EQ(resolvers.size(), 3ull);
ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());
ASSERT_TRUE(resolvers[1]->IsValidAfterAssetManagerChange());
ASSERT_FALSE(resolvers[2]->IsValidAfterAssetManagerChange());
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, CanCreateShellsWithGLBackend) {
#if !SHELL_ENABLE_GL
// GL emulation does not exist on Fuchsia.
GTEST_SKIP();
#else
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.rendering_backend = ShellTestPlatformView::BackendType::kGLBackend,
}),
});
ASSERT_NE(shell, nullptr);
ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
PlatformViewNotifyCreated(shell.get());
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
#endif // !SHELL_ENABLE_GL
}
TEST_F(ShellTest, CanCreateShellsWithVulkanBackend) {
#if !SHELL_ENABLE_VULKAN
GTEST_SKIP();
#else
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.rendering_backend =
ShellTestPlatformView::BackendType::kVulkanBackend,
}),
});
ASSERT_NE(shell, nullptr);
ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
PlatformViewNotifyCreated(shell.get());
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
#endif // !SHELL_ENABLE_VULKAN
}
TEST_F(ShellTest, CanCreateShellsWithMetalBackend) {
#if !SHELL_ENABLE_METAL
GTEST_SKIP();
#else
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.rendering_backend =
ShellTestPlatformView::BackendType::kMetalBackend,
}),
});
ASSERT_NE(shell, nullptr);
ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
PlatformViewNotifyCreated(shell.get());
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
#endif // !SHELL_ENABLE_METAL
}
TEST_F(ShellTest, UserTagSetOnStartup) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
// Make sure the shell launch does not kick off the creation of the VM
// instance by already creating one upfront.
auto vm_settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(vm_settings);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
auto settings = vm_settings;
fml::AutoResetWaitableEvent isolate_create_latch;
// ensure that "AppStartUpTag" is set during isolate creation.
settings.root_isolate_create_callback = [&](const DartIsolate& isolate) {
Dart_Handle current_tag = Dart_GetCurrentUserTag();
Dart_Handle startup_tag = Dart_NewUserTag("AppStartUp");
EXPECT_TRUE(Dart_IdentityEquals(current_tag, startup_tag));
isolate_create_latch.Signal();
};
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
RunEngine(shell.get(), std::move(configuration));
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
DestroyShell(std::move(shell));
isolate_create_latch.Wait();
}
TEST_F(ShellTest, PrefetchDefaultFontManager) {
auto settings = CreateSettingsForFixture();
settings.prefetched_default_font_manager = true;
std::unique_ptr<Shell> shell;
auto get_font_manager_count = [&] {
fml::AutoResetWaitableEvent latch;
size_t font_manager_count;
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetUITaskRunner(),
[this, &shell, &latch, &font_manager_count]() {
font_manager_count =
GetFontCollection(shell.get())->GetFontManagersCount();
latch.Signal();
});
latch.Wait();
return font_manager_count;
};
size_t initial_font_manager_count = 0;
settings.root_isolate_create_callback = [&](const auto& isolate) {
ASSERT_GT(initial_font_manager_count, 0ul);
// Should not have fetched the default font manager yet, since the root
// isolate was only just created.
ASSERT_EQ(get_font_manager_count(), initial_font_manager_count);
};
shell = CreateShell(settings);
initial_font_manager_count = get_font_manager_count();
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
// If the prefetched_default_font_manager flag is set, then the default font
// manager will not be added until the engine starts running.
ASSERT_EQ(get_font_manager_count(), initial_font_manager_count + 1);
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, OnPlatformViewCreatedWhenUIThreadIsBusy) {
// This test will deadlock if the threading logic in
// Shell::OnCreatePlatformView is wrong.
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
fml::AutoResetWaitableEvent latch;
fml::TaskRunner::RunNowOrPostTask(shell->GetTaskRunners().GetUITaskRunner(),
[&latch]() { latch.Wait(); });
ShellTest::PlatformViewNotifyCreated(shell.get());
latch.Signal();
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, UIWorkAfterOnPlatformViewDestroyed) {
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("drawFrames");
fml::AutoResetWaitableEvent latch;
fml::AutoResetWaitableEvent notify_native_latch;
AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
notify_native_latch.Signal();
latch.Wait();
}));
RunEngine(shell.get(), std::move(configuration));
// Wait to make sure we get called back from Dart and thus have latched
// the UI thread before we create/destroy the platform view.
notify_native_latch.Wait();
ShellTest::PlatformViewNotifyCreated(shell.get());
fml::AutoResetWaitableEvent destroy_latch;
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[&shell, &destroy_latch]() {
shell->GetPlatformView()->NotifyDestroyed();
destroy_latch.Signal();
});
destroy_latch.Wait();
// Unlatch the UI thread and let it send us a scene to render.
latch.Signal();
// Flush the UI task runner to make sure we process the render/scheduleFrame
// request.
fml::AutoResetWaitableEvent ui_flush_latch;
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetUITaskRunner(),
[&ui_flush_latch]() { ui_flush_latch.Signal(); });
ui_flush_latch.Wait();
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, UsesPlatformMessageHandler) {
TaskRunners task_runners = GetTaskRunnersForFixture();
auto settings = CreateSettingsForFixture();
MockPlatformViewDelegate platform_view_delegate;
auto platform_message_handler =
std::make_shared<MockPlatformMessageHandler>();
int message_id = 1;
EXPECT_CALL(*platform_message_handler, HandlePlatformMessage(_));
EXPECT_CALL(*platform_message_handler,
InvokePlatformMessageEmptyResponseCallback(message_id));
Shell::CreateCallback<PlatformView> platform_view_create_callback =
[&platform_view_delegate, task_runners,
platform_message_handler](flutter::Shell& shell) {
auto result = std::make_unique<MockPlatformView>(platform_view_delegate,
task_runners);
EXPECT_CALL(*result, GetPlatformMessageHandler())
.WillOnce(Return(platform_message_handler));
return result;
};
auto shell = CreateShell({
.settings = settings,
.task_runners = task_runners,
.platform_view_create_callback = platform_view_create_callback,
});
EXPECT_EQ(platform_message_handler, shell->GetPlatformMessageHandler());
PostSync(task_runners.GetUITaskRunner(), [&shell]() {
size_t data_size = 4;
fml::MallocMapping bytes =
fml::MallocMapping(static_cast<uint8_t*>(malloc(data_size)), data_size);
fml::RefPtr<MockPlatformMessageResponse> response =
MockPlatformMessageResponse::Create();
auto message = std::make_unique<PlatformMessage>(
/*channel=*/"foo", /*data=*/std::move(bytes), /*response=*/response);
(static_cast<Engine::Delegate*>(shell.get()))
->OnEngineHandlePlatformMessage(std::move(message));
});
shell->GetPlatformMessageHandler()
->InvokePlatformMessageEmptyResponseCallback(message_id);
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, SpawnWorksWithOnError) {
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
configuration.SetEntrypoint("onErrorA");
auto second_configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(second_configuration.IsValid());
second_configuration.SetEntrypoint("onErrorB");
fml::CountDownLatch latch(2);
AddNativeCallback(
"NotifyErrorA", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
auto string_handle = Dart_GetNativeArgument(args, 0);
const char* c_str;
Dart_StringToCString(string_handle, &c_str);
EXPECT_STREQ(c_str, "Exception: I should be coming from A");
latch.CountDown();
}));
AddNativeCallback(
"NotifyErrorB", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
auto string_handle = Dart_GetNativeArgument(args, 0);
const char* c_str;
Dart_StringToCString(string_handle, &c_str);
EXPECT_STREQ(c_str, "Exception: I should be coming from B");
latch.CountDown();
}));
RunEngine(shell.get(), std::move(configuration));
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
PostSync(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[this, &spawner = shell, &second_configuration, &latch]() {
::testing::NiceMock<MockPlatformViewDelegate> platform_view_delegate;
auto spawn = spawner->Spawn(
std::move(second_configuration), "",
[&platform_view_delegate](Shell& shell) {
auto result =
std::make_unique<::testing::NiceMock<MockPlatformView>>(
platform_view_delegate, shell.GetTaskRunners());
ON_CALL(*result, CreateRenderingSurface())
.WillByDefault(::testing::Invoke([] {
return std::make_unique<::testing::NiceMock<MockSurface>>();
}));
return result;
},
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
ASSERT_NE(nullptr, spawn.get());
ASSERT_TRUE(ValidateShell(spawn.get()));
// Before destroying the shell, wait for expectations of the spawned
// isolate to be met.
latch.Wait();
DestroyShell(std::move(spawn));
});
DestroyShell(std::move(shell));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, ImmutableBufferLoadsAssetOnBackgroundThread) {
Settings settings = CreateSettingsForFixture();
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
fml::CountDownLatch latch(1);
AddNativeCallback("NotifyNative",
CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); }));
// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("testThatAssetLoadingHappensOnWorkerThread");
auto asset_manager = configuration.GetAssetManager();
auto test_resolver = std::make_unique<ThreadCheckingAssetResolver>(
shell->GetDartVM()->GetConcurrentMessageLoop());
auto leaked_resolver = test_resolver.get();
asset_manager->PushBack(std::move(test_resolver));
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
latch.Wait();
EXPECT_EQ(leaked_resolver->mapping_requests[0], "DoesNotExist");
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell), task_runners);
}
TEST_F(ShellTest, PictureToImageSync) {
#if !SHELL_ENABLE_GL
// This test uses the GL backend.
GTEST_SKIP();
#else
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.rendering_backend = ShellTestPlatformView::BackendType::kGLBackend,
}),
});
AddNativeCallback("NativeOnBeforeToImageSync",
CREATE_NATIVE_ENTRY([&](auto args) {
// nop
}));
fml::CountDownLatch latch(2);
AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
// Teardown and set up rasterizer again.
PlatformViewNotifyDestroyed(shell.get());
PlatformViewNotifyCreated(shell.get());
latch.CountDown();
}));
ASSERT_NE(shell, nullptr);
ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
PlatformViewNotifyCreated(shell.get());
configuration.SetEntrypoint("toImageSync");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
latch.Wait();
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
#endif // !SHELL_ENABLE_GL
}
TEST_F(ShellTest, PictureToImageSyncImpellerNoSurface) {
#if !SHELL_ENABLE_METAL
// This test uses the Metal backend.
GTEST_SKIP();
#else
auto settings = CreateSettingsForFixture();
settings.enable_impeller = true;
std::unique_ptr<Shell> shell = CreateShell({
.settings = settings,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.rendering_backend =
ShellTestPlatformView::BackendType::kMetalBackend,
}),
});
AddNativeCallback("NativeOnBeforeToImageSync",
CREATE_NATIVE_ENTRY([&](auto args) {
// nop
}));
fml::CountDownLatch latch(2);
AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
// Teardown and set up rasterizer again.
PlatformViewNotifyDestroyed(shell.get());
PlatformViewNotifyCreated(shell.get());
latch.CountDown();
}));
ASSERT_NE(shell, nullptr);
ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
// Important: Do not create the platform view yet!
// This test is making sure that the rasterizer can create the texture
// as expected without a surface.
configuration.SetEntrypoint("toImageSync");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
latch.Wait();
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
#endif // !SHELL_ENABLE_METAL
}
#if SHELL_ENABLE_GL
// This test uses the GL backend and refers to symbols in egl.h
TEST_F(ShellTest, PictureToImageSyncWithTrampledContext) {
// make it easier to trample the GL context by running on a single task
// runner.
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform);
auto task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell({
.settings = settings,
.task_runners = task_runners,
.platform_view_create_callback = ShellTestPlatformViewBuilder({
.rendering_backend = ShellTestPlatformView::BackendType::kGLBackend,
}),
});
AddNativeCallback(
"NativeOnBeforeToImageSync", CREATE_NATIVE_ENTRY([&](auto args) {
// Trample the GL context. If the rasterizer fails
// to make the right one current again, test will
// fail.
::eglMakeCurrent(::eglGetCurrentDisplay(), NULL, NULL, NULL);
}));
fml::CountDownLatch latch(2);
AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
// Teardown and set up rasterizer again.
PlatformViewNotifyDestroyed(shell.get());
PlatformViewNotifyCreated(shell.get());
latch.CountDown();
}));
ASSERT_NE(shell, nullptr);
ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
PlatformViewNotifyCreated(shell.get());
configuration.SetEntrypoint("toImageSync");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
latch.Wait();
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell), task_runners);
}
#endif // SHELL_ENABLE_GL
TEST_F(ShellTest, PluginUtilitiesCallbackHandleErrorHandling) {
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);
fml::AutoResetWaitableEvent latch;
bool test_passed;
AddNativeCallback("NotifyNativeBool", CREATE_NATIVE_ENTRY([&](auto args) {
Dart_Handle exception = nullptr;
test_passed = tonic::DartConverter<bool>::FromArguments(
args, 0, exception);
latch.Signal();
}));
ASSERT_NE(shell, nullptr);
ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
PlatformViewNotifyCreated(shell.get());
configuration.SetEntrypoint("testPluginUtilitiesCallbackHandle");
RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());
latch.Wait();
ASSERT_TRUE(test_passed);
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
}
TEST_F(ShellTest, NotifyIdleRejectsPastAndNearFuture) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform | ThreadHost::UI |
ThreadHost::IO | ThreadHost::RASTER);
auto platform_task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
thread_host.raster_thread->GetTaskRunner(),
thread_host.ui_thread->GetTaskRunner(),
thread_host.io_thread->GetTaskRunner());
auto shell = CreateShell(settings, task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));
fml::AutoResetWaitableEvent latch;
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
fml::TaskRunner::RunNowOrPostTask(
task_runners.GetUITaskRunner(), [&latch, &shell]() {
auto runtime_controller = const_cast<RuntimeController*>(
shell->GetEngine()->GetRuntimeController());
auto now = fml::TimeDelta::FromMicroseconds(Dart_TimelineGetMicros());
EXPECT_FALSE(runtime_controller->NotifyIdle(
now - fml::TimeDelta::FromMilliseconds(10)));
EXPECT_FALSE(runtime_controller->NotifyIdle(now));
EXPECT_FALSE(runtime_controller->NotifyIdle(
now + fml::TimeDelta::FromNanoseconds(100)));
EXPECT_TRUE(runtime_controller->NotifyIdle(
now + fml::TimeDelta::FromMilliseconds(100)));
latch.Signal();
});
latch.Wait();
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, NotifyIdleNotCalledInLatencyMode) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform | ThreadHost::UI |
ThreadHost::IO | ThreadHost::RASTER);
auto platform_task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
thread_host.raster_thread->GetTaskRunner(),
thread_host.ui_thread->GetTaskRunner(),
thread_host.io_thread->GetTaskRunner());
auto shell = CreateShell(settings, task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));
// we start off in balanced mode, where we expect idle notifications to
// succeed. After the first `NotifyNativeBool` we expect to be in latency
// mode, where we expect idle notifications to fail.
fml::CountDownLatch latch(2);
AddNativeCallback(
"NotifyNativeBool", CREATE_NATIVE_ENTRY([&](auto args) {
Dart_Handle exception = nullptr;
bool is_in_latency_mode =
tonic::DartConverter<bool>::FromArguments(args, 0, exception);
auto runtime_controller = const_cast<RuntimeController*>(
shell->GetEngine()->GetRuntimeController());
bool success =
runtime_controller->NotifyIdle(fml::TimeDelta::FromMicroseconds(
Dart_TimelineGetMicros() + 100000));
EXPECT_EQ(success, !is_in_latency_mode);
latch.CountDown();
}));
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("performanceModeImpactsNotifyIdle");
RunEngine(shell.get(), std::move(configuration));
latch.Wait();
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, NotifyDestroyed) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform | ThreadHost::UI |
ThreadHost::IO | ThreadHost::RASTER);
auto platform_task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
thread_host.raster_thread->GetTaskRunner(),
thread_host.ui_thread->GetTaskRunner(),
thread_host.io_thread->GetTaskRunner());
auto shell = CreateShell(settings, task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));
fml::CountDownLatch latch(1);
AddNativeCallback("NotifyDestroyed", CREATE_NATIVE_ENTRY([&](auto args) {
auto runtime_controller = const_cast<RuntimeController*>(
shell->GetEngine()->GetRuntimeController());
bool success = runtime_controller->NotifyDestroyed();
EXPECT_TRUE(success);
latch.CountDown();
}));
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("callNotifyDestroyed");
RunEngine(shell.get(), std::move(configuration));
latch.Wait();
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, PrintsErrorWhenPlatformMessageSentFromWrongThread) {
#if FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG || OS_FUCHSIA
GTEST_SKIP() << "Test is for debug mode only on non-fuchsia targets.";
#else
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform);
auto task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto shell = CreateShell(settings, task_runners);
{
fml::testing::LogCapture log_capture;
// The next call will result in a thread checker violation.
fml::ThreadChecker::DisableNextThreadCheckFailure();
SendPlatformMessage(shell.get(), std::make_unique<PlatformMessage>(
"com.test.plugin", nullptr));
EXPECT_THAT(
log_capture.str(),
::testing::EndsWith(
"The 'com.test.plugin' channel sent a message from native to "
"Flutter on a non-platform thread. Platform channel messages "
"must be sent on the platform thread. Failure to do so may "
"result in data loss or crashes, and must be fixed in the "
"plugin or application code creating that channel.\nSee "
"https://docs.flutter.dev/platform-integration/"
"platform-channels#channels-and-platform-threading for more "
"information.\n"));
}
{
fml::testing::LogCapture log_capture;
// The next call will result in a thread checker violation.
fml::ThreadChecker::DisableNextThreadCheckFailure();
SendPlatformMessage(shell.get(), std::make_unique<PlatformMessage>(
"com.test.plugin", nullptr));
EXPECT_EQ(log_capture.str(), "");
}
DestroyShell(std::move(shell), task_runners);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
#endif
}
TEST_F(ShellTest, DiesIfSoftwareRenderingAndImpellerAreEnabledDeathTest) {
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "Fuchsia";
#else
::testing::FLAGS_gtest_death_test_style = "threadsafe";
Settings settings = CreateSettingsForFixture();
settings.enable_impeller = true;
settings.enable_software_rendering = true;
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform);
auto task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
EXPECT_DEATH_IF_SUPPORTED(
CreateShell(settings, task_runners),
"Software rendering is incompatible with Impeller.");
#endif // OS_FUCHSIA
}
// Parse the arguments of NativeReportViewIdsCallback and
// store them in hasImplicitView and viewIds.
static void ParseViewIdsCallback(const Dart_NativeArguments& args,
bool* hasImplicitView,
std::vector<int64_t>* viewIds) {
Dart_Handle exception = nullptr;
viewIds->clear();
*hasImplicitView =
tonic::DartConverter<bool>::FromArguments(args, 0, exception);
ASSERT_EQ(exception, nullptr);
*viewIds = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
args, 1, exception);
ASSERT_EQ(exception, nullptr);
}
TEST_F(ShellTest, ShellStartsWithImplicitView) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
ASSERT_TRUE(shell);
bool hasImplicitView;
std::vector<int64_t> viewIds;
fml::AutoResetWaitableEvent reportLatch;
auto nativeViewIdsCallback = [&reportLatch, &hasImplicitView,
&viewIds](Dart_NativeArguments args) {
ParseViewIdsCallback(args, &hasImplicitView, &viewIds);
reportLatch.Signal();
};
AddNativeCallback("NativeReportViewIdsCallback",
CREATE_NATIVE_ENTRY(nativeViewIdsCallback));
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("testReportViewIds");
RunEngine(shell.get(), std::move(configuration));
reportLatch.Wait();
ASSERT_TRUE(hasImplicitView);
ASSERT_EQ(viewIds.size(), 1u);
ASSERT_EQ(viewIds[0], 0ll);
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell), task_runners);
}
// Tests that Shell::AddView and Shell::RemoveView works.
TEST_F(ShellTest, ShellCanAddViewOrRemoveView) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host(ThreadHost::ThreadHostConfig(
"io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform | ThreadHost::Type::RASTER |
ThreadHost::Type::IO | ThreadHost::Type::UI));
TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
thread_host.raster_thread->GetTaskRunner(),
thread_host.ui_thread->GetTaskRunner(),
thread_host.io_thread->GetTaskRunner());
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
ASSERT_TRUE(shell);
bool hasImplicitView;
std::vector<int64_t> viewIds;
fml::AutoResetWaitableEvent reportLatch;
auto nativeViewIdsCallback = [&reportLatch, &hasImplicitView,
&viewIds](Dart_NativeArguments args) {
ParseViewIdsCallback(args, &hasImplicitView, &viewIds);
reportLatch.Signal();
};
AddNativeCallback("NativeReportViewIdsCallback",
CREATE_NATIVE_ENTRY(nativeViewIdsCallback));
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("testReportViewIds");
RunEngine(shell.get(), std::move(configuration));
reportLatch.Wait();
ASSERT_TRUE(hasImplicitView);
ASSERT_EQ(viewIds.size(), 1u);
ASSERT_EQ(viewIds[0], 0ll);
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(),
[&shell] { shell->AddView(2, ViewportMetrics{}); });
reportLatch.Wait();
ASSERT_TRUE(hasImplicitView);
ASSERT_EQ(viewIds.size(), 2u);
ASSERT_EQ(viewIds[1], 2ll);
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(),
[&shell] { shell->RemoveView(2); });
reportLatch.Wait();
ASSERT_TRUE(hasImplicitView);
ASSERT_EQ(viewIds.size(), 1u);
ASSERT_EQ(viewIds[0], 0ll);
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(),
[&shell] { shell->AddView(4, ViewportMetrics{}); });
reportLatch.Wait();
ASSERT_TRUE(hasImplicitView);
ASSERT_EQ(viewIds.size(), 2u);
ASSERT_EQ(viewIds[1], 4ll);
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell), task_runners);
}
// Parse the arguments of NativeReportViewWidthsCallback and
// store them in viewWidths.
static void ParseViewWidthsCallback(const Dart_NativeArguments& args,
std::map<int64_t, int64_t>* viewWidths) {
Dart_Handle exception = nullptr;
viewWidths->clear();
std::vector<int64_t> viewWidthPacket =
tonic::DartConverter<std::vector<int64_t>>::FromArguments(args, 0,
exception);
ASSERT_EQ(exception, nullptr);
ASSERT_EQ(viewWidthPacket.size() % 2, 0ul);
for (size_t packetIndex = 0; packetIndex < viewWidthPacket.size();
packetIndex += 2) {
(*viewWidths)[viewWidthPacket[packetIndex]] =
viewWidthPacket[packetIndex + 1];
}
}
// Ensure that PlatformView::SetViewportMetrics and Shell::AddView that were
// dispatched before the isolate is run have been flushed to the Dart VM when
// the main function starts.
TEST_F(ShellTest, ShellFlushesPlatformStatesByMain) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host(ThreadHost::ThreadHostConfig(
"io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform | ThreadHost::Type::RASTER |
ThreadHost::Type::IO | ThreadHost::Type::UI));
TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
thread_host.raster_thread->GetTaskRunner(),
thread_host.ui_thread->GetTaskRunner(),
thread_host.io_thread->GetTaskRunner());
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
ASSERT_TRUE(shell);
PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] {
auto platform_view = shell->GetPlatformView();
// The construtor for ViewportMetrics{_, width, _, _, _} (only the 2nd
// argument matters in this test).
platform_view->SetViewportMetrics(0, ViewportMetrics{1, 10, 1, 0, 0});
shell->AddView(1, ViewportMetrics{1, 30, 1, 0, 0});
platform_view->SetViewportMetrics(0, ViewportMetrics{1, 20, 1, 0, 0});
});
bool first_report = true;
std::map<int64_t, int64_t> viewWidths;
fml::AutoResetWaitableEvent reportLatch;
auto nativeViewWidthsCallback = [&reportLatch, &viewWidths,
&first_report](Dart_NativeArguments args) {
EXPECT_TRUE(first_report);
first_report = false;
ParseViewWidthsCallback(args, &viewWidths);
reportLatch.Signal();
};
AddNativeCallback("NativeReportViewWidthsCallback",
CREATE_NATIVE_ENTRY(nativeViewWidthsCallback));
PlatformViewNotifyCreated(shell.get());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("testReportViewWidths");
RunEngine(shell.get(), std::move(configuration));
reportLatch.Wait();
EXPECT_EQ(viewWidths.size(), 2u);
EXPECT_EQ(viewWidths[0], 20ll);
EXPECT_EQ(viewWidths[1], 30ll);
PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell), task_runners);
}
} // namespace testing
} // namespace flutter
// NOLINTEND(clang-analyzer-core.StackAddressEscape)