[fuchsia][scenic] Reland Move pointer injector to embedder. (flutter/engine#34692)

This PR moves the pointer injector library from fuchsia views library
present in fuchsia.git to the flutter embedder.

Test: ffx test run "fuchsia-pkg://fuchsia.com/flutter_runner_tests#meta/flutter_runner_tests.cm"
This commit is contained in:
Shivesh Ganju 2022-07-15 17:27:00 -04:00 committed by GitHub
parent 3dfca9f974
commit 271b59d7a6
24 changed files with 1395 additions and 14 deletions

View File

@ -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

View File

@ -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",

View File

@ -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<fuchsia::ui::pointerinjector::Registry>(
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),

View File

@ -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 =

View File

@ -35,7 +35,7 @@ namespace flutter_runner {
using ViewCallback = std::function<void()>;
using FlatlandViewCreatedCallback = std::function<void(
fuchsia::ui::composition::ContentId,
fuchsia::ui::composition::ChildViewWatcherPtr child_view_watcher)>;
fuchsia::ui::composition::ChildViewWatcherHandle child_view_watcher)>;
using FlatlandViewIdCallback =
std::function<void(fuchsia::ui::composition::ContentId)>;

View File

@ -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));

View File

@ -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,

View File

@ -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<fuchsia::ui::scenic::SessionListener>
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<flutter::PlatformMessage> message =
std::make_unique<flutter::PlatformMessage>(
"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));

View File

@ -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<fuchsia::ui::scenic::SessionListener>
session_listener_request,
fit::closure on_session_listener_error_callback,

View File

@ -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.
]
}

View File

@ -26,6 +26,7 @@
"fuchsia.ui.input.ImeService",
"fuchsia.ui.input3.Keyboard",
"fuchsia.ui.scenic.Scenic",
"fuchsia.ui.pointerinjector.Registry",
"fuchsia.vulkan.loader.Loader"
]
}

View File

@ -26,6 +26,7 @@
"fuchsia.ui.input.ImeService",
"fuchsia.ui.input3.Keyboard",
"fuchsia.ui.scenic.Scenic",
"fuchsia.ui.pointerinjector.Registry",
"fuchsia.vulkan.loader.Loader"
]
}

View File

@ -27,6 +27,7 @@
"fuchsia.ui.input.ImeService",
"fuchsia.ui.input3.Keyboard",
"fuchsia.ui.scenic.Scenic",
"fuchsia.ui.pointerinjector.Registry",
"fuchsia.vulkan.loader.Loader"
]
}

View File

@ -27,6 +27,7 @@
"fuchsia.ui.input.ImeService",
"fuchsia.ui.input3.Keyboard",
"fuchsia.ui.scenic.Scenic",
"fuchsia.ui.pointerinjector.Registry",
"fuchsia.vulkan.loader.Loader"
]
}

View File

@ -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<PointerInjectorDelegate>(
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;
}

View File

@ -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<FocusDelegate> focus_delegate_;
std::shared_ptr<PointerDelegate> pointer_delegate_;
std::unique_ptr<PointerInjectorDelegate> pointer_injector_delegate_;
fidl::Binding<fuchsia::ui::input::InputMethodEditorClient> ime_client_;
fuchsia::ui::input::InputMethodEditorPtr ime_;

View File

@ -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<fuchsia::ui::scenic::SessionListener> 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<fuchsia::ui::scenic::SessionListener>
session_listener_request_;
fit::closure on_session_listener_error_callback_;

View File

@ -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<float, 9> 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<flutter::PlatformMessageResponse> 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<fuv_ViewRef> 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<fup_EventPhase>(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<fuv_ViewRef> 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<flutter::PlatformMessageResponse> response,
std::string value) {
if (response) {
response->Complete(std::make_unique<fml::DataMapping>(
std::vector<uint8_t>(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<fup_Event> 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<std::array<float, 2>, 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

View File

@ -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 <fuchsia/ui/pointerinjector/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <queue>
#include <unordered_map>
#include <vector>
#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<fuchsia::ui::pointerinjector::RegistryPtr>(
registry.Bind())),
host_view_ref_(std::make_shared<fuchsia::ui::views::ViewRef>(
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<flutter::PlatformMessageResponse> 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<fuchsia::ui::views::ViewRef> 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<fuchsia::ui::views::ViewRef> view_ref;
// Logical size of the view's coordinate system.
std::array<float, 2> 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<fuchsia::ui::pointerinjector::RegistryPtr> registry,
std::shared_ptr<fuchsia::ui::views::ViewRef> host_view_ref,
std::optional<fuchsia::ui::views::ViewRef> 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<fuchsia::ui::pointerinjector::RegistryPtr> registry_;
// ViewRef for the main flutter app launching the embedded child views.
std::shared_ptr<fuchsia::ui::views::ViewRef> host_view_ref_;
// ViewRef for a flatland view. For GFX this value is set as std::nullopt.
// Set in |OnCreateView|.
std::optional<fuchsia::ui::views::ViewRef> 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<std::vector<fuchsia::ui::pointerinjector::Event>>
injector_events_;
fml::WeakPtrFactory<PointerInjectorEndpoint>
weak_factory_; // Must be the last member.
FML_DISALLOW_COPY_AND_ASSIGN(PointerInjectorEndpoint);
};
void Complete(fml::RefPtr<flutter::PlatformMessageResponse> 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<ViewId, PointerInjectorEndpoint> valid_views_;
std::shared_ptr<fuchsia::ui::pointerinjector::RegistryPtr> registry_;
// ViewRef for the main flutter app launching the embedded child views.
std::shared_ptr<fuchsia::ui::views::ViewRef> 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_

View File

@ -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 <fuchsia/ui/pointerinjector/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#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<float, 9> 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<fuv_ViewRef> 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<bool> {
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<bool>(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<bool()> 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<bool()> 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<MockInjectorRegistry>(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<PointerInjectorDelegate>(
std::move(registry), std::move(host_view_ref_clone), is_flatland_);
}
void CreateView(uint64_t view_id,
std::optional<fuv_ViewRef> 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<PointerInjectorDelegate> pointer_injector_delegate_;
std::unique_ptr<MockInjectorRegistry> 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<fuv_ViewRef> 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<fuv_ViewRef> 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<fuv_ViewRef> 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<fuv_ViewRef> 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<fuv_ViewRef> 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<std::array<float, 2>, 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<fuv_ViewRef> 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<uint64_t>(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<uint32_t>(pointer_id));
EXPECT_EQ(pointer_sample.phase(), static_cast<fup_EventPhase>(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<fuv_ViewRef> 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<fuv_ViewRef> 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<uint32_t>(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

View File

@ -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",
]
}

View File

@ -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 <fuchsia/ui/pointerinjector/cpp/fidl.h>
#include <lib/fidl/cpp/binding_set.h>
#include <unordered_map>
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<fuchsia::ui::pointerinjector::Registry> registry)
: registry_(this, std::move(registry)) {}
// |fuchsia.ui.pointerinjector.Registry.Register|.
void Register(
fuchsia::ui::pointerinjector::Config config,
fidl::InterfaceRequest<fuchsia::ui::pointerinjector::Device> 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<fuchsia::ui::pointerinjector::Event> 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<fuchsia::ui::pointerinjector::Event>& 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<fuchsia::ui::pointerinjector::Event> events_;
std::unordered_map<uint32_t,
fidl::Binding<fuchsia::ui::pointerinjector::Device>>
bindings_;
fidl::Binding<fuchsia::ui::pointerinjector::Registry> registry_;
FML_DISALLOW_COPY_AND_ASSIGN(MockInjectorRegistry);
};
} // namespace flutter_runner::testing
#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_MOCK_INJECTOR_REGISTRY_H_

View File

@ -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);

View File

@ -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