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