// Copyright 2015 The Chromium 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 "flutter/shell/common/engine.h" #include #include #include #include #include #include #include #include "flutter/assets/directory_asset_bundle.h" #include "flutter/assets/unzipper_provider.h" #include "flutter/assets/zip_asset_store.h" #include "flutter/common/settings.h" #include "flutter/common/threads.h" #include "flutter/glue/trace_event.h" #include "flutter/lib/snapshot/snapshot.h" #include "flutter/runtime/asset_font_selector.h" #include "flutter/runtime/dart_controller.h" #include "flutter/runtime/dart_init.h" #include "flutter/runtime/runtime_init.h" #include "flutter/runtime/test_font_selector.h" #include "flutter/shell/common/animator.h" #include "flutter/shell/common/platform_view.h" #include "flutter/sky/engine/public/web/Sky.h" #include "lib/ftl/files/eintr_wrapper.h" #include "lib/ftl/files/file.h" #include "lib/ftl/files/path.h" #include "lib/ftl/files/unique_fd.h" #include "lib/ftl/functional/make_copyable.h" #include "third_party/rapidjson/rapidjson/document.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkPictureRecorder.h" namespace shell { namespace { constexpr char kAssetChannel[] = "flutter/assets"; constexpr char kLifecycleChannel[] = "flutter/lifecycle"; constexpr char kNavigationChannel[] = "flutter/navigation"; constexpr char kLocalizationChannel[] = "flutter/localization"; bool PathExists(const std::string& path) { return access(path.c_str(), R_OK) == 0; } std::string FindPackagesPath(const std::string& main_dart) { std::string directory = files::GetDirectoryName(main_dart); std::string packages_path = directory + "/.packages"; if (!PathExists(packages_path)) { directory = files::GetDirectoryName(directory); packages_path = directory + "/.packages"; if (!PathExists(packages_path)) packages_path = std::string(); } return packages_path; } std::string GetScriptUriFromPath(const std::string& path) { return "file://" + path; } } // namespace Engine::Engine(PlatformView* platform_view) : platform_view_(platform_view->GetWeakPtr()), animator_(std::make_unique( platform_view->rasterizer().GetWeakRasterizerPtr(), platform_view->GetVsyncWaiter(), this)), load_script_error_(tonic::kNoError), activity_running_(false), have_surface_(false), weak_factory_(this) {} Engine::~Engine() {} ftl::WeakPtr Engine::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } #if !FLUTTER_AOT #elif OS(IOS) #elif OS(ANDROID) static const uint8_t* MemMapSnapshot(const std::string& aot_snapshot_path, const std::string& default_file_name, const std::string& settings_file_name, bool executable) { std::string asset_path; if (settings_file_name.empty()) { asset_path = aot_snapshot_path + "/" + default_file_name; } else { asset_path = aot_snapshot_path + "/" + settings_file_name; } struct stat info; if (stat(asset_path.c_str(), &info) < 0) { return nullptr; } int64_t asset_size = info.st_size; ftl::UniqueFD fd(HANDLE_EINTR(open(asset_path.c_str(), O_RDONLY))); if (fd.get() == -1) { return nullptr; } int mmap_flags = PROT_READ; if (executable) mmap_flags |= PROT_EXEC; void* symbol = mmap(NULL, asset_size, mmap_flags, MAP_PRIVATE, fd.get(), 0); if (symbol == MAP_FAILED) { return nullptr; } return reinterpret_cast(symbol); } #endif static const uint8_t* default_isolate_snapshot_data = nullptr; static const uint8_t* default_isolate_snapshot_instr = nullptr; void Engine::Init() { const uint8_t* vm_snapshot_data; const uint8_t* vm_snapshot_instr; #if !FLUTTER_AOT vm_snapshot_data = ::kDartVmSnapshotData; vm_snapshot_instr = ::kDartVmSnapshotInstructions; default_isolate_snapshot_data = ::kDartIsolateCoreSnapshotData; default_isolate_snapshot_instr = ::kDartIsolateCoreSnapshotInstructions; #elif OS(IOS) const char* kDartApplicationLibraryPath = "App.framework/App"; const char* application_library_path = kDartApplicationLibraryPath; const blink::Settings& settings = blink::Settings::Get(); const std::string& application_library_path_setting = settings.application_library_path; if (!application_library_path_setting.empty()) { application_library_path = application_library_path_setting.c_str(); } dlerror(); // clear previous errors on thread void* library_handle = dlopen(application_library_path, RTLD_NOW); const char* err = dlerror(); if (err != nullptr) { FTL_LOG(FATAL) << "dlopen failed: " << err; } vm_snapshot_data = reinterpret_cast( dlsym(library_handle, "kDartVmSnapshotData")); vm_snapshot_instr = reinterpret_cast( dlsym(library_handle, "kDartVmSnapshotInstructions")); default_isolate_snapshot_data = reinterpret_cast( dlsym(library_handle, "kDartIsolateSnapshotData")); default_isolate_snapshot_instr = reinterpret_cast( dlsym(library_handle, "kDartIsolateSnapshotInstructions")); #elif OS(ANDROID) const blink::Settings& settings = blink::Settings::Get(); const std::string& aot_snapshot_path = settings.aot_snapshot_path; FTL_CHECK(!aot_snapshot_path.empty()); vm_snapshot_data = MemMapSnapshot(aot_snapshot_path, "vm_snapshot_data", settings.aot_vm_snapshot_data_filename, false); vm_snapshot_instr = MemMapSnapshot(aot_snapshot_path, "vm_snapshot_instr", settings.aot_vm_snapshot_instr_filename, true); default_isolate_snapshot_data = MemMapSnapshot(aot_snapshot_path, "isolate_snapshot_data", settings.aot_isolate_snapshot_data_filename, false); default_isolate_snapshot_instr = MemMapSnapshot(aot_snapshot_path, "isolate_snapshot_instr", settings.aot_isolate_snapshot_instr_filename, true); #else #error Unknown OS #endif blink::InitRuntime(vm_snapshot_data, vm_snapshot_instr, default_isolate_snapshot_data, default_isolate_snapshot_instr); } void Engine::RunBundle(const std::string& bundle_path) { TRACE_EVENT0("flutter", "Engine::RunBundle"); ConfigureAssetBundle(bundle_path); ConfigureRuntime(GetScriptUriFromPath(bundle_path)); if (blink::IsRunningPrecompiledCode()) { runtime_->dart_controller()->RunFromPrecompiledSnapshot(); } else { std::vector kernel; if (GetAssetAsBuffer(blink::kKernelAssetKey, &kernel)) { runtime_->dart_controller()->RunFromKernel(kernel.data(), kernel.size()); return; } std::vector snapshot; if (!GetAssetAsBuffer(blink::kSnapshotAssetKey, &snapshot)) return; runtime_->dart_controller()->RunFromScriptSnapshot(snapshot.data(), snapshot.size()); } } void Engine::RunBundleAndSnapshot(const std::string& bundle_path, const std::string& snapshot_override) { TRACE_EVENT0("flutter", "Engine::RunBundleAndSnapshot"); if (snapshot_override.empty()) { RunBundle(bundle_path); return; } ConfigureAssetBundle(bundle_path); ConfigureRuntime(GetScriptUriFromPath(bundle_path)); if (blink::IsRunningPrecompiledCode()) { runtime_->dart_controller()->RunFromPrecompiledSnapshot(); } else { std::vector snapshot; if (!files::ReadFileToVector(snapshot_override, &snapshot)) return; runtime_->dart_controller()->RunFromScriptSnapshot(snapshot.data(), snapshot.size()); } } void Engine::RunBundleAndSource(const std::string& bundle_path, const std::string& main, const std::string& packages) { TRACE_EVENT0("flutter", "Engine::RunBundleAndSource"); FTL_CHECK(!blink::IsRunningPrecompiledCode()) << "Cannot run from source in a precompiled build."; std::string packages_path = packages; if (packages_path.empty()) packages_path = FindPackagesPath(main); if (!bundle_path.empty()) ConfigureAssetBundle(bundle_path); ConfigureRuntime(GetScriptUriFromPath(main)); load_script_error_ = runtime_->dart_controller()->RunFromSource(main, packages_path); } void Engine::BeginFrame(ftl::TimePoint frame_time) { TRACE_EVENT0("flutter", "Engine::BeginFrame"); if (runtime_) runtime_->BeginFrame(frame_time); } void Engine::RunFromSource(const std::string& main, const std::string& packages, const std::string& bundle_path) { RunBundleAndSource(bundle_path, main, packages); } Dart_Port Engine::GetUIIsolateMainPort() { if (!runtime_) return ILLEGAL_PORT; return runtime_->GetMainPort(); } std::string Engine::GetUIIsolateName() { if (!runtime_) { return ""; } return runtime_->GetIsolateName(); } bool Engine::UIIsolateHasLivePorts() { if (!runtime_) return false; return runtime_->HasLivePorts(); } tonic::DartErrorHandleType Engine::GetUIIsolateLastError() { if (!runtime_) return tonic::kNoError; return runtime_->GetLastError(); } tonic::DartErrorHandleType Engine::GetLoadScriptError() { return load_script_error_; } void Engine::OnOutputSurfaceCreated(const ftl::Closure& gpu_continuation) { blink::Threads::Gpu()->PostTask(gpu_continuation); have_surface_ = true; StartAnimatorIfPossible(); if (runtime_) ScheduleFrame(); } void Engine::OnOutputSurfaceDestroyed(const ftl::Closure& gpu_continuation) { have_surface_ = false; StopAnimator(); blink::Threads::Gpu()->PostTask(gpu_continuation); } void Engine::SetViewportMetrics(const blink::ViewportMetrics& metrics) { viewport_metrics_ = metrics; if (runtime_) runtime_->SetViewportMetrics(viewport_metrics_); } void Engine::DispatchPlatformMessage( ftl::RefPtr message) { if (message->channel() == kLifecycleChannel) { if (HandleLifecyclePlatformMessage(message.get())) return; } else if (message->channel() == kLocalizationChannel) { if (HandleLocalizationPlatformMessage(std::move(message))) return; } if (runtime_) { runtime_->DispatchPlatformMessage(std::move(message)); return; } // If there's no runtime_, we need to buffer some navigation messages. if (message->channel() == kNavigationChannel) HandleNavigationPlatformMessage(std::move(message)); } bool Engine::HandleLifecyclePlatformMessage(blink::PlatformMessage* message) { const auto& data = message->data(); std::string state(reinterpret_cast(data.data()), data.size()); if (state == "AppLifecycleState.paused") { activity_running_ = false; StopAnimator(); } else if (state == "AppLifecycleState.resumed") { activity_running_ = true; StartAnimatorIfPossible(); } return false; } bool Engine::HandleNavigationPlatformMessage( ftl::RefPtr message) { FTL_DCHECK(!runtime_); const auto& data = message->data(); rapidjson::Document document; document.Parse(reinterpret_cast(data.data()), data.size()); if (document.HasParseError() || !document.IsObject()) return false; auto root = document.GetObject(); auto method = root.FindMember("method"); if (method == root.MemberEnd() || method->value != "pushRoute") return false; pending_push_route_message_ = std::move(message); return true; } bool Engine::HandleLocalizationPlatformMessage( ftl::RefPtr message) { const auto& data = message->data(); rapidjson::Document document; document.Parse(reinterpret_cast(data.data()), data.size()); if (document.HasParseError() || !document.IsObject()) return false; auto root = document.GetObject(); auto method = root.FindMember("method"); if (method == root.MemberEnd() || method->value != "setLocale") return false; auto args = root.FindMember("args"); if (args == root.MemberEnd() || !args->value.IsArray()) return false; const auto& language = args->value[0]; const auto& country = args->value[1]; if (!language.IsString() || !country.IsString()) return false; language_code_ = language.GetString(); country_code_ = country.GetString(); if (runtime_) runtime_->SetLocale(language_code_, country_code_); return true; } void Engine::DispatchPointerDataPacket(const PointerDataPacket& packet) { if (runtime_) runtime_->DispatchPointerDataPacket(packet); } void Engine::DispatchSemanticsAction(int id, blink::SemanticsAction action) { if (runtime_) runtime_->DispatchSemanticsAction(id, action); } void Engine::SetSemanticsEnabled(bool enabled) { semantics_enabled_ = enabled; if (runtime_) runtime_->SetSemanticsEnabled(semantics_enabled_); } void Engine::ConfigureAssetBundle(const std::string& path) { struct stat stat_result = {}; directory_asset_bundle_.reset(); // TODO(abarth): We should reset asset_store_ as well, but that might break // custom font loading in hot reload. if (::stat(path.c_str(), &stat_result) != 0) { FTL_LOG(INFO) << "Could not configure asset bundle at path: " << path; return; } if (S_ISDIR(stat_result.st_mode)) { directory_asset_bundle_ = std::make_unique(path); return; } if (S_ISREG(stat_result.st_mode)) { asset_store_ = ftl::MakeRefCounted( blink::GetUnzipperProviderForPath(path)); return; } } void Engine::ConfigureRuntime(const std::string& script_uri) { runtime_ = blink::RuntimeController::Create(this); runtime_->CreateDartController(std::move(script_uri), default_isolate_snapshot_data, default_isolate_snapshot_instr); runtime_->SetViewportMetrics(viewport_metrics_); runtime_->SetLocale(language_code_, country_code_); runtime_->SetSemanticsEnabled(semantics_enabled_); if (pending_push_route_message_) runtime_->DispatchPlatformMessage(std::move(pending_push_route_message_)); } void Engine::DidCreateMainIsolate(Dart_Isolate isolate) { if (blink::Settings::Get().use_test_fonts) { blink::TestFontSelector::Install(); } else if (asset_store_) { blink::AssetFontSelector::Install(asset_store_); } } void Engine::DidCreateSecondaryIsolate(Dart_Isolate isolate) {} void Engine::StopAnimator() { animator_->Stop(); } void Engine::StartAnimatorIfPossible() { if (activity_running_ && have_surface_) animator_->Start(); } void Engine::ScheduleFrame() { animator_->RequestFrame(); } void Engine::Render(std::unique_ptr layer_tree) { if (!layer_tree) return; SkISize frame_size = SkISize::Make(viewport_metrics_.physical_width, viewport_metrics_.physical_height); if (frame_size.isEmpty()) return; layer_tree->set_frame_size(frame_size); animator_->Render(std::move(layer_tree)); } void Engine::UpdateSemantics(std::vector update) { blink::Threads::Platform()->PostTask(ftl::MakeCopyable( [ platform_view = platform_view_, update = std::move(update) ]() mutable { if (platform_view) platform_view->UpdateSemantics(std::move(update)); })); } void Engine::HandlePlatformMessage( ftl::RefPtr message) { if (message->channel() == kAssetChannel) { HandleAssetPlatformMessage(std::move(message)); return; } blink::Threads::Platform()->PostTask([ platform_view = platform_view_, message = std::move(message) ]() mutable { if (platform_view) platform_view->HandlePlatformMessage(std::move(message)); }); } void Engine::HandleAssetPlatformMessage( ftl::RefPtr message) { ftl::RefPtr response = message->response(); if (!response) return; const auto& data = message->data(); std::string asset_name(reinterpret_cast(data.data()), data.size()); std::vector asset_data; if (GetAssetAsBuffer(asset_name, &asset_data)) { response->Complete(std::move(asset_data)); } else { response->CompleteEmpty(); } } bool Engine::GetAssetAsBuffer(const std::string& name, std::vector* data) { return (directory_asset_bundle_ && directory_asset_bundle_->GetAsBuffer(name, data)) || (asset_store_ && asset_store_->GetAsBuffer(name, data)); } } // namespace shell