chunhtai 26037dff87
Add set semantics enabled API and wire iOS a11y bridge (#161265)
fixes https://github.com/flutter/flutter/issues/158399

old pr https://github.com/flutter/engine/pull/56691

previously the only correct way to enable semantics is that ios
embedding receive signal from native OS, it call SetSemanticsEnabled to
shell and then to dart to enable semantics tree generation.

If for some reason framework decide to enable semantics first, e.g.
through SemanticsBinding.instance.ensureSemantics(typically due to
integration test or ci that wants to test semantics), the update will be
dropped in shell. Even if it later on receive signal from native OS to
turn on semantics, it can't construct the complete accessibility tree in
embedding because the updatesemantics sends diff update and previous
updates are gone forever. It will end up in a broken state.

This pr changes so that the only source of truth will be in the
framework side. When framework starts generating the the semantics tree,
it will call SetSemanticsTreeEnabled through dart:ui, and the embedding
needs to prepare itself to accept semantics update after receiving the
message.

This however require some refactoring on iOS embedding because it will
only create a11y bridge when receiving OS notification when assitive
technologies turns on.

This requires three phase transition

add an empty dart:ui API setSemanticsTreeEnabled
makes framework calls the empty API.
merge this pr with actual implementation of setSemanticsTreeEnabled

I will do the android part in a separate pr


## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2025-03-24 20:11:13 +00:00

2311 lines
88 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 "fml/task_runner.h"
#define RAPIDJSON_HAS_STDSTRING 1
#include "flutter/shell/common/shell.h"
#include <memory>
#include <sstream>
#include <utility>
#include <vector>
#include "flutter/assets/directory_asset_bundle.h"
#include "flutter/common/constants.h"
#include "flutter/common/graphics/persistent_cache.h"
#include "flutter/fml/base32.h"
#include "flutter/fml/file.h"
#include "flutter/fml/icu_util.h"
#include "flutter/fml/log_settings.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/message_loop.h"
#include "flutter/fml/paths.h"
#include "flutter/fml/trace_event.h"
#include "flutter/runtime/dart_vm.h"
#include "flutter/shell/common/base64.h"
#include "flutter/shell/common/engine.h"
#include "flutter/shell/common/skia_event_tracer_impl.h"
#include "flutter/shell/common/switches.h"
#include "flutter/shell/common/vsync_waiter.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#include "third_party/dart/runtime/include/dart_tools_api.h"
#include "third_party/skia/include/codec/SkBmpDecoder.h"
#include "third_party/skia/include/codec/SkCodec.h"
#include "third_party/skia/include/codec/SkGifDecoder.h"
#include "third_party/skia/include/codec/SkIcoDecoder.h"
#include "third_party/skia/include/codec/SkJpegDecoder.h"
#include "third_party/skia/include/codec/SkPngDecoder.h"
#include "third_party/skia/include/codec/SkWbmpDecoder.h"
#include "third_party/skia/include/codec/SkWebpDecoder.h"
#include "third_party/skia/include/core/SkGraphics.h"
#include "third_party/tonic/common/log.h"
namespace flutter {
constexpr char kSkiaChannel[] = "flutter/skia";
constexpr char kSystemChannel[] = "flutter/system";
constexpr char kTypeKey[] = "type";
constexpr char kFontChange[] = "fontsChange";
namespace {
std::unique_ptr<Engine> CreateEngine(
Engine::Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker,
DartVM& vm,
const fml::RefPtr<const DartSnapshot>& isolate_snapshot,
const TaskRunners& task_runners,
const PlatformData& platform_data,
const Settings& settings,
std::unique_ptr<Animator> animator,
const fml::WeakPtr<IOManager>& io_manager,
const fml::RefPtr<SkiaUnrefQueue>& unref_queue,
const fml::TaskRunnerAffineWeakPtr<SnapshotDelegate>& snapshot_delegate,
const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch,
impeller::RuntimeStageBackend runtime_stage_backend) {
return std::make_unique<Engine>(delegate, //
dispatcher_maker, //
vm, //
isolate_snapshot, //
task_runners, //
platform_data, //
settings, //
std::move(animator), //
io_manager, //
unref_queue, //
snapshot_delegate, //
gpu_disabled_switch, //
runtime_stage_backend);
}
void RegisterCodecsWithSkia() {
// These are in the order they will be attempted to be decoded from.
// If we have data to back it up, we can order these by "frequency used in
// the wild" for a very small performance bump, but for now we mirror the
// order Skia had them in.
SkCodecs::Register(SkPngDecoder::Decoder());
SkCodecs::Register(SkJpegDecoder::Decoder());
SkCodecs::Register(SkWebpDecoder::Decoder());
SkCodecs::Register(SkGifDecoder::Decoder());
SkCodecs::Register(SkBmpDecoder::Decoder());
SkCodecs::Register(SkWbmpDecoder::Decoder());
SkCodecs::Register(SkIcoDecoder::Decoder());
}
// Though there can be multiple shells, some settings apply to all components in
// the process. These have to be set up before the shell or any of its
// sub-components can be initialized. In a perfect world, this would be empty.
// TODO(chinmaygarde): The unfortunate side effect of this call is that settings
// that cause shell initialization failures will still lead to some of their
// settings being applied.
void PerformInitializationTasks(Settings& settings) {
{
fml::LogSettings log_settings;
log_settings.min_log_level =
settings.verbose_logging ? fml::kLogInfo : fml::kLogError;
fml::SetLogSettings(log_settings);
}
static std::once_flag gShellSettingsInitialization = {};
std::call_once(gShellSettingsInitialization, [&settings] {
tonic::SetLogHandler(
[](const char* message) { FML_LOG(ERROR) << message; });
if (settings.trace_skia) {
InitSkiaEventTracer(settings.trace_skia, settings.trace_skia_allowlist);
}
if (!settings.trace_allowlist.empty()) {
fml::tracing::TraceSetAllowlist(settings.trace_allowlist);
}
if (!settings.skia_deterministic_rendering_on_cpu) {
SkGraphics::Init();
} else {
FML_DLOG(INFO) << "Skia deterministic rendering is enabled.";
}
RegisterCodecsWithSkia();
if (settings.icu_initialization_required) {
if (!settings.icu_data_path.empty()) {
fml::icu::InitializeICU(settings.icu_data_path);
} else if (settings.icu_mapper) {
fml::icu::InitializeICUFromMapping(settings.icu_mapper());
} else {
FML_DLOG(WARNING) << "Skipping ICU initialization in the shell.";
}
}
});
#if !SLIMPELLER
PersistentCache::SetCacheSkSL(settings.cache_sksl);
#endif // !SLIMPELLER
}
} // namespace
std::pair<DartVMRef, fml::RefPtr<const DartSnapshot>>
Shell::InferVmInitDataFromSettings(Settings& settings) {
// Always use the `vm_snapshot` and `isolate_snapshot` provided by the
// settings to launch the VM. If the VM is already running, the snapshot
// arguments are ignored.
auto vm_snapshot = DartSnapshot::VMSnapshotFromSettings(settings);
auto isolate_snapshot = DartSnapshot::IsolateSnapshotFromSettings(settings);
auto vm = DartVMRef::Create(settings, vm_snapshot, isolate_snapshot);
// If the settings did not specify an `isolate_snapshot`, fall back to the
// one the VM was launched with.
if (!isolate_snapshot) {
isolate_snapshot = vm->GetVMData()->GetIsolateSnapshot();
}
return {std::move(vm), isolate_snapshot};
}
std::unique_ptr<Shell> Shell::Create(
const PlatformData& platform_data,
const TaskRunners& task_runners,
Settings settings,
const Shell::CreateCallback<PlatformView>& on_create_platform_view,
const Shell::CreateCallback<Rasterizer>& on_create_rasterizer,
bool is_gpu_disabled) {
// This must come first as it initializes tracing.
PerformInitializationTasks(settings);
TRACE_EVENT0("flutter", "Shell::Create");
auto [vm, isolate_snapshot] = InferVmInitDataFromSettings(settings);
auto resource_cache_limit_calculator =
std::make_shared<ResourceCacheLimitCalculator>(
settings.resource_cache_max_bytes_threshold);
return CreateWithSnapshot(platform_data, //
task_runners, //
/*parent_thread_merger=*/nullptr, //
/*parent_io_manager=*/nullptr, //
resource_cache_limit_calculator, //
settings, //
std::move(vm), //
std::move(isolate_snapshot), //
on_create_platform_view, //
on_create_rasterizer, //
CreateEngine, is_gpu_disabled);
}
static impeller::RuntimeStageBackend DetermineRuntimeStageBackend(
const std::shared_ptr<impeller::Context>& impeller_context) {
if (!impeller_context) {
return impeller::RuntimeStageBackend::kSkSL;
}
return impeller_context->GetRuntimeStageBackend();
}
std::unique_ptr<Shell> Shell::CreateShellOnPlatformThread(
DartVMRef vm,
fml::RefPtr<fml::RasterThreadMerger> parent_merger,
std::shared_ptr<ShellIOManager> parent_io_manager,
const std::shared_ptr<ResourceCacheLimitCalculator>&
resource_cache_limit_calculator,
const TaskRunners& task_runners,
const PlatformData& platform_data,
const Settings& settings,
fml::RefPtr<const DartSnapshot> isolate_snapshot,
const Shell::CreateCallback<PlatformView>& on_create_platform_view,
const Shell::CreateCallback<Rasterizer>& on_create_rasterizer,
const Shell::EngineCreateCallback& on_create_engine,
bool is_gpu_disabled) {
if (!task_runners.IsValid()) {
FML_LOG(ERROR) << "Task runners to run the shell were invalid.";
return nullptr;
}
auto shell = std::unique_ptr<Shell>(
new Shell(std::move(vm), task_runners, std::move(parent_merger),
resource_cache_limit_calculator, settings, is_gpu_disabled));
// Create the platform view on the platform thread (this thread).
auto platform_view = on_create_platform_view(*shell.get());
if (!platform_view || !platform_view->GetWeakPtr()) {
return nullptr;
}
// Create the rasterizer on the raster thread.
std::promise<std::unique_ptr<Rasterizer>> rasterizer_promise;
auto rasterizer_future = rasterizer_promise.get_future();
std::promise<fml::TaskRunnerAffineWeakPtr<SnapshotDelegate>>
snapshot_delegate_promise;
auto snapshot_delegate_future = snapshot_delegate_promise.get_future();
fml::TaskRunner::RunNowOrPostTask(
task_runners.GetRasterTaskRunner(),
[&rasterizer_promise, //
&snapshot_delegate_promise,
on_create_rasterizer, //
shell = shell.get(), //
impeller_context = platform_view->GetImpellerContext() //
]() {
TRACE_EVENT0("flutter", "ShellSetupGPUSubsystem");
std::unique_ptr<Rasterizer> rasterizer(on_create_rasterizer(*shell));
rasterizer->SetImpellerContext(impeller_context);
snapshot_delegate_promise.set_value(rasterizer->GetSnapshotDelegate());
rasterizer_promise.set_value(std::move(rasterizer));
});
// Ask the platform view for the vsync waiter. This will be used by the engine
// to create the animator.
auto vsync_waiter = platform_view->CreateVSyncWaiter();
if (!vsync_waiter) {
return nullptr;
}
// Create the IO manager on the IO thread. The IO manager must be initialized
// first because it has state that the other subsystems depend on. It must
// first be booted and the necessary references obtained to initialize the
// other subsystems.
std::promise<std::shared_ptr<ShellIOManager>> io_manager_promise;
auto io_manager_future = io_manager_promise.get_future();
std::promise<fml::WeakPtr<ShellIOManager>> weak_io_manager_promise;
auto weak_io_manager_future = weak_io_manager_promise.get_future();
std::promise<fml::RefPtr<SkiaUnrefQueue>> unref_queue_promise;
auto unref_queue_future = unref_queue_promise.get_future();
auto io_task_runner = shell->GetTaskRunners().GetIOTaskRunner();
// The platform_view will be stored into shell's platform_view_ in
// shell->Setup(std::move(platform_view), ...) at the end.
PlatformView* platform_view_ptr = platform_view.get();
fml::TaskRunner::RunNowOrPostTask(
io_task_runner,
[&io_manager_promise, //
&weak_io_manager_promise, //
&parent_io_manager, //
&unref_queue_promise, //
platform_view_ptr, //
io_task_runner, //
is_backgrounded_sync_switch = shell->GetIsGpuDisabledSyncSwitch() //
]() {
TRACE_EVENT0("flutter", "ShellSetupIOSubsystem");
std::shared_ptr<ShellIOManager> io_manager;
if (parent_io_manager) {
io_manager = parent_io_manager;
} else {
io_manager = std::make_shared<ShellIOManager>(
platform_view_ptr->CreateResourceContext(), // resource context
is_backgrounded_sync_switch, // sync switch
io_task_runner, // unref queue task runner
platform_view_ptr->GetImpellerContext() // impeller context
);
}
weak_io_manager_promise.set_value(io_manager->GetWeakPtr());
unref_queue_promise.set_value(io_manager->GetSkiaUnrefQueue());
io_manager_promise.set_value(io_manager);
});
// Send dispatcher_maker to the engine constructor because shell won't have
// platform_view set until Shell::Setup is called later.
auto dispatcher_maker = platform_view->GetDispatcherMaker();
// Create the engine on the UI thread.
std::promise<std::unique_ptr<Engine>> engine_promise;
auto engine_future = engine_promise.get_future();
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetUITaskRunner(),
fml::MakeCopyable([&engine_promise, //
shell = shell.get(), //
&dispatcher_maker, //
&platform_data, //
isolate_snapshot = std::move(isolate_snapshot), //
vsync_waiter = std::move(vsync_waiter), //
&weak_io_manager_future, //
&snapshot_delegate_future, //
&unref_queue_future, //
&on_create_engine,
runtime_stage_backend = DetermineRuntimeStageBackend(
platform_view->GetImpellerContext())]() mutable {
TRACE_EVENT0("flutter", "ShellSetupUISubsystem");
const auto& task_runners = shell->GetTaskRunners();
// The animator is owned by the UI thread but it gets its vsync pulses
// from the platform.
auto animator = std::make_unique<Animator>(*shell, task_runners,
std::move(vsync_waiter));
engine_promise.set_value(on_create_engine(
*shell, //
dispatcher_maker, //
*shell->GetDartVM(), //
std::move(isolate_snapshot), //
task_runners, //
platform_data, //
shell->GetSettings(), //
std::move(animator), //
weak_io_manager_future.get(), //
unref_queue_future.get(), //
snapshot_delegate_future.get(), //
shell->is_gpu_disabled_sync_switch_, //
runtime_stage_backend //
));
}));
if (!shell->Setup(std::move(platform_view), //
engine_future.get(), //
rasterizer_future.get(), //
io_manager_future.get()) //
) {
return nullptr;
}
return shell;
}
std::unique_ptr<Shell> Shell::CreateWithSnapshot(
const PlatformData& platform_data,
const TaskRunners& task_runners,
const fml::RefPtr<fml::RasterThreadMerger>& parent_thread_merger,
const std::shared_ptr<ShellIOManager>& parent_io_manager,
const std::shared_ptr<ResourceCacheLimitCalculator>&
resource_cache_limit_calculator,
Settings settings,
DartVMRef vm,
fml::RefPtr<const DartSnapshot> isolate_snapshot,
const Shell::CreateCallback<PlatformView>& on_create_platform_view,
const Shell::CreateCallback<Rasterizer>& on_create_rasterizer,
const Shell::EngineCreateCallback& on_create_engine,
bool is_gpu_disabled) {
// This must come first as it initializes tracing.
PerformInitializationTasks(settings);
TRACE_EVENT0("flutter", "Shell::CreateWithSnapshot");
const bool callbacks_valid =
on_create_platform_view && on_create_rasterizer && on_create_engine;
if (!task_runners.IsValid() || !callbacks_valid) {
return nullptr;
}
fml::AutoResetWaitableEvent latch;
std::unique_ptr<Shell> shell;
auto platform_task_runner = task_runners.GetPlatformTaskRunner();
fml::TaskRunner::RunNowOrPostTask(
platform_task_runner,
fml::MakeCopyable([&latch, //
&shell, //
parent_thread_merger, //
parent_io_manager, //
resource_cache_limit_calculator, //
task_runners = task_runners, //
platform_data = platform_data, //
settings = settings, //
vm = std::move(vm), //
isolate_snapshot = std::move(isolate_snapshot), //
on_create_platform_view = on_create_platform_view, //
on_create_rasterizer = on_create_rasterizer, //
on_create_engine = on_create_engine,
is_gpu_disabled]() mutable {
shell = CreateShellOnPlatformThread(std::move(vm), //
parent_thread_merger, //
parent_io_manager, //
resource_cache_limit_calculator, //
task_runners, //
platform_data, //
settings, //
std::move(isolate_snapshot), //
on_create_platform_view, //
on_create_rasterizer, //
on_create_engine, is_gpu_disabled);
latch.Signal();
}));
latch.Wait();
return shell;
}
Shell::Shell(DartVMRef vm,
const TaskRunners& task_runners,
fml::RefPtr<fml::RasterThreadMerger> parent_merger,
const std::shared_ptr<ResourceCacheLimitCalculator>&
resource_cache_limit_calculator,
const Settings& settings,
bool is_gpu_disabled)
: task_runners_(task_runners),
parent_raster_thread_merger_(std::move(parent_merger)),
resource_cache_limit_calculator_(resource_cache_limit_calculator),
settings_(settings),
vm_(std::move(vm)),
is_gpu_disabled_sync_switch_(new fml::SyncSwitch(is_gpu_disabled)),
weak_factory_gpu_(nullptr),
weak_factory_(this) {
FML_CHECK(!settings.enable_software_rendering || !settings.enable_impeller)
<< "Software rendering is incompatible with Impeller.";
if (!settings.enable_impeller && settings.warn_on_impeller_opt_out) {
FML_LOG(IMPORTANT)
<< "[Action Required] The application opted out of Impeller by either "
"using the --no-enable-impeller flag or FLTEnableImpeller=false "
"plist flag. This option is going to go away in an upcoming Flutter "
"release. Remove the explicit opt-out. If you need to opt-out, "
"report a bug describing the issue.";
}
FML_CHECK(vm_) << "Must have access to VM to create a shell.";
FML_DCHECK(task_runners_.IsValid());
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
display_manager_ = std::make_unique<DisplayManager>();
resource_cache_limit_calculator->AddResourceCacheLimitItem(
weak_factory_.GetWeakPtr());
// Generate a WeakPtrFactory for use with the raster thread. This does not
// need to wait on a latch because it can only ever be used from the raster
// thread from this class, so we have ordering guarantees.
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetRasterTaskRunner(), fml::MakeCopyable([this]() mutable {
this->weak_factory_gpu_ =
std::make_unique<fml::TaskRunnerAffineWeakPtrFactory<Shell>>(this);
}));
// Install service protocol handlers.
service_protocol_handlers_[ServiceProtocol::kScreenshotExtensionName] = {
task_runners_.GetRasterTaskRunner(),
std::bind(&Shell::OnServiceProtocolScreenshot, this,
std::placeholders::_1, std::placeholders::_2)};
service_protocol_handlers_[ServiceProtocol::kScreenshotSkpExtensionName] = {
task_runners_.GetRasterTaskRunner(),
std::bind(&Shell::OnServiceProtocolScreenshotSKP, this,
std::placeholders::_1, std::placeholders::_2)};
service_protocol_handlers_[ServiceProtocol::kRunInViewExtensionName] = {
task_runners_.GetUITaskRunner(),
std::bind(&Shell::OnServiceProtocolRunInView, this, std::placeholders::_1,
std::placeholders::_2)};
service_protocol_handlers_
[ServiceProtocol::kFlushUIThreadTasksExtensionName] = {
task_runners_.GetUITaskRunner(),
std::bind(&Shell::OnServiceProtocolFlushUIThreadTasks, this,
std::placeholders::_1, std::placeholders::_2)};
service_protocol_handlers_
[ServiceProtocol::kSetAssetBundlePathExtensionName] = {
task_runners_.GetUITaskRunner(),
std::bind(&Shell::OnServiceProtocolSetAssetBundlePath, this,
std::placeholders::_1, std::placeholders::_2)};
service_protocol_handlers_
[ServiceProtocol::kGetDisplayRefreshRateExtensionName] = {
task_runners_.GetUITaskRunner(),
std::bind(&Shell::OnServiceProtocolGetDisplayRefreshRate, this,
std::placeholders::_1, std::placeholders::_2)};
service_protocol_handlers_[ServiceProtocol::kGetSkSLsExtensionName] = {
task_runners_.GetIOTaskRunner(),
std::bind(&Shell::OnServiceProtocolGetSkSLs, this, std::placeholders::_1,
std::placeholders::_2)};
service_protocol_handlers_
[ServiceProtocol::kEstimateRasterCacheMemoryExtensionName] = {
task_runners_.GetRasterTaskRunner(),
std::bind(&Shell::OnServiceProtocolEstimateRasterCacheMemory, this,
std::placeholders::_1, std::placeholders::_2)};
service_protocol_handlers_[ServiceProtocol::kReloadAssetFonts] = {
task_runners_.GetPlatformTaskRunner(),
std::bind(&Shell::OnServiceProtocolReloadAssetFonts, this,
std::placeholders::_1, std::placeholders::_2)};
}
Shell::~Shell() {
#if !SLIMPELLER
PersistentCache::GetCacheForProcess()->RemoveWorkerTaskRunner(
task_runners_.GetIOTaskRunner());
#endif // !SLIMPELLER
vm_->GetServiceProtocol()->RemoveHandler(this);
fml::AutoResetWaitableEvent platiso_latch, ui_latch, gpu_latch,
platform_latch, io_latch;
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetPlatformTaskRunner(),
fml::MakeCopyable([this, &platiso_latch]() mutable {
engine_->ShutdownPlatformIsolates();
platiso_latch.Signal();
}));
platiso_latch.Wait();
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetUITaskRunner(),
fml::MakeCopyable([this, &ui_latch]() mutable {
engine_.reset();
ui_latch.Signal();
}));
ui_latch.Wait();
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetRasterTaskRunner(),
fml::MakeCopyable(
[this, rasterizer = std::move(rasterizer_), &gpu_latch]() mutable {
rasterizer.reset();
this->weak_factory_gpu_.reset();
gpu_latch.Signal();
}));
gpu_latch.Wait();
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetIOTaskRunner(),
fml::MakeCopyable([io_manager = std::move(io_manager_),
platform_view = platform_view_.get(),
&io_latch]() mutable {
io_manager.reset();
if (platform_view) {
platform_view->ReleaseResourceContext();
}
io_latch.Signal();
}));
io_latch.Wait();
// The platform view must go last because it may be holding onto platform side
// counterparts to resources owned by subsystems running on other threads. For
// example, the NSOpenGLContext on the Mac.
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetPlatformTaskRunner(),
fml::MakeCopyable([platform_view = std::move(platform_view_),
&platform_latch]() mutable {
platform_view.reset();
platform_latch.Signal();
}));
platform_latch.Wait();
}
std::unique_ptr<Shell> Shell::Spawn(
RunConfiguration run_configuration,
const std::string& initial_route,
const CreateCallback<PlatformView>& on_create_platform_view,
const CreateCallback<Rasterizer>& on_create_rasterizer) const {
FML_DCHECK(task_runners_.IsValid());
// It's safe to store this value since it is set on the platform thread.
bool is_gpu_disabled = false;
GetIsGpuDisabledSyncSwitch()->Execute(
fml::SyncSwitch::Handlers()
.SetIfFalse([&is_gpu_disabled] { is_gpu_disabled = false; })
.SetIfTrue([&is_gpu_disabled] { is_gpu_disabled = true; }));
std::unique_ptr<Shell> result = CreateWithSnapshot(
PlatformData{}, task_runners_, rasterizer_->GetRasterThreadMerger(),
io_manager_, resource_cache_limit_calculator_, GetSettings(), vm_,
vm_->GetVMData()->GetIsolateSnapshot(), on_create_platform_view,
on_create_rasterizer,
[engine = this->engine_.get(), initial_route](
Engine::Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker, DartVM& vm,
const fml::RefPtr<const DartSnapshot>& isolate_snapshot,
const TaskRunners& task_runners, const PlatformData& platform_data,
const Settings& settings, std::unique_ptr<Animator> animator,
const fml::WeakPtr<IOManager>& io_manager,
const fml::RefPtr<SkiaUnrefQueue>& unref_queue,
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate,
const std::shared_ptr<fml::SyncSwitch>& is_gpu_disabled_sync_switch,
impeller::RuntimeStageBackend runtime_stage_backend) {
return engine->Spawn(
/*delegate=*/delegate,
/*dispatcher_maker=*/dispatcher_maker,
/*settings=*/settings,
/*animator=*/std::move(animator),
/*initial_route=*/initial_route,
/*io_manager=*/io_manager,
/*snapshot_delegate=*/std::move(snapshot_delegate),
/*gpu_disabled_switch=*/is_gpu_disabled_sync_switch);
},
is_gpu_disabled);
result->RunEngine(std::move(run_configuration));
return result;
}
void Shell::NotifyLowMemoryWarning() const {
auto trace_id = fml::tracing::TraceNonce();
TRACE_EVENT_ASYNC_BEGIN0("flutter", "Shell::NotifyLowMemoryWarning",
trace_id);
// This does not require a current isolate but does require a running VM.
// Since a valid shell will not be returned to the embedder without a valid
// DartVMRef, we can be certain that this is a safe spot to assume a VM is
// running.
::Dart_NotifyLowMemory();
task_runners_.GetRasterTaskRunner()->PostTask(
[rasterizer = rasterizer_->GetWeakPtr(), trace_id = trace_id]() {
if (rasterizer) {
rasterizer->NotifyLowMemoryWarning();
}
TRACE_EVENT_ASYNC_END0("flutter", "Shell::NotifyLowMemoryWarning",
trace_id);
});
// The IO Manager uses resource cache limits of 0, so it is not necessary
// to purge them.
}
void Shell::FlushMicrotaskQueue() const {
if (engine_) {
engine_->FlushMicrotaskQueue();
}
}
void Shell::RunEngine(RunConfiguration run_configuration) {
RunEngine(std::move(run_configuration), nullptr);
}
void Shell::RunEngine(
RunConfiguration run_configuration,
const std::function<void(Engine::RunStatus)>& result_callback) {
auto result = [platform_runner = task_runners_.GetPlatformTaskRunner(),
result_callback](Engine::RunStatus run_result) {
if (!result_callback) {
return;
}
platform_runner->PostTask(
[result_callback, run_result]() { result_callback(run_result); });
};
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetUITaskRunner(),
fml::MakeCopyable(
[run_configuration = std::move(run_configuration),
weak_engine = weak_engine_, result]() mutable {
if (!weak_engine) {
FML_LOG(ERROR)
<< "Could not launch engine with configuration - no engine.";
result(Engine::RunStatus::Failure);
return;
}
auto run_result = weak_engine->Run(std::move(run_configuration));
if (run_result == flutter::Engine::RunStatus::Failure) {
FML_LOG(ERROR) << "Could not launch engine with configuration.";
}
result(run_result);
}));
}
std::optional<DartErrorCode> Shell::GetUIIsolateLastError() const {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
if (!weak_engine_) {
return std::nullopt;
}
switch (weak_engine_->GetUIIsolateLastError()) {
case tonic::kCompilationErrorType:
return DartErrorCode::CompilationError;
case tonic::kApiErrorType:
return DartErrorCode::ApiError;
case tonic::kUnknownErrorType:
return DartErrorCode::UnknownError;
case tonic::kNoError:
return DartErrorCode::NoError;
}
return DartErrorCode::UnknownError;
}
bool Shell::EngineHasLivePorts() const {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
if (!weak_engine_) {
return false;
}
return weak_engine_->UIIsolateHasLivePorts();
}
bool Shell::EngineHasPendingMicrotasks() const {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
if (!weak_engine_) {
return false;
}
return weak_engine_->UIIsolateHasPendingMicrotasks();
}
bool Shell::IsSetup() const {
return is_set_up_;
}
bool Shell::Setup(std::unique_ptr<PlatformView> platform_view,
std::unique_ptr<Engine> engine,
std::unique_ptr<Rasterizer> rasterizer,
const std::shared_ptr<ShellIOManager>& io_manager) {
if (is_set_up_) {
return false;
}
if (!platform_view || !engine || !rasterizer || !io_manager) {
return false;
}
platform_view_ = std::move(platform_view);
platform_message_handler_ = platform_view_->GetPlatformMessageHandler();
route_messages_through_platform_thread_.store(true);
task_runners_.GetPlatformTaskRunner()->PostTask(
[self = weak_factory_.GetWeakPtr()] {
if (self) {
self->route_messages_through_platform_thread_.store(false);
}
});
engine_ = std::move(engine);
rasterizer_ = std::move(rasterizer);
io_manager_ = io_manager;
// Set the external view embedder for the rasterizer.
auto view_embedder = platform_view_->CreateExternalViewEmbedder();
rasterizer_->SetExternalViewEmbedder(view_embedder);
rasterizer_->SetSnapshotSurfaceProducer(
platform_view_->CreateSnapshotSurfaceProducer());
// The weak ptr must be generated in the platform thread which owns the unique
// ptr.
weak_engine_ = engine_->GetWeakPtr();
weak_rasterizer_ = rasterizer_->GetWeakPtr();
weak_platform_view_ = platform_view_->GetWeakPtr();
// Add the implicit view with empty metrics.
engine_->AddView(kFlutterImplicitViewId, ViewportMetrics{}, [](bool added) {
FML_DCHECK(added) << "Failed to add the implicit view";
});
// Setup the time-consuming default font manager right after engine created.
if (!settings_.prefetched_default_font_manager) {
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(),
[engine = weak_engine_] {
if (engine) {
engine->SetupDefaultFontManager();
}
});
}
is_set_up_ = true;
#if !SLIMPELLER
PersistentCache::GetCacheForProcess()->AddWorkerTaskRunner(
task_runners_.GetIOTaskRunner());
PersistentCache::GetCacheForProcess()->SetIsDumpingSkp(
settings_.dump_skp_on_shader_compilation);
if (settings_.purge_persistent_cache) {
PersistentCache::GetCacheForProcess()->Purge();
}
#endif // !SLIMPELLER
return true;
}
const Settings& Shell::GetSettings() const {
return settings_;
}
const TaskRunners& Shell::GetTaskRunners() const {
return task_runners_;
}
const fml::RefPtr<fml::RasterThreadMerger> Shell::GetParentRasterThreadMerger()
const {
return parent_raster_thread_merger_;
}
fml::TaskRunnerAffineWeakPtr<Rasterizer> Shell::GetRasterizer() const {
FML_DCHECK(is_set_up_);
return weak_rasterizer_;
}
fml::WeakPtr<Engine> Shell::GetEngine() {
FML_DCHECK(is_set_up_);
return weak_engine_;
}
fml::WeakPtr<PlatformView> Shell::GetPlatformView() {
FML_DCHECK(is_set_up_);
return weak_platform_view_;
}
fml::WeakPtr<ShellIOManager> Shell::GetIOManager() {
FML_DCHECK(is_set_up_);
return io_manager_->GetWeakPtr();
}
DartVM* Shell::GetDartVM() {
return &vm_;
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) {
TRACE_EVENT0("flutter", "Shell::OnPlatformViewCreated");
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
// Prevent any request to change the thread configuration for raster and
// platform queues while the platform view is being created.
//
// This prevents false positives such as this method starts assuming that the
// raster and platform queues have a given thread configuration, but then the
// configuration is changed by a task, and the assumption is no longer true.
//
// This incorrect assumption can lead to deadlock.
// See `should_post_raster_task` for more.
rasterizer_->DisableThreadMergerIfNeeded();
// The normal flow executed by this method is that the platform thread is
// starting the sequence and waiting on the latch. Later the UI thread posts
// raster_task to the raster thread which signals the latch. If the raster and
// the platform threads are the same this results in a deadlock as the
// raster_task will never be posted to the platform/raster thread that is
// blocked on a latch. To avoid the described deadlock, if the raster and the
// platform threads are the same, should_post_raster_task will be false, and
// then instead of posting a task to the raster thread, the ui thread just
// signals the latch and the platform/raster thread follows with executing
// raster_task.
const bool should_post_raster_task =
!task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread();
auto raster_task = fml::MakeCopyable(
[&waiting_for_first_frame = waiting_for_first_frame_, //
rasterizer = rasterizer_->GetWeakPtr(), //
surface = std::move(surface) //
]() mutable {
if (rasterizer) {
// Enables the thread merger which may be used by the external view
// embedder.
rasterizer->EnableThreadMergerIfNeeded();
rasterizer->Setup(std::move(surface));
}
waiting_for_first_frame.store(true);
});
auto ui_task = [engine = engine_->GetWeakPtr()] {
if (engine) {
engine->ScheduleFrame();
}
};
// Threading: Capture platform view by raw pointer and not the weak pointer.
// We are going to use the pointer on the IO thread which is not safe with a
// weak pointer. However, we are preventing the platform view from being
// collected by using a latch.
auto* platform_view = platform_view_.get();
FML_DCHECK(platform_view);
fml::AutoResetWaitableEvent latch;
auto io_task = [io_manager = io_manager_->GetWeakPtr(), platform_view,
ui_task_runner = task_runners_.GetUITaskRunner(), ui_task,
raster_task_runner = task_runners_.GetRasterTaskRunner(),
raster_task, should_post_raster_task, &latch] {
if (io_manager && !io_manager->GetResourceContext()) {
sk_sp<GrDirectContext> resource_context =
platform_view->CreateResourceContext();
io_manager->NotifyResourceContextAvailable(resource_context);
}
// Step 1: Post a task on the UI thread to tell the engine that it has
// an output surface.
fml::TaskRunner::RunNowOrPostTask(ui_task_runner, ui_task);
// Step 2: Tell the raster thread that it should create a surface for
// its rasterizer.
if (should_post_raster_task) {
fml::TaskRunner::RunNowOrPostTask(raster_task_runner, raster_task);
}
latch.Signal();
};
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetIOTaskRunner(), io_task);
latch.Wait();
if (!should_post_raster_task) {
// See comment on should_post_raster_task, in this case the raster_task
// wasn't executed, and we just run it here as the platform thread
// is the raster thread.
raster_task();
}
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewDestroyed() {
TRACE_EVENT0("flutter", "Shell::OnPlatformViewDestroyed");
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
// Prevent any request to change the thread configuration for raster and
// platform queues while the platform view is being destroyed.
//
// This prevents false positives such as this method starts assuming that the
// raster and platform queues have a given thread configuration, but then the
// configuration is changed by a task, and the assumption is no longer true.
//
// This incorrect assumption can lead to deadlock.
rasterizer_->DisableThreadMergerIfNeeded();
// Notify the Dart VM that the PlatformView has been destroyed and some
// cleanup activity can be done (e.g: garbage collect the Dart heap).
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(),
[engine = engine_->GetWeakPtr()]() {
if (engine) {
engine->NotifyDestroyed();
}
});
// Note:
// This is a synchronous operation because certain platforms depend on
// setup/suspension of all activities that may be interacting with the GPU in
// a synchronous fashion.
// The UI thread does not need to be serialized here - there is sufficient
// guardrailing in the rasterizer to allow the UI thread to post work to it
// even after the surface has been torn down.
fml::AutoResetWaitableEvent latch;
auto io_task = [io_manager = io_manager_.get(), &latch]() {
// Execute any pending Skia object deletions while GPU access is still
// allowed.
io_manager->GetIsGpuDisabledSyncSwitch()->Execute(
fml::SyncSwitch::Handlers().SetIfFalse(
[&] { io_manager->GetSkiaUnrefQueue()->Drain(); }));
// Step 4: All done. Signal the latch that the platform thread is waiting
// on.
latch.Signal();
};
auto raster_task = [rasterizer = rasterizer_->GetWeakPtr(),
io_task_runner = task_runners_.GetIOTaskRunner(),
io_task]() {
if (rasterizer) {
// Enables the thread merger which is required prior tearing down the
// rasterizer. If the raster and platform threads are merged, tearing down
// the rasterizer unmerges the threads.
rasterizer->EnableThreadMergerIfNeeded();
rasterizer->Teardown();
}
// Step 2: Tell the IO thread to complete its remaining work.
fml::TaskRunner::RunNowOrPostTask(io_task_runner, io_task);
};
// Step 1: Post a task to the Raster thread (possibly this thread) to tell the
// rasterizer the output surface is going away.
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetRasterTaskRunner(),
raster_task);
latch.Wait();
// On Android, the external view embedder may post a task to the platform
// thread, and wait until it completes if overlay surfaces must be released.
// However, the platform thread might be blocked when Dart is initializing.
// In this situation, calling TeardownExternalViewEmbedder is safe because no
// platform views have been created before Flutter renders the first frame.
// Overall, the longer term plan is to remove this implementation once
// https://github.com/flutter/flutter/issues/96679 is fixed.
rasterizer_->TeardownExternalViewEmbedder();
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewScheduleFrame() {
TRACE_EVENT0("flutter", "Shell::OnPlatformViewScheduleFrame");
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(),
[engine = engine_->GetWeakPtr()]() {
if (engine) {
engine->ScheduleFrame();
}
});
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewSetViewportMetrics(int64_t view_id,
const ViewportMetrics& metrics) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
if (metrics.device_pixel_ratio <= 0 || metrics.physical_width <= 0 ||
metrics.physical_height <= 0) {
// Ignore invalid view-port metrics.
return;
}
// This is the formula Android uses.
// https://android.googlesource.com/platform/frameworks/base/+/39ae5bac216757bc201490f4c7b8c0f63006c6cd/libs/hwui/renderthread/CacheManager.cpp#45
resource_cache_limit_ =
metrics.physical_width * metrics.physical_height * 12 * 4;
size_t resource_cache_max_bytes =
resource_cache_limit_calculator_->GetResourceCacheMaxBytes();
task_runners_.GetRasterTaskRunner()->PostTask(
[rasterizer = rasterizer_->GetWeakPtr(), resource_cache_max_bytes] {
if (rasterizer) {
rasterizer->SetResourceCacheMaxBytes(resource_cache_max_bytes, false);
}
});
fml::TaskRunner::RunNowAndFlushMessages(
task_runners_.GetUITaskRunner(),
[engine = engine_->GetWeakPtr(), view_id, metrics]() {
if (engine) {
engine->SetViewportMetrics(view_id, metrics);
}
});
{
std::scoped_lock<std::mutex> lock(resize_mutex_);
expected_frame_sizes_[view_id] =
SkISize::Make(metrics.physical_width, metrics.physical_height);
device_pixel_ratio_ = metrics.device_pixel_ratio;
}
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewDispatchPlatformMessage(
std::unique_ptr<PlatformMessage> message) {
FML_DCHECK(is_set_up_);
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
if (!task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()) {
std::scoped_lock lock(misbehaving_message_channels_mutex_);
auto inserted = misbehaving_message_channels_.insert(message->channel());
if (inserted.second) {
FML_LOG(ERROR)
<< "The '" << message->channel()
<< "' 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.\n"
"See https://docs.flutter.dev/platform-integration/"
"platform-channels#channels-and-platform-threading for more "
"information.";
}
}
#endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
// The static leak checker gets confused by the use of fml::MakeCopyable.
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
fml::TaskRunner::RunNowAndFlushMessages(
task_runners_.GetUITaskRunner(),
fml::MakeCopyable([engine = engine_->GetWeakPtr(),
message = std::move(message)]() mutable {
if (engine) {
engine->DispatchPlatformMessage(std::move(message));
}
}));
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewDispatchPointerDataPacket(
std::unique_ptr<PointerDataPacket> packet) {
TRACE_EVENT0_WITH_FLOW_IDS(
"flutter", "Shell::OnPlatformViewDispatchPointerDataPacket",
/*flow_id_count=*/1, /*flow_ids=*/&next_pointer_flow_id_);
TRACE_FLOW_BEGIN("flutter", "PointerEvent", next_pointer_flow_id_);
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetUITaskRunner()->PostTask(
fml::MakeCopyable([engine = weak_engine_, packet = std::move(packet),
flow_id = next_pointer_flow_id_]() mutable {
if (engine) {
engine->DispatchPointerDataPacket(std::move(packet), flow_id);
}
}));
next_pointer_flow_id_++;
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewDispatchSemanticsAction(int64_t view_id,
int32_t node_id,
SemanticsAction action,
fml::MallocMapping args) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
fml::TaskRunner::RunNowAndFlushMessages(
task_runners_.GetUITaskRunner(),
fml::MakeCopyable([engine = engine_->GetWeakPtr(), view_id, node_id,
action, args = std::move(args)]() mutable {
if (engine) {
engine->DispatchSemanticsAction(view_id, node_id, action,
std::move(args));
}
}));
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewSetSemanticsEnabled(bool enabled) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
fml::TaskRunner::RunNowAndFlushMessages(
task_runners_.GetUITaskRunner(),
[engine = engine_->GetWeakPtr(), enabled] {
if (engine) {
engine->SetSemanticsEnabled(enabled);
}
});
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewSetAccessibilityFeatures(int32_t flags) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
fml::TaskRunner::RunNowAndFlushMessages(
task_runners_.GetUITaskRunner(), [engine = engine_->GetWeakPtr(), flags] {
if (engine) {
engine->SetAccessibilityFeatures(flags);
}
});
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewRegisterTexture(
std::shared_ptr<flutter::Texture> texture) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetRasterTaskRunner()->PostTask(
[rasterizer = rasterizer_->GetWeakPtr(), texture] {
if (rasterizer) {
if (auto registry = rasterizer->GetTextureRegistry()) {
registry->RegisterTexture(texture);
}
}
});
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewUnregisterTexture(int64_t texture_id) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetRasterTaskRunner()->PostTask(
[rasterizer = rasterizer_->GetWeakPtr(), texture_id]() {
if (rasterizer) {
if (auto registry = rasterizer->GetTextureRegistry()) {
registry->UnregisterTexture(texture_id);
}
}
});
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
// Tell the rasterizer that one of its textures has a new frame available.
task_runners_.GetRasterTaskRunner()->PostTask(
[rasterizer = rasterizer_->GetWeakPtr(), texture_id]() {
if (!rasterizer) {
return;
}
auto registry = rasterizer->GetTextureRegistry();
if (!registry) {
return;
}
auto texture = registry->GetTexture(texture_id);
if (!texture) {
return;
}
texture->MarkNewFrameAvailable();
});
// Schedule a new frame without having to rebuild the layer tree.
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(),
[engine = engine_->GetWeakPtr()]() {
if (engine) {
engine->ScheduleFrame(false);
}
});
}
// |PlatformView::Delegate|
void Shell::OnPlatformViewSetNextFrameCallback(const fml::closure& closure) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetRasterTaskRunner()->PostTask(
[rasterizer = rasterizer_->GetWeakPtr(), closure = closure]() {
if (rasterizer) {
rasterizer->SetNextFrameCallback(closure);
}
});
}
// |PlatformView::Delegate|
const Settings& Shell::OnPlatformViewGetSettings() const {
return settings_;
}
// |Animator::Delegate|
void Shell::OnAnimatorBeginFrame(fml::TimePoint frame_target_time,
uint64_t frame_number) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
// record the target time for use by rasterizer.
{
std::scoped_lock time_recorder_lock(time_recorder_mutex_);
latest_frame_target_time_.emplace(frame_target_time);
}
if (engine_) {
engine_->BeginFrame(frame_target_time, frame_number);
}
}
// |Animator::Delegate|
void Shell::OnAnimatorNotifyIdle(fml::TimeDelta deadline) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
if (engine_) {
engine_->NotifyIdle(deadline);
}
}
void Shell::OnAnimatorUpdateLatestFrameTargetTime(
fml::TimePoint frame_target_time) {
FML_DCHECK(is_set_up_);
// record the target time for use by rasterizer.
{
std::scoped_lock time_recorder_lock(time_recorder_mutex_);
if (!latest_frame_target_time_) {
latest_frame_target_time_ = frame_target_time;
} else if (latest_frame_target_time_ < frame_target_time) {
latest_frame_target_time_ = frame_target_time;
}
}
}
// |Animator::Delegate|
void Shell::OnAnimatorDraw(std::shared_ptr<FramePipeline> pipeline) {
FML_DCHECK(is_set_up_);
task_runners_.GetRasterTaskRunner()->PostTask(fml::MakeCopyable(
[&waiting_for_first_frame = waiting_for_first_frame_,
&waiting_for_first_frame_condition = waiting_for_first_frame_condition_,
rasterizer = rasterizer_->GetWeakPtr(),
weak_pipeline = std::weak_ptr<FramePipeline>(pipeline)]() mutable {
if (rasterizer) {
std::shared_ptr<FramePipeline> pipeline = weak_pipeline.lock();
if (pipeline) {
rasterizer->Draw(pipeline);
}
if (waiting_for_first_frame.load()) {
waiting_for_first_frame.store(false);
waiting_for_first_frame_condition.notify_all();
}
}
}));
}
// |Animator::Delegate|
void Shell::OnAnimatorDrawLastLayerTrees(
std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
FML_DCHECK(is_set_up_);
auto task = fml::MakeCopyable(
[rasterizer = rasterizer_->GetWeakPtr(),
frame_timings_recorder = std::move(frame_timings_recorder)]() mutable {
if (rasterizer) {
rasterizer->DrawLastLayerTrees(std::move(frame_timings_recorder));
}
});
task_runners_.GetRasterTaskRunner()->PostTask(task);
}
// |Engine::Delegate|
void Shell::OnEngineUpdateSemantics(int64_t view_id,
SemanticsNodeUpdates update,
CustomAccessibilityActionUpdates actions) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetPlatformTaskRunner()->RunNowOrPostTask(
task_runners_.GetPlatformTaskRunner(),
[view = platform_view_->GetWeakPtr(), update = std::move(update),
actions = std::move(actions), view_id = view_id] {
if (view) {
view->UpdateSemantics(view_id, update, actions);
}
});
}
// |Engine::Delegate|
void Shell::OnEngineSetSemanticsTreeEnabled(bool enabled) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetPlatformTaskRunner()->RunNowOrPostTask(
task_runners_.GetPlatformTaskRunner(),
[view = platform_view_->GetWeakPtr(), enabled] {
if (view) {
view->SetSemanticsTreeEnabled(enabled);
}
});
}
// |Engine::Delegate|
void Shell::OnEngineHandlePlatformMessage(
std::unique_ptr<PlatformMessage> message) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
if (message->channel() == kSkiaChannel) {
HandleEngineSkiaMessage(std::move(message));
return;
}
if (platform_message_handler_) {
if (route_messages_through_platform_thread_ &&
!platform_message_handler_
->DoesHandlePlatformMessageOnPlatformThread()) {
#if _WIN32
// On Windows capturing a TaskRunner with a TaskRunner will cause an
// uncaught exception in process shutdown because of the deletion order of
// global variables. See also
// https://github.com/flutter/flutter/issues/111575.
// This won't be an issue until Windows supports background platform
// channels (https://github.com/flutter/flutter/issues/93945). Then this
// can potentially be addressed by capturing a weak_ptr to an object that
// retains the ui TaskRunner, instead of the TaskRunner directly.
FML_DCHECK(false);
#endif
// We route messages through the platform thread temporarily when the
// shell is being initialized to be backwards compatible with setting
// message handlers in the same event as starting the isolate, but after
// it is started.
auto ui_task_runner = task_runners_.GetUITaskRunner();
task_runners_.GetPlatformTaskRunner()->PostTask(fml::MakeCopyable(
[weak_platform_message_handler =
std::weak_ptr<PlatformMessageHandler>(platform_message_handler_),
message = std::move(message), ui_task_runner]() mutable {
ui_task_runner->PostTask(
fml::MakeCopyable([weak_platform_message_handler,
message = std::move(message)]() mutable {
auto platform_message_handler =
weak_platform_message_handler.lock();
if (platform_message_handler) {
platform_message_handler->HandlePlatformMessage(
std::move(message));
}
}));
}));
} else {
platform_message_handler_->HandlePlatformMessage(std::move(message));
}
} else {
task_runners_.GetPlatformTaskRunner()->PostTask(
fml::MakeCopyable([view = platform_view_->GetWeakPtr(),
message = std::move(message)]() mutable {
if (view) {
view->HandlePlatformMessage(std::move(message));
}
}));
}
}
void Shell::OnEngineChannelUpdate(std::string name, bool listening) {
FML_DCHECK(is_set_up_);
task_runners_.GetPlatformTaskRunner()->PostTask(
[view = platform_view_->GetWeakPtr(), name = std::move(name), listening] {
if (view) {
view->SendChannelUpdate(name, listening);
}
});
}
void Shell::HandleEngineSkiaMessage(std::unique_ptr<PlatformMessage> message) {
const auto& data = message->data();
rapidjson::Document document;
document.Parse(reinterpret_cast<const char*>(data.GetMapping()),
data.GetSize());
if (document.HasParseError() || !document.IsObject()) {
return;
}
auto root = document.GetObject();
auto method = root.FindMember("method");
if (method->value != "Skia.setResourceCacheMaxBytes") {
return;
}
auto args = root.FindMember("args");
if (args == root.MemberEnd() || !args->value.IsInt()) {
return;
}
task_runners_.GetRasterTaskRunner()->PostTask(
[rasterizer = rasterizer_->GetWeakPtr(), max_bytes = args->value.GetInt(),
response = message->response()] {
if (rasterizer) {
rasterizer->SetResourceCacheMaxBytes(static_cast<size_t>(max_bytes),
true);
}
if (response) {
// The framework side expects this to be valid json encoded as a list.
// Return `[true]` to signal success.
std::vector<uint8_t> data = {'[', 't', 'r', 'u', 'e', ']'};
response->Complete(
std::make_unique<fml::DataMapping>(std::move(data)));
}
});
}
// |Engine::Delegate|
void Shell::OnPreEngineRestart() {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
fml::AutoResetWaitableEvent latch;
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetPlatformTaskRunner(),
[view = platform_view_->GetWeakPtr(), &latch]() {
if (view) {
view->OnPreEngineRestart();
}
latch.Signal();
});
// This is blocking as any embedded platform views has to be flushed before
// we re-run the Dart code.
latch.Wait();
}
// |Engine::Delegate|
void Shell::OnRootIsolateCreated() {
if (is_added_to_service_protocol_) {
return;
}
auto description = GetServiceProtocolDescription();
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetPlatformTaskRunner(),
[self = weak_factory_.GetWeakPtr(),
description = std::move(description)]() {
if (self) {
self->vm_->GetServiceProtocol()->AddHandler(self.get(), description);
}
});
is_added_to_service_protocol_ = true;
}
// |Engine::Delegate|
void Shell::UpdateIsolateDescription(const std::string isolate_name,
int64_t isolate_port) {
Handler::Description description(isolate_port, isolate_name);
vm_->GetServiceProtocol()->SetHandlerDescription(this, description);
}
void Shell::SetNeedsReportTimings(bool value) {
needs_report_timings_ = value;
}
// |Engine::Delegate|
std::unique_ptr<std::vector<std::string>> Shell::ComputePlatformResolvedLocale(
const std::vector<std::string>& supported_locale_data) {
return platform_view_->ComputePlatformResolvedLocales(supported_locale_data);
}
void Shell::LoadDartDeferredLibrary(
intptr_t loading_unit_id,
std::unique_ptr<const fml::Mapping> snapshot_data,
std::unique_ptr<const fml::Mapping> snapshot_instructions) {
task_runners_.GetUITaskRunner()->PostTask(fml::MakeCopyable(
[engine = engine_->GetWeakPtr(), loading_unit_id,
data = std::move(snapshot_data),
instructions = std::move(snapshot_instructions)]() mutable {
if (engine) {
engine->LoadDartDeferredLibrary(loading_unit_id, std::move(data),
std::move(instructions));
}
}));
}
void Shell::LoadDartDeferredLibraryError(intptr_t loading_unit_id,
const std::string error_message,
bool transient) {
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetUITaskRunner(),
[engine = weak_engine_, loading_unit_id, error_message, transient] {
if (engine) {
engine->LoadDartDeferredLibraryError(loading_unit_id, error_message,
transient);
}
});
}
void Shell::UpdateAssetResolverByType(
std::unique_ptr<AssetResolver> updated_asset_resolver,
AssetResolver::AssetResolverType type) {
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetUITaskRunner(),
fml::MakeCopyable(
[engine = weak_engine_, type,
asset_resolver = std::move(updated_asset_resolver)]() mutable {
if (engine) {
engine->GetAssetManager()->UpdateResolverByType(
std::move(asset_resolver), type);
}
}));
}
// |Engine::Delegate|
void Shell::RequestDartDeferredLibrary(intptr_t loading_unit_id) {
task_runners_.GetPlatformTaskRunner()->PostTask(
[view = platform_view_->GetWeakPtr(), loading_unit_id] {
if (view) {
view->RequestDartDeferredLibrary(loading_unit_id);
}
});
}
// |Engine::Delegate|
double Shell::GetScaledFontSize(double unscaled_font_size,
int configuration_id) const {
return platform_view_->GetScaledFontSize(unscaled_font_size,
configuration_id);
}
void Shell::RequestViewFocusChange(const ViewFocusChangeRequest& request) {
FML_DCHECK(is_set_up_);
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetPlatformTaskRunner(),
[view = platform_view_->GetWeakPtr(), request] {
if (view) {
view->RequestViewFocusChange(request);
}
});
}
void Shell::ReportTimings() {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());
auto timings = std::move(unreported_timings_);
unreported_timings_ = {};
task_runners_.GetUITaskRunner()->PostTask([timings, engine = weak_engine_] {
if (engine) {
engine->ReportTimings(timings);
}
});
}
size_t Shell::UnreportedFramesCount() const {
// Check that this is running on the raster thread to avoid race conditions.
FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());
FML_DCHECK(unreported_timings_.size() % (FrameTiming::kStatisticsCount) == 0);
return unreported_timings_.size() / (FrameTiming::kStatisticsCount);
}
void Shell::OnFrameRasterized(const FrameTiming& timing) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());
// The C++ callback defined in settings.h and set by Flutter runner. This is
// independent of the timings report to the Dart side.
if (settings_.frame_rasterized_callback) {
settings_.frame_rasterized_callback(timing);
}
if (!needs_report_timings_) {
return;
}
size_t old_count = unreported_timings_.size();
(void)old_count;
for (auto phase : FrameTiming::kPhases) {
unreported_timings_.push_back(
timing.Get(phase).ToEpochDelta().ToMicroseconds());
}
unreported_timings_.push_back(timing.GetLayerCacheCount());
unreported_timings_.push_back(timing.GetLayerCacheBytes());
unreported_timings_.push_back(timing.GetPictureCacheCount());
unreported_timings_.push_back(timing.GetPictureCacheBytes());
unreported_timings_.push_back(timing.GetFrameNumber());
FML_DCHECK(unreported_timings_.size() ==
old_count + FrameTiming::kStatisticsCount);
// In tests using iPhone 6S with profile mode, sending a batch of 1 frame or a
// batch of 100 frames have roughly the same cost of less than 0.1ms. Sending
// a batch of 500 frames costs about 0.2ms. The 1 second threshold usually
// kicks in before we reaching the following 100 frames threshold. The 100
// threshold here is mainly for unit tests (so we don't have to write a
// 1-second unit test), and make sure that our vector won't grow too big with
// future 120fps, 240fps, or 1000fps displays.
//
// In the profile/debug mode, the timings are used by development tools which
// require a latency of no more than 100ms. Hence we lower that 1-second
// threshold to 100ms because performance overhead isn't that critical in
// those cases.
if (!first_frame_rasterized_ || UnreportedFramesCount() >= 100) {
first_frame_rasterized_ = true;
ReportTimings();
} else if (!frame_timings_report_scheduled_) {
#if FLUTTER_RELEASE
constexpr int kBatchTimeInMilliseconds = 1000;
#else
constexpr int kBatchTimeInMilliseconds = 100;
#endif
// Also make sure that frame times get reported with a max latency of 1
// second. Otherwise, the timings of last few frames of an animation may
// never be reported until the next animation starts.
frame_timings_report_scheduled_ = true;
task_runners_.GetRasterTaskRunner()->PostDelayedTask(
[self = weak_factory_gpu_->GetWeakPtr()]() {
if (!self) {
return;
}
self->frame_timings_report_scheduled_ = false;
if (self->UnreportedFramesCount() > 0) {
self->ReportTimings();
}
},
fml::TimeDelta::FromMilliseconds(kBatchTimeInMilliseconds));
}
}
fml::Milliseconds Shell::GetFrameBudget() {
if (cached_display_refresh_rate_.has_value()) {
return cached_display_refresh_rate_.value();
}
double display_refresh_rate = display_manager_->GetMainDisplayRefreshRate();
if (display_refresh_rate > 0) {
cached_display_refresh_rate_ =
fml::RefreshRateToFrameBudget(display_refresh_rate);
} else {
cached_display_refresh_rate_ = fml::kDefaultFrameBudget;
}
return cached_display_refresh_rate_.value_or(fml::kDefaultFrameBudget);
}
fml::TimePoint Shell::GetLatestFrameTargetTime() const {
std::scoped_lock time_recorder_lock(time_recorder_mutex_);
FML_CHECK(latest_frame_target_time_.has_value())
<< "GetLatestFrameTargetTime called before OnAnimatorBeginFrame";
// Covered by FML_CHECK().
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
return latest_frame_target_time_.value();
}
// |Rasterizer::Delegate|
bool Shell::ShouldDiscardLayerTree(int64_t view_id,
const flutter::LayerTree& tree) {
std::scoped_lock<std::mutex> lock(resize_mutex_);
auto expected_frame_size = ExpectedFrameSize(view_id);
return !expected_frame_size.isEmpty() &&
ToSkISize(tree.frame_size()) != expected_frame_size;
}
// |ServiceProtocol::Handler|
fml::RefPtr<fml::TaskRunner> Shell::GetServiceProtocolHandlerTaskRunner(
std::string_view method) const {
FML_DCHECK(is_set_up_);
auto found = service_protocol_handlers_.find(method);
if (found != service_protocol_handlers_.end()) {
return found->second.first;
}
return task_runners_.GetUITaskRunner();
}
// |ServiceProtocol::Handler|
bool Shell::HandleServiceProtocolMessage(
std::string_view method, // one if the extension names specified above.
const ServiceProtocolMap& params,
rapidjson::Document* response) {
auto found = service_protocol_handlers_.find(method);
if (found != service_protocol_handlers_.end()) {
return found->second.second(params, response);
}
return false;
}
// |ServiceProtocol::Handler|
ServiceProtocol::Handler::Description Shell::GetServiceProtocolDescription()
const {
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
if (!weak_engine_) {
return ServiceProtocol::Handler::Description();
}
return {
weak_engine_->GetUIIsolateMainPort(),
weak_engine_->GetUIIsolateName(),
};
}
static void ServiceProtocolParameterError(rapidjson::Document* response,
std::string error_details) {
auto& allocator = response->GetAllocator();
response->SetObject();
const int64_t kInvalidParams = -32602;
response->AddMember("code", kInvalidParams, allocator);
response->AddMember("message", "Invalid params", allocator);
{
rapidjson::Value details(rapidjson::kObjectType);
details.AddMember("details", std::move(error_details), allocator);
response->AddMember("data", details, allocator);
}
}
static void ServiceProtocolFailureError(rapidjson::Document* response,
std::string message) {
auto& allocator = response->GetAllocator();
response->SetObject();
const int64_t kJsonServerError = -32000;
response->AddMember("code", kJsonServerError, allocator);
response->AddMember("message", std::move(message), allocator);
}
// Service protocol handler
bool Shell::OnServiceProtocolScreenshot(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response) {
FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());
auto screenshot = rasterizer_->ScreenshotLastLayerTree(
Rasterizer::ScreenshotType::CompressedImage, true);
if (screenshot.data) {
response->SetObject();
auto& allocator = response->GetAllocator();
response->AddMember("type", "Screenshot", allocator);
rapidjson::Value image;
image.SetString(static_cast<const char*>(screenshot.data->data()),
screenshot.data->size(), allocator);
response->AddMember("screenshot", image, allocator);
return true;
}
ServiceProtocolFailureError(response, "Could not capture image screenshot.");
return false;
}
// Service protocol handler
bool Shell::OnServiceProtocolScreenshotSKP(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response) {
FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());
if (settings_.enable_impeller) {
ServiceProtocolFailureError(
response, "Cannot capture SKP screenshot with Impeller enabled.");
return false;
}
auto screenshot = rasterizer_->ScreenshotLastLayerTree(
Rasterizer::ScreenshotType::SkiaPicture, true);
if (screenshot.data) {
response->SetObject();
auto& allocator = response->GetAllocator();
response->AddMember("type", "ScreenshotSkp", allocator);
rapidjson::Value skp;
skp.SetString(static_cast<const char*>(screenshot.data->data()),
screenshot.data->size(), allocator);
response->AddMember("skp", skp, allocator);
return true;
}
ServiceProtocolFailureError(response, "Could not capture SKP screenshot.");
return false;
}
// Service protocol handler
bool Shell::OnServiceProtocolRunInView(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response) {
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
if (params.count("mainScript") == 0) {
ServiceProtocolParameterError(response,
"'mainScript' parameter is missing.");
return false;
}
if (params.count("assetDirectory") == 0) {
ServiceProtocolParameterError(response,
"'assetDirectory' parameter is missing.");
return false;
}
std::string main_script_path =
fml::paths::FromURI(params.at("mainScript").data());
std::string asset_directory_path =
fml::paths::FromURI(params.at("assetDirectory").data());
auto main_script_file_mapping =
std::make_unique<fml::FileMapping>(fml::OpenFile(
main_script_path.c_str(), false, fml::FilePermission::kRead));
auto isolate_configuration = IsolateConfiguration::CreateForKernel(
std::move(main_script_file_mapping));
RunConfiguration configuration(std::move(isolate_configuration));
configuration.SetEntrypointAndLibrary(engine_->GetLastEntrypoint(),
engine_->GetLastEntrypointLibrary());
configuration.SetEntrypointArgs(engine_->GetLastEntrypointArgs());
configuration.AddAssetResolver(std::make_unique<DirectoryAssetBundle>(
fml::OpenDirectory(asset_directory_path.c_str(), false,
fml::FilePermission::kRead),
false));
// Preserve any original asset resolvers to avoid syncing unchanged assets
// over the DevFS connection.
auto old_asset_manager = engine_->GetAssetManager();
if (old_asset_manager != nullptr) {
for (auto& old_resolver : old_asset_manager->TakeResolvers()) {
if (old_resolver->IsValidAfterAssetManagerChange()) {
configuration.AddAssetResolver(std::move(old_resolver));
}
}
}
auto& allocator = response->GetAllocator();
response->SetObject();
if (engine_->Restart(std::move(configuration))) {
response->AddMember("type", "Success", allocator);
auto new_description = GetServiceProtocolDescription();
rapidjson::Value view(rapidjson::kObjectType);
new_description.Write(this, view, allocator);
response->AddMember("view", view, allocator);
return true;
} else {
FML_DLOG(ERROR) << "Could not run configuration in engine.";
ServiceProtocolFailureError(response,
"Could not run configuration in engine.");
return false;
}
FML_DCHECK(false);
return false;
}
// Service protocol handler
bool Shell::OnServiceProtocolFlushUIThreadTasks(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response) {
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
// This API should not be invoked by production code.
// It can potentially starve the service isolate if the main isolate pauses
// at a breakpoint or is in an infinite loop.
//
// It should be invoked from the VM Service and blocks it until UI thread
// tasks are processed.
response->SetObject();
response->AddMember("type", "Success", response->GetAllocator());
return true;
}
bool Shell::OnServiceProtocolGetDisplayRefreshRate(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response) {
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
response->SetObject();
response->AddMember("type", "DisplayRefreshRate", response->GetAllocator());
response->AddMember("fps", display_manager_->GetMainDisplayRefreshRate(),
response->GetAllocator());
return true;
}
double Shell::GetMainDisplayRefreshRate() {
return display_manager_->GetMainDisplayRefreshRate();
}
void Shell::RegisterImageDecoder(ImageGeneratorFactory factory,
int32_t priority) {
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
FML_DCHECK(is_set_up_);
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetUITaskRunner(),
[engine = engine_->GetWeakPtr(), factory = std::move(factory),
priority]() {
if (engine) {
engine->GetImageGeneratorRegistry()->AddFactory(factory, priority);
}
});
}
bool Shell::OnServiceProtocolGetSkSLs(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response) {
FML_DCHECK(task_runners_.GetIOTaskRunner()->RunsTasksOnCurrentThread());
response->SetObject();
response->AddMember("type", "GetSkSLs", response->GetAllocator());
rapidjson::Value shaders_json(rapidjson::kObjectType);
#if !SLIMPELLER
PersistentCache* persistent_cache = PersistentCache::GetCacheForProcess();
std::vector<PersistentCache::SkSLCache> sksls = persistent_cache->LoadSkSLs();
for (const auto& sksl : sksls) {
size_t b64_size = Base64::EncodedSize(sksl.value->size());
sk_sp<SkData> b64_data = SkData::MakeUninitialized(b64_size + 1);
char* b64_char = static_cast<char*>(b64_data->writable_data());
Base64::Encode(sksl.value->data(), sksl.value->size(), b64_char);
b64_char[b64_size] = 0; // make it null terminated for printing
rapidjson::Value shader_value(b64_char, response->GetAllocator());
std::string_view key_view(reinterpret_cast<const char*>(sksl.key->data()),
sksl.key->size());
auto encode_result = fml::Base32Encode(key_view);
if (!encode_result.first) {
continue;
}
rapidjson::Value shader_key(encode_result.second, response->GetAllocator());
shaders_json.AddMember(shader_key, shader_value, response->GetAllocator());
}
#endif // !SLIMPELLER
response->AddMember("SkSLs", shaders_json, response->GetAllocator());
return true;
}
bool Shell::OnServiceProtocolEstimateRasterCacheMemory(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response) {
FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());
uint64_t layer_cache_byte_size = 0u;
uint64_t picture_cache_byte_size = 0u;
#if !SLIMPELLER
const auto& raster_cache = rasterizer_->compositor_context()->raster_cache();
layer_cache_byte_size = raster_cache.EstimateLayerCacheByteSize();
picture_cache_byte_size = raster_cache.EstimatePictureCacheByteSize();
#endif // !SLIMPELLER
response->SetObject();
response->AddMember("type", "EstimateRasterCacheMemory",
response->GetAllocator());
response->AddMember<uint64_t>("layerBytes", layer_cache_byte_size,
response->GetAllocator());
response->AddMember<uint64_t>("pictureBytes", picture_cache_byte_size,
response->GetAllocator());
return true;
}
// Service protocol handler
bool Shell::OnServiceProtocolSetAssetBundlePath(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response) {
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
if (params.count("assetDirectory") == 0) {
ServiceProtocolParameterError(response,
"'assetDirectory' parameter is missing.");
return false;
}
auto& allocator = response->GetAllocator();
response->SetObject();
auto asset_manager = std::make_shared<AssetManager>();
if (!asset_manager->PushFront(std::make_unique<DirectoryAssetBundle>(
fml::OpenDirectory(params.at("assetDirectory").data(), false,
fml::FilePermission::kRead),
false))) {
// The new asset directory path was invalid.
FML_DLOG(ERROR) << "Could not update asset directory.";
ServiceProtocolFailureError(response, "Could not update asset directory.");
return false;
}
// Preserve any original asset resolvers to avoid syncing unchanged assets
// over the DevFS connection.
auto old_asset_manager = engine_->GetAssetManager();
if (old_asset_manager != nullptr) {
for (auto& old_resolver : old_asset_manager->TakeResolvers()) {
if (old_resolver->IsValidAfterAssetManagerChange()) {
asset_manager->PushBack(std::move(old_resolver));
}
}
}
if (engine_->UpdateAssetManager(asset_manager)) {
response->AddMember("type", "Success", allocator);
auto new_description = GetServiceProtocolDescription();
rapidjson::Value view(rapidjson::kObjectType);
new_description.Write(this, view, allocator);
response->AddMember("view", view, allocator);
return true;
} else {
FML_DLOG(ERROR) << "Could not update asset directory.";
ServiceProtocolFailureError(response, "Could not update asset directory.");
return false;
}
FML_DCHECK(false);
return false;
}
void Shell::SendFontChangeNotification() {
// After system fonts are reloaded, we send a system channel message
// to notify flutter framework.
rapidjson::Document document;
document.SetObject();
auto& allocator = document.GetAllocator();
rapidjson::Value message_value;
message_value.SetString(kFontChange, allocator);
document.AddMember(kTypeKey, message_value, allocator);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document.Accept(writer);
std::string message = buffer.GetString();
std::unique_ptr<PlatformMessage> fontsChangeMessage =
std::make_unique<flutter::PlatformMessage>(
kSystemChannel,
fml::MallocMapping::Copy(message.c_str(), message.length()), nullptr);
OnPlatformViewDispatchPlatformMessage(std::move(fontsChangeMessage));
}
bool Shell::OnServiceProtocolReloadAssetFonts(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response) {
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
if (!engine_) {
return false;
}
engine_->GetFontCollection().RegisterFonts(engine_->GetAssetManager());
engine_->GetFontCollection().GetFontCollection()->ClearFontFamilyCache();
SendFontChangeNotification();
auto& allocator = response->GetAllocator();
response->SetObject();
response->AddMember("type", "Success", allocator);
return true;
}
void Shell::OnPlatformViewAddView(int64_t view_id,
const ViewportMetrics& viewport_metrics,
AddViewCallback callback) {
TRACE_EVENT0("flutter", "Shell::AddView");
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
FML_DCHECK(view_id != kFlutterImplicitViewId)
<< "Unexpected request to add the implicit view #"
<< kFlutterImplicitViewId << ". This view should never be added.";
task_runners_.GetUITaskRunner()->RunNowOrPostTask(
task_runners_.GetUITaskRunner(), [engine = engine_->GetWeakPtr(), //
viewport_metrics, //
view_id, //
callback = std::move(callback) //
] {
if (engine) {
engine->AddView(view_id, viewport_metrics, callback);
}
});
}
void Shell::OnPlatformViewRemoveView(int64_t view_id,
RemoveViewCallback callback) {
TRACE_EVENT0("flutter", "Shell::RemoveView");
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
FML_DCHECK(view_id != kFlutterImplicitViewId)
<< "Unexpected request to remove the implicit view #"
<< kFlutterImplicitViewId << ". This view should never be removed.";
expected_frame_sizes_.erase(view_id);
task_runners_.GetUITaskRunner()->RunNowOrPostTask(
task_runners_.GetUITaskRunner(),
[&task_runners = task_runners_, //
engine = engine_->GetWeakPtr(), //
rasterizer = rasterizer_->GetWeakPtr(), //
view_id, //
callback = std::move(callback) //
] {
if (engine) {
bool removed = engine->RemoveView(view_id);
callback(removed);
}
// Don't wait for the raster task here, which only cleans up memory and
// does not affect functionality. Make sure it is done after Dart
// removes the view to avoid receiving another rasterization request
// that adds back the view record.
task_runners.GetRasterTaskRunner()->PostTask([rasterizer, view_id]() {
if (rasterizer) {
rasterizer->CollectView(view_id);
}
});
});
}
void Shell::OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) {
TRACE_EVENT0("flutter", "Shell:: OnPlatformViewSendViewFocusEvent");
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetUITaskRunner()->RunNowOrPostTask(
task_runners_.GetUITaskRunner(),
[engine = engine_->GetWeakPtr(), event = event] {
if (engine) {
engine->SendViewFocusEvent(event);
}
});
}
Rasterizer::Screenshot Shell::Screenshot(
Rasterizer::ScreenshotType screenshot_type,
bool base64_encode) {
if (settings_.enable_impeller) {
switch (screenshot_type) {
case Rasterizer::ScreenshotType::SkiaPicture:
FML_LOG(ERROR)
<< "Impeller backend cannot produce ScreenshotType::SkiaPicture.";
return {};
case Rasterizer::ScreenshotType::UncompressedImage:
case Rasterizer::ScreenshotType::CompressedImage:
case Rasterizer::ScreenshotType::SurfaceData:
break;
}
}
TRACE_EVENT0("flutter", "Shell::Screenshot");
fml::AutoResetWaitableEvent latch;
Rasterizer::Screenshot screenshot;
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetRasterTaskRunner(), [&latch, //
rasterizer = GetRasterizer(), //
&screenshot, //
screenshot_type, //
base64_encode //
]() {
if (rasterizer) {
screenshot = rasterizer->ScreenshotLastLayerTree(screenshot_type,
base64_encode);
}
latch.Signal();
});
latch.Wait();
return screenshot;
}
fml::Status Shell::WaitForFirstFrame(fml::TimeDelta timeout) {
FML_DCHECK(is_set_up_);
if (task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread() ||
task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()) {
return fml::Status(fml::StatusCode::kFailedPrecondition,
"WaitForFirstFrame called from thread that can't wait "
"because it is responsible for generating the frame.");
}
// Check for overflow.
auto now = std::chrono::steady_clock::now();
auto max_duration = std::chrono::steady_clock::time_point::max() - now;
auto desired_duration = std::chrono::milliseconds(timeout.ToMilliseconds());
auto duration =
now + (desired_duration > max_duration ? max_duration : desired_duration);
std::unique_lock<std::mutex> lock(waiting_for_first_frame_mutex_);
bool success = waiting_for_first_frame_condition_.wait_until(
lock, duration, [&waiting_for_first_frame = waiting_for_first_frame_] {
return !waiting_for_first_frame.load();
});
if (success) {
return fml::Status();
} else {
return fml::Status(fml::StatusCode::kDeadlineExceeded, "timeout");
}
}
bool Shell::ReloadSystemFonts() {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
if (!engine_) {
return false;
}
engine_->SetupDefaultFontManager();
engine_->GetFontCollection().GetFontCollection()->ClearFontFamilyCache();
// After system fonts are reloaded, we send a system channel message
// to notify flutter framework.
SendFontChangeNotification();
return true;
}
std::shared_ptr<const fml::SyncSwitch> Shell::GetIsGpuDisabledSyncSwitch()
const {
return is_gpu_disabled_sync_switch_;
}
void Shell::SetGpuAvailability(GpuAvailability availability) {
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
switch (availability) {
case GpuAvailability::kAvailable:
is_gpu_disabled_sync_switch_->SetSwitch(false);
return;
case GpuAvailability::kFlushAndMakeUnavailable: {
fml::AutoResetWaitableEvent latch;
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetIOTaskRunner(),
[io_manager = io_manager_.get(), &latch]() {
io_manager->GetSkiaUnrefQueue()->Drain();
latch.Signal();
});
latch.Wait();
}
// FALLTHROUGH
case GpuAvailability::kUnavailable:
is_gpu_disabled_sync_switch_->SetSwitch(true);
return;
default:
FML_DCHECK(false);
}
}
void Shell::OnDisplayUpdates(std::vector<std::unique_ptr<Display>> displays) {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
std::vector<DisplayData> display_data;
display_data.reserve(displays.size());
for (const auto& display : displays) {
display_data.push_back(display->GetDisplayData());
}
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(),
[engine = engine_->GetWeakPtr(),
display_data = std::move(display_data)]() {
if (engine) {
engine->SetDisplays(display_data);
}
});
display_manager_->HandleDisplayUpdates(std::move(displays));
}
fml::TimePoint Shell::GetCurrentTimePoint() {
return fml::TimePoint::Now();
}
const std::shared_ptr<PlatformMessageHandler>&
Shell::GetPlatformMessageHandler() const {
return platform_message_handler_;
}
const std::weak_ptr<VsyncWaiter> Shell::GetVsyncWaiter() const {
if (!engine_) {
return {};
}
return engine_->GetVsyncWaiter();
}
const std::shared_ptr<fml::ConcurrentTaskRunner>
Shell::GetConcurrentWorkerTaskRunner() const {
FML_DCHECK(vm_);
if (!vm_) {
return nullptr;
}
return vm_->GetConcurrentWorkerTaskRunner();
}
SkISize Shell::ExpectedFrameSize(int64_t view_id) {
auto found = expected_frame_sizes_.find(view_id);
if (found == expected_frame_sizes_.end()) {
return SkISize::MakeEmpty();
}
return found->second;
}
} // namespace flutter