blob: 96b5ebcc32addd5535f7ef7a5d0c2af3560f273c [file] [log] [blame]
// Copyright 2016 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/content_handler/runtime_holder.h"
#include <utility>
#include "application/lib/app/connect.h"
#include "dart/runtime/include/dart_api.h"
#include "flutter/assets/zip_asset_store.h"
#include "flutter/common/threads.h"
#include "flutter/content_handler/rasterizer.h"
#include "flutter/lib/ui/window/pointer_data_packet.h"
#include "flutter/runtime/asset_font_selector.h"
#include "flutter/runtime/dart_controller.h"
#include "lib/fidl/dart/sdk_ext/src/natives.h"
#include "lib/ftl/functional/make_copyable.h"
#include "lib/ftl/logging.h"
#include "lib/ftl/time/time_delta.h"
#include "lib/tonic/mx/mx_converter.h"
#include "lib/zip/create_unzipper.h"
#include "third_party/rapidjson/rapidjson/document.h"
#include "third_party/rapidjson/rapidjson/stringbuffer.h"
#include "third_party/rapidjson/rapidjson/writer.h"
using tonic::DartConverter;
using tonic::ToDart;
namespace flutter_runner {
namespace {
constexpr char kSnapshotKey[] = "snapshot_blob.bin";
constexpr char kAssetChannel[] = "flutter/assets";
// Maximum number of frames in flight.
constexpr int kMaxPipelineDepth = 3;
// When the max pipeline depth is exceeded, drain to this number of frames
// to recover before acknowleding the invalidation and scheduling more frames.
constexpr int kRecoveryPipelineDepth = 1;
blink::PointerData::Change GetChangeFromEventType(mozart::EventType type) {
switch (type) {
case mozart::EventType::POINTER_CANCEL:
return blink::PointerData::Change::kCancel;
case mozart::EventType::POINTER_DOWN:
return blink::PointerData::Change::kDown;
case mozart::EventType::POINTER_MOVE:
return blink::PointerData::Change::kMove;
case mozart::EventType::POINTER_UP:
return blink::PointerData::Change::kUp;
default:
return blink::PointerData::Change::kCancel;
}
}
blink::PointerData::DeviceKind GetKindFromEventKind(mozart::PointerKind kind) {
switch (kind) {
case mozart::PointerKind::TOUCH:
return blink::PointerData::DeviceKind::kTouch;
case mozart::PointerKind::MOUSE:
return blink::PointerData::DeviceKind::kMouse;
default:
return blink::PointerData::DeviceKind::kTouch;
}
}
} // namespace
RuntimeHolder::RuntimeHolder()
: view_listener_binding_(this),
input_listener_binding_(this),
weak_factory_(this) {}
RuntimeHolder::~RuntimeHolder() {
blink::Threads::Gpu()->PostTask(
ftl::MakeCopyable([rasterizer = std::move(rasterizer_)](){
// Deletes rasterizer.
}));
}
void RuntimeHolder::Init(
fidl::InterfaceHandle<modular::ApplicationEnvironment> environment,
fidl::InterfaceRequest<modular::ServiceProvider> outgoing_services,
std::vector<char> bundle) {
FTL_DCHECK(!rasterizer_);
rasterizer_ = Rasterizer::Create();
FTL_DCHECK(rasterizer_);
environment_.Bind(std::move(environment));
environment_->GetServices(fidl::GetProxy(&environment_services_));
ConnectToService(environment_services_.get(), fidl::GetProxy(&view_manager_));
outgoing_services_ = std::move(outgoing_services);
InitRootBundle(std::move(bundle));
}
void RuntimeHolder::CreateView(
const std::string& script_uri,
fidl::InterfaceRequest<mozart::ViewOwner> view_owner_request,
fidl::InterfaceRequest<modular::ServiceProvider> services) {
if (view_listener_binding_.is_bound()) {
// TODO(jeffbrown): Refactor this to support multiple view instances
// sharing the same underlying root bundle (but with different runtimes).
FTL_LOG(ERROR) << "The view has already been created.";
return;
}
std::vector<uint8_t> snapshot;
if (!asset_store_->GetAsBuffer(kSnapshotKey, &snapshot)) {
FTL_LOG(ERROR) << "Unable to load snapshot from root bundle.";
return;
}
mozart::ViewListenerPtr view_listener;
view_listener_binding_.Bind(fidl::GetProxy(&view_listener));
view_manager_->CreateView(fidl::GetProxy(&view_),
std::move(view_owner_request),
std::move(view_listener), script_uri);
modular::ServiceProviderPtr view_services;
view_->GetServiceProvider(GetProxy(&view_services));
// Listen for input events.
ConnectToService(view_services.get(), GetProxy(&input_connection_));
mozart::InputListenerPtr input_listener;
input_listener_binding_.Bind(GetProxy(&input_listener));
input_connection_->SetListener(std::move(input_listener));
#if FLUTTER_ENABLE_VULKAN
direct_input_ = std::make_unique<DirectInput>(
[this](const blink::PointerDataPacket& packet) -> void {
runtime_->DispatchPointerDataPacket(packet);
});
FTL_DCHECK(direct_input_->IsValid());
direct_input_->WaitForReadAvailability();
#endif // FLUTTER_ENABLE_VULKAN
mozart::ScenePtr scene;
view_->CreateScene(fidl::GetProxy(&scene));
blink::Threads::Gpu()->PostTask(ftl::MakeCopyable([
rasterizer = rasterizer_.get(), scene = std::move(scene)
]() mutable { rasterizer->SetScene(std::move(scene)); }));
runtime_ = blink::RuntimeController::Create(this);
runtime_->CreateDartController(script_uri);
runtime_->SetViewportMetrics(viewport_metrics_);
#if FLUTTER_ENABLE_VULKAN
direct_input_->SetViewportMetrics(viewport_metrics_);
#endif // FLUTTER_ENABLE_VULKAN
runtime_->dart_controller()->RunFromSnapshot(snapshot.data(),
snapshot.size());
}
void RuntimeHolder::ScheduleFrame() {
if (pending_invalidation_ || deferred_invalidation_callback_)
return;
pending_invalidation_ = true;
view_->Invalidate();
}
void RuntimeHolder::Render(std::unique_ptr<flow::LayerTree> layer_tree) {
if (!is_ready_to_draw_)
return; // Only draw once per frame.
is_ready_to_draw_ = false;
layer_tree->set_frame_size(SkISize::Make(viewport_metrics_.physical_width,
viewport_metrics_.physical_height));
layer_tree->set_scene_version(scene_version_);
blink::Threads::Gpu()->PostTask(ftl::MakeCopyable([
rasterizer = rasterizer_.get(), layer_tree = std::move(layer_tree),
self = GetWeakPtr()
]() mutable {
rasterizer->Draw(std::move(layer_tree), [self]() {
if (self)
self->OnFrameComplete();
});
}));
}
void RuntimeHolder::UpdateSemantics(std::vector<blink::SemanticsNode> update) {}
void RuntimeHolder::HandlePlatformMessage(
ftl::RefPtr<blink::PlatformMessage> message) {
if (message->channel() == kAssetChannel) {
HandleAssetPlatformMessage(std::move(message));
return;
}
if (auto response = message->response())
response->CompleteWithError();
}
void RuntimeHolder::DidCreateMainIsolate(Dart_Isolate isolate) {
if (asset_store_)
blink::AssetFontSelector::Install(asset_store_);
InitFidlInternal();
InitMozartInternal();
}
void RuntimeHolder::InitFidlInternal() {
fidl::InterfaceHandle<modular::ApplicationEnvironment> environment;
environment_->Duplicate(GetProxy(&environment));
Dart_Handle fidl_internal = Dart_LookupLibrary(ToDart("dart:fidl.internal"));
DART_CHECK_VALID(Dart_SetNativeResolver(
fidl_internal, fidl::dart::NativeLookup, fidl::dart::NativeSymbol));
DART_CHECK_VALID(Dart_SetField(
fidl_internal, ToDart("_environment"),
DartConverter<mx::channel>::ToDart(environment.PassHandle())));
DART_CHECK_VALID(Dart_SetField(
fidl_internal, ToDart("_outgoingServices"),
DartConverter<mx::channel>::ToDart(outgoing_services_.PassChannel())));
}
void RuntimeHolder::InitMozartInternal() {
fidl::InterfaceHandle<mozart::ViewContainer> view_container;
view_->GetContainer(fidl::GetProxy(&view_container));
Dart_Handle mozart_internal =
Dart_LookupLibrary(ToDart("dart:mozart.internal"));
DART_CHECK_VALID(Dart_SetField(
mozart_internal, ToDart("_viewContainer"),
DartConverter<mx::channel>::ToDart(view_container.PassHandle())));
}
void RuntimeHolder::InitRootBundle(std::vector<char> bundle) {
root_bundle_data_ = std::move(bundle);
asset_store_ = ftl::MakeRefCounted<blink::ZipAssetStore>(
GetUnzipperProviderForRootBundle());
}
void RuntimeHolder::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 (asset_store_ && asset_store_->GetAsBuffer(asset_name, &asset_data)) {
response->Complete(std::move(asset_data));
} else {
response->CompleteWithError();
}
}
blink::UnzipperProvider RuntimeHolder::GetUnzipperProviderForRootBundle() {
return [self = GetWeakPtr()]() {
if (!self)
return zip::UniqueUnzipper();
return zip::CreateUnzipper(&self->root_bundle_data_);
};
}
void RuntimeHolder::OnEvent(mozart::EventPtr event,
const OnEventCallback& callback) {
bool handled = false;
if (event->pointer_data) {
blink::PointerData pointer_data;
pointer_data.time_stamp = event->time_stamp;
pointer_data.change = GetChangeFromEventType(event->action);
pointer_data.kind = GetKindFromEventKind(event->pointer_data->kind);
pointer_data.device = event->pointer_data->pointer_id;
pointer_data.physical_x = event->pointer_data->x;
pointer_data.physical_y = event->pointer_data->y;
switch (pointer_data.change) {
case blink::PointerData::Change::kDown:
down_pointers_.insert(pointer_data.device);
break;
case blink::PointerData::Change::kCancel:
case blink::PointerData::Change::kUp:
down_pointers_.erase(pointer_data.device);
break;
case blink::PointerData::Change::kMove:
if (down_pointers_.count(pointer_data.device) == 0)
pointer_data.change = blink::PointerData::Change::kHover;
break;
case blink::PointerData::Change::kAdd:
case blink::PointerData::Change::kRemove:
case blink::PointerData::Change::kHover:
FTL_DCHECK(down_pointers_.count(pointer_data.device) == 0);
break;
}
blink::PointerDataPacket packet(1);
packet.SetPointerData(0, pointer_data);
runtime_->DispatchPointerDataPacket(packet);
handled = true;
} else if (event->key_data) {
const char* type = nullptr;
if (event->action == mozart::EventType::KEY_PRESSED)
type = "keydown";
else if (event->action == mozart::EventType::KEY_RELEASED)
type = "keyup";
if (type) {
rapidjson::Document document;
auto& allocator = document.GetAllocator();
document.SetObject();
document.AddMember("type", rapidjson::Value(type, strlen(type)),
allocator);
document.AddMember("keymap", rapidjson::Value("fuchsia"), allocator);
document.AddMember("hidUsage", event->key_data->hid_usage, allocator);
document.AddMember("codePoint", event->key_data->code_point, allocator);
document.AddMember("modifiers", event->key_data->modifiers, allocator);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document.Accept(writer);
const uint8_t* data =
reinterpret_cast<const uint8_t*>(buffer.GetString());
runtime_->DispatchPlatformMessage(
ftl::MakeRefCounted<blink::PlatformMessage>(
"flutter/keyevent",
std::vector<uint8_t>(data, data + buffer.GetSize()), nullptr));
handled = true;
}
}
callback(handled);
}
void RuntimeHolder::OnInvalidation(mozart::ViewInvalidationPtr invalidation,
const OnInvalidationCallback& callback) {
FTL_DCHECK(invalidation);
pending_invalidation_ = false;
// Apply view property changes.
if (invalidation->properties) {
view_properties_ = std::move(invalidation->properties);
viewport_metrics_.physical_width =
view_properties_->view_layout->size->width;
viewport_metrics_.physical_height =
view_properties_->view_layout->size->height;
viewport_metrics_.device_pixel_ratio = 2.0;
// TODO(abarth): Use view_properties_->display_metrics->device_pixel_ratio
// once that's reasonable.
runtime_->SetViewportMetrics(viewport_metrics_);
#if FLUTTER_ENABLE_VULKAN
if (direct_input_) {
direct_input_->SetViewportMetrics(viewport_metrics_);
}
#endif // FLUTTER_ENABLE_VULKAN
}
// Remember the scene version for rendering.
scene_version_ = invalidation->scene_version;
// TODO(jeffbrown): Flow the frame time through the rendering pipeline.
if (outstanding_requests_ >= kMaxPipelineDepth) {
FTL_DCHECK(!deferred_invalidation_callback_);
deferred_invalidation_callback_ = callback;
return;
}
++outstanding_requests_;
BeginFrame();
// TODO(jeffbrown): Consider running the callback earlier.
// Note that this may result in the view processing stale view properties
// (such as size) if it prematurely acks the frame but takes too long
// to handle it.
callback();
}
ftl::WeakPtr<RuntimeHolder> RuntimeHolder::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void RuntimeHolder::BeginFrame() {
FTL_DCHECK(outstanding_requests_ > 0);
FTL_DCHECK(outstanding_requests_ <= kMaxPipelineDepth)
<< outstanding_requests_;
FTL_DCHECK(!is_ready_to_draw_);
is_ready_to_draw_ = true;
runtime_->BeginFrame(ftl::TimePoint::Now());
const bool was_ready_to_draw = is_ready_to_draw_;
is_ready_to_draw_ = false;
// If we were still ready to draw when done with the frame, that means we
// didn't draw anything this frame and we should acknowledge the frame
// ourselves instead of waiting for the rasterizer to acknowledge it.
if (was_ready_to_draw)
OnFrameComplete();
}
void RuntimeHolder::OnFrameComplete() {
FTL_DCHECK(outstanding_requests_ > 0);
--outstanding_requests_;
if (deferred_invalidation_callback_ &&
outstanding_requests_ <= kRecoveryPipelineDepth) {
// Schedule frame first to avoid potentially generating a second
// invalidation in case the view manager already has one pending
// awaiting acknowledgement of the deferred invalidation.
OnInvalidationCallback callback =
std::move(deferred_invalidation_callback_);
ScheduleFrame();
callback();
}
}
} // namespace flutter_runner