diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ba31a6a01fb51..23bb99b4b353c 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2076,6 +2076,9 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/platform_view_unittest.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/pointer_delegate.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/pointer_delegate.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/pointer_delegate_unittests.cc +FILE: ../../../flutter/shell/platform/fuchsia/flutter/pointer_injector_delegate.cc +FILE: ../../../flutter/shell/platform/fuchsia/flutter/pointer_injector_delegate.h +FILE: ../../../flutter/shell/platform/fuchsia/flutter/pointer_injector_delegate_unittest.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/program_metadata.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/runner.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/runner.h diff --git a/shell/platform/fuchsia/flutter/BUILD.gn b/shell/platform/fuchsia/flutter/BUILD.gn index ca8f5f2c207b0..865b34d224c24 100644 --- a/shell/platform/fuchsia/flutter/BUILD.gn +++ b/shell/platform/fuchsia/flutter/BUILD.gn @@ -92,6 +92,8 @@ template("runner_sources") { "platform_view.h", "pointer_delegate.cc", "pointer_delegate.h", + "pointer_injector_delegate.cc", + "pointer_injector_delegate.h", "program_metadata.h", "runner.cc", "runner.h", @@ -158,6 +160,7 @@ template("runner_sources") { "$fuchsia_sdk_root/fidl:fuchsia.ui.pointer", "$fuchsia_sdk_root/fidl:fuchsia.ui.views", "$fuchsia_sdk_root/fidl:fuchsia.ui.scenic", + "$fuchsia_sdk_root/fidl:fuchsia.ui.pointerinjector", "$fuchsia_sdk_root/pkg:async-cpp", "$fuchsia_sdk_root/pkg:async-default", "$fuchsia_sdk_root/pkg:async-loop", @@ -472,6 +475,7 @@ if (enable_unittests) { "keyboard_unittest.cc", "platform_view_unittest.cc", "pointer_delegate_unittests.cc", + "pointer_injector_delegate_unittest.cc", "tests/engine_unittests.cc", "tests/fake_flatland_unittests.cc", "tests/fake_session_unittests.cc", diff --git a/shell/platform/fuchsia/flutter/engine.cc b/shell/platform/fuchsia/flutter/engine.cc index b8d7c9eb639a1..ab94955d6776a 100644 --- a/shell/platform/fuchsia/flutter/engine.cc +++ b/shell/platform/fuchsia/flutter/engine.cc @@ -213,6 +213,17 @@ void Engine::Initialize( << "fuchsia::ui::input3::Keyboard connection failed: " << zx_status_get_string(keyboard_status); + // Connect to Pointerinjector service. + fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry; + zx_status_t pointerinjector_registry_status = + runner_services->Connect( + pointerinjector_registry.NewRequest()); + if (pointerinjector_registry_status != ZX_OK) { + FML_LOG(WARNING) + << "fuchsia::ui::pointerinjector::Registry connection failed: " + << zx_status_get_string(pointerinjector_registry_status); + } + // Make clones of the `ViewRef` before sending it to various places. fuchsia::ui::views::ViewRef platform_view_ref; view_ref_pair.view_ref.Clone(&platform_view_ref); @@ -401,6 +412,7 @@ void Engine::Initialize( view_ref_focused = std::move(view_ref_focused), touch_source = std::move(touch_source), mouse_source = std::move(mouse_source), + pointerinjector_registry = std::move(pointerinjector_registry), on_session_listener_error_callback = std::move(on_session_listener_error_callback), on_enable_wireframe_callback = @@ -479,6 +491,7 @@ void Engine::Initialize( std::move(mouse_source), std::move(focuser), std::move(view_ref_focused), std::move(parent_viewport_watcher), + std::move(pointerinjector_registry), std::move(on_enable_wireframe_callback), std::move(on_create_flatland_view_callback), std::move(on_update_view_callback), @@ -496,6 +509,7 @@ void Engine::Initialize( std::move(keyboard), std::move(touch_source), std::move(mouse_source), std::move(focuser), std::move(view_ref_focused), + std::move(pointerinjector_registry), std::move(session_listener_request), std::move(on_session_listener_error_callback), std::move(on_enable_wireframe_callback), diff --git a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc index 03924fcdda5d0..8eca3629343d5 100644 --- a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc +++ b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc @@ -358,7 +358,7 @@ void FlatlandExternalViewEmbedder::CreateView( FlatlandView new_view = {.transform_id = transform_id, .viewport_id = viewport_id}; flatland_->flatland()->CreateTransform(new_view.transform_id); - fuchsia::ui::composition::ChildViewWatcherPtr child_view_watcher; + fuchsia::ui::composition::ChildViewWatcherHandle child_view_watcher; new_view.pending_create_viewport_callback = [this, transform_id, viewport_id, view_id, child_view_watcher_request = diff --git a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h index edcae120aeca9..a47cd42055965 100644 --- a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h +++ b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h @@ -35,7 +35,7 @@ namespace flutter_runner { using ViewCallback = std::function; using FlatlandViewCreatedCallback = std::function; + fuchsia::ui::composition::ChildViewWatcherHandle child_view_watcher)>; using FlatlandViewIdCallback = std::function; diff --git a/shell/platform/fuchsia/flutter/flatland_platform_view.cc b/shell/platform/fuchsia/flutter/flatland_platform_view.cc index 1f382f0245d51..b6babae7d45fc 100644 --- a/shell/platform/fuchsia/flutter/flatland_platform_view.cc +++ b/shell/platform/fuchsia/flutter/flatland_platform_view.cc @@ -21,6 +21,7 @@ FlatlandPlatformView::FlatlandPlatformView( fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused, fuchsia::ui::composition::ParentViewportWatcherHandle parent_viewport_watcher, + fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry, OnEnableWireframe wireframe_enabled_callback, OnCreateFlatlandView on_create_view_callback, OnUpdateView on_update_view_callback, @@ -43,6 +44,7 @@ FlatlandPlatformView::FlatlandPlatformView( std::move(mouse_source), std::move(focuser), std::move(view_ref_focused), + std::move(pointerinjector_registry), std::move(wireframe_enabled_callback), std::move(on_update_view_callback), std::move(on_create_surface_callback), @@ -148,6 +150,10 @@ void FlatlandPlatformView::OnChildViewViewRef( focus_delegate_->OnChildViewViewRef(view_id, std::move(view_ref)); + fuchsia::ui::views::ViewRef view_ref_clone; + fidl::Clone(view_ref, &view_ref_clone); + pointer_injector_delegate_->OnCreateView(view_id, std::move(view_ref_clone)); + child_view_info_.at(content_id) .child_view_watcher->GetViewRef( [this, content_id, view_id](fuchsia::ui::views::ViewRef view_ref) { @@ -164,16 +170,32 @@ void FlatlandPlatformView::OnCreateView(ViewCallback on_view_created, task_runners_.GetPlatformTaskRunner(), view_id = view_id_raw]( fuchsia::ui::composition::ContentId content_id, - fuchsia::ui::composition::ChildViewWatcherPtr - child_view_watcher) { + fuchsia::ui::composition::ChildViewWatcherHandle + child_view_watcher_handle) { FML_CHECK(weak); FML_CHECK(weak->child_view_info_.count(content_id.value) == 0); + + // 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 = + child_view_watcher_handle.Bind(); FML_CHECK(child_view_watcher); - child_view_watcher.set_error_handler([](zx_status_t status) { - FML_LOG(ERROR) << "Interface error on: ChildViewWatcher status: " - << status; - }); + child_view_watcher.set_error_handler( + [weak, view_id](zx_status_t status) { + FML_LOG(ERROR) << "Interface error on: ChildViewWatcher status: " + << status; + + if (!weak) { + FML_LOG(WARNING) + << "Flatland View bound to PlatformView after PlatformView was " + "destroyed; ignoring."; + return; + } + + // Disconnected views cannot listen to pointer events. + weak->pointer_injector_delegate_->OnDestroyView(view_id); + }); platform_task_runner->PostTask( fml::MakeCopyable([weak, view_id, content_id, @@ -226,6 +248,7 @@ void FlatlandPlatformView::OnDisposeView(int64_t view_id_raw) { FML_DCHECK(weak->child_view_info_.count(content_id.value) == 1); 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)); diff --git a/shell/platform/fuchsia/flutter/flatland_platform_view.h b/shell/platform/fuchsia/flutter/flatland_platform_view.h index e51650cfe6f53..04ab78707a3ee 100644 --- a/shell/platform/fuchsia/flutter/flatland_platform_view.h +++ b/shell/platform/fuchsia/flutter/flatland_platform_view.h @@ -35,6 +35,7 @@ class FlatlandPlatformView final : public flutter_runner::PlatformView { fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused, fuchsia::ui::composition::ParentViewportWatcherHandle parent_viewport_watcher, + fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry, OnEnableWireframe wireframe_enabled_callback, OnCreateFlatlandView on_create_view_callback, OnUpdateView on_update_view_callback, diff --git a/shell/platform/fuchsia/flutter/gfx_platform_view.cc b/shell/platform/fuchsia/flutter/gfx_platform_view.cc index 22940721f2e35..0d2ba0272e7cd 100644 --- a/shell/platform/fuchsia/flutter/gfx_platform_view.cc +++ b/shell/platform/fuchsia/flutter/gfx_platform_view.cc @@ -19,6 +19,7 @@ GfxPlatformView::GfxPlatformView( fuchsia::ui::pointer::MouseSourceHandle mouse_source, fuchsia::ui::views::FocuserHandle focuser, fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused, + fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry, fidl::InterfaceRequest session_listener_request, fit::closure on_session_listener_error_callback, @@ -44,6 +45,7 @@ GfxPlatformView::GfxPlatformView( std::move(mouse_source), std::move(focuser), std::move(view_ref_focused), + std::move(pointerinjector_registry), std::move(wireframe_enabled_callback), std::move(on_update_view_callback), std::move(on_create_surface_callback), @@ -295,6 +297,9 @@ bool GfxPlatformView::OnChildViewDisconnected( << "}"; auto call = out.str(); + // A disconnected view cannot listen to pointer events. + pointer_injector_delegate_->OnDestroyView(view_id_mapping->second); + std::unique_ptr message = std::make_unique( "flutter/platform_views", @@ -350,6 +355,7 @@ void GfxPlatformView::OnCreateView(ViewCallback on_view_created, FML_DCHECK(weak->child_view_ids_.count(resource_id) == 0); weak->child_view_ids_[resource_id] = view_id; + weak->pointer_injector_delegate_->OnCreateView(view_id); }); }; on_create_view_callback_(view_id_raw, std::move(on_view_created), @@ -358,10 +364,10 @@ void GfxPlatformView::OnCreateView(ViewCallback on_view_created, void GfxPlatformView::OnDisposeView(int64_t view_id_raw) { auto on_view_unbound = - [weak = weak_factory_.GetWeakPtr(), + [weak = weak_factory_.GetWeakPtr(), view_id = view_id_raw, platform_task_runner = task_runners_.GetPlatformTaskRunner()]( scenic::ResourceId resource_id) { - platform_task_runner->PostTask([weak, resource_id]() { + platform_task_runner->PostTask([weak, resource_id, view_id]() { if (!weak) { FML_LOG(WARNING) << "ViewHolder unbound from PlatformView after PlatformView" @@ -371,6 +377,7 @@ void GfxPlatformView::OnDisposeView(int64_t view_id_raw) { FML_DCHECK(weak->child_view_ids_.count(resource_id) == 1); weak->child_view_ids_.erase(resource_id); + weak->pointer_injector_delegate_->OnDestroyView(view_id); }); }; on_destroy_view_callback_(view_id_raw, std::move(on_view_unbound)); diff --git a/shell/platform/fuchsia/flutter/gfx_platform_view.h b/shell/platform/fuchsia/flutter/gfx_platform_view.h index 4ecc1debe1193..1004a85808c56 100644 --- a/shell/platform/fuchsia/flutter/gfx_platform_view.h +++ b/shell/platform/fuchsia/flutter/gfx_platform_view.h @@ -36,6 +36,7 @@ class GfxPlatformView final : public flutter_runner::PlatformView, fuchsia::ui::pointer::MouseSourceHandle mouse_source, fuchsia::ui::views::FocuserHandle focuser, fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused, + fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry, fidl::InterfaceRequest session_listener_request, fit::closure on_session_listener_error_callback, diff --git a/shell/platform/fuchsia/flutter/meta/common.shard.cml b/shell/platform/fuchsia/flutter/meta/common.shard.cml index 1795d508a26f4..8be3af896d84d 100644 --- a/shell/platform/fuchsia/flutter/meta/common.shard.cml +++ b/shell/platform/fuchsia/flutter/meta/common.shard.cml @@ -48,6 +48,7 @@ "fuchsia.ui.input.ImeService", "fuchsia.ui.input3.Keyboard", "fuchsia.ui.scenic.Scenic", + "fuchsia.ui.pointerinjector.Registry", "fuchsia.vulkan.loader.Loader" // Copied from vulkan/client.shard.cml. ] } diff --git a/shell/platform/fuchsia/flutter/meta/flutter_aot_product_runner.cmx b/shell/platform/fuchsia/flutter/meta/flutter_aot_product_runner.cmx index bc88a3d3c6cd8..ec1e1975c6e58 100644 --- a/shell/platform/fuchsia/flutter/meta/flutter_aot_product_runner.cmx +++ b/shell/platform/fuchsia/flutter/meta/flutter_aot_product_runner.cmx @@ -26,6 +26,7 @@ "fuchsia.ui.input.ImeService", "fuchsia.ui.input3.Keyboard", "fuchsia.ui.scenic.Scenic", + "fuchsia.ui.pointerinjector.Registry", "fuchsia.vulkan.loader.Loader" ] } diff --git a/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx b/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx index bc88a3d3c6cd8..ec1e1975c6e58 100644 --- a/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx +++ b/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cmx @@ -26,6 +26,7 @@ "fuchsia.ui.input.ImeService", "fuchsia.ui.input3.Keyboard", "fuchsia.ui.scenic.Scenic", + "fuchsia.ui.pointerinjector.Registry", "fuchsia.vulkan.loader.Loader" ] } diff --git a/shell/platform/fuchsia/flutter/meta/flutter_jit_product_runner.cmx b/shell/platform/fuchsia/flutter/meta/flutter_jit_product_runner.cmx index c0e0d8c185f21..b0a626ca1b065 100644 --- a/shell/platform/fuchsia/flutter/meta/flutter_jit_product_runner.cmx +++ b/shell/platform/fuchsia/flutter/meta/flutter_jit_product_runner.cmx @@ -27,6 +27,7 @@ "fuchsia.ui.input.ImeService", "fuchsia.ui.input3.Keyboard", "fuchsia.ui.scenic.Scenic", + "fuchsia.ui.pointerinjector.Registry", "fuchsia.vulkan.loader.Loader" ] } diff --git a/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx b/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx index c0e0d8c185f21..b0a626ca1b065 100644 --- a/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx +++ b/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx @@ -27,6 +27,7 @@ "fuchsia.ui.input.ImeService", "fuchsia.ui.input3.Keyboard", "fuchsia.ui.scenic.Scenic", + "fuchsia.ui.pointerinjector.Registry", "fuchsia.vulkan.loader.Loader" ] } diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc index 4f9a05d1e8080..4b7690baf8acc 100644 --- a/shell/platform/fuchsia/flutter/platform_view.cc +++ b/shell/platform/fuchsia/flutter/platform_view.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flow/embedded_views.h" +#include "pointer_injector_delegate.h" #define RAPIDJSON_HAS_STDSTRING 1 #include "platform_view.h" @@ -62,6 +63,7 @@ PlatformView::PlatformView( fuchsia::ui::pointer::MouseSourceHandle mouse_source, fuchsia::ui::views::FocuserHandle focuser, fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused, + fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry, OnEnableWireframe wireframe_enabled_callback, OnUpdateView on_update_view_callback, OnCreateSurface on_create_surface_callback, @@ -101,6 +103,9 @@ PlatformView::PlatformView( SetInterfaceErrorHandler(keyboard_listener_binding_, "Keyboard Listener"); SetInterfaceErrorHandler(keyboard_, "Keyboard"); + fuchsia::ui::views::ViewRef view_ref_clone; + fidl::Clone(view_ref, &view_ref_clone); + // Configure keyboard listener. keyboard_->AddListener(std::move(view_ref), keyboard_listener_binding_.NewBinding(), [] {}); @@ -150,6 +155,11 @@ PlatformView::PlatformView( }); } + // Configure the pointer injector delegate. + pointer_injector_delegate_ = std::make_unique( + std::move(pointerinjector_registry), std::move(view_ref_clone), + is_flatland); + // Finally! Register the native platform message handlers. RegisterPlatformMessageHandlers(); } @@ -845,6 +855,10 @@ bool PlatformView::HandleFlutterPlatformViewsChannelPlatformMessage( } } 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; } diff --git a/shell/platform/fuchsia/flutter/platform_view.h b/shell/platform/fuchsia/flutter/platform_view.h index 25d8aedd6b7a8..41bc46b8064f8 100644 --- a/shell/platform/fuchsia/flutter/platform_view.h +++ b/shell/platform/fuchsia/flutter/platform_view.h @@ -33,6 +33,7 @@ #include "flutter/shell/platform/fuchsia/flutter/vsync_waiter.h" #include "focus_delegate.h" #include "pointer_delegate.h" +#include "pointer_injector_delegate.h" namespace flutter_runner { @@ -75,6 +76,7 @@ class PlatformView : public flutter::PlatformView, fuchsia::ui::pointer::MouseSourceHandle mouse_source, fuchsia::ui::views::FocuserHandle focuser, fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused, + fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry, OnEnableWireframe wireframe_enabled_callback, OnUpdateView on_update_view_callback, OnCreateSurface on_create_surface_callback, @@ -187,6 +189,7 @@ class PlatformView : public flutter::PlatformView, std::shared_ptr focus_delegate_; std::shared_ptr pointer_delegate_; + std::unique_ptr pointer_injector_delegate_; fidl::Binding ime_client_; fuchsia::ui::input::InputMethodEditorPtr ime_; diff --git a/shell/platform/fuchsia/flutter/platform_view_unittest.cc b/shell/platform/fuchsia/flutter/platform_view_unittest.cc index 761a4d6cd3679..b39fdb60edde6 100644 --- a/shell/platform/fuchsia/flutter/platform_view_unittest.cc +++ b/shell/platform/fuchsia/flutter/platform_view_unittest.cc @@ -271,6 +271,12 @@ class PlatformViewBuilder { return *this; } + PlatformViewBuilder& SetPointerinjectorRegistry( + fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry) { + pointerinjector_registry_ = std::move(pointerinjector_registry); + return *this; + } + PlatformViewBuilder& SetSessionListenerRequest( fidl::InterfaceRequest request) { session_listener_request_ = std::move(request); @@ -316,7 +322,8 @@ class PlatformViewBuilder { external_external_view_embedder_, std::move(ime_service_), std::move(keyboard_), std::move(touch_source_), std::move(mouse_source_), std::move(focuser_), - std::move(view_ref_focused_), std::move(session_listener_request_), + std::move(view_ref_focused_), std::move(pointerinjector_registry_), + std::move(session_listener_request_), std::move(on_session_listener_error_callback_), std::move(wireframe_enabled_callback_), std::move(on_create_view_callback_), @@ -343,6 +350,7 @@ class PlatformViewBuilder { fuchsia::ui::pointer::MouseSourceHandle mouse_source_; fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused_; fuchsia::ui::views::FocuserHandle focuser_; + fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry_; fidl::InterfaceRequest session_listener_request_; fit::closure on_session_listener_error_callback_; diff --git a/shell/platform/fuchsia/flutter/pointer_injector_delegate.cc b/shell/platform/fuchsia/flutter/pointer_injector_delegate.cc new file mode 100644 index 0000000000000..5be5b695a3f5c --- /dev/null +++ b/shell/platform/fuchsia/flutter/pointer_injector_delegate.cc @@ -0,0 +1,308 @@ +// 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. + +#include "pointer_injector_delegate.h" +#include "flutter/fml/logging.h" + +namespace flutter_runner { + +using fup_Config = fuchsia::ui::pointerinjector::Config; +using fup_Context = fuchsia::ui::pointerinjector::Context; +using fup_Data = fuchsia::ui::pointerinjector::Data; +using fup_DeviceType = fuchsia::ui::pointerinjector::DeviceType; +using fup_DispatchPolicy = fuchsia::ui::pointerinjector::DispatchPolicy; +using fup_Event = fuchsia::ui::pointerinjector::Event; +using fup_EventPhase = fuchsia::ui::pointerinjector::EventPhase; +using fup_PointerSample = fuchsia::ui::pointerinjector::PointerSample; +using fup_Target = fuchsia::ui::pointerinjector::Target; +using fup_Viewport = fuchsia::ui::pointerinjector::Viewport; +using fuv_ViewRef = fuchsia::ui::views::ViewRef; +const auto fup_MAX_INJECT = fuchsia::ui::pointerinjector::MAX_INJECT; + +namespace { + +// clang-format off + static constexpr std::array kIdentityMatrix = { + 1, 0, 0, // column one + 0, 1, 0, // column two + 0, 0, 1, // column three + }; +// clang-format on + +} // namespace + +bool PointerInjectorDelegate::HandlePlatformMessage( + rapidjson::Value request, + fml::RefPtr response) { + if (!registry_->is_bound()) { + FML_LOG(WARNING) + << "Lost connection to fuchsia.ui.pointerinjector.Registry"; + return false; + } + + auto method = request.FindMember("method"); + if (method == request.MemberEnd() || !method->value.IsString()) { + return false; + } + + if (method->value == kPointerInjectorMethodPrefix) { + auto args_it = request.FindMember("args"); + if (args_it == request.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 uint64"; + return false; + } + auto id = view_id->value.GetUint64(); + + auto phase = args.FindMember("phase"); + if (!phase->value.IsInt()) { + FML_LOG(ERROR) << "Argument 'phase' is not a int"; + return false; + } + + auto pointer_x = args.FindMember("x"); + if (!pointer_x->value.IsFloat() && !pointer_x->value.IsInt()) { + FML_LOG(ERROR) << "Argument 'Pointer.X' is not a float"; + return false; + } + + auto pointer_y = args.FindMember("y"); + if (!pointer_y->value.IsFloat() && !pointer_y->value.IsInt()) { + FML_LOG(ERROR) << "Argument 'Pointer.Y' is not a float"; + return false; + } + + auto pointer_id = args.FindMember("pointerId"); + if (!pointer_id->value.IsUint()) { + FML_LOG(ERROR) << "Argument 'pointerId' is not a uint32"; + return false; + } + + auto trace_flow_id = args.FindMember("traceFlowId"); + if (!trace_flow_id->value.IsInt()) { + FML_LOG(ERROR) << "Argument 'traceFlowId' is not a int"; + return false; + } + + // For GFX, the viewRef for the view is provided through the platform + // message. For flatland, the viewRef is provided through |OnCreateView|. + std::optional view_ref; + if (!is_flatland_) { + auto view_ref_arg = args.FindMember("viewRef"); + if (!view_ref_arg->value.IsUint64()) { + FML_LOG(ERROR) << "Argument 'viewRef' is not a uint64"; + return false; + } + + zx_handle_t handle = view_ref_arg->value.GetUint64(); + zx_handle_t out_handle; + zx_status_t status = + zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, &out_handle); + if (status != ZX_OK) { + FML_LOG(ERROR) << "Argument 'viewRef' is not valid"; + return false; + } + auto ref = fuv_ViewRef({ + .reference = zx::eventpair(out_handle), + }); + view_ref = std::move(ref); + } + + auto width = args.FindMember("logicalWidth"); + if (!width->value.IsFloat() && !width->value.IsInt()) { + FML_LOG(ERROR) << "Argument 'logicalWidth' is not a float"; + return false; + } + + auto height = args.FindMember("logicalHeight"); + if (!height->value.IsFloat() && !height->value.IsInt()) { + FML_LOG(ERROR) << "Argument 'logicalHeight' is not a float"; + return false; + } + + auto timestamp = args.FindMember("timestamp"); + if (!timestamp->value.IsInt()) { + FML_LOG(ERROR) << "Argument 'timestamp' is not a int"; + return false; + } + + PointerInjectorRequest request = { + .x = pointer_x->value.GetFloat(), + .y = pointer_y->value.GetFloat(), + .pointer_id = pointer_id->value.GetUint(), + .phase = static_cast(phase->value.GetInt()), + .trace_flow_id = trace_flow_id->value.GetUint64(), + .view_ref = std::move(view_ref), + .logical_size = {width->value.GetFloat(), height->value.GetFloat()}, + .timestamp = timestamp->value.GetInt()}; + + // Inject the pointer event if the view has been created. + if (valid_views_.count(id) > 0) { + valid_views_.at(id).InjectEvent(std::move(request)); + Complete(std::move(response), "[0]"); + } else { + return false; + } + } else { + return false; + } + // All of our methods complete the platform message response. + return true; +} + +void PointerInjectorDelegate::OnCreateView( + uint64_t view_id, + std::optional view_ref) { + FML_CHECK(valid_views_.count(view_id) == 0); + + auto [_, success] = valid_views_.try_emplace( + view_id, registry_, host_view_ref_, std::move(view_ref)); + + FML_CHECK(success); +} + +fup_Event PointerInjectorDelegate::ExtractPointerEvent( + PointerInjectorRequest request) { + fup_Event event; + event.set_timestamp(request.timestamp); + event.set_trace_flow_id(request.trace_flow_id); + + fup_PointerSample pointer_sample; + pointer_sample.set_pointer_id(request.pointer_id); + pointer_sample.set_phase(request.phase); + pointer_sample.set_position_in_viewport({request.x, request.y}); + + fup_Data data; + data.set_pointer_sample(std::move(pointer_sample)); + + event.set_data(std::move(data)); + return event; +} + +void PointerInjectorDelegate::Complete( + fml::RefPtr response, + std::string value) { + if (response) { + response->Complete(std::make_unique( + std::vector(value.begin(), value.end()))); + } +} + +void PointerInjectorDelegate::PointerInjectorEndpoint::InjectEvent( + PointerInjectorRequest request) { + if (!registered_) { + RegisterInjector(request); + } + + auto event = ExtractPointerEvent(std::move(request)); + + // Add the event to |injector_events_| and dispatch it to the view. + EnqueueEvent(std::move(event)); + + DispatchPendingEvents(); +} + +void PointerInjectorDelegate::PointerInjectorEndpoint::DispatchPendingEvents() { + // Return if there is already a |fuchsia.ui.pointerinjector.Device.Inject| + // call in flight. The new pointer events will be dispatched once the + // in-progress call terminates. + if (injection_in_flight_) { + return; + } + + // Dispatch the events present in |injector_events_|. Note that we recursively + // call |DispatchPendingEvents| in the callback passed to the + // |f.u.p.Device.Inject| call. This ensures that there is only one + // |f.u.p.Device.Inject| call at a time. If a new pointer event comes when + // there is a |f.u.p.Device.Inject| call in progress, it gets buffered in + // |injector_events_| and is picked up later. + if (!injector_events_.empty()) { + auto events = std::move(injector_events_.front()); + injector_events_.pop(); + injection_in_flight_ = true; + + FML_CHECK(device_.is_bound()); + FML_CHECK(events.size() <= fup_MAX_INJECT); + + device_->Inject(std::move(events), [weak = weak_factory_.GetWeakPtr()] { + if (!weak) { + FML_LOG(WARNING) << "Use after free attempted."; + return; + } + weak->injection_in_flight_ = false; + weak->DispatchPendingEvents(); + }); + } +} + +void PointerInjectorDelegate::PointerInjectorEndpoint::EnqueueEvent( + fup_Event event) { + // Add |event| in |injector_events_| keeping in mind that the vector size does + // not exceed |fup_MAX_INJECT|. + if (!injector_events_.empty() && + injector_events_.back().size() < fup_MAX_INJECT) { + injector_events_.back().push_back(std::move(event)); + } else { + std::vector vec; + vec.reserve(fup_MAX_INJECT); + vec.push_back(std::move(event)); + injector_events_.push(std::move(vec)); + } +} + +void PointerInjectorDelegate::PointerInjectorEndpoint::RegisterInjector( + const PointerInjectorRequest& request) { + if (registered_) { + return; + } + + fup_Config config; + config.set_device_id(1); + config.set_device_type(fup_DeviceType::TOUCH); + config.set_dispatch_policy(fup_DispatchPolicy::EXCLUSIVE_TARGET); + + fup_Context context; + fuv_ViewRef context_clone; + fidl::Clone(*host_view_ref_, &context_clone); + context.set_view(std::move(context_clone)); + config.set_context(std::move(context)); + + FML_CHECK(request.view_ref.has_value() || view_ref_.has_value()); + fup_Target target; + fuv_ViewRef target_clone; + + // GFX. + if (request.view_ref.has_value()) { + fidl::Clone(*request.view_ref, &target_clone); + } + // Flatland. + else { + fidl::Clone(*view_ref_, &target_clone); + } + target.set_view(std::move(target_clone)); + config.set_target(std::move(target)); + + fup_Viewport viewport; + viewport.set_viewport_to_context_transform(kIdentityMatrix); + std::array, 2> extents{ + {/*min*/ {0, 0}, + /*max*/ {request.logical_size[0], request.logical_size[1]}}}; + viewport.set_extents(std::move(extents)); + config.set_viewport(std::move(viewport)); + + FML_CHECK(registry_->is_bound()); + + (*registry_)->Register(std::move(config), device_.NewRequest(), [] {}); + + registered_ = true; +} + +} // namespace flutter_runner diff --git a/shell/platform/fuchsia/flutter/pointer_injector_delegate.h b/shell/platform/fuchsia/flutter/pointer_injector_delegate.h new file mode 100644 index 0000000000000..7c041fa346a1a --- /dev/null +++ b/shell/platform/fuchsia/flutter/pointer_injector_delegate.h @@ -0,0 +1,189 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_POINTER_INJECTOR_DELEGATE_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_POINTER_INJECTOR_DELEGATE_H_ + +#include +#include + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/memory/weak_ptr.h" +#include "flutter/lib/ui/window/platform_message.h" +#include "third_party/rapidjson/include/rapidjson/document.h" + +namespace flutter_runner { + +// This class is responsible for handling the platform messages related to +// pointer events and managing the lifecycle of +// |fuchsia.ui.pointerinjector.Device| client side endpoint for embedded views. +class PointerInjectorDelegate { + public: + static constexpr auto kPointerInjectorMethodPrefix = + "View.pointerinjector.inject"; + + PointerInjectorDelegate(fuchsia::ui::pointerinjector::RegistryHandle registry, + fuchsia::ui::views::ViewRef host_view_ref, + bool is_flatland) + : registry_(std::make_shared( + registry.Bind())), + host_view_ref_(std::make_shared( + std::move(host_view_ref))), + is_flatland_(is_flatland) {} + + // Handles the following pointer event related platform message requests: + // View.Pointerinjector.inject + // - Attempts to dispatch a pointer event to the given viewRef. Completes + // with [0] when the pointer event is sent to the given viewRef. + bool HandlePlatformMessage( + rapidjson::Value request, + fml::RefPtr response); + + // Adds an endpoint for |view_id| in |valid_views_| for lifecycle management. + // Called in |GFXPlatformView::OnCreateView()| and + // |FlatlandPlatformView::OnChildViewViewRef()|. + void OnCreateView( + uint64_t view_id, + std::optional view_ref = std::nullopt); + + // Closes the |fuchsia.ui.pointerinjector.Device| channel for |view_id| and + // cleans up resources. + void OnDestroyView(uint64_t view_id) { valid_views_.erase(view_id); }; + + private: + using ViewId = int64_t; + + struct PointerInjectorRequest { + // The position of the pointer event in viewport's coordinate system. + float x = 0.f, y = 0.f; + + // |fuchsia.ui.pointerinjector.PointerSample.pointer_id|. + uint32_t pointer_id = 0; + + // |fuchsia.ui.pointerinjector.PointerSample.phase|. + fuchsia::ui::pointerinjector::EventPhase phase = + fuchsia::ui::pointerinjector::EventPhase::ADD; + + // |fuchsia.ui.pointerinjector.Event.trace_flow_id|. + uint64_t trace_flow_id = 0; + + // The view for which dispatch is attempted for the pointer event. For + // flatland views, this value is set as std::nullopt. + std::optional view_ref; + + // Logical size of the view's coordinate system. + std::array logical_size = {0.f, 0.f}; + + // |fuchsia.ui.pointerinjector.Event.timestamp|. + zx_time_t timestamp = 0; + }; + + // This class is responsible for dispatching pointer events to a view by first + // registering the injector device using + // |fuchsia.ui.pointerinjector.Registry.Register| and then injecting the + // pointer event using |fuchsia.ui.pointerinjector.Device.Inject|. + class PointerInjectorEndpoint { + public: + PointerInjectorEndpoint( + std::shared_ptr registry, + std::shared_ptr host_view_ref, + std::optional view_ref) + : registry_(std::move(registry)), + host_view_ref_(std::move(host_view_ref)), + view_ref_(std::move(view_ref)), + weak_factory_(this) { + // Try to re-register the |device_| if the |device_| gets closed due to + // some error. + device_.set_error_handler( + [weak = weak_factory_.GetWeakPtr()](auto status) { + FML_LOG(WARNING) + << "fuchsia.ui.pointerinjector.Device closed " << status; + if (!weak) { + return; + } + weak->registered_ = false; + }); + } + + // Registers |device_| if it has not been registered and calls + // |DispatchPendingEvents()| to dispatch |request| to the view. + void InjectEvent(PointerInjectorRequest request); + + private: + // Registers with the pointer injector service. + // + // Sets |registered_| to true immediately after submitting the registration + // request. This means that the registration request may still be in-flight + // on the server side when the function returns. Events can safely be + // injected into the channel while registration is pending ("feed forward"). + void RegisterInjector(const PointerInjectorRequest& request); + + // Recursively calls |fuchsia.ui.pointerinjector.Device.Inject| to dispatch + // the pointer events in |injector_events_| to the view. + void DispatchPendingEvents(); + + void EnqueueEvent(fuchsia::ui::pointerinjector::Event event); + + // Set to true if there is a |fuchsia.ui.pointerinjector.Device.Inject| call + // in progress. If true, the |fuchsia.ui.pointerinjector.Event| is buffered + // in |injector_events_|. + bool injection_in_flight_ = false; + + // Set to true if |device_| has been registered using + // |fuchsia.ui.pointerinjector.Registry.Register|. False otherwise. + bool registered_ = false; + + std::shared_ptr registry_; + + // ViewRef for the main flutter app launching the embedded child views. + std::shared_ptr host_view_ref_; + + // ViewRef for a flatland view. For GFX this value is set as std::nullopt. + // Set in |OnCreateView|. + std::optional view_ref_; + + fuchsia::ui::pointerinjector::DevicePtr device_; + + // A queue containing all the pending |fuchsia.ui.pointerinjector.Event|s + // which have to be dispatched to the view. + // Note: The size of a vector inside |injector_events_| should not exceed + // |fuchsia.ui.pointerinjector.MAX_INJECT|. + std::queue> + injector_events_; + + fml::WeakPtrFactory + weak_factory_; // Must be the last member. + + FML_DISALLOW_COPY_AND_ASSIGN(PointerInjectorEndpoint); + }; + + void Complete(fml::RefPtr response, + std::string value); + + // Generates a |fuchsia.ui.pointerinjector.Event| from |request| by extracting + // information like timestamp, trace flow id and pointer sample from + // |request|. + static fuchsia::ui::pointerinjector::Event ExtractPointerEvent( + PointerInjectorRequest request); + + // A map of valid views keyed by its view id. A view can receive pointer + // events only if it is present in |valid_views_|. + std::unordered_map valid_views_; + + std::shared_ptr registry_; + + // ViewRef for the main flutter app launching the embedded child views. + std::shared_ptr host_view_ref_; + + bool is_flatland_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(PointerInjectorDelegate); +}; + +} // namespace flutter_runner +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_POINTER_INJECTOR_DELEGATE_H_ diff --git a/shell/platform/fuchsia/flutter/pointer_injector_delegate_unittest.cc b/shell/platform/fuchsia/flutter/pointer_injector_delegate_unittest.cc new file mode 100644 index 0000000000000..1005671e39225 --- /dev/null +++ b/shell/platform/fuchsia/flutter/pointer_injector_delegate_unittest.cc @@ -0,0 +1,688 @@ +// 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. + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "pointer_injector_delegate.h" +#include "tests/fakes/mock_injector_registry.h" +#include "tests/fakes/platform_message.h" + +namespace flutter_runner::testing { + +using fup_DeviceType = fuchsia::ui::pointerinjector::DeviceType; +using fup_DispatchPolicy = fuchsia::ui::pointerinjector::DispatchPolicy; +using fup_EventPhase = fuchsia::ui::pointerinjector::EventPhase; +using fup_RegistryHandle = fuchsia::ui::pointerinjector::RegistryHandle; +using fuv_ViewRef = fuchsia::ui::views::ViewRef; + +namespace { + +// clang-format off + static constexpr std::array kIdentityMatrix = { + 1, 0, 0, // column one + 0, 1, 0, // column two + 0, 0, 1, // column three + }; +// clang-format on + +rapidjson::Value ParsePlatformMessage(std::string json) { + rapidjson::Document document; + document.Parse(json); + if (document.HasParseError() || !document.IsObject()) { + FML_LOG(ERROR) << "Could not parse document"; + return rapidjson::Value(); + } + return document.GetObject(); +} + +zx_koid_t ExtractKoid(const zx::object_base& object) { + zx_info_handle_basic_t info{}; + if (object.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, + nullptr) != ZX_OK) { + return ZX_KOID_INVALID; // no info + } + + return info.koid; +} + +zx_koid_t ExtractKoid(const fuv_ViewRef& view_ref) { + return ExtractKoid(view_ref.reference); +} + +class PlatformMessageBuilder { + public: + PlatformMessageBuilder& SetViewId(uint64_t view_id) { + view_id_ = view_id; + return *this; + } + + PlatformMessageBuilder& SetPointerX(float x) { + pointer_x_ = x; + return *this; + } + + PlatformMessageBuilder& SetPointerY(float y) { + pointer_y_ = y; + return *this; + } + + PlatformMessageBuilder& SetPhase(int phase) { + phase_ = phase; + return *this; + } + + PlatformMessageBuilder& SetPointerId(int pointer_id) { + pointer_id_ = pointer_id; + return *this; + } + + PlatformMessageBuilder& SetTraceFlowId(int trace_flow_id) { + trace_flow_id_ = trace_flow_id; + return *this; + } + + PlatformMessageBuilder& SetViewRefMaybe(std::optional view_ref) { + if (view_ref.has_value()) { + view_ref_ = std::move(*view_ref); + } + return *this; + } + + PlatformMessageBuilder& SetLogicalWidth(float width) { + width_ = width; + return *this; + } + + PlatformMessageBuilder& SetLogicalHeight(float height) { + height_ = height; + return *this; + } + + PlatformMessageBuilder& SetTimestamp(int timestamp) { + timestamp_ = timestamp; + return *this; + } + + rapidjson::Value Build() { + std::ostringstream message; + message << "{" + << " \"method\":\"" + << PointerInjectorDelegate::kPointerInjectorMethodPrefix << "\"," + << " \"args\": {" + << " \"viewId\":" << view_id_ << "," + << " \"x\":" << pointer_x_ << "," + << " \"y\":" << pointer_y_ << "," + << " \"phase\":" << phase_ << "," + << " \"pointerId\":" << pointer_id_ << "," + << " \"traceFlowId\":" << trace_flow_id_ << "," + << " \"viewRef\":" << view_ref_.reference.get() << "," + << " \"logicalWidth\":" << width_ << "," + << " \"logicalHeight\":" << height_ << "," + << " \"timestamp\":" << timestamp_ << " }" + << "}"; + return ParsePlatformMessage(message.str()); + } + + private: + uint64_t view_id_ = 0; + float pointer_x_ = 0.f, pointer_y_ = 0.f; + int phase_ = 1, pointer_id_ = 0, trace_flow_id_ = 0; + fuv_ViewRef view_ref_; + float width_ = 0.f, height_ = 0.f; + int timestamp_ = 0; +}; + +} // namespace + +class PointerInjectorDelegateTest : public ::testing::Test, + public ::testing::WithParamInterface { + protected: + PointerInjectorDelegateTest() + : loop_(&kAsyncLoopConfigAttachToCurrentThread) {} + + // TODO(fxbug.dev/104285): Replace the RunLoop methods with the one provided + // by the sdk. + void RunLoopUntilIdle() { loop_.RunUntilIdle(); } + + bool RunGivenLoopWithTimeout(async::Loop* loop, zx::duration timeout) { + // This cannot be a local variable because the delayed task below can + // execute after this function returns. + auto canceled = std::make_shared(false); + bool timed_out = false; + async::PostDelayedTask( + loop->dispatcher(), + [loop, canceled, &timed_out] { + if (*canceled) { + return; + } + timed_out = true; + loop->Quit(); + }, + timeout); + loop->Run(); + loop->ResetQuit(); + + if (!timed_out) { + *canceled = true; + } + return timed_out; + } + + bool RunLoopWithTimeoutOrUntil(fit::function condition, + zx::duration timeout, + zx::duration step) { + const zx::time timeout_deadline = zx::deadline_after(timeout); + + while (zx::clock::get_monotonic() < timeout_deadline && + loop_.GetState() == ASYNC_LOOP_RUNNABLE) { + if (condition()) { + loop_.ResetQuit(); + return true; + } + + if (step == zx::duration::infinite()) { + // Performs a single unit of work, possibly blocking until there is work + // to do or the timeout deadline arrives. + loop_.Run(timeout_deadline, true); + } else { + // Performs work until the step deadline arrives. + RunGivenLoopWithTimeout(&loop_, step); + } + } + + loop_.ResetQuit(); + return condition(); + } + + void RunLoopUntil(fit::function condition, + zx::duration step = zx::msec(10)) { + RunLoopWithTimeoutOrUntil(std::move(condition), zx::duration::infinite(), + step); + } + + void SetUp() override { + auto view_ref_pair = scenic::ViewRefPair::New(); + + host_view_ref_ = std::move(view_ref_pair.view_ref); + + fup_RegistryHandle registry; + registry_ = std::make_unique(registry.NewRequest()); + + fuv_ViewRef host_view_ref_clone; + fidl::Clone(host_view_ref_, &host_view_ref_clone); + + is_flatland_ = GetParam(); + pointer_injector_delegate_ = std::make_unique( + std::move(registry), std::move(host_view_ref_clone), is_flatland_); + } + + void CreateView(uint64_t view_id, + std::optional view_ref = std::nullopt) { + if (!is_flatland_) { + pointer_injector_delegate_->OnCreateView(view_id); + } else { + fuv_ViewRef ref; + if (view_ref.has_value()) { + ref = std::move(*view_ref); + } else { + auto view_ref_pair = scenic::ViewRefPair::New(); + ref = std::move(view_ref_pair.view_ref); + } + pointer_injector_delegate_->OnCreateView(view_id, std::move(ref)); + } + } + + std::unique_ptr pointer_injector_delegate_; + std::unique_ptr registry_; + fuv_ViewRef host_view_ref_; + bool is_flatland_ = false; + + private: + async::Loop loop_; +}; + +TEST_P(PointerInjectorDelegateTest, IncorrectPlatformMessage_ShouldFail) { + const uint64_t view_id = 1; + + // Create a view. + CreateView(view_id); + + // A platform message in incorrect JSON format should fail. + { + auto response = FakePlatformMessageResponse::Create(); + + EXPECT_FALSE(pointer_injector_delegate_->HandlePlatformMessage( + ParsePlatformMessage("{Incorrect Json}"), response)); + } + + // |PointerInjectorDelegate| only handles "View.Pointerinjector.inject" + // platform messages. + { + auto response = FakePlatformMessageResponse::Create(); + + EXPECT_FALSE(pointer_injector_delegate_->HandlePlatformMessage( + ParsePlatformMessage("{\"method\":\"View.focus.getCurrent\"}"), + response)); + } + + // A platform message with no args should fail. + { + auto response = FakePlatformMessageResponse::Create(); + + EXPECT_FALSE(pointer_injector_delegate_->HandlePlatformMessage( + ParsePlatformMessage("{\"method\":\"View.Pointerinjector.inject\"}"), + response)); + } +} + +TEST_P(PointerInjectorDelegateTest, ViewsReceiveInjectedEvents) { + const uint64_t num_events = 150; + + // Inject |num_events| platform messages for view 1. + { + const uint64_t view_id = 1; + + CreateView(view_id); + + auto view_ref_pair = scenic::ViewRefPair::New(); + + for (size_t i = 0; i < num_events; i++) { + auto response = FakePlatformMessageResponse::Create(); + + // Flatland views do not rely on ViewRef to be passed in the platform + // message. + std::optional view_ref_clone; + if (fuv_ViewRef temp_ref; !is_flatland_) { + fidl::Clone(view_ref_pair.view_ref, &temp_ref); + view_ref_clone = std::move(temp_ref); + } + + EXPECT_TRUE(pointer_injector_delegate_->HandlePlatformMessage( + PlatformMessageBuilder() + .SetViewId(view_id) + .SetViewRefMaybe(std::move(view_ref_clone)) + .Build(), + response)); + + response->ExpectCompleted("[0]"); + } + } + + // Inject |num_events| platform messages for view 2. + { + const uint64_t view_id = 2; + + CreateView(view_id); + + auto view_ref_pair = scenic::ViewRefPair::New(); + + for (size_t i = 0; i < num_events; i++) { + auto response = FakePlatformMessageResponse::Create(); + + // Flatland views do not rely on ViewRef to be passed in the platform + // message. + std::optional view_ref_clone; + if (fuv_ViewRef temp_ref; !is_flatland_) { + fidl::Clone(view_ref_pair.view_ref, &temp_ref); + view_ref_clone = std::move(temp_ref); + } + + EXPECT_TRUE(pointer_injector_delegate_->HandlePlatformMessage( + PlatformMessageBuilder() + .SetViewId(view_id) + .SetViewRefMaybe(std::move(view_ref_clone)) + .Build(), + response)); + + response->ExpectCompleted("[0]"); + } + } + + // The mock Pointerinjector registry server receives |num_events| pointer + // events from |f.u.p.Device.Inject| calls for each view. + RunLoopUntil( + [this] { return registry_->num_events_received() == 2 * num_events; }); + + // The mock Pointerinjector registry server receives a + // |f.u.p.Registry.Register| call for each view. + EXPECT_TRUE(registry_->num_register_calls() == 2); +} + +TEST_P(PointerInjectorDelegateTest, + ViewsDontReceivePointerEventsBeforeCreation) { + const uint64_t num_events = 150; + const uint64_t view_id_1 = 1; + + // Inject |num_events| platform messages for |view_id_1|. + { + auto view_ref_pair = scenic::ViewRefPair::New(); + + for (size_t i = 0; i < num_events; i++) { + auto response = FakePlatformMessageResponse::Create(); + + // Flatland views do not rely on ViewRef to be passed in the platform + // message. + std::optional view_ref_clone; + if (fuv_ViewRef temp_ref; !is_flatland_) { + fidl::Clone(view_ref_pair.view_ref, &temp_ref); + view_ref_clone = std::move(temp_ref); + } + + EXPECT_FALSE(pointer_injector_delegate_->HandlePlatformMessage( + PlatformMessageBuilder() + .SetViewId(view_id_1) + .SetViewRefMaybe(std::move(view_ref_clone)) + .Build(), + response)); + } + } + + const uint64_t view_id_2 = 2; + + // Inject |num_events| platform messages for |view_id_2|. + { + auto view_ref_pair = scenic::ViewRefPair::New(); + + for (size_t i = 0; i < num_events; i++) { + auto response = FakePlatformMessageResponse::Create(); + + // Flatland views do not rely on ViewRef to be passed in the platform + // message. + std::optional view_ref_clone; + if (fuv_ViewRef temp_ref; !is_flatland_) { + fidl::Clone(view_ref_pair.view_ref, &temp_ref); + view_ref_clone = std::move(temp_ref); + } + + EXPECT_FALSE(pointer_injector_delegate_->HandlePlatformMessage( + PlatformMessageBuilder() + .SetViewId(view_id_2) + .SetViewRefMaybe(std::move(view_ref_clone)) + .Build(), + response)); + } + } + + RunLoopUntilIdle(); + + // The views do not receive any pointer events till they get created. + EXPECT_TRUE(registry_->num_events_received() == 0); +} + +// PointerInjectorDelegate should generate a correct |f.u.p.Config| from a +// platform message. +TEST_P(PointerInjectorDelegateTest, ValidRegistrationConfigTest) { + const uint64_t view_id = 1; + + const float x = 2.f, y = 2.f, width = 5.f, height = 5.f; + const int phase = 2, pointer_id = 5, trace_flow_id = 5, timestamp = 10; + + auto response = FakePlatformMessageResponse::Create(); + + auto view_ref_pair = scenic::ViewRefPair::New(); + std::optional view_ref_clone; + + // Create the view. + if (!is_flatland_) { + CreateView(view_id); + fuv_ViewRef temp_ref; + fidl::Clone(view_ref_pair.view_ref, &temp_ref); + view_ref_clone = std::move(temp_ref); + } else { + fuv_ViewRef view_ref; + fidl::Clone(view_ref_pair.view_ref, &view_ref); + CreateView(view_id, std::move(view_ref)); + } + + // Inject a platform message. + EXPECT_TRUE(pointer_injector_delegate_->HandlePlatformMessage( + PlatformMessageBuilder() + .SetViewId(view_id) + .SetPointerX(x) + .SetPointerY(y) + .SetPhase(phase) + .SetPointerId(pointer_id) + .SetTraceFlowId(trace_flow_id) + .SetViewRefMaybe(std::move(view_ref_clone)) + .SetLogicalWidth(width) + .SetLogicalHeight(height) + .SetTimestamp(timestamp) + .Build(), + response)); + + response->ExpectCompleted("[0]"); + + // The mock Pointerinjector registry server receives a pointer event from + // |f.u.p.Device.Inject| call for the view. + RunLoopUntil([this] { return registry_->num_events_received() == 1; }); + + // The mock Pointerinjector registry server receives a + // |f.u.p.Registry.Register| call for the view. + ASSERT_TRUE(registry_->num_register_calls() == 1); + + const auto& config = registry_->config(); + + ASSERT_TRUE(config.has_device_id()); + EXPECT_EQ(config.device_id(), 1u); + + ASSERT_TRUE(config.has_device_type()); + EXPECT_EQ(config.device_type(), fup_DeviceType::TOUCH); + + ASSERT_TRUE(config.has_dispatch_policy()); + EXPECT_EQ(config.dispatch_policy(), fup_DispatchPolicy::EXCLUSIVE_TARGET); + + ASSERT_TRUE(config.has_context()); + ASSERT_TRUE(config.context().is_view()); + EXPECT_EQ(ExtractKoid(config.context().view()), ExtractKoid(host_view_ref_)); + + ASSERT_TRUE(config.has_target()); + ASSERT_TRUE(config.target().is_view()); + EXPECT_EQ(ExtractKoid(config.target().view()), + ExtractKoid(view_ref_pair.view_ref)); + + ASSERT_TRUE(config.has_viewport()); + ASSERT_TRUE(config.viewport().has_viewport_to_context_transform()); + EXPECT_EQ(config.viewport().viewport_to_context_transform(), kIdentityMatrix); + + std::array, 2> extents{{{0, 0}, {width, height}}}; + ASSERT_TRUE(config.viewport().has_extents()); + EXPECT_EQ(config.viewport().extents(), extents); +} + +// PointerInjectorDelegate generates a correct f.u.p.Event from the platform +// message. +TEST_P(PointerInjectorDelegateTest, ValidPointerEventTest) { + const uint64_t view_id = 1; + + const float x = 2.f, y = 2.f, width = 5.f, height = 5.f; + const int phase = 2, pointer_id = 5, trace_flow_id = 5, timestamp = 10; + + auto response = FakePlatformMessageResponse::Create(); + + auto view_ref_pair = scenic::ViewRefPair::New(); + + std::optional view_ref_clone; + + // Create the view. + if (!is_flatland_) { + CreateView(view_id); + fuv_ViewRef temp_ref; + fidl::Clone(view_ref_pair.view_ref, &temp_ref); + view_ref_clone = std::move(temp_ref); + } else { + fuv_ViewRef view_ref; + fidl::Clone(view_ref_pair.view_ref, &view_ref); + CreateView(view_id, std::move(view_ref)); + } + + // Inject a platform message. + EXPECT_TRUE(pointer_injector_delegate_->HandlePlatformMessage( + PlatformMessageBuilder() + .SetViewId(view_id) + .SetPointerX(x) + .SetPointerY(y) + .SetPhase(phase) + .SetPointerId(pointer_id) + .SetTraceFlowId(trace_flow_id) + .SetViewRefMaybe(std::move(view_ref_clone)) + .SetLogicalWidth(width) + .SetLogicalHeight(height) + .SetTimestamp(timestamp) + .Build(), + response)); + + response->ExpectCompleted("[0]"); + + // The mock Pointerinjector registry server receives a pointer event from + // |f.u.p.Device.Inject| call for the view. + RunLoopUntil([this] { return registry_->num_events_received() == 1; }); + + // The mock Pointerinjector registry server receives a + // |f.u.p.Registry.Register| call for the view. + ASSERT_TRUE(registry_->num_register_calls() == 1); + + const auto& events = registry_->events(); + + ASSERT_EQ(events.size(), 1u); + + const auto& event = events[0]; + + ASSERT_TRUE(event.has_timestamp()); + EXPECT_EQ(event.timestamp(), timestamp); + + ASSERT_TRUE(event.has_trace_flow_id()); + EXPECT_EQ(event.trace_flow_id(), static_cast(trace_flow_id)); + + ASSERT_TRUE(event.has_data()); + ASSERT_TRUE(event.data().is_pointer_sample()); + + const auto& pointer_sample = event.data().pointer_sample(); + + ASSERT_TRUE(pointer_sample.has_pointer_id()); + ASSERT_TRUE(pointer_sample.has_phase()); + ASSERT_TRUE(pointer_sample.has_position_in_viewport()); + EXPECT_EQ(pointer_sample.pointer_id(), static_cast(pointer_id)); + EXPECT_EQ(pointer_sample.phase(), static_cast(phase)); + EXPECT_THAT(pointer_sample.position_in_viewport(), + ::testing::ElementsAre(x, y)); +} + +TEST_P(PointerInjectorDelegateTest, DestroyedViewsDontGetPointerEvents) { + const uint64_t view_id = 1, num_events = 150; + + auto view_ref_pair = scenic::ViewRefPair::New(); + + // Create the view. + CreateView(view_id); + + // Inject |num_events| platform messages. + for (size_t i = 0; i < num_events; i++) { + auto response = FakePlatformMessageResponse::Create(); + + // Flatland views do not rely on ViewRef to be passed in the platform + // message. + std::optional view_ref_clone; + if (fuv_ViewRef temp_ref; !is_flatland_) { + fidl::Clone(view_ref_pair.view_ref, &temp_ref); + view_ref_clone = std::move(temp_ref); + } + + EXPECT_TRUE(pointer_injector_delegate_->HandlePlatformMessage( + PlatformMessageBuilder() + .SetViewId(view_id) + .SetViewRefMaybe(std::move(view_ref_clone)) + .Build(), + response)); + + response->ExpectCompleted("[0]"); + } + + // Destroy the view. + pointer_injector_delegate_->OnDestroyView(view_id); + + // The view does not receive |num_events| pointer events as it gets destroyed + // before all the pointer events could be dispatched. + const zx::duration timeout = zx::sec(1), step = zx::msec(10); + EXPECT_FALSE(RunLoopWithTimeoutOrUntil( + [this] { return registry_->num_events_received() == num_events; }, + timeout, step)); + + EXPECT_LT(registry_->num_events_received(), num_events); +} + +TEST_P(PointerInjectorDelegateTest, ViewsGetPointerEventsInFIFO) { + const uint64_t view_id = 1, num_events = 150; + + auto view_ref_pair = scenic::ViewRefPair::New(); + + // Create the view. + CreateView(view_id); + + // Inject |num_events| platform messages. + for (size_t i = 0; i < num_events; i++) { + auto response = FakePlatformMessageResponse::Create(); + + // Flatland views do not rely on ViewRef to be passed in the platform + // message. + std::optional view_ref_clone; + if (fuv_ViewRef temp_ref; !is_flatland_) { + fidl::Clone(view_ref_pair.view_ref, &temp_ref); + view_ref_clone = std::move(temp_ref); + } + + EXPECT_TRUE(pointer_injector_delegate_->HandlePlatformMessage( + PlatformMessageBuilder() + .SetViewId(view_id) + .SetPointerId(static_cast(i)) + .SetViewRefMaybe(std::move(view_ref_clone)) + .Build(), + response)); + + response->ExpectCompleted("[0]"); + } + + // The mock Pointerinjector registry server receives |num_events| pointer + // events from |f.u.p.Device.Inject| call for the view. + RunLoopUntil( + [this] { return registry_->num_events_received() == num_events; }); + + // The mock Pointerinjector registry server receives a + // |f.u.p.Registry.Register| call for the view. + ASSERT_TRUE(registry_->num_register_calls() == 1); + + auto& events = registry_->events(); + + // The view should receive the pointer events in a FIFO order. As we injected + // platform messages with an increasing |pointer_id|, the received pointer + // events should also have the |pointer_id| in an increasing order. + for (size_t i = 0; i < events.size() - 1; i++) { + ASSERT_TRUE(events[i].has_data()); + ASSERT_TRUE(events[i + 1].has_data()); + ASSERT_TRUE(events[i].data().is_pointer_sample()); + ASSERT_TRUE(events[i + 1].data().is_pointer_sample()); + + const auto& pointer_sample_1 = events[i].data().pointer_sample(); + const auto& pointer_sample_2 = events[i + 1].data().pointer_sample(); + + ASSERT_TRUE(pointer_sample_1.has_pointer_id()); + ASSERT_TRUE(pointer_sample_2.has_pointer_id()); + + EXPECT_TRUE(pointer_sample_1.pointer_id() < pointer_sample_2.pointer_id()); + } +} + +INSTANTIATE_TEST_SUITE_P(PointerInjectorDelegateParameterizedTest, + PointerInjectorDelegateTest, + ::testing::Bool()); + +} // namespace flutter_runner::testing diff --git a/shell/platform/fuchsia/flutter/tests/fakes/BUILD.gn b/shell/platform/fuchsia/flutter/tests/fakes/BUILD.gn index 259ded8020fe2..590df78802f17 100644 --- a/shell/platform/fuchsia/flutter/tests/fakes/BUILD.gn +++ b/shell/platform/fuchsia/flutter/tests/fakes/BUILD.gn @@ -9,6 +9,7 @@ group("fakes") { public_deps = [ ":focus", + ":pointer", "scenic", ] } @@ -30,3 +31,16 @@ source_set("focus") { "//third_party/rapidjson", ] } + +source_set("pointer") { + testonly = true + + sources = [ "mock_injector_registry.h" ] + + deps = [ + "//build/fuchsia/pkg:sys_cpp_testing", + "//flutter/lib/ui", + "//flutter/testing", + "//third_party/rapidjson", + ] +} diff --git a/shell/platform/fuchsia/flutter/tests/fakes/mock_injector_registry.h b/shell/platform/fuchsia/flutter/tests/fakes/mock_injector_registry.h new file mode 100644 index 0000000000000..467cd9da43d12 --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/fakes/mock_injector_registry.h @@ -0,0 +1,91 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_MOCK_INJECTOR_REGISTRY_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_MOCK_INJECTOR_REGISTRY_H_ + +#include +#include + +#include + +namespace flutter_runner::testing { + +// A test stub to act as the protocol server. A test can control what is sent +// back by this server implementation, via the ScheduleCallback call. +class MockInjectorRegistry : public fuchsia::ui::pointerinjector::Registry, + public fuchsia::ui::pointerinjector::Device { + public: + explicit MockInjectorRegistry( + fidl::InterfaceRequest registry) + : registry_(this, std::move(registry)) {} + + // |fuchsia.ui.pointerinjector.Registry.Register|. + void Register( + fuchsia::ui::pointerinjector::Config config, + fidl::InterfaceRequest injector, + RegisterCallback callback) override { + num_register_calls_++; + const uint32_t id = next_id_++; + + auto [it, success] = bindings_.try_emplace(id, this, std::move(injector)); + + it->second.set_error_handler( + [this, id](zx_status_t status) { bindings_.erase(id); }); + + config_ = std::move(config); + + callback(); + } + + // |fuchsia.ui.pointerinjector.Device.Inject|. + void Inject(std::vector events, + InjectCallback callback) override { + num_events_received_ += events.size(); + + for (auto& event : events) { + events_.push_back(std::move(event)); + } + + callback(); + } + + // Returns the |fuchsia::ui::pointerinjector::Config| received in the last + // |Register(...)| call. + const fuchsia::ui::pointerinjector::Config& config() const { return config_; } + + // Returns all the |fuchsia::ui::pointerinjector::Event|s received from the + // |Inject(...)| calls. + const std::vector& events() const { + return events_; + } + + uint32_t num_register_calls() { return num_register_calls_; } + + size_t num_registered() { return bindings_.size(); } + + uint32_t num_events_received() const { return num_events_received_; } + + private: + uint32_t next_id_ = 0; + + uint32_t num_events_received_ = 0; + + uint32_t num_register_calls_ = 0; + + fuchsia::ui::pointerinjector::Config config_; + + std::vector events_; + + std::unordered_map> + bindings_; + + fidl::Binding registry_; + + FML_DISALLOW_COPY_AND_ASSIGN(MockInjectorRegistry); +}; +} // namespace flutter_runner::testing + +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_MOCK_INJECTOR_REGISTRY_H_ diff --git a/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc b/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc index 288ba0eb03c0f..0c88990e41e07 100644 --- a/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc +++ b/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc @@ -514,7 +514,7 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView) { external_view_embedder.CreateView( child_view_id, []() {}, [](fuchsia::ui::composition::ContentId, - fuchsia::ui::composition::ChildViewWatcherPtr) {}); + fuchsia::ui::composition::ChildViewWatcherHandle) {}); // Draw the scene. The scene graph shouldn't change yet. const SkISize frame_size_signed = SkISize::Make(512, 512); @@ -645,7 +645,7 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView_NoOverlay) { external_view_embedder.CreateView( child_view_id, []() {}, [](fuchsia::ui::composition::ContentId, - fuchsia::ui::composition::ChildViewWatcherPtr) {}); + fuchsia::ui::composition::ChildViewWatcherHandle) {}); // Draw the scene. The scene graph shouldn't change yet. const SkISize frame_size_signed = SkISize::Make(512, 512); @@ -758,7 +758,7 @@ TEST_F(FlatlandExternalViewEmbedderTest, external_view_embedder.CreateView( child_view_id, []() {}, [](fuchsia::ui::composition::ContentId, - fuchsia::ui::composition::ChildViewWatcherPtr) {}); + fuchsia::ui::composition::ChildViewWatcherHandle) {}); // Draw the scene without the view. The scene graph shouldn't change yet. const SkISize frame_size_signed = SkISize::Make(512, 512); diff --git a/shell/platform/fuchsia/flutter/tests/flatland_platform_view_unittest.cc b/shell/platform/fuchsia/flutter/tests/flatland_platform_view_unittest.cc index 07cd2ccf2826c..0e804e1f68890 100644 --- a/shell/platform/fuchsia/flutter/tests/flatland_platform_view_unittest.cc +++ b/shell/platform/fuchsia/flutter/tests/flatland_platform_view_unittest.cc @@ -375,6 +375,12 @@ class PlatformViewBuilder { return *this; } + PlatformViewBuilder& SetPointerInjectorRegistry( + fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry) { + pointerinjector_registry_ = std::move(pointerinjector_registry); + return *this; + } + PlatformViewBuilder& SetEnableWireframeCallback(OnEnableWireframe callback) { wireframe_enabled_callback_ = std::move(callback); return *this; @@ -422,6 +428,7 @@ class PlatformViewBuilder { std::move(keyboard_), std::move(touch_source_), std::move(mouse_source_), std::move(focuser_), std::move(view_ref_focused_), std::move(parent_viewport_watcher_), + std::move(pointerinjector_registry_), std::move(wireframe_enabled_callback_), std::move(on_create_view_callback_), std::move(on_update_view_callback_), @@ -447,6 +454,7 @@ class PlatformViewBuilder { fuchsia::ui::pointer::MouseSourceHandle mouse_source_; fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused_; fuchsia::ui::views::FocuserHandle focuser_; + fuchsia::ui::pointerinjector::RegistryHandle pointerinjector_registry_; fit::closure on_session_listener_error_callback_; OnEnableWireframe wireframe_enabled_callback_; fuchsia::ui::composition::ParentViewportWatcherHandle