blob: a85b1c651df6207e842a5bca754b1ede0eb104ea [file] [log] [blame]
// 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 <dlfcn.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
#include <utility>
#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<Animator>(
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> 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<const uint8_t*>(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<const uint8_t*>(
dlsym(library_handle, "kDartVmSnapshotData"));
vm_snapshot_instr = reinterpret_cast<const uint8_t*>(
dlsym(library_handle, "kDartVmSnapshotInstructions"));
default_isolate_snapshot_data = reinterpret_cast<const uint8_t*>(
dlsym(library_handle, "kDartIsolateSnapshotData"));
default_isolate_snapshot_instr = reinterpret_cast<const uint8_t*>(
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<uint8_t> kernel;
if (GetAssetAsBuffer(blink::kKernelAssetKey, &kernel)) {
runtime_->dart_controller()->RunFromKernel(kernel.data(), kernel.size());
return;
}
std::vector<uint8_t> 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<uint8_t> 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<blink::PlatformMessage> 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 may still need to set the initial route.
if (message->channel() == kNavigationChannel)
HandleNavigationPlatformMessage(std::move(message));
}
bool Engine::HandleLifecyclePlatformMessage(blink::PlatformMessage* message) {
const auto& data = message->data();
std::string state(reinterpret_cast<const char*>(data.data()), data.size());
if (state == "AppLifecycleState.paused" ||
state == "AppLifecycleState.suspending") {
activity_running_ = false;
StopAnimator();
} else if (state == "AppLifecycleState.resumed" ||
state == "AppLifecycle.inactive") {
activity_running_ = true;
StartAnimatorIfPossible();
}
return false;
}
bool Engine::HandleNavigationPlatformMessage(
ftl::RefPtr<blink::PlatformMessage> message) {
FTL_DCHECK(!runtime_);
const auto& data = message->data();
rapidjson::Document document;
document.Parse(reinterpret_cast<const char*>(data.data()), data.size());
if (document.HasParseError() || !document.IsObject())
return false;
auto root = document.GetObject();
auto method = root.FindMember("method");
if (method->value != "setInitialRoute")
return false;
auto route = root.FindMember("args");
initial_route_ = std::move(route->value.GetString());
return true;
}
bool Engine::HandleLocalizationPlatformMessage(
ftl::RefPtr<blink::PlatformMessage> message) {
const auto& data = message->data();
rapidjson::Document document;
document.Parse(reinterpret_cast<const char*>(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<blink::DirectoryAssetBundle>(path);
return;
}
if (S_ISREG(stat_result.st_mode)) {
asset_store_ = ftl::MakeRefCounted<blink::ZipAssetStore>(
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_);
}
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();
}
std::string Engine::DefaultRouteName() {
if (!initial_route_.empty()) {
return initial_route_;
}
return "/";
}
void Engine::ScheduleFrame() {
animator_->RequestFrame();
}
void Engine::Render(std::unique_ptr<flow::LayerTree> 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<blink::SemanticsNode> 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<blink::PlatformMessage> 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<blink::PlatformMessage> message) {
ftl::RefPtr<blink::PlatformMessageResponse> response = message->response();
if (!response)
return;
const auto& data = message->data();
std::string asset_name(reinterpret_cast<const char*>(data.data()),
data.size());
std::vector<uint8_t> 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<uint8_t>* data) {
return (directory_asset_bundle_ &&
directory_asset_bundle_->GetAsBuffer(name, data)) ||
(asset_store_ && asset_store_->GetAsBuffer(name, data));
}
} // namespace shell