| // 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 <dlfcn.h> |
| #include <magenta/dlfcn.h> |
| #include <mxio/namespace.h> |
| #include <utility> |
| |
| #include "dart-pkg/zircon/sdk_ext/handle.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/content_handler/service_protocol_hooks.h" |
| #include "flutter/lib/snapshot/snapshot.h" |
| #include "flutter/lib/ui/window/pointer_data_packet.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 "lib/app/cpp/connect.h" |
| #include "lib/fsl/vmo/vector.h" |
| #include "lib/fxl/functional/make_copyable.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/fxl/time/time_delta.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 kKernelKey[] = "kernel_blob.bin"; |
| constexpr char kSnapshotKey[] = "snapshot_blob.bin"; |
| constexpr char kDylibKey[] = "libapp.so"; |
| constexpr char kAssetChannel[] = "flutter/assets"; |
| constexpr char kKeyEventChannel[] = "flutter/keyevent"; |
| constexpr char kTextInputChannel[] = "flutter/textinput"; |
| |
| blink::PointerData::Change GetChangeFromPointerEventPhase( |
| mozart::PointerEvent::Phase phase) { |
| switch (phase) { |
| case mozart::PointerEvent::Phase::ADD: |
| return blink::PointerData::Change::kAdd; |
| case mozart::PointerEvent::Phase::HOVER: |
| return blink::PointerData::Change::kHover; |
| case mozart::PointerEvent::Phase::DOWN: |
| return blink::PointerData::Change::kDown; |
| case mozart::PointerEvent::Phase::MOVE: |
| return blink::PointerData::Change::kMove; |
| case mozart::PointerEvent::Phase::UP: |
| return blink::PointerData::Change::kUp; |
| case mozart::PointerEvent::Phase::REMOVE: |
| return blink::PointerData::Change::kRemove; |
| case mozart::PointerEvent::Phase::CANCEL: |
| return blink::PointerData::Change::kCancel; |
| default: |
| return blink::PointerData::Change::kCancel; |
| } |
| } |
| |
| blink::PointerData::DeviceKind GetKindFromPointerType( |
| mozart::PointerEvent::Type type) { |
| switch (type) { |
| case mozart::PointerEvent::Type::TOUCH: |
| return blink::PointerData::DeviceKind::kTouch; |
| case mozart::PointerEvent::Type::MOUSE: |
| return blink::PointerData::DeviceKind::kMouse; |
| default: |
| return blink::PointerData::DeviceKind::kTouch; |
| } |
| } |
| |
| } // namespace |
| |
| RuntimeHolder::RuntimeHolder() |
| : view_listener_binding_(this), |
| input_listener_binding_(this), |
| text_input_binding_(this), |
| weak_factory_(this) {} |
| |
| RuntimeHolder::~RuntimeHolder() { |
| blink::Threads::Gpu()->PostTask( |
| fxl::MakeCopyable([rasterizer = std::move(rasterizer_)](){ |
| // Deletes rasterizer. |
| })); |
| } |
| |
| void RuntimeHolder::Init( |
| mxio_ns_t* namespc, |
| std::unique_ptr<app::ApplicationContext> context, |
| fidl::InterfaceRequest<app::ServiceProvider> outgoing_services, |
| std::vector<char> bundle) { |
| FXL_DCHECK(!rasterizer_); |
| rasterizer_ = Rasterizer::Create(); |
| FXL_DCHECK(rasterizer_); |
| |
| namespc_ = namespc; |
| context_ = std::move(context); |
| outgoing_services_ = std::move(outgoing_services); |
| |
| context_->ConnectToEnvironmentService(view_manager_.NewRequest()); |
| |
| InitRootBundle(std::move(bundle)); |
| |
| const uint8_t* vm_snapshot_data; |
| const uint8_t* vm_snapshot_instr; |
| const uint8_t* default_isolate_snapshot_data; |
| const uint8_t* default_isolate_snapshot_instr; |
| if (!Dart_IsPrecompiledRuntime()) { |
| vm_snapshot_data = ::kDartVmSnapshotData; |
| vm_snapshot_instr = ::kDartVmSnapshotInstructions; |
| default_isolate_snapshot_data = ::kDartIsolateCoreSnapshotData; |
| default_isolate_snapshot_instr = ::kDartIsolateCoreSnapshotInstructions; |
| } else { |
| std::vector<uint8_t> dylib_blob; |
| if (!asset_store_->GetAsBuffer(kDylibKey, &dylib_blob)) { |
| FXL_LOG(ERROR) << "Failed to extract app dylib"; |
| return; |
| } |
| |
| mx::vmo dylib_vmo; |
| if (!fsl::VmoFromVector(dylib_blob, &dylib_vmo)) { |
| FXL_LOG(ERROR) << "Failed to load app dylib"; |
| return; |
| } |
| |
| dlerror(); |
| dylib_handle_ = dlopen_vmo(dylib_vmo.get(), RTLD_LAZY); |
| if (dylib_handle_ == nullptr) { |
| FXL_LOG(ERROR) << "dlopen failed: " << dlerror(); |
| return; |
| } |
| vm_snapshot_data = reinterpret_cast<const uint8_t*>( |
| dlsym(dylib_handle_, "_kDartVmSnapshotData")); |
| vm_snapshot_instr = reinterpret_cast<const uint8_t*>( |
| dlsym(dylib_handle_, "_kDartVmSnapshotInstructions")); |
| default_isolate_snapshot_data = reinterpret_cast<const uint8_t*>( |
| dlsym(dylib_handle_, "_kDartIsolateSnapshotData")); |
| default_isolate_snapshot_instr = reinterpret_cast<const uint8_t*>( |
| dlsym(dylib_handle_, "_kDartIsolateSnapshotInstructions")); |
| } |
| |
| // TODO(rmacnak): We should generate the AOT vm snapshot separately from |
| // each app so we can initialize before receiving the first app bundle. |
| static bool first_app = true; |
| if (first_app) { |
| first_app = false; |
| blink::InitRuntime(vm_snapshot_data, vm_snapshot_instr, |
| default_isolate_snapshot_data, |
| default_isolate_snapshot_instr); |
| |
| blink::SetRegisterNativeServiceProtocolExtensionHook( |
| ServiceProtocolHooks::RegisterHooks); |
| } |
| } |
| |
| void RuntimeHolder::CreateView( |
| const std::string& script_uri, |
| fidl::InterfaceRequest<mozart::ViewOwner> view_owner_request, |
| fidl::InterfaceRequest<app::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). |
| FXL_LOG(ERROR) << "The view has already been created."; |
| return; |
| } |
| |
| std::vector<uint8_t> kernel; |
| std::vector<uint8_t> snapshot; |
| if (!Dart_IsPrecompiledRuntime()) { |
| if (!asset_store_->GetAsBuffer(kKernelKey, &kernel) && |
| !asset_store_->GetAsBuffer(kSnapshotKey, &snapshot)) { |
| FXL_LOG(ERROR) << "Unable to load kernel or snapshot from root bundle."; |
| return; |
| } |
| } |
| |
| // Create the view. |
| mx::eventpair import_token, export_token; |
| mx_status_t status = mx::eventpair::create(0u, &import_token, &export_token); |
| if (status != MX_OK) { |
| FXL_LOG(ERROR) << "Could not create an event pair."; |
| return; |
| } |
| mozart::ViewListenerPtr view_listener; |
| view_listener_binding_.Bind(view_listener.NewRequest()); |
| view_manager_->CreateView(view_.NewRequest(), // view |
| std::move(view_owner_request), // view owner |
| std::move(view_listener), // view listener |
| std::move(export_token), // export token |
| script_uri // diagnostic label |
| ); |
| app::ServiceProviderPtr view_services; |
| view_->GetServiceProvider(view_services.NewRequest()); |
| |
| // Listen for input events. |
| ConnectToService(view_services.get(), fidl::GetProxy(&input_connection_)); |
| mozart::InputListenerPtr input_listener; |
| input_listener_binding_.Bind(GetProxy(&input_listener)); |
| input_connection_->SetEventListener(std::move(input_listener)); |
| |
| // Setup the session. |
| fidl::InterfaceHandle<scenic::SceneManager> scene_manager; |
| view_manager_->GetSceneManager(scene_manager.NewRequest()); |
| |
| blink::Threads::Gpu()->PostTask(fxl::MakeCopyable([ |
| rasterizer = rasterizer_.get(), // |
| scene_manager = std::move(scene_manager), // |
| import_token = std::move(import_token), // |
| weak_runtime_holder = GetWeakPtr() |
| ]() mutable { |
| ASSERT_IS_GPU_THREAD; |
| rasterizer->SetScene( |
| std::move(scene_manager), std::move(import_token), |
| // TODO(MZ-222): Ideally we would immediately redraw the previous layer |
| // tree when the metrics change since there's no need to rerecord it. |
| // However, we want to make sure there's only one outstanding frame. |
| // We should improve the frame scheduling so that the rasterizer thread |
| // can self-schedule re-rasterization. |
| [weak_runtime_holder] { |
| // This is on the GPU thread thread. Post to the Platform/UI |
| // thread for the completion callback. |
| ASSERT_IS_GPU_THREAD; |
| blink::Threads::Platform()->PostTask([weak_runtime_holder]() { |
| // On the Platform/UI thread. |
| ASSERT_IS_UI_THREAD; |
| if (weak_runtime_holder) { |
| weak_runtime_holder->OnRedrawFrame(); |
| } |
| }); |
| }); |
| })); |
| runtime_ = blink::RuntimeController::Create(this); |
| |
| const uint8_t* isolate_snapshot_data; |
| const uint8_t* isolate_snapshot_instr; |
| if (!Dart_IsPrecompiledRuntime()) { |
| isolate_snapshot_data = ::kDartIsolateCoreSnapshotData; |
| isolate_snapshot_instr = ::kDartIsolateCoreSnapshotInstructions; |
| } else { |
| isolate_snapshot_data = reinterpret_cast<const uint8_t*>( |
| dlsym(dylib_handle_, "_kDartIsolateSnapshotData")); |
| isolate_snapshot_instr = reinterpret_cast<const uint8_t*>( |
| dlsym(dylib_handle_, "_kDartIsolateSnapshotInstructions")); |
| } |
| std::vector<uint8_t> empty_platform_kernel; |
| runtime_->CreateDartController(script_uri, isolate_snapshot_data, |
| isolate_snapshot_instr, |
| std::move(empty_platform_kernel)); |
| |
| runtime_->SetViewportMetrics(viewport_metrics_); |
| |
| if (Dart_IsPrecompiledRuntime()) { |
| runtime_->dart_controller()->RunFromPrecompiledSnapshot(); |
| } else if (!kernel.empty()) { |
| runtime_->dart_controller()->RunFromKernel(std::move(kernel)); |
| } else { |
| runtime_->dart_controller()->RunFromScriptSnapshot(snapshot.data(), |
| snapshot.size()); |
| } |
| |
| runtime_->dart_controller()->dart_state()->SetReturnCodeCallback( |
| [this](int32_t return_code) { return_code_ = return_code; }); |
| } |
| |
| Dart_Port RuntimeHolder::GetUIIsolateMainPort() { |
| if (!runtime_) |
| return ILLEGAL_PORT; |
| return runtime_->GetMainPort(); |
| } |
| |
| std::string RuntimeHolder::GetUIIsolateName() { |
| if (!runtime_) { |
| return ""; |
| } |
| return runtime_->GetIsolateName(); |
| } |
| |
| std::string RuntimeHolder::DefaultRouteName() { |
| return "/"; |
| } |
| |
| void RuntimeHolder::ScheduleFrame() { |
| ASSERT_IS_UI_THREAD; |
| if (!frame_scheduled_) { |
| frame_scheduled_ = true; |
| if (!frame_outstanding_) |
| PostBeginFrame(); |
| } |
| } |
| |
| void RuntimeHolder::Render(std::unique_ptr<flow::LayerTree> layer_tree) { |
| if (!frame_outstanding_ || frame_rendering_) { |
| // TODO(MZ-193): We probably shouldn't be discarding the layer tree here. |
| // But then, Flutter shouldn't be calling Render() if we didn't call |
| // BeginFrame(). |
| return; // Spurious. |
| } |
| |
| frame_rendering_ = true; |
| |
| layer_tree->set_construction_time(fxl::TimePoint::Now() - |
| last_begin_frame_time_); |
| layer_tree->set_frame_size(SkISize::Make(viewport_metrics_.physical_width, |
| viewport_metrics_.physical_height)); |
| layer_tree->set_device_pixel_ratio(viewport_metrics_.device_pixel_ratio); |
| |
| // We are on the Platform/UI thread. Post to the GPU thread to render. |
| ASSERT_IS_PLATFORM_THREAD; |
| blink::Threads::Gpu()->PostTask(fxl::MakeCopyable([ |
| rasterizer = rasterizer_.get(), // |
| layer_tree = std::move(layer_tree), // |
| weak_runtime_holder = GetWeakPtr() // |
| ]() mutable { |
| // On the GPU Thread. |
| ASSERT_IS_GPU_THREAD; |
| rasterizer->Draw(std::move(layer_tree), [weak_runtime_holder]() { |
| // This is on the GPU thread thread. Post to the Platform/UI thread |
| // for the completion callback. |
| ASSERT_IS_GPU_THREAD; |
| blink::Threads::Platform()->PostTask([weak_runtime_holder]() { |
| // On the Platform/UI thread. |
| ASSERT_IS_UI_THREAD; |
| if (weak_runtime_holder) { |
| weak_runtime_holder->frame_rendering_ = false; |
| weak_runtime_holder->OnFrameComplete(); |
| } |
| }); |
| }); |
| })); |
| } |
| |
| void RuntimeHolder::UpdateSemantics(std::vector<blink::SemanticsNode> update) {} |
| |
| void RuntimeHolder::HandlePlatformMessage( |
| fxl::RefPtr<blink::PlatformMessage> message) { |
| if (message->channel() == kAssetChannel) { |
| if (HandleAssetPlatformMessage(message.get())) |
| return; |
| } else if (message->channel() == kTextInputChannel) { |
| if (HandleTextInputPlatformMessage(message.get())) |
| return; |
| } |
| if (auto response = message->response()) |
| response->CompleteEmpty(); |
| } |
| |
| void RuntimeHolder::DidCreateMainIsolate(Dart_Isolate isolate) { |
| if (asset_store_) |
| blink::AssetFontSelector::Install(asset_store_); |
| InitDartIoInternal(); |
| InitFuchsia(); |
| InitMozartInternal(); |
| } |
| |
| void RuntimeHolder::InitDartIoInternal() { |
| Dart_Handle io_lib = Dart_LookupLibrary(ToDart("dart:io")); |
| |
| // Set up the namespace. |
| Dart_Handle namespace_type = |
| Dart_GetType(io_lib, ToDart("_Namespace"), 0, nullptr); |
| DART_CHECK_VALID(namespace_type); |
| Dart_Handle namespace_args[1]; |
| namespace_args[0] = Dart_NewInteger(reinterpret_cast<intptr_t>(namespc_)); |
| DART_CHECK_VALID(namespace_args[0]); |
| DART_CHECK_VALID(Dart_Invoke(namespace_type, ToDart("_setupNamespace"), 1, |
| namespace_args)); |
| |
| // Disable dart:io exit() |
| Dart_Handle embedder_config_type = |
| Dart_GetType(io_lib, ToDart("_EmbedderConfig"), 0, nullptr); |
| DART_CHECK_VALID(embedder_config_type); |
| DART_CHECK_VALID( |
| Dart_SetField(embedder_config_type, ToDart("_mayExit"), Dart_False())); |
| } |
| |
| void RuntimeHolder::InitFuchsia() { |
| fidl::InterfaceHandle<app::ApplicationEnvironment> environment; |
| context_->ConnectToEnvironmentService(environment.NewRequest()); |
| fuchsia::dart::Initialize(std::move(environment), |
| std::move(outgoing_services_)); |
| } |
| |
| 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_SetNativeResolver(mozart_internal, mozart::NativeLookup, |
| mozart::NativeSymbol)); |
| |
| DART_CHECK_VALID( |
| Dart_SetField(mozart_internal, ToDart("_context"), |
| DartConverter<uint64_t>::ToDart(reinterpret_cast<intptr_t>( |
| static_cast<mozart::NativesDelegate*>(this))))); |
| |
| DART_CHECK_VALID(Dart_SetField(mozart_internal, ToDart("_viewContainer"), |
| ToDart(zircon::dart::Handle::Create( |
| view_container.PassHandle().release())))); |
| } |
| |
| void RuntimeHolder::InitRootBundle(std::vector<char> bundle) { |
| root_bundle_data_ = std::move(bundle); |
| asset_store_ = fxl::MakeRefCounted<blink::ZipAssetStore>( |
| GetUnzipperProviderForRootBundle()); |
| } |
| |
| mozart::View* RuntimeHolder::GetMozartView() { |
| return view_.get(); |
| } |
| |
| bool RuntimeHolder::HandleAssetPlatformMessage( |
| blink::PlatformMessage* message) { |
| fxl::RefPtr<blink::PlatformMessageResponse> response = message->response(); |
| if (!response) |
| return false; |
| 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->CompleteEmpty(); |
| } |
| return true; |
| } |
| |
| bool RuntimeHolder::HandleTextInputPlatformMessage( |
| 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.IsString()) |
| return false; |
| |
| if (method->value == "TextInput.show") { |
| if (input_method_editor_) { |
| input_method_editor_->Show(); |
| } |
| } else if (method->value == "TextInput.hide") { |
| if (input_method_editor_) { |
| input_method_editor_->Hide(); |
| } |
| } else if (method->value == "TextInput.setClient") { |
| current_text_input_client_ = 0; |
| if (text_input_binding_.is_bound()) |
| text_input_binding_.Close(); |
| input_method_editor_ = nullptr; |
| |
| auto args = root.FindMember("args"); |
| if (args == root.MemberEnd() || !args->value.IsArray() || |
| args->value.Size() != 2) |
| return false; |
| const auto& configuration = args->value[1]; |
| if (!configuration.IsObject()) |
| return false; |
| // TODO(abarth): Read the keyboard type form the configuration. |
| current_text_input_client_ = args->value[0].GetInt(); |
| mozart::TextInputStatePtr state = mozart::TextInputState::New(); |
| state->text = std::string(); |
| state->selection = mozart::TextSelection::New(); |
| state->composing = mozart::TextRange::New(); |
| input_connection_->GetInputMethodEditor( |
| mozart::KeyboardType::TEXT, mozart::InputMethodAction::DONE, |
| std::move(state), text_input_binding_.NewBinding(), |
| fidl::GetProxy(&input_method_editor_)); |
| } else if (method->value == "TextInput.setEditingState") { |
| if (input_method_editor_) { |
| auto args_it = root.FindMember("args"); |
| if (args_it == root.MemberEnd() || !args_it->value.IsObject()) |
| return false; |
| const auto& args = args_it->value; |
| mozart::TextInputStatePtr state = mozart::TextInputState::New(); |
| state->selection = mozart::TextSelection::New(); |
| state->composing = mozart::TextRange::New(); |
| // TODO(abarth): Deserialize state. |
| auto text = args.FindMember("text"); |
| if (text != args.MemberEnd() && text->value.IsString()) |
| state->text = text->value.GetString(); |
| auto selection_base = args.FindMember("selectionBase"); |
| if (selection_base != args.MemberEnd() && selection_base->value.IsInt()) |
| state->selection->base = selection_base->value.GetInt(); |
| auto selection_extent = args.FindMember("selectionExtent"); |
| if (selection_extent != args.MemberEnd() && |
| selection_extent->value.IsInt()) |
| state->selection->extent = selection_extent->value.GetInt(); |
| auto selection_affinity = args.FindMember("selectionAffinity"); |
| if (selection_affinity != args.MemberEnd() && |
| selection_affinity->value.IsString() && |
| selection_affinity->value == "TextAffinity.upstream") |
| state->selection->affinity = mozart::TextAffinity::UPSTREAM; |
| else |
| state->selection->affinity = mozart::TextAffinity::DOWNSTREAM; |
| // We ignore selectionIsDirectional because that concept doesn't exist on |
| // Fuchsia. |
| auto composing_base = args.FindMember("composingBase"); |
| if (composing_base != args.MemberEnd() && composing_base->value.IsInt()) |
| state->composing->start = composing_base->value.GetInt(); |
| auto composing_extent = args.FindMember("composingExtent"); |
| if (composing_extent != args.MemberEnd() && |
| composing_extent->value.IsInt()) |
| state->composing->end = composing_extent->value.GetInt(); |
| input_method_editor_->SetState(std::move(state)); |
| } |
| } else if (method->value == "TextInput.clearClient") { |
| current_text_input_client_ = 0; |
| if (text_input_binding_.is_bound()) |
| text_input_binding_.Close(); |
| input_method_editor_ = nullptr; |
| } else { |
| FXL_DLOG(ERROR) << "Unknown " << kTextInputChannel << " method " |
| << method->value.GetString(); |
| } |
| |
| return false; |
| } |
| |
| blink::UnzipperProvider RuntimeHolder::GetUnzipperProviderForRootBundle() { |
| return [self = GetWeakPtr()]() { |
| if (!self) |
| return zip::UniqueUnzipper(); |
| return zip::CreateUnzipper(&self->root_bundle_data_); |
| }; |
| } |
| |
| void RuntimeHolder::OnEvent(mozart::InputEventPtr event, |
| const OnEventCallback& callback) { |
| bool handled = false; |
| if (event->is_pointer()) { |
| const mozart::PointerEventPtr& pointer = event->get_pointer(); |
| blink::PointerData pointer_data; |
| pointer_data.time_stamp = pointer->event_time / 1000; |
| pointer_data.change = GetChangeFromPointerEventPhase(pointer->phase); |
| pointer_data.kind = GetKindFromPointerType(pointer->type); |
| pointer_data.device = pointer->pointer_id; |
| pointer_data.physical_x = pointer->x * viewport_metrics_.device_pixel_ratio; |
| pointer_data.physical_y = pointer->y * viewport_metrics_.device_pixel_ratio; |
| |
| 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: |
| FXL_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->is_keyboard()) { |
| const mozart::KeyboardEventPtr& keyboard = event->get_keyboard(); |
| const char* type = nullptr; |
| if (keyboard->phase == mozart::KeyboardEvent::Phase::PRESSED) |
| type = "keydown"; |
| else if (keyboard->phase == mozart::KeyboardEvent::Phase::REPEAT) |
| type = "keydown"; // TODO change this to keyrepeat |
| else if (keyboard->phase == mozart::KeyboardEvent::Phase::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", keyboard->hid_usage, allocator); |
| document.AddMember("codePoint", keyboard->code_point, allocator); |
| document.AddMember("modifiers", keyboard->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( |
| fxl::MakeRefCounted<blink::PlatformMessage>( |
| kKeyEventChannel, |
| std::vector<uint8_t>(data, data + buffer.GetSize()), nullptr)); |
| handled = true; |
| } |
| } |
| callback(handled); |
| } |
| |
| void RuntimeHolder::OnPropertiesChanged( |
| mozart::ViewPropertiesPtr properties, |
| const OnPropertiesChangedCallback& callback) { |
| FXL_DCHECK(properties); |
| |
| // Attempt to read the device pixel ratio. |
| float pixel_ratio = 1.f; |
| if (auto& metrics = properties->display_metrics) { |
| pixel_ratio = metrics->device_pixel_ratio; |
| } |
| |
| // Apply view property changes. |
| if (auto& layout = properties->view_layout) { |
| viewport_metrics_.physical_width = layout->size->width * pixel_ratio; |
| viewport_metrics_.physical_height = layout->size->height * pixel_ratio; |
| viewport_metrics_.physical_padding_top = layout->inset->top * pixel_ratio; |
| viewport_metrics_.physical_padding_right = |
| layout->inset->right * pixel_ratio; |
| viewport_metrics_.physical_padding_bottom = |
| layout->inset->bottom * pixel_ratio; |
| viewport_metrics_.physical_padding_left = layout->inset->left * pixel_ratio; |
| viewport_metrics_.device_pixel_ratio = pixel_ratio; |
| runtime_->SetViewportMetrics(viewport_metrics_); |
| } |
| |
| ScheduleFrame(); |
| |
| callback(); |
| } |
| |
| void RuntimeHolder::DidUpdateState(mozart::TextInputStatePtr state, |
| mozart::InputEventPtr event) { |
| rapidjson::Document document; |
| auto& allocator = document.GetAllocator(); |
| |
| rapidjson::Value encoded_state(rapidjson::kObjectType); |
| encoded_state.AddMember("text", state->text.get(), allocator); |
| encoded_state.AddMember("selectionBase", state->selection->base, allocator); |
| encoded_state.AddMember("selectionExtent", state->selection->extent, |
| allocator); |
| switch (state->selection->affinity) { |
| case mozart::TextAffinity::UPSTREAM: |
| encoded_state.AddMember("selectionAffinity", |
| rapidjson::Value("TextAffinity.upstream"), |
| allocator); |
| break; |
| case mozart::TextAffinity::DOWNSTREAM: |
| encoded_state.AddMember("selectionAffinity", |
| rapidjson::Value("TextAffinity.downstream"), |
| allocator); |
| break; |
| } |
| encoded_state.AddMember("selectionIsDirectional", true, allocator); |
| encoded_state.AddMember("composingBase", state->composing->start, allocator); |
| encoded_state.AddMember("composingExtent", state->composing->end, allocator); |
| |
| rapidjson::Value args(rapidjson::kArrayType); |
| args.PushBack(current_text_input_client_, allocator); |
| args.PushBack(encoded_state, allocator); |
| |
| document.SetObject(); |
| document.AddMember("method", |
| rapidjson::Value("TextInputClient.updateEditingState"), |
| allocator); |
| document.AddMember("args", args, 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(fxl::MakeRefCounted<blink::PlatformMessage>( |
| kTextInputChannel, std::vector<uint8_t>(data, data + buffer.GetSize()), |
| nullptr)); |
| } |
| |
| void RuntimeHolder::OnAction(mozart::InputMethodAction action) { |
| rapidjson::Document document; |
| auto& allocator = document.GetAllocator(); |
| |
| rapidjson::Value args(rapidjson::kArrayType); |
| args.PushBack(current_text_input_client_, allocator); |
| |
| // Done is currently the only text input action defined by Flutter. |
| args.PushBack("TextInputAction.done", allocator); |
| |
| document.SetObject(); |
| document.AddMember( |
| "method", rapidjson::Value("TextInputClient.performAction"), allocator); |
| document.AddMember("args", args, 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(fxl::MakeRefCounted<blink::PlatformMessage>( |
| kTextInputChannel, std::vector<uint8_t>(data, data + buffer.GetSize()), |
| nullptr)); |
| } |
| |
| fxl::WeakPtr<RuntimeHolder> RuntimeHolder::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void RuntimeHolder::PostBeginFrame() { |
| blink::Threads::Platform()->PostTask([weak_runtime_holder = GetWeakPtr()]() { |
| // On the Platform/UI thread. |
| ASSERT_IS_UI_THREAD; |
| if (weak_runtime_holder) { |
| weak_runtime_holder->BeginFrame(); |
| } |
| }); |
| } |
| |
| void RuntimeHolder::BeginFrame() { |
| ASSERT_IS_UI_THREAD |
| FXL_DCHECK(frame_scheduled_); |
| FXL_DCHECK(!frame_outstanding_); |
| frame_scheduled_ = false; |
| frame_outstanding_ = true; |
| last_begin_frame_time_ = fxl::TimePoint::Now(); |
| runtime_->BeginFrame(last_begin_frame_time_); |
| } |
| |
| void RuntimeHolder::OnFrameComplete() { |
| ASSERT_IS_UI_THREAD |
| FXL_DCHECK(frame_outstanding_); |
| frame_outstanding_ = false; |
| if (frame_scheduled_) |
| PostBeginFrame(); |
| } |
| |
| void RuntimeHolder::OnRedrawFrame() { |
| if (!frame_outstanding_) |
| ScheduleFrame(); |
| } |
| |
| } // namespace flutter_runner |