| // 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. |
| |
| #define RAPIDJSON_HAS_STDSTRING 1 |
| |
| #include "platform_view.h" |
| |
| #include <fuchsia/ui/app/cpp/fidl.h> |
| #include <zircon/status.h> |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <limits> |
| #include <sstream> |
| |
| #include "flutter/fml/logging.h" |
| #include "flutter/fml/make_copyable.h" |
| #include "flutter/lib/ui/window/pointer_data.h" |
| #include "flutter/shell/platform/common/client_wrapper/include/flutter/encodable_value.h" |
| #include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h" |
| #include "third_party/rapidjson/include/rapidjson/document.h" |
| #include "third_party/rapidjson/include/rapidjson/stringbuffer.h" |
| #include "third_party/rapidjson/include/rapidjson/writer.h" |
| |
| #include "flutter/fml/make_copyable.h" |
| #include "logging.h" |
| #include "pointer_injector_delegate.h" |
| #include "runtime/dart/utils/inlines.h" |
| #include "text_delegate.h" |
| #include "vsync_waiter.h" |
| |
| namespace { |
| // Helper to extract a given member with a given type from a rapidjson object. |
| template <typename T, typename O, typename F> |
| bool CallWithMember(O obj, const char* member_name, F func) { |
| auto it = obj.FindMember(member_name); |
| if (it == obj.MemberEnd()) { |
| return false; |
| } |
| if (!it->value.template Is<T>()) { |
| return false; |
| } |
| func(it->value.template Get<T>()); |
| return true; |
| } |
| } // namespace |
| |
| namespace flutter_runner { |
| |
| static constexpr char kFlutterPlatformChannel[] = "flutter/platform"; |
| static constexpr char kAccessibilityChannel[] = "flutter/accessibility"; |
| static constexpr char kFlutterPlatformViewsChannel[] = "flutter/platform_views"; |
| static constexpr char kFuchsiaShaderWarmupChannel[] = "fuchsia/shader_warmup"; |
| static constexpr char kFuchsiaInputTestChannel[] = "fuchsia/input_test"; |
| static constexpr char kFuchsiaChildViewChannel[] = "fuchsia/child_view"; |
| static constexpr int64_t kFlutterImplicitViewId = 0ll; |
| |
| PlatformView::PlatformView( |
| flutter::PlatformView::Delegate& delegate, |
| flutter::TaskRunners task_runners, |
| fuchsia::ui::views::ViewRef view_ref, |
| std::shared_ptr<flutter::ExternalViewEmbedder> external_view_embedder, |
| fuchsia::ui::input::ImeServiceHandle ime_service, |
| fuchsia::ui::input3::KeyboardHandle keyboard, |
| fuchsia::ui::pointer::TouchSourceHandle touch_source, |
| fuchsia::ui::pointer::MouseSourceHandle mouse_source, |
| fuchsia::ui::views::FocuserHandle focuser, |
| fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused, |
| fuchsia::ui::composition::ParentViewportWatcherHandle |
| parent_viewport_watcher, |
| fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry, |
| OnEnableWireframeCallback wireframe_enabled_callback, |
| OnCreateViewCallback on_create_view_callback, |
| OnUpdateViewCallback on_update_view_callback, |
| OnDestroyViewCallback on_destroy_view_callback, |
| OnCreateSurfaceCallback on_create_surface_callback, |
| OnSemanticsNodeUpdateCallback on_semantics_node_update_callback, |
| OnRequestAnnounceCallback on_request_announce_callback, |
| OnShaderWarmupCallback on_shader_warmup_callback, |
| AwaitVsyncCallback await_vsync_callback, |
| AwaitVsyncForSecondaryCallbackCallback |
| await_vsync_for_secondary_callback_callback, |
| std::shared_ptr<sys::ServiceDirectory> dart_application_svc) |
| : flutter::PlatformView(delegate, std::move(task_runners)), |
| external_view_embedder_(external_view_embedder), |
| focus_delegate_( |
| std::make_shared<FocusDelegate>(std::move(view_ref_focused), |
| std::move(focuser))), |
| pointer_delegate_( |
| std::make_shared<PointerDelegate>(std::move(touch_source), |
| std::move(mouse_source))), |
| wireframe_enabled_callback_(std::move(wireframe_enabled_callback)), |
| on_update_view_callback_(std::move(on_update_view_callback)), |
| on_create_surface_callback_(std::move(on_create_surface_callback)), |
| on_semantics_node_update_callback_( |
| std::move(on_semantics_node_update_callback)), |
| on_request_announce_callback_(std::move(on_request_announce_callback)), |
| on_create_view_callback_(std::move(on_create_view_callback)), |
| on_destroy_view_callback_(std::move(on_destroy_view_callback)), |
| on_shader_warmup_callback_(std::move(on_shader_warmup_callback)), |
| await_vsync_callback_(await_vsync_callback), |
| await_vsync_for_secondary_callback_callback_( |
| await_vsync_for_secondary_callback_callback), |
| dart_application_svc_(dart_application_svc), |
| parent_viewport_watcher_(parent_viewport_watcher.Bind()), |
| weak_factory_(this) { |
| fuchsia::ui::views::ViewRef view_ref_clone; |
| fidl::Clone(view_ref, &view_ref_clone); |
| |
| text_delegate_ = |
| std::make_unique<TextDelegate>( |
| std::move(view_ref), std::move(ime_service), std::move(keyboard), |
| [weak = weak_factory_.GetWeakPtr()]( |
| std::unique_ptr<flutter::PlatformMessage> message) { |
| if (!weak) { |
| FML_LOG(WARNING) |
| << "PlatformView use-after-free attempted. Ignoring."; |
| } |
| weak->delegate_.OnPlatformViewDispatchPlatformMessage( |
| std::move(message)); |
| }); |
| |
| // Begin watching for focus changes. |
| focus_delegate_->WatchLoop([weak = weak_factory_.GetWeakPtr()](bool focused) { |
| if (!weak) { |
| FML_LOG(WARNING) << "PlatformView use-after-free attempted. Ignoring."; |
| return; |
| } |
| |
| // Ensure last_text_state_ is set to make sure Flutter actually wants |
| // an IME. |
| if (focused && weak->text_delegate_->HasTextState()) { |
| weak->text_delegate_->ActivateIme(); |
| } else if (!focused) { |
| weak->text_delegate_->DeactivateIme(); |
| } |
| }); |
| |
| // Begin watching for pointer events. |
| pointer_delegate_->WatchLoop([weak = weak_factory_.GetWeakPtr()]( |
| std::vector<flutter::PointerData> events) { |
| if (!weak) { |
| FML_LOG(WARNING) << "PlatformView use-after-free attempted. Ignoring."; |
| return; |
| } |
| |
| if (events.empty()) { |
| return; // No work, bounce out. |
| } |
| |
| // If pixel ratio hasn't been set, use a default value of 1. |
| const float pixel_ratio = weak->view_pixel_ratio_.value_or(1.f); |
| auto packet = std::make_unique<flutter::PointerDataPacket>(events.size()); |
| for (size_t i = 0; i < events.size(); ++i) { |
| auto& event = events[i]; |
| // Translate logical to physical coordinates, as per |
| // flutter::PointerData contract. Done here because pixel ratio comes |
| // from the graphics API. |
| event.physical_x = event.physical_x * pixel_ratio; |
| event.physical_y = event.physical_y * pixel_ratio; |
| packet->SetPointerData(i, event); |
| } |
| weak->DispatchPointerDataPacket(std::move(packet)); |
| }); |
| |
| // Configure the pointer injector delegate. |
| pointer_injector_delegate_ = std::make_unique<PointerInjectorDelegate>( |
| std::move(pointerinjector_registry), std::move(view_ref_clone)); |
| |
| // This is only used by the integration tests. |
| if (dart_application_svc) { |
| // Connect to TouchInputListener |
| fuchsia::ui::test::input::TouchInputListenerHandle touch_input_listener; |
| zx_status_t touch_input_listener_status = |
| dart_application_svc |
| ->Connect<fuchsia::ui::test::input::TouchInputListener>( |
| touch_input_listener.NewRequest()); |
| if (touch_input_listener_status != ZX_OK) { |
| FML_LOG(WARNING) |
| << "fuchsia::ui::test::input::TouchInputListener connection failed: " |
| << zx_status_get_string(touch_input_listener_status); |
| } else { |
| touch_input_listener_.Bind(std::move(touch_input_listener)); |
| } |
| |
| // Connect to KeyboardInputListener |
| fuchsia::ui::test::input::KeyboardInputListenerHandle |
| keyboard_input_listener; |
| zx_status_t keyboard_input_listener_status = |
| dart_application_svc |
| ->Connect<fuchsia::ui::test::input::KeyboardInputListener>( |
| keyboard_input_listener.NewRequest()); |
| if (keyboard_input_listener_status != ZX_OK) { |
| FML_LOG(WARNING) << "fuchsia::ui::test::input::KeyboardInputListener " |
| "connection failed: " |
| << zx_status_get_string(keyboard_input_listener_status); |
| } else { |
| keyboard_input_listener_.Bind(std::move(keyboard_input_listener)); |
| } |
| // Connect to MouseInputListener |
| fuchsia::ui::test::input::MouseInputListenerHandle mouse_input_listener; |
| zx_status_t mouse_input_listener_status = |
| dart_application_svc |
| ->Connect<fuchsia::ui::test::input::MouseInputListener>( |
| mouse_input_listener.NewRequest()); |
| if (mouse_input_listener_status != ZX_OK) { |
| FML_LOG(WARNING) |
| << "fuchsia::ui::test::input::MouseInputListener connection failed: " |
| << zx_status_get_string(mouse_input_listener_status); |
| } else { |
| mouse_input_listener_.Bind(std::move(mouse_input_listener)); |
| } |
| } |
| |
| // Finally! Register the native platform message handlers. |
| RegisterPlatformMessageHandlers(); |
| |
| parent_viewport_watcher_.set_error_handler([](zx_status_t status) { |
| FML_LOG(ERROR) << "Interface error on: ParentViewportWatcher status: " |
| << status; |
| }); |
| |
| parent_viewport_watcher_->GetLayout( |
| fit::bind_member(this, &PlatformView::OnGetLayout)); |
| parent_viewport_watcher_->GetStatus( |
| fit::bind_member(this, &PlatformView::OnParentViewportStatus)); |
| } |
| |
| PlatformView::~PlatformView() = default; |
| |
| void PlatformView::RegisterPlatformMessageHandlers() { |
| platform_message_handlers_[kFlutterPlatformChannel] = |
| std::bind(&PlatformView::HandleFlutterPlatformChannelPlatformMessage, |
| this, std::placeholders::_1); |
| platform_message_handlers_[kTextInputChannel] = |
| std::bind(&TextDelegate::HandleFlutterTextInputChannelPlatformMessage, |
| text_delegate_.get(), std::placeholders::_1); |
| platform_message_handlers_[kAccessibilityChannel] = |
| std::bind(&PlatformView::HandleAccessibilityChannelPlatformMessage, this, |
| std::placeholders::_1); |
| platform_message_handlers_[kFlutterPlatformViewsChannel] = |
| std::bind(&PlatformView::HandleFlutterPlatformViewsChannelPlatformMessage, |
| this, std::placeholders::_1); |
| platform_message_handlers_[kFuchsiaShaderWarmupChannel] = |
| std::bind(&HandleFuchsiaShaderWarmupChannelPlatformMessage, |
| on_shader_warmup_callback_, std::placeholders::_1); |
| platform_message_handlers_[kFuchsiaInputTestChannel] = |
| std::bind(&PlatformView::HandleFuchsiaInputTestChannelPlatformMessage, |
| this, std::placeholders::_1); |
| platform_message_handlers_[kFuchsiaChildViewChannel] = |
| std::bind(&PlatformView::HandleFuchsiaChildViewChannelPlatformMessage, |
| this, std::placeholders::_1); |
| } |
| |
| static flutter::PointerData::Change GetChangeFromPointerEventPhase( |
| fuchsia::ui::input::PointerEventPhase phase) { |
| switch (phase) { |
| case fuchsia::ui::input::PointerEventPhase::ADD: |
| return flutter::PointerData::Change::kAdd; |
| case fuchsia::ui::input::PointerEventPhase::HOVER: |
| return flutter::PointerData::Change::kHover; |
| case fuchsia::ui::input::PointerEventPhase::DOWN: |
| return flutter::PointerData::Change::kDown; |
| case fuchsia::ui::input::PointerEventPhase::MOVE: |
| return flutter::PointerData::Change::kMove; |
| case fuchsia::ui::input::PointerEventPhase::UP: |
| return flutter::PointerData::Change::kUp; |
| case fuchsia::ui::input::PointerEventPhase::REMOVE: |
| return flutter::PointerData::Change::kRemove; |
| case fuchsia::ui::input::PointerEventPhase::CANCEL: |
| return flutter::PointerData::Change::kCancel; |
| default: |
| return flutter::PointerData::Change::kCancel; |
| } |
| } |
| |
| static flutter::PointerData::DeviceKind GetKindFromPointerType( |
| fuchsia::ui::input::PointerEventType type) { |
| switch (type) { |
| case fuchsia::ui::input::PointerEventType::TOUCH: |
| return flutter::PointerData::DeviceKind::kTouch; |
| case fuchsia::ui::input::PointerEventType::MOUSE: |
| return flutter::PointerData::DeviceKind::kMouse; |
| default: |
| return flutter::PointerData::DeviceKind::kTouch; |
| } |
| } |
| |
| // TODO(SCN-1278): Remove this. |
| // Turns two floats (high bits, low bits) into a 64-bit uint. |
| static trace_flow_id_t PointerTraceHACK(float fa, float fb) { |
| uint32_t ia, ib; |
| memcpy(&ia, &fa, sizeof(uint32_t)); |
| memcpy(&ib, &fb, sizeof(uint32_t)); |
| return (((uint64_t)ia) << 32) | ib; |
| } |
| |
| // For certain scenarios that must avoid floating-point drift, compute a |
| // coordinate that falls within the logical view bounding box. |
| std::array<float, 2> PlatformView::ClampToViewSpace(const float x, |
| const float y) const { |
| if (!view_logical_size_.has_value() || !view_logical_origin_.has_value()) { |
| return {x, y}; // If we can't do anything, return the original values. |
| } |
| |
| const auto origin = view_logical_origin_.value(); |
| const auto size = view_logical_size_.value(); |
| const float min_x = origin[0]; |
| const float max_x = origin[0] + size[0]; |
| const float min_y = origin[1]; |
| const float max_y = origin[1] + size[1]; |
| if (min_x <= x && x < max_x && min_y <= y && y < max_y) { |
| return {x, y}; // No clamping to perform. |
| } |
| |
| // View boundary is [min_x, max_x) x [min_y, max_y). Note that min is |
| // inclusive, but max is exclusive - so we subtract epsilon. |
| const float max_x_inclusive = max_x - std::numeric_limits<float>::epsilon(); |
| const float max_y_inclusive = max_y - std::numeric_limits<float>::epsilon(); |
| const float& clamped_x = std::clamp(x, min_x, max_x_inclusive); |
| const float& clamped_y = std::clamp(y, min_y, max_y_inclusive); |
| FML_LOG(INFO) << "Clamped (" << x << ", " << y << ") to (" << clamped_x |
| << ", " << clamped_y << ")."; |
| return {clamped_x, clamped_y}; |
| } |
| |
| void PlatformView::OnGetLayout(fuchsia::ui::composition::LayoutInfo info) { |
| view_logical_size_ = {static_cast<float>(info.logical_size().width), |
| static_cast<float>(info.logical_size().height)}; |
| |
| if (info.has_device_pixel_ratio()) { |
| // Both values should be identical for the Vec2 for DPR. |
| FML_DCHECK(info.device_pixel_ratio().x == info.device_pixel_ratio().y); |
| view_pixel_ratio_ = info.device_pixel_ratio().x; |
| } |
| |
| float pixel_ratio = view_pixel_ratio_ ? *view_pixel_ratio_ : 1.0f; |
| flutter::ViewportMetrics metrics{ |
| pixel_ratio, // device_pixel_ratio |
| std::round(view_logical_size_.value()[0] * |
| pixel_ratio), // physical_width |
| std::round(view_logical_size_.value()[1] * |
| pixel_ratio), // physical_height |
| 0.0f, // physical_padding_top |
| 0.0f, // physical_padding_right |
| 0.0f, // physical_padding_bottom |
| 0.0f, // physical_padding_left |
| 0.0f, // physical_view_inset_top |
| 0.0f, // physical_view_inset_right |
| 0.0f, // physical_view_inset_bottom |
| 0.0f, // physical_view_inset_left |
| 0.0f, // p_physical_system_gesture_inset_top |
| 0.0f, // p_physical_system_gesture_inset_right |
| 0.0f, // p_physical_system_gesture_inset_bottom |
| 0.0f, // p_physical_system_gesture_inset_left, |
| -1.0, // p_physical_touch_slop, |
| {}, // p_physical_display_features_bounds |
| {}, // p_physical_display_features_type |
| {}, // p_physical_display_features_state |
| 0, // p_display_id |
| }; |
| SetViewportMetrics(kFlutterImplicitViewId, metrics); |
| |
| parent_viewport_watcher_->GetLayout( |
| fit::bind_member(this, &PlatformView::OnGetLayout)); |
| } |
| |
| void PlatformView::OnParentViewportStatus( |
| fuchsia::ui::composition::ParentViewportStatus status) { |
| // TODO(fxbug.dev/116001): Investigate if it is useful to send hidden/shown |
| // signals. |
| parent_viewport_status_ = status; |
| parent_viewport_watcher_->GetStatus( |
| fit::bind_member(this, &PlatformView::OnParentViewportStatus)); |
| } |
| |
| void PlatformView::OnChildViewStatus( |
| uint64_t content_id, |
| fuchsia::ui::composition::ChildViewStatus status) { |
| FML_DCHECK(child_view_info_.count(content_id) == 1); |
| |
| std::ostringstream out; |
| out << "{" |
| << "\"method\":\"View.viewStateChanged\"," |
| << "\"args\":{" |
| << " \"viewId\":" << child_view_info_.at(content_id).view_id |
| << "," // ViewId |
| << " \"is_rendering\":true," // IsViewRendering |
| << " \"state\":true" // IsViewRendering |
| << " }" |
| << "}"; |
| auto call = out.str(); |
| |
| std::unique_ptr<flutter::PlatformMessage> message = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", |
| fml::MallocMapping::Copy(call.c_str(), call.size()), nullptr); |
| DispatchPlatformMessage(std::move(message)); |
| |
| child_view_info_.at(content_id) |
| .child_view_watcher->GetStatus( |
| [this, content_id](fuchsia::ui::composition::ChildViewStatus status) { |
| OnChildViewStatus(content_id, status); |
| }); |
| } |
| |
| void PlatformView::OnChildViewViewRef(uint64_t content_id, |
| uint64_t view_id, |
| fuchsia::ui::views::ViewRef view_ref) { |
| FML_CHECK(child_view_info_.count(content_id) == 1); |
| |
| fuchsia::ui::views::ViewRef view_ref_clone; |
| fidl::Clone(view_ref, &view_ref_clone); |
| |
| focus_delegate_->OnChildViewViewRef(view_id, std::move(view_ref)); |
| |
| pointer_injector_delegate_->OnCreateView(view_id, std::move(view_ref_clone)); |
| OnChildViewConnected(content_id); |
| } |
| |
| void PlatformView::OnCreateView(ViewCallback on_view_created, |
| int64_t view_id_raw, |
| bool hit_testable, |
| bool focusable) { |
| auto on_view_bound = [weak = weak_factory_.GetWeakPtr(), |
| platform_task_runner = |
| task_runners_.GetPlatformTaskRunner(), |
| view_id = view_id_raw]( |
| fuchsia::ui::composition::ContentId content_id, |
| fuchsia::ui::composition::ChildViewWatcherHandle |
| child_view_watcher_handle) { |
| FML_CHECK(weak); |
| FML_CHECK(weak->child_view_info_.count(content_id.value) == 0); |
| |
| platform_task_runner->PostTask(fml::MakeCopyable( |
| [weak, view_id, content_id, |
| watcher_handle = std::move(child_view_watcher_handle)]() mutable { |
| if (!weak) { |
| FML_LOG(WARNING) |
| << "View bound to PlatformView after PlatformView was " |
| "destroyed; ignoring."; |
| return; |
| } |
| |
| // Bind the child view watcher to the platform thread so that the FIDL |
| // calls are handled on the platform thread. |
| fuchsia::ui::composition::ChildViewWatcherPtr child_view_watcher = |
| watcher_handle.Bind(); |
| FML_CHECK(child_view_watcher); |
| |
| child_view_watcher.set_error_handler([weak, view_id, content_id]( |
| zx_status_t status) { |
| FML_LOG(WARNING) |
| << "Child disconnected. ChildViewWatcher status: " << status; |
| |
| if (!weak) { |
| FML_LOG(WARNING) << "View bound to PlatformView after " |
| "PlatformView was " |
| "destroyed; ignoring."; |
| return; |
| } |
| |
| // Disconnected views cannot listen to pointer events. |
| weak->pointer_injector_delegate_->OnDestroyView(view_id); |
| |
| weak->OnChildViewDisconnected(content_id.value); |
| }); |
| |
| weak->child_view_info_.emplace( |
| std::piecewise_construct, std::forward_as_tuple(content_id.value), |
| std::forward_as_tuple(view_id, std::move(child_view_watcher))); |
| |
| weak->child_view_info_.at(content_id.value) |
| .child_view_watcher->GetStatus( |
| [weak, id = content_id.value]( |
| fuchsia::ui::composition::ChildViewStatus status) { |
| weak->OnChildViewStatus(id, status); |
| }); |
| |
| weak->child_view_info_.at(content_id.value) |
| .child_view_watcher->GetViewRef( |
| [weak, content_id = content_id.value, |
| view_id](fuchsia::ui::views::ViewRef view_ref) { |
| weak->OnChildViewViewRef(content_id, view_id, |
| std::move(view_ref)); |
| }); |
| })); |
| }; |
| |
| on_create_view_callback_(view_id_raw, std::move(on_view_created), |
| std::move(on_view_bound), hit_testable, focusable); |
| } |
| |
| void PlatformView::OnDisposeView(int64_t view_id_raw) { |
| auto on_view_unbound = |
| [weak = weak_factory_.GetWeakPtr(), |
| platform_task_runner = task_runners_.GetPlatformTaskRunner(), |
| view_id_raw](fuchsia::ui::composition::ContentId content_id) { |
| platform_task_runner->PostTask([weak, content_id, view_id_raw]() { |
| if (!weak) { |
| FML_LOG(WARNING) |
| << "View unbound from PlatformView after PlatformView" |
| "was destroyed; ignoring."; |
| return; |
| } |
| |
| FML_DCHECK(weak->child_view_info_.count(content_id.value) == 1); |
| weak->OnChildViewDisconnected(content_id.value); |
| weak->child_view_info_.erase(content_id.value); |
| weak->focus_delegate_->OnDisposeChildView(view_id_raw); |
| weak->pointer_injector_delegate_->OnDestroyView(view_id_raw); |
| }); |
| }; |
| on_destroy_view_callback_(view_id_raw, std::move(on_view_unbound)); |
| } |
| |
| void PlatformView::OnChildViewConnected(uint64_t content_id) { |
| FML_CHECK(child_view_info_.count(content_id) == 1); |
| std::ostringstream out; |
| out << "{" |
| << "\"method\":\"View.viewConnected\"," |
| << "\"args\":{" |
| << " \"viewId\":" << child_view_info_.at(content_id).view_id << " }" |
| << "}"; |
| auto call = out.str(); |
| |
| std::unique_ptr<flutter::PlatformMessage> message = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", |
| fml::MallocMapping::Copy(call.c_str(), call.size()), nullptr); |
| DispatchPlatformMessage(std::move(message)); |
| } |
| |
| void PlatformView::OnChildViewDisconnected(uint64_t content_id) { |
| FML_CHECK(child_view_info_.count(content_id) == 1); |
| std::ostringstream out; |
| out << "{" |
| << "\"method\":\"View.viewDisconnected\"," |
| << "\"args\":{" |
| << " \"viewId\":" << child_view_info_.at(content_id).view_id << " }" |
| << "}"; |
| auto call = out.str(); |
| |
| std::unique_ptr<flutter::PlatformMessage> message = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", |
| fml::MallocMapping::Copy(call.c_str(), call.size()), nullptr); |
| DispatchPlatformMessage(std::move(message)); |
| } |
| |
| bool PlatformView::OnHandlePointerEvent( |
| const fuchsia::ui::input::PointerEvent& pointer) { |
| TRACE_EVENT0("flutter", "PlatformView::OnHandlePointerEvent"); |
| |
| // TODO(SCN-1278): Use proper trace_id for tracing flow. |
| trace_flow_id_t trace_id = |
| PointerTraceHACK(pointer.radius_major, pointer.radius_minor); |
| TRACE_FLOW_END("input", "dispatch_event_to_client", trace_id); |
| |
| const float pixel_ratio = |
| view_pixel_ratio_.has_value() ? *view_pixel_ratio_ : 0.f; |
| |
| flutter::PointerData pointer_data; |
| pointer_data.Clear(); |
| 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 events are in logical pixels, so scale to physical. |
| pointer_data.physical_x = pointer.x * pixel_ratio; |
| pointer_data.physical_y = pointer.y * pixel_ratio; |
| // Buttons are single bit values starting with kMousePrimaryButton = 1. |
| pointer_data.buttons = static_cast<uint64_t>(pointer.buttons); |
| |
| switch (pointer_data.change) { |
| case flutter::PointerData::Change::kDown: { |
| // Make the pointer start in the view space, despite numerical drift. |
| auto clamped_pointer = ClampToViewSpace(pointer.x, pointer.y); |
| pointer_data.physical_x = clamped_pointer[0] * pixel_ratio; |
| pointer_data.physical_y = clamped_pointer[1] * pixel_ratio; |
| |
| down_pointers_.insert(pointer_data.device); |
| break; |
| } |
| case flutter::PointerData::Change::kCancel: |
| case flutter::PointerData::Change::kUp: |
| down_pointers_.erase(pointer_data.device); |
| break; |
| case flutter::PointerData::Change::kMove: |
| if (down_pointers_.count(pointer_data.device) == 0) { |
| pointer_data.change = flutter::PointerData::Change::kHover; |
| } |
| break; |
| case flutter::PointerData::Change::kAdd: |
| if (down_pointers_.count(pointer_data.device) != 0) { |
| FML_LOG(ERROR) << "Received add event for down pointer."; |
| } |
| break; |
| case flutter::PointerData::Change::kRemove: |
| if (down_pointers_.count(pointer_data.device) != 0) { |
| FML_LOG(ERROR) << "Received remove event for down pointer."; |
| } |
| break; |
| case flutter::PointerData::Change::kHover: |
| if (down_pointers_.count(pointer_data.device) != 0) { |
| FML_LOG(ERROR) << "Received hover event for down pointer."; |
| } |
| break; |
| case flutter::PointerData::Change::kPanZoomStart: |
| case flutter::PointerData::Change::kPanZoomUpdate: |
| case flutter::PointerData::Change::kPanZoomEnd: |
| FML_DLOG(ERROR) << "Unexpectedly received pointer pan/zoom event"; |
| break; |
| } |
| |
| auto packet = std::make_unique<flutter::PointerDataPacket>(1); |
| packet->SetPointerData(0, pointer_data); |
| DispatchPointerDataPacket(std::move(packet)); |
| return true; |
| } |
| |
| // |flutter::PlatformView| |
| std::unique_ptr<flutter::VsyncWaiter> PlatformView::CreateVSyncWaiter() { |
| return std::make_unique<flutter_runner::VsyncWaiter>( |
| await_vsync_callback_, await_vsync_for_secondary_callback_callback_, |
| task_runners_); |
| } |
| |
| // |flutter::PlatformView| |
| std::unique_ptr<flutter::Surface> PlatformView::CreateRenderingSurface() { |
| return on_create_surface_callback_ ? on_create_surface_callback_() : nullptr; |
| } |
| |
| // |flutter::PlatformView| |
| std::shared_ptr<flutter::ExternalViewEmbedder> |
| PlatformView::CreateExternalViewEmbedder() { |
| return external_view_embedder_; |
| } |
| |
| // |flutter::PlatformView| |
| void PlatformView::HandlePlatformMessage( |
| std::unique_ptr<flutter::PlatformMessage> message) { |
| if (!message) { |
| return; |
| } |
| const std::string channel = message->channel(); |
| auto found = platform_message_handlers_.find(channel); |
| if (found == platform_message_handlers_.end()) { |
| const bool already_errored = unregistered_channels_.count(channel); |
| if (!already_errored) { |
| FML_LOG(INFO) |
| << "Platform view received message on channel '" << message->channel() |
| << "' with no registered handler. An empty response will be " |
| "generated. Please implement the native message handler. This " |
| "message will appear only once per channel."; |
| unregistered_channels_.insert(channel); |
| } |
| flutter::PlatformView::HandlePlatformMessage(std::move(message)); |
| return; |
| } |
| auto response = message->response(); |
| bool response_handled = found->second(std::move(message)); |
| |
| // Ensure all responses are completed. |
| if (response && !response_handled) { |
| // response_handled should be true if the response was completed. |
| FML_DCHECK(!response->is_complete()); |
| response->CompleteEmpty(); |
| } |
| } |
| |
| // |flutter::PlatformView| |
| void PlatformView::SetSemanticsEnabled(bool enabled) { |
| flutter::PlatformView::SetSemanticsEnabled(enabled); |
| if (enabled) { |
| SetAccessibilityFeatures(static_cast<int32_t>( |
| flutter::AccessibilityFeatureFlag::kAccessibleNavigation)); |
| } else { |
| SetAccessibilityFeatures(0); |
| } |
| } |
| |
| // |flutter::PlatformView| |
| void PlatformView::UpdateSemantics( |
| flutter::SemanticsNodeUpdates update, |
| flutter::CustomAccessibilityActionUpdates actions) { |
| const float pixel_ratio = |
| view_pixel_ratio_.has_value() ? *view_pixel_ratio_ : 0.f; |
| |
| on_semantics_node_update_callback_(update, pixel_ratio); |
| } |
| |
| // Channel handler for kAccessibilityChannel |
| bool PlatformView::HandleAccessibilityChannelPlatformMessage( |
| std::unique_ptr<flutter::PlatformMessage> message) { |
| FML_DCHECK(message->channel() == kAccessibilityChannel); |
| |
| const flutter::StandardMessageCodec& standard_message_codec = |
| flutter::StandardMessageCodec::GetInstance(nullptr); |
| std::unique_ptr<flutter::EncodableValue> decoded = |
| standard_message_codec.DecodeMessage(message->data().GetMapping(), |
| message->data().GetSize()); |
| |
| flutter::EncodableMap map = std::get<flutter::EncodableMap>(*decoded); |
| std::string type = |
| std::get<std::string>(map.at(flutter::EncodableValue("type"))); |
| if (type == "announce") { |
| flutter::EncodableMap data_map = std::get<flutter::EncodableMap>( |
| map.at(flutter::EncodableValue("data"))); |
| std::string text = |
| std::get<std::string>(data_map.at(flutter::EncodableValue("message"))); |
| |
| on_request_announce_callback_(text); |
| } |
| |
| // Complete with an empty response. |
| return false; |
| } |
| |
| // Channel handler for kFlutterPlatformChannel |
| bool PlatformView::HandleFlutterPlatformChannelPlatformMessage( |
| std::unique_ptr<flutter::PlatformMessage> message) { |
| FML_DCHECK(message->channel() == kFlutterPlatformChannel); |
| |
| // Fuchsia does not handle any platform messages at this time. |
| |
| // Complete with an empty response. |
| return false; |
| } |
| |
| bool PlatformView::HandleFlutterPlatformViewsChannelPlatformMessage( |
| std::unique_ptr<flutter::PlatformMessage> message) { |
| FML_DCHECK(message->channel() == kFlutterPlatformViewsChannel); |
| const auto& data = message->data(); |
| rapidjson::Document document; |
| document.Parse(reinterpret_cast<const char*>(data.GetMapping()), |
| data.GetSize()); |
| if (document.HasParseError() || !document.IsObject()) { |
| FML_LOG(ERROR) << "Could not parse document"; |
| return false; |
| } |
| auto root = document.GetObject(); |
| auto method_member = root.FindMember("method"); |
| if (method_member == root.MemberEnd() || !method_member->value.IsString()) { |
| return false; |
| } |
| std::string method(method_member->value.GetString()); |
| |
| if (method == "View.enableWireframe") { |
| auto args_it = root.FindMember("args"); |
| if (args_it == root.MemberEnd() || !args_it->value.IsObject()) { |
| FML_LOG(ERROR) << "No arguments found."; |
| return false; |
| } |
| const auto& args = args_it->value; |
| |
| auto enable = args.FindMember("enable"); |
| if (!enable->value.IsBool()) { |
| FML_LOG(ERROR) << "Argument 'enable' is not a bool"; |
| return false; |
| } |
| |
| wireframe_enabled_callback_(enable->value.GetBool()); |
| } else if (method == "View.create") { |
| auto args_it = root.FindMember("args"); |
| if (args_it == root.MemberEnd() || !args_it->value.IsObject()) { |
| FML_LOG(ERROR) << "No arguments found."; |
| return false; |
| } |
| const auto& args = args_it->value; |
| |
| auto view_id = args.FindMember("viewId"); |
| if (!view_id->value.IsUint64()) { |
| FML_LOG(ERROR) << "Argument 'viewId' is not a int64"; |
| return false; |
| } |
| |
| auto hit_testable = args.FindMember("hitTestable"); |
| if (!hit_testable->value.IsBool()) { |
| FML_LOG(ERROR) << "Argument 'hitTestable' is not a bool"; |
| return false; |
| } |
| |
| auto focusable = args.FindMember("focusable"); |
| if (!focusable->value.IsBool()) { |
| FML_LOG(ERROR) << "Argument 'focusable' is not a bool"; |
| return false; |
| } |
| |
| auto on_view_created = fml::MakeCopyable( |
| [platform_task_runner = task_runners_.GetPlatformTaskRunner(), |
| message = std::move(message)]() { |
| // The client is waiting for view creation. Send an empty response |
| // back to signal the view was created. |
| if (message->response()) { |
| message->response()->Complete(std::make_unique<fml::DataMapping>( |
| std::vector<uint8_t>({'[', '0', ']'}))); |
| } |
| }); |
| OnCreateView(std::move(on_view_created), view_id->value.GetUint64(), |
| hit_testable->value.GetBool(), focusable->value.GetBool()); |
| return true; |
| } else if (method == "View.update") { |
| auto args_it = root.FindMember("args"); |
| if (args_it == root.MemberEnd() || !args_it->value.IsObject()) { |
| FML_LOG(ERROR) << "No arguments found."; |
| return false; |
| } |
| const auto& args = args_it->value; |
| |
| auto view_id = args.FindMember("viewId"); |
| if (!view_id->value.IsUint64()) { |
| FML_LOG(ERROR) << "Argument 'viewId' is not a int64"; |
| return false; |
| } |
| |
| auto hit_testable = args.FindMember("hitTestable"); |
| if (!hit_testable->value.IsBool()) { |
| FML_LOG(ERROR) << "Argument 'hitTestable' is not a bool"; |
| return false; |
| } |
| |
| auto focusable = args.FindMember("focusable"); |
| if (!focusable->value.IsBool()) { |
| FML_LOG(ERROR) << "Argument 'focusable' is not a bool"; |
| return false; |
| } |
| |
| SkRect view_occlusion_hint_raw = SkRect::MakeEmpty(); |
| auto view_occlusion_hint = args.FindMember("viewOcclusionHintLTRB"); |
| if (view_occlusion_hint != args.MemberEnd()) { |
| if (view_occlusion_hint->value.IsArray()) { |
| const auto& view_occlusion_hint_array = |
| view_occlusion_hint->value.GetArray(); |
| if (view_occlusion_hint_array.Size() == 4) { |
| bool parse_error = false; |
| for (int i = 0; i < 4; i++) { |
| auto& array_val = view_occlusion_hint_array[i]; |
| if (!array_val.IsDouble()) { |
| FML_LOG(ERROR) << "Argument 'viewOcclusionHintLTRB' element " << i |
| << " is not a double"; |
| parse_error = true; |
| break; |
| } |
| } |
| |
| if (!parse_error) { |
| view_occlusion_hint_raw = |
| SkRect::MakeLTRB(view_occlusion_hint_array[0].GetDouble(), |
| view_occlusion_hint_array[1].GetDouble(), |
| view_occlusion_hint_array[2].GetDouble(), |
| view_occlusion_hint_array[3].GetDouble()); |
| } |
| } else { |
| FML_LOG(ERROR) |
| << "Argument 'viewOcclusionHintLTRB' expected size 4; got " |
| << view_occlusion_hint_array.Size(); |
| } |
| } else { |
| FML_LOG(ERROR) |
| << "Argument 'viewOcclusionHintLTRB' is not a double array"; |
| } |
| } else { |
| FML_LOG(WARNING) << "Argument 'viewOcclusionHintLTRB' is missing"; |
| } |
| |
| on_update_view_callback_( |
| view_id->value.GetUint64(), view_occlusion_hint_raw, |
| hit_testable->value.GetBool(), focusable->value.GetBool()); |
| if (message->response()) { |
| message->response()->Complete(std::make_unique<fml::DataMapping>( |
| std::vector<uint8_t>({'[', '0', ']'}))); |
| return true; |
| } |
| } else if (method == "View.dispose") { |
| auto args_it = root.FindMember("args"); |
| if (args_it == root.MemberEnd() || !args_it->value.IsObject()) { |
| FML_LOG(ERROR) << "No arguments found."; |
| return false; |
| } |
| const auto& args = args_it->value; |
| |
| auto view_id = args.FindMember("viewId"); |
| if (!view_id->value.IsUint64()) { |
| FML_LOG(ERROR) << "Argument 'viewId' is not a int64"; |
| return false; |
| } |
| |
| OnDisposeView(view_id->value.GetUint64()); |
| if (message->response()) { |
| message->response()->Complete(std::make_unique<fml::DataMapping>( |
| std::vector<uint8_t>({'[', '0', ']'}))); |
| return true; |
| } |
| } else if (method.rfind("View.focus", 0) == 0) { |
| return focus_delegate_->HandlePlatformMessage(root, message->response()); |
| } else if (method.rfind(PointerInjectorDelegate::kPointerInjectorMethodPrefix, |
| 0) == 0) { |
| return pointer_injector_delegate_->HandlePlatformMessage( |
| root, message->response()); |
| } else { |
| FML_LOG(ERROR) << "Unknown " << message->channel() << " method " << method; |
| } |
| // Complete with an empty response by default. |
| return false; |
| } |
| |
| bool PlatformView::HandleFuchsiaShaderWarmupChannelPlatformMessage( |
| OnShaderWarmupCallback on_shader_warmup_callback, |
| std::unique_ptr<flutter::PlatformMessage> message) { |
| FML_DCHECK(message->channel() == kFuchsiaShaderWarmupChannel); |
| |
| if (!on_shader_warmup_callback) { |
| FML_LOG(ERROR) << "No shader warmup callback set!"; |
| std::string result = "[0]"; |
| message->response()->Complete( |
| std::make_unique<fml::DataMapping>(std::vector<uint8_t>( |
| (const uint8_t*)result.c_str(), |
| (const uint8_t*)result.c_str() + result.length()))); |
| return true; |
| } |
| |
| const auto& data = message->data(); |
| rapidjson::Document document; |
| document.Parse(reinterpret_cast<const char*>(data.GetMapping()), |
| data.GetSize()); |
| if (document.HasParseError() || !document.IsObject()) { |
| FML_LOG(ERROR) << "Could not parse document"; |
| return false; |
| } |
| auto root = document.GetObject(); |
| auto method = root.FindMember("method"); |
| if (method == root.MemberEnd() || !method->value.IsString() || |
| method->value != "WarmupSkps") { |
| FML_LOG(ERROR) << "Invalid method name"; |
| return false; |
| } |
| |
| auto args_it = root.FindMember("args"); |
| if (args_it == root.MemberEnd() || !args_it->value.IsObject()) { |
| FML_LOG(ERROR) << "No arguments found."; |
| return false; |
| } |
| |
| auto shaders_it = root["args"].FindMember("shaders"); |
| if (shaders_it == root["args"].MemberEnd() || !shaders_it->value.IsArray()) { |
| FML_LOG(ERROR) << "No shaders found."; |
| return false; |
| } |
| |
| auto width_it = root["args"].FindMember("width"); |
| auto height_it = root["args"].FindMember("height"); |
| if (width_it == root["args"].MemberEnd() || !width_it->value.IsNumber()) { |
| FML_LOG(ERROR) << "Invalid width"; |
| return false; |
| } |
| if (height_it == root["args"].MemberEnd() || !height_it->value.IsNumber()) { |
| FML_LOG(ERROR) << "Invalid height"; |
| return false; |
| } |
| auto width = width_it->value.GetUint64(); |
| auto height = height_it->value.GetUint64(); |
| |
| std::vector<std::string> skp_paths; |
| const auto& shaders = shaders_it->value; |
| for (rapidjson::Value::ConstValueIterator itr = shaders.Begin(); |
| itr != shaders.End(); ++itr) { |
| skp_paths.push_back((*itr).GetString()); |
| } |
| |
| auto completion_callback = [response = |
| message->response()](uint32_t num_successes) { |
| std::ostringstream result_stream; |
| result_stream << "[" << num_successes << "]"; |
| |
| std::string result(result_stream.str()); |
| |
| response->Complete(std::make_unique<fml::DataMapping>(std::vector<uint8_t>( |
| (const uint8_t*)result.c_str(), |
| (const uint8_t*)result.c_str() + result.length()))); |
| }; |
| |
| on_shader_warmup_callback(skp_paths, completion_callback, width, height); |
| // The response has already been completed by us. |
| return true; |
| } |
| |
| // Channel handler for kFuchsiaInputTestChannel |
| bool PlatformView::HandleFuchsiaInputTestChannelPlatformMessage( |
| std::unique_ptr<flutter::PlatformMessage> message) { |
| FML_DCHECK(message->channel() == kFuchsiaInputTestChannel); |
| |
| const auto& data = message->data(); |
| rapidjson::Document document; |
| document.Parse(reinterpret_cast<const char*>(data.GetMapping()), |
| data.GetSize()); |
| if (document.HasParseError() || !document.IsObject()) { |
| FML_LOG(ERROR) << "Could not parse document"; |
| return false; |
| } |
| auto root = document.GetObject(); |
| auto method = root.FindMember("method"); |
| if (method == root.MemberEnd() || !method->value.IsString()) { |
| FML_LOG(ERROR) << "Missing method"; |
| return false; |
| } |
| |
| FML_LOG(INFO) << "fuchsia/input_test: method=" << method->value.GetString(); |
| |
| if (method->value == "TouchInputListener.ReportTouchInput") { |
| if (!touch_input_listener_) { |
| FML_LOG(ERROR) << "TouchInputListener not found."; |
| return false; |
| } |
| |
| fuchsia::ui::test::input::TouchInputListenerReportTouchInputRequest request; |
| CallWithMember<double>( |
| root, "local_x", [&](double local_x) { request.set_local_x(local_x); }); |
| CallWithMember<double>( |
| root, "local_y", [&](double local_y) { request.set_local_y(local_y); }); |
| CallWithMember<int64_t>(root, "time_received", [&](uint64_t time_received) { |
| request.set_time_received(time_received); |
| }); |
| CallWithMember<std::string>(root, "component_name", |
| [&](std::string component_name) { |
| request.set_component_name(component_name); |
| }); |
| |
| touch_input_listener_->ReportTouchInput(std::move(request)); |
| return true; |
| } |
| |
| if (method->value == "KeyboardInputListener.ReportTextInput") { |
| if (!keyboard_input_listener_) { |
| FML_LOG(ERROR) << "KeyboardInputListener not found."; |
| return false; |
| } |
| |
| fuchsia::ui::test::input::KeyboardInputListenerReportTextInputRequest |
| request; |
| CallWithMember<std::string>( |
| root, "text", [&](std::string text) { request.set_text(text); }); |
| |
| keyboard_input_listener_->ReportTextInput(std::move(request)); |
| return true; |
| } |
| |
| if (method->value == "MouseInputListener.ReportMouseInput") { |
| if (!mouse_input_listener_) { |
| FML_LOG(ERROR) << "MouseInputListener not found."; |
| return false; |
| } |
| |
| fuchsia::ui::test::input::MouseInputListenerReportMouseInputRequest request; |
| CallWithMember<double>( |
| root, "local_x", [&](double local_x) { request.set_local_x(local_x); }); |
| CallWithMember<double>( |
| root, "local_y", [&](double local_y) { request.set_local_y(local_y); }); |
| CallWithMember<int64_t>(root, "time_received", [&](uint64_t time_received) { |
| request.set_time_received(time_received); |
| }); |
| CallWithMember<std::string>(root, "component_name", |
| [&](std::string component_name) { |
| request.set_component_name(component_name); |
| }); |
| CallWithMember<int>(root, "buttons", [&](int button_mask) { |
| std::vector<fuchsia::ui::test::input::MouseButton> buttons; |
| if (button_mask & 1) { |
| buttons.push_back(fuchsia::ui::test::input::MouseButton::FIRST); |
| } |
| if (button_mask & 2) { |
| buttons.push_back(fuchsia::ui::test::input::MouseButton::SECOND); |
| } |
| if (button_mask & 4) { |
| buttons.push_back(fuchsia::ui::test::input::MouseButton::THIRD); |
| } |
| request.set_buttons(buttons); |
| }); |
| CallWithMember<std::string>(root, "phase", [&](std::string phase) { |
| if (phase == "add") { |
| request.set_phase(fuchsia::ui::test::input::MouseEventPhase::ADD); |
| } else if (phase == "hover") { |
| request.set_phase(fuchsia::ui::test::input::MouseEventPhase::HOVER); |
| } else if (phase == "down") { |
| request.set_phase(fuchsia::ui::test::input::MouseEventPhase::DOWN); |
| } else if (phase == "move") { |
| request.set_phase(fuchsia::ui::test::input::MouseEventPhase::MOVE); |
| } else if (phase == "up") { |
| request.set_phase(fuchsia::ui::test::input::MouseEventPhase::UP); |
| } else { |
| FML_LOG(ERROR) << "Unexpected mouse phase: " << phase; |
| } |
| }); |
| CallWithMember<double>( |
| root, "wheel_x_physical_pixel", [&](double wheel_x_physical_pixel) { |
| request.set_wheel_x_physical_pixel(wheel_x_physical_pixel); |
| }); |
| CallWithMember<double>( |
| root, "wheel_y_physical_pixel", [&](double wheel_y_physical_pixel) { |
| request.set_wheel_y_physical_pixel(wheel_y_physical_pixel); |
| }); |
| |
| mouse_input_listener_->ReportMouseInput(std::move(request)); |
| return true; |
| } |
| |
| FML_LOG(ERROR) << "fuchsia/input_test: unrecognized method " |
| << method->value.GetString(); |
| return false; |
| } |
| |
| // Channel handler for kFuchsiaChildViewChannel |
| bool PlatformView::HandleFuchsiaChildViewChannelPlatformMessage( |
| std::unique_ptr<flutter::PlatformMessage> message) { |
| FML_DCHECK(message->channel() == kFuchsiaChildViewChannel); |
| |
| if (message->data().GetSize() != 1 || |
| (message->data().GetMapping()[0] != '1')) { |
| FML_LOG(ERROR) << kFuchsiaChildViewChannel |
| << " data must be singularly '1'."; |
| return false; |
| } |
| |
| FML_DCHECK(message->data().GetMapping()[0] == '1'); |
| |
| if (!message->response()) { |
| FML_LOG(ERROR) << kFuchsiaChildViewChannel |
| << " must have a response callback."; |
| return false; |
| } |
| |
| if (!dart_application_svc_) { |
| FML_LOG(ERROR) << "No service directory."; |
| return false; |
| } |
| |
| fuchsia::ui::app::ViewProviderHandle view_provider_handle; |
| zx_status_t status = |
| dart_application_svc_->Connect(view_provider_handle.NewRequest()); |
| if (status != ZX_OK) { |
| FML_LOG(ERROR) << "Failed to connect to view provider."; |
| return false; |
| } |
| fuchsia::ui::app::ViewProviderPtr view_provider; |
| view_provider.Bind(std::move(view_provider_handle)); |
| |
| zx::handle view_id; |
| |
| zx::channel view_tokens[2]; |
| fuchsia::ui::views::ViewportCreationToken viewport_creation_token; |
| fuchsia::ui::views::ViewCreationToken view_creation_token; |
| status = zx::channel::create(0, &viewport_creation_token.value, |
| &view_creation_token.value); |
| if (status != ZX_OK) { |
| FML_LOG(ERROR) << "Creating view tokens: " << zx_status_get_string(status); |
| return false; |
| } |
| |
| fuchsia::ui::app::CreateView2Args create_view_args; |
| create_view_args.set_view_creation_token(std::move(view_creation_token)); |
| view_provider->CreateView2(std::move(create_view_args)); |
| |
| view_id = std::move(viewport_creation_token.value); |
| |
| if (view_id) { |
| message->response()->Complete( |
| std::make_unique<fml::DataMapping>(std::to_string(view_id.release()) |
| |
| )); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| } // namespace flutter_runner |