Flutter Engine
platform_view.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "flow/embedded_views.h"
6 #define RAPIDJSON_HAS_STDSTRING 1
7 
8 #include "platform_view.h"
9 
10 #include <fuchsia/ui/gfx/cpp/fidl.h>
11 
12 #include <cstring>
13 #include <sstream>
14 
15 #include "flutter/fml/logging.h"
16 #include "flutter/lib/ui/window/pointer_data.h"
17 #include "flutter/lib/ui/window/window.h"
18 #include "third_party/rapidjson/include/rapidjson/document.h"
19 #include "third_party/rapidjson/include/rapidjson/stringbuffer.h"
20 #include "third_party/rapidjson/include/rapidjson/writer.h"
21 
22 #include "logging.h"
24 #include "vsync_waiter.h"
25 
26 #if defined(LEGACY_FUCHSIA_EMBEDDER)
27 #include "flutter/lib/ui/compositing/scene_host.h"
28 #endif
29 
30 namespace flutter_runner {
31 
32 static constexpr char kFlutterPlatformChannel[] = "flutter/platform";
33 static constexpr char kTextInputChannel[] = "flutter/textinput";
34 static constexpr char kKeyEventChannel[] = "flutter/keyevent";
35 static constexpr char kAccessibilityChannel[] = "flutter/accessibility";
36 static constexpr char kFlutterPlatformViewsChannel[] = "flutter/platform_views";
37 
38 // FL(77): Terminate engine if Fuchsia system FIDL connections have error.
39 template <class T>
40 void SetInterfaceErrorHandler(fidl::InterfacePtr<T>& interface,
41  std::string name) {
42  interface.set_error_handler([name](zx_status_t status) {
43  FML_LOG(ERROR) << "Interface error on: " << name;
44  });
45 }
46 template <class T>
47 void SetInterfaceErrorHandler(fidl::Binding<T>& binding, std::string name) {
48  binding.set_error_handler([name](zx_status_t status) {
49  FML_LOG(ERROR) << "Interface error on: " << name;
50  });
51 }
52 
55  std::string debug_label,
56  fuchsia::ui::views::ViewRef view_ref,
57  flutter::TaskRunners task_runners,
58  std::shared_ptr<sys::ServiceDirectory> runner_services,
59  fidl::InterfaceHandle<fuchsia::sys::ServiceProvider>
60  parent_environment_service_provider_handle,
61  fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener>
62  session_listener_request,
63  fidl::InterfaceHandle<fuchsia::ui::views::Focuser> focuser,
64  fit::closure session_listener_error_callback,
65  OnEnableWireframe wireframe_enabled_callback,
66  OnCreateView on_create_view_callback,
67  OnUpdateView on_update_view_callback,
68  OnDestroyView on_destroy_view_callback,
69  OnCreateSurface on_create_surface_callback,
70  std::shared_ptr<flutter::ExternalViewEmbedder> external_view_embedder,
71  fml::TimeDelta vsync_offset,
72  zx_handle_t vsync_event_handle)
73  : flutter::PlatformView(delegate, std::move(task_runners)),
74  debug_label_(std::move(debug_label)),
75  view_ref_(std::move(view_ref)),
76  focuser_(focuser.Bind()),
77  session_listener_binding_(this, std::move(session_listener_request)),
78  session_listener_error_callback_(
79  std::move(session_listener_error_callback)),
80  wireframe_enabled_callback_(std::move(wireframe_enabled_callback)),
81  on_create_view_callback_(std::move(on_create_view_callback)),
82  on_update_view_callback_(std::move(on_update_view_callback)),
83  on_destroy_view_callback_(std::move(on_destroy_view_callback)),
84  on_create_surface_callback_(std::move(on_create_surface_callback)),
85  external_view_embedder_(external_view_embedder),
86  ime_client_(this),
87  vsync_offset_(std::move(vsync_offset)),
88  vsync_event_handle_(vsync_event_handle) {
89  // Register all error handlers.
90  SetInterfaceErrorHandler(session_listener_binding_, "SessionListener");
91  SetInterfaceErrorHandler(ime_, "Input Method Editor");
92  SetInterfaceErrorHandler(text_sync_service_, "Text Sync Service");
93  SetInterfaceErrorHandler(parent_environment_service_provider_,
94  "Parent Environment Service Provider");
95  // Access the IME service.
96  parent_environment_service_provider_ =
97  parent_environment_service_provider_handle.Bind();
98 
99  parent_environment_service_provider_.get()->ConnectToService(
100  fuchsia::ui::input::ImeService::Name_,
101  text_sync_service_.NewRequest().TakeChannel());
102 
103  // Finally! Register the native platform message handlers.
104  RegisterPlatformMessageHandlers();
105 
106  fuchsia::ui::views::ViewRef accessibility_view_ref;
107  view_ref_.Clone(&accessibility_view_ref);
108  accessibility_bridge_ = std::make_unique<AccessibilityBridge>(
109  *this, runner_services, std::move(accessibility_view_ref));
110 }
111 
112 PlatformView::~PlatformView() = default;
113 
114 void PlatformView::RegisterPlatformMessageHandlers() {
115  platform_message_handlers_[kFlutterPlatformChannel] =
116  std::bind(&PlatformView::HandleFlutterPlatformChannelPlatformMessage,
117  this, std::placeholders::_1);
118  platform_message_handlers_[kTextInputChannel] =
119  std::bind(&PlatformView::HandleFlutterTextInputChannelPlatformMessage,
120  this, std::placeholders::_1);
121  platform_message_handlers_[kAccessibilityChannel] =
122  std::bind(&PlatformView::HandleAccessibilityChannelPlatformMessage, this,
123  std::placeholders::_1);
124  platform_message_handlers_[kFlutterPlatformViewsChannel] =
125  std::bind(&PlatformView::HandleFlutterPlatformViewsChannelPlatformMessage,
126  this, std::placeholders::_1);
127 }
128 
129 // |fuchsia::ui::input::InputMethodEditorClient|
130 void PlatformView::DidUpdateState(
131  fuchsia::ui::input::TextInputState state,
132  std::unique_ptr<fuchsia::ui::input::InputEvent> input_event) {
133  rapidjson::Document document;
134  auto& allocator = document.GetAllocator();
135  rapidjson::Value encoded_state(rapidjson::kObjectType);
136  encoded_state.AddMember("text", state.text, allocator);
137  encoded_state.AddMember("selectionBase", state.selection.base, allocator);
138  encoded_state.AddMember("selectionExtent", state.selection.extent, allocator);
139  switch (state.selection.affinity) {
140  case fuchsia::ui::input::TextAffinity::UPSTREAM:
141  encoded_state.AddMember("selectionAffinity",
142  rapidjson::Value("TextAffinity.upstream"),
143  allocator);
144  break;
145  case fuchsia::ui::input::TextAffinity::DOWNSTREAM:
146  encoded_state.AddMember("selectionAffinity",
147  rapidjson::Value("TextAffinity.downstream"),
148  allocator);
149  break;
150  }
151  encoded_state.AddMember("selectionIsDirectional", true, allocator);
152  encoded_state.AddMember("composingBase", state.composing.start, allocator);
153  encoded_state.AddMember("composingExtent", state.composing.end, allocator);
154 
155  rapidjson::Value args(rapidjson::kArrayType);
156  args.PushBack(current_text_input_client_, allocator);
157  args.PushBack(encoded_state, allocator);
158 
159  document.SetObject();
160  document.AddMember("method",
161  rapidjson::Value("TextInputClient.updateEditingState"),
162  allocator);
163  document.AddMember("args", args, allocator);
164 
165  rapidjson::StringBuffer buffer;
166  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
167  document.Accept(writer);
168 
169  const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
170  DispatchPlatformMessage(fml::MakeRefCounted<flutter::PlatformMessage>(
171  kTextInputChannel, // channel
172  std::vector<uint8_t>(data, data + buffer.GetSize()), // message
173  nullptr) // response
174  );
175  last_text_state_ =
176  std::make_unique<fuchsia::ui::input::TextInputState>(state);
177 
178  // Handle keyboard input events for HID keys only.
179  // TODO(SCN-1189): Are we done here?
180  if (input_event && input_event->keyboard().hid_usage != 0) {
181  OnHandleKeyboardEvent(input_event->keyboard());
182  }
183 }
184 
185 // |fuchsia::ui::input::InputMethodEditorClient|
186 void PlatformView::OnAction(fuchsia::ui::input::InputMethodAction action) {
187  rapidjson::Document document;
188  auto& allocator = document.GetAllocator();
189 
190  rapidjson::Value args(rapidjson::kArrayType);
191  args.PushBack(current_text_input_client_, allocator);
192 
193  // Done is currently the only text input action defined by Flutter.
194  args.PushBack("TextInputAction.done", allocator);
195 
196  document.SetObject();
197  document.AddMember(
198  "method", rapidjson::Value("TextInputClient.performAction"), allocator);
199  document.AddMember("args", args, allocator);
200 
201  rapidjson::StringBuffer buffer;
202  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
203  document.Accept(writer);
204 
205  const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
206  DispatchPlatformMessage(fml::MakeRefCounted<flutter::PlatformMessage>(
207  kTextInputChannel, // channel
208  std::vector<uint8_t>(data, data + buffer.GetSize()), // message
209  nullptr) // response
210  );
211 }
212 
213 void PlatformView::OnScenicError(std::string error) {
214  FML_LOG(ERROR) << "Session error: " << error;
215  session_listener_error_callback_();
216 }
217 
218 void PlatformView::OnScenicEvent(
219  std::vector<fuchsia::ui::scenic::Event> events) {
220  TRACE_EVENT0("flutter", "PlatformView::OnScenicEvent");
221 
222  bool metrics_changed = false;
223  for (const auto& event : events) {
224  switch (event.Which()) {
225  case fuchsia::ui::scenic::Event::Tag::kGfx:
226  switch (event.gfx().Which()) {
227  case fuchsia::ui::gfx::Event::Tag::kMetrics: {
228  const fuchsia::ui::gfx::Metrics& metrics =
229  event.gfx().metrics().metrics;
230  const float new_view_pixel_ratio = metrics.scale_x;
231  if (new_view_pixel_ratio <= 0.f) {
232  FML_DLOG(ERROR)
233  << "Got an invalid pixel ratio from Scenic; ignoring: "
234  << new_view_pixel_ratio;
235  break;
236  }
237 
238  // Avoid metrics update when possible -- it is computationally
239  // expensive.
240  if (view_pixel_ratio_.has_value() &&
241  *view_pixel_ratio_ == new_view_pixel_ratio) {
242  FML_DLOG(ERROR)
243  << "Got an identical pixel ratio from Scenic; ignoring: "
244  << new_view_pixel_ratio;
245  break;
246  }
247 
248  view_pixel_ratio_ = new_view_pixel_ratio;
249  metrics_changed = true;
250  break;
251  }
252  case fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged: {
253  const fuchsia::ui::gfx::BoundingBox& bounding_box =
254  event.gfx().view_properties_changed().properties.bounding_box;
255  const std::pair<float, float> new_view_size = {
256  std::max(bounding_box.max.x - bounding_box.min.x, 0.0f),
257  std::max(bounding_box.max.y - bounding_box.min.y, 0.0f)};
258  if (new_view_size.first <= 0.f || new_view_size.second <= 0.f) {
259  FML_DLOG(ERROR)
260  << "Got an invalid view size from Scenic; ignoring: "
261  << new_view_size.first << " " << new_view_size.second;
262  break;
263  }
264 
265  // Avoid metrics update when possible -- it is computationally
266  // expensive.
267  if (view_logical_size_.has_value() &&
268  *view_logical_size_ == new_view_size) {
269  FML_DLOG(ERROR)
270  << "Got an identical view size from Scenic; ignoring: "
271  << new_view_size.first << " " << new_view_size.second;
272  break;
273  }
274 
275  view_logical_size_ = new_view_size;
276  metrics_changed = true;
277  break;
278  }
279  case fuchsia::ui::gfx::Event::Tag::kViewConnected:
280  OnChildViewConnected(event.gfx().view_connected().view_holder_id);
281  break;
282  case fuchsia::ui::gfx::Event::Tag::kViewDisconnected:
283  OnChildViewDisconnected(
284  event.gfx().view_disconnected().view_holder_id);
285  break;
286  case fuchsia::ui::gfx::Event::Tag::kViewStateChanged:
287  OnChildViewStateChanged(
288  event.gfx().view_state_changed().view_holder_id,
289  event.gfx().view_state_changed().state.is_rendering);
290  break;
291  case fuchsia::ui::gfx::Event::Tag::Invalid:
292  FML_DCHECK(false) << "Flutter PlatformView::OnScenicEvent: Got "
293  "an invalid GFX event.";
294  break;
295  default:
296  // We don't care about some event types, so not handling them is OK.
297  break;
298  }
299  break;
300  case fuchsia::ui::scenic::Event::Tag::kInput:
301  switch (event.input().Which()) {
302  case fuchsia::ui::input::InputEvent::Tag::kFocus: {
303  OnHandleFocusEvent(event.input().focus());
304  break;
305  }
306  case fuchsia::ui::input::InputEvent::Tag::kPointer: {
307  OnHandlePointerEvent(event.input().pointer());
308  break;
309  }
310  case fuchsia::ui::input::InputEvent::Tag::kKeyboard: {
311  OnHandleKeyboardEvent(event.input().keyboard());
312  break;
313  }
314  case fuchsia::ui::input::InputEvent::Tag::Invalid: {
315  FML_DCHECK(false)
316  << "Flutter PlatformView::OnScenicEvent: Got an invalid INPUT "
317  "event.";
318  }
319  }
320  break;
321  default: {
322  break;
323  }
324  }
325  }
326 
327  if (view_pixel_ratio_.has_value() && view_logical_size_.has_value() &&
328  metrics_changed) {
329  const float pixel_ratio = *view_pixel_ratio_;
330  const std::pair<float, float> logical_size = *view_logical_size_;
332  pixel_ratio, // device_pixel_ratio
333  logical_size.first * pixel_ratio, // physical_width
334  logical_size.second * pixel_ratio, // physical_height
335  0.0f, // physical_padding_top
336  0.0f, // physical_padding_right
337  0.0f, // physical_padding_bottom
338  0.0f, // physical_padding_left
339  0.0f, // physical_view_inset_top
340  0.0f, // physical_view_inset_right
341  0.0f, // physical_view_inset_bottom
342  0.0f, // physical_view_inset_left
343  0.0f, // p_physical_system_gesture_inset_top
344  0.0f, // p_physical_system_gesture_inset_right
345  0.0f, // p_physical_system_gesture_inset_bottom
346  0.0f, // p_physical_system_gesture_inset_left
347  });
348  }
349 }
350 
351 void PlatformView::OnChildViewConnected(scenic::ResourceId view_holder_id) {
352 #if defined(LEGACY_FUCHSIA_EMBEDDER)
353  task_runners_.GetUITaskRunner()->PostTask([view_holder_id]() {
354  flutter::SceneHost::OnViewConnected(view_holder_id);
355  });
356 #endif // LEGACY_FUCHSIA_EMBEDDER
357  std::string call = "{\"method\":\"View.viewConnected\",\"args\":null}";
358 
360  fml::MakeRefCounted<flutter::PlatformMessage>(
361  "flutter/platform_views",
362  std::vector<uint8_t>(call.begin(), call.end()), nullptr);
363  DispatchPlatformMessage(message);
364 }
365 
366 void PlatformView::OnChildViewDisconnected(scenic::ResourceId view_holder_id) {
367 #if defined(LEGACY_FUCHSIA_EMBEDDER)
368  task_runners_.GetUITaskRunner()->PostTask([view_holder_id]() {
370  });
371 #endif // LEGACY_FUCHSIA_EMBEDDER
372  std::string call = "{\"method\":\"View.viewDisconnected\",\"args\":null}";
373 
375  fml::MakeRefCounted<flutter::PlatformMessage>(
376  "flutter/platform_views",
377  std::vector<uint8_t>(call.begin(), call.end()), nullptr);
378  DispatchPlatformMessage(message);
379 }
380 
381 void PlatformView::OnChildViewStateChanged(scenic::ResourceId view_holder_id,
382  bool state) {
383 #if defined(LEGACY_FUCHSIA_EMBEDDER)
384  task_runners_.GetUITaskRunner()->PostTask([view_holder_id, state]() {
385  flutter::SceneHost::OnViewStateChanged(view_holder_id, state);
386  });
387 #endif // LEGACY_FUCHSIA_EMBEDDER
388  std::ostringstream out;
389  std::string str_state = state ? "true" : "false";
390  out << "{\"method\":\"View.viewStateChanged\",\"args\":{\"state\":"
391  << str_state << "}}";
392  auto call = out.str();
393 
395  fml::MakeRefCounted<flutter::PlatformMessage>(
396  "flutter/platform_views",
397  std::vector<uint8_t>(call.begin(), call.end()), nullptr);
398  DispatchPlatformMessage(message);
399 }
400 
402  fuchsia::ui::input::PointerEventPhase phase) {
403  switch (phase) {
404  case fuchsia::ui::input::PointerEventPhase::ADD:
406  case fuchsia::ui::input::PointerEventPhase::HOVER:
408  case fuchsia::ui::input::PointerEventPhase::DOWN:
410  case fuchsia::ui::input::PointerEventPhase::MOVE:
412  case fuchsia::ui::input::PointerEventPhase::UP:
414  case fuchsia::ui::input::PointerEventPhase::REMOVE:
416  case fuchsia::ui::input::PointerEventPhase::CANCEL:
418  default:
420  }
421 }
422 
424  fuchsia::ui::input::PointerEventType type) {
425  switch (type) {
426  case fuchsia::ui::input::PointerEventType::TOUCH:
428  case fuchsia::ui::input::PointerEventType::MOUSE:
430  default:
432  }
433 }
434 
435 // TODO(SCN-1278): Remove this.
436 // Turns two floats (high bits, low bits) into a 64-bit uint.
437 static trace_flow_id_t PointerTraceHACK(float fa, float fb) {
438  uint32_t ia, ib;
439  memcpy(&ia, &fa, sizeof(uint32_t));
440  memcpy(&ib, &fb, sizeof(uint32_t));
441  return (((uint64_t)ia) << 32) | ib;
442 }
443 
444 bool PlatformView::OnHandlePointerEvent(
445  const fuchsia::ui::input::PointerEvent& pointer) {
446  TRACE_EVENT0("flutter", "PlatformView::OnHandlePointerEvent");
447 
448  // TODO(SCN-1278): Use proper trace_id for tracing flow.
449  trace_flow_id_t trace_id =
450  PointerTraceHACK(pointer.radius_major, pointer.radius_minor);
451  TRACE_FLOW_END("input", "dispatch_event_to_client", trace_id);
452 
453  const float pixel_ratio =
454  view_pixel_ratio_.has_value() ? *view_pixel_ratio_ : 0.f;
455 
456  flutter::PointerData pointer_data;
457  pointer_data.Clear();
458  pointer_data.time_stamp = pointer.event_time / 1000;
459  pointer_data.change = GetChangeFromPointerEventPhase(pointer.phase);
460  pointer_data.kind = GetKindFromPointerType(pointer.type);
461  pointer_data.device = pointer.pointer_id;
462  // Pointer events are in logical pixels, so scale to physical.
463  pointer_data.physical_x = pointer.x * pixel_ratio;
464  pointer_data.physical_y = pointer.y * pixel_ratio;
465  // Buttons are single bit values starting with kMousePrimaryButton = 1.
466  pointer_data.buttons = static_cast<uint64_t>(pointer.buttons);
467 
468  switch (pointer_data.change) {
470  down_pointers_.insert(pointer_data.device);
471  break;
474  down_pointers_.erase(pointer_data.device);
475  break;
477  if (down_pointers_.count(pointer_data.device) == 0) {
479  }
480  break;
482  if (down_pointers_.count(pointer_data.device) != 0) {
483  FML_DLOG(ERROR) << "Received add event for down pointer.";
484  }
485  break;
487  if (down_pointers_.count(pointer_data.device) != 0) {
488  FML_DLOG(ERROR) << "Received remove event for down pointer.";
489  }
490  break;
492  if (down_pointers_.count(pointer_data.device) != 0) {
493  FML_DLOG(ERROR) << "Received hover event for down pointer.";
494  }
495  break;
496  }
497 
498  auto packet = std::make_unique<flutter::PointerDataPacket>(1);
499  packet->SetPointerData(0, pointer_data);
500  DispatchPointerDataPacket(std::move(packet));
501  return true;
502 }
503 
504 bool PlatformView::OnHandleKeyboardEvent(
505  const fuchsia::ui::input::KeyboardEvent& keyboard) {
506  const char* type = nullptr;
507  if (keyboard.phase == fuchsia::ui::input::KeyboardEventPhase::PRESSED) {
508  type = "keydown";
509  } else if (keyboard.phase == fuchsia::ui::input::KeyboardEventPhase::REPEAT) {
510  type = "keydown"; // TODO change this to keyrepeat
511  } else if (keyboard.phase ==
512  fuchsia::ui::input::KeyboardEventPhase::RELEASED) {
513  type = "keyup";
514  }
515 
516  if (type == nullptr) {
517  FML_DLOG(ERROR) << "Unknown key event phase.";
518  return false;
519  }
520 
521  rapidjson::Document document;
522  auto& allocator = document.GetAllocator();
523  document.SetObject();
524  document.AddMember("type", rapidjson::Value(type, strlen(type)), allocator);
525  document.AddMember("keymap", rapidjson::Value("fuchsia"), allocator);
526  document.AddMember("hidUsage", keyboard.hid_usage, allocator);
527  document.AddMember("codePoint", keyboard.code_point, allocator);
528  document.AddMember("modifiers", keyboard.modifiers, allocator);
529  rapidjson::StringBuffer buffer;
530  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
531  document.Accept(writer);
532 
533  const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
534  DispatchPlatformMessage(fml::MakeRefCounted<flutter::PlatformMessage>(
535  kKeyEventChannel, // channel
536  std::vector<uint8_t>(data, data + buffer.GetSize()), // data
537  nullptr) // response
538  );
539 
540  return true;
541 }
542 
543 bool PlatformView::OnHandleFocusEvent(
544  const fuchsia::ui::input::FocusEvent& focus) {
545  // Ensure last_text_state_ is set to make sure Flutter actually wants an IME.
546  if (focus.focused && last_text_state_ != nullptr) {
547  ActivateIme();
548  return true;
549  } else if (!focus.focused) {
550  DeactivateIme();
551  return true;
552  }
553  return false;
554 }
555 
556 void PlatformView::ActivateIme() {
557  DEBUG_CHECK(last_text_state_ != nullptr, LOG_TAG, "");
558 
559  text_sync_service_->GetInputMethodEditor(
560  fuchsia::ui::input::KeyboardType::TEXT, // keyboard type
561  fuchsia::ui::input::InputMethodAction::DONE, // input method action
562  *last_text_state_, // initial state
563  ime_client_.NewBinding(), // client
564  ime_.NewRequest() // editor
565  );
566 }
567 
568 void PlatformView::DeactivateIme() {
569  if (ime_) {
570  text_sync_service_->HideKeyboard();
571  ime_ = nullptr;
572  }
573  if (ime_client_.is_bound()) {
574  ime_client_.Unbind();
575  }
576 }
577 
578 // |flutter::PlatformView|
579 std::unique_ptr<flutter::VsyncWaiter> PlatformView::CreateVSyncWaiter() {
580  return std::make_unique<flutter_runner::VsyncWaiter>(
581  debug_label_, vsync_event_handle_, task_runners_, vsync_offset_);
582 }
583 
584 // |flutter::PlatformView|
585 std::unique_ptr<flutter::Surface> PlatformView::CreateRenderingSurface() {
586  return on_create_surface_callback_ ? on_create_surface_callback_() : nullptr;
587 }
588 
589 // |flutter::PlatformView|
590 void PlatformView::HandlePlatformMessage(
592  if (!message) {
593  return;
594  }
595  const std::string channel = message->channel();
596  auto found = platform_message_handlers_.find(channel);
597  if (found == platform_message_handlers_.end()) {
598  const bool already_errored = unregistered_channels_.count(channel);
599  if (!already_errored) {
600  FML_LOG(INFO)
601  << "Platform view received message on channel '" << message->channel()
602  << "' with no registered handler. And empty response will be "
603  "generated. Please implement the native message handler. This "
604  "message will appear only once per channel.";
605  unregistered_channels_.insert(channel);
606  }
608  return;
609  }
610  found->second(std::move(message));
611 }
612 
613 // |flutter::PlatformView|
614 // |flutter_runner::AccessibilityBridge::Delegate|
617  if (enabled) {
618  SetAccessibilityFeatures(static_cast<int32_t>(
620  } else {
622  }
623 }
624 
625 // |flutter::PlatformView|
626 // |flutter_runner::AccessibilityBridge::Delegate|
628  flutter::SemanticsAction action) {
630 }
631 
632 // |flutter::PlatformView|
633 void PlatformView::UpdateSemantics(
636  const float pixel_ratio =
637  view_pixel_ratio_.has_value() ? *view_pixel_ratio_ : 0.f;
638 
639  accessibility_bridge_->AddSemanticsNodeUpdate(update, pixel_ratio);
640 }
641 
642 // Channel handler for kAccessibilityChannel
643 void PlatformView::HandleAccessibilityChannelPlatformMessage(
646 }
647 
648 // Channel handler for kFlutterPlatformChannel
649 void PlatformView::HandleFlutterPlatformChannelPlatformMessage(
652  const auto& data = message->data();
653  rapidjson::Document document;
654  document.Parse(reinterpret_cast<const char*>(data.data()), data.size());
655  if (document.HasParseError() || !document.IsObject()) {
656  return;
657  }
658 
659  auto root = document.GetObject();
660  auto method = root.FindMember("method");
661  if (method == root.MemberEnd() || !method->value.IsString()) {
662  return;
663  }
664 
665  // Fuchsia does not handle any platform messages at this time.
666  message->response()->CompleteEmpty();
667 }
668 
669 // Channel handler for kTextInputChannel
670 void PlatformView::HandleFlutterTextInputChannelPlatformMessage(
672  FML_DCHECK(message->channel() == kTextInputChannel);
673  const auto& data = message->data();
674  rapidjson::Document document;
675  document.Parse(reinterpret_cast<const char*>(data.data()), data.size());
676  if (document.HasParseError() || !document.IsObject()) {
677  return;
678  }
679  auto root = document.GetObject();
680  auto method = root.FindMember("method");
681  if (method == root.MemberEnd() || !method->value.IsString()) {
682  return;
683  }
684 
685  if (method->value == "TextInput.show") {
686  if (ime_) {
687  text_sync_service_->ShowKeyboard();
688  }
689  } else if (method->value == "TextInput.hide") {
690  if (ime_) {
691  text_sync_service_->HideKeyboard();
692  }
693  } else if (method->value == "TextInput.setClient") {
694  current_text_input_client_ = 0;
695  DeactivateIme();
696  auto args = root.FindMember("args");
697  if (args == root.MemberEnd() || !args->value.IsArray() ||
698  args->value.Size() != 2)
699  return;
700  const auto& configuration = args->value[1];
701  if (!configuration.IsObject()) {
702  return;
703  }
704  // TODO(abarth): Read the keyboard type from the configuration.
705  current_text_input_client_ = args->value[0].GetInt();
706 
707  auto initial_text_input_state = fuchsia::ui::input::TextInputState{};
708  initial_text_input_state.text = "";
709  last_text_state_ = std::make_unique<fuchsia::ui::input::TextInputState>(
710  initial_text_input_state);
711  ActivateIme();
712  } else if (method->value == "TextInput.setEditingState") {
713  if (ime_) {
714  auto args_it = root.FindMember("args");
715  if (args_it == root.MemberEnd() || !args_it->value.IsObject()) {
716  return;
717  }
718  const auto& args = args_it->value;
719  fuchsia::ui::input::TextInputState state;
720  state.text = "";
721  // TODO(abarth): Deserialize state.
722  auto text = args.FindMember("text");
723  if (text != args.MemberEnd() && text->value.IsString())
724  state.text = text->value.GetString();
725  auto selection_base = args.FindMember("selectionBase");
726  if (selection_base != args.MemberEnd() && selection_base->value.IsInt())
727  state.selection.base = selection_base->value.GetInt();
728  auto selection_extent = args.FindMember("selectionExtent");
729  if (selection_extent != args.MemberEnd() &&
730  selection_extent->value.IsInt())
731  state.selection.extent = selection_extent->value.GetInt();
732  auto selection_affinity = args.FindMember("selectionAffinity");
733  if (selection_affinity != args.MemberEnd() &&
734  selection_affinity->value.IsString() &&
735  selection_affinity->value == "TextAffinity.upstream")
736  state.selection.affinity = fuchsia::ui::input::TextAffinity::UPSTREAM;
737  else
738  state.selection.affinity = fuchsia::ui::input::TextAffinity::DOWNSTREAM;
739  // We ignore selectionIsDirectional because that concept doesn't exist on
740  // Fuchsia.
741  auto composing_base = args.FindMember("composingBase");
742  if (composing_base != args.MemberEnd() && composing_base->value.IsInt())
743  state.composing.start = composing_base->value.GetInt();
744  auto composing_extent = args.FindMember("composingExtent");
745  if (composing_extent != args.MemberEnd() &&
746  composing_extent->value.IsInt())
747  state.composing.end = composing_extent->value.GetInt();
748  ime_->SetState(std::move(state));
749  }
750  } else if (method->value == "TextInput.clearClient") {
751  current_text_input_client_ = 0;
752  last_text_state_ = nullptr;
753  DeactivateIme();
754  } else {
755  FML_DLOG(ERROR) << "Unknown " << message->channel() << " method "
756  << method->value.GetString();
757  }
758 }
759 
760 void PlatformView::HandleFlutterPlatformViewsChannelPlatformMessage(
763  const auto& data = message->data();
764  rapidjson::Document document;
765  document.Parse(reinterpret_cast<const char*>(data.data()), data.size());
766  if (document.HasParseError() || !document.IsObject()) {
767  FML_LOG(ERROR) << "Could not parse document";
768  return;
769  }
770  auto root = document.GetObject();
771  auto method = root.FindMember("method");
772  if (method == root.MemberEnd() || !method->value.IsString()) {
773  return;
774  }
775 
776  if (method->value == "View.enableWireframe") {
777  auto args_it = root.FindMember("args");
778  if (args_it == root.MemberEnd() || !args_it->value.IsObject()) {
779  FML_LOG(ERROR) << "No arguments found.";
780  return;
781  }
782  const auto& args = args_it->value;
783 
784  auto enable = args.FindMember("enable");
785  if (!enable->value.IsBool()) {
786  FML_LOG(ERROR) << "Argument 'enable' is not a bool";
787  return;
788  }
789 
790  wireframe_enabled_callback_(enable->value.GetBool());
791  } else if (method->value == "View.create") {
792  auto args_it = root.FindMember("args");
793  if (args_it == root.MemberEnd() || !args_it->value.IsObject()) {
794  FML_LOG(ERROR) << "No arguments found.";
795  return;
796  }
797  const auto& args = args_it->value;
798 
799  auto view_id = args.FindMember("viewId");
800  if (!view_id->value.IsUint64()) {
801  FML_LOG(ERROR) << "Argument 'viewId' is not a int64";
802  return;
803  }
804 
805  auto hit_testable = args.FindMember("hitTestable");
806  if (!hit_testable->value.IsBool()) {
807  FML_LOG(ERROR) << "Argument 'hitTestable' is not a bool";
808  return;
809  }
810 
811  auto focusable = args.FindMember("focusable");
812  if (!focusable->value.IsBool()) {
813  FML_LOG(ERROR) << "Argument 'focusable' is not a bool";
814  return;
815  }
816 
817  on_create_view_callback_(view_id->value.GetUint64(),
818  hit_testable->value.GetBool(),
819  focusable->value.GetBool());
820  // The client is waiting for view creation. Send an empty response back
821  // to signal the view was created.
822  if (message->response().get()) {
823  message->response()->Complete(
824  std::make_unique<fml::NonOwnedMapping>((const uint8_t*)"[0]", 3u));
825  }
826  } else if (method->value == "View.update") {
827  auto args_it = root.FindMember("args");
828  if (args_it == root.MemberEnd() || !args_it->value.IsObject()) {
829  FML_LOG(ERROR) << "No arguments found.";
830  return;
831  }
832  const auto& args = args_it->value;
833 
834  auto view_id = args.FindMember("viewId");
835  if (!view_id->value.IsUint64()) {
836  FML_LOG(ERROR) << "Argument 'viewId' is not a int64";
837  return;
838  }
839 
840  auto hit_testable = args.FindMember("hitTestable");
841  if (!hit_testable->value.IsBool()) {
842  FML_LOG(ERROR) << "Argument 'hitTestable' is not a bool";
843  return;
844  }
845 
846  auto focusable = args.FindMember("focusable");
847  if (!focusable->value.IsBool()) {
848  FML_LOG(ERROR) << "Argument 'focusable' is not a bool";
849  return;
850  }
851 
852  on_update_view_callback_(view_id->value.GetUint64(),
853  hit_testable->value.GetBool(),
854  focusable->value.GetBool());
855  } else if (method->value == "View.dispose") {
856  auto args_it = root.FindMember("args");
857  if (args_it == root.MemberEnd() || !args_it->value.IsObject()) {
858  FML_LOG(ERROR) << "No arguments found.";
859  return;
860  }
861  const auto& args = args_it->value;
862 
863  auto view_id = args.FindMember("viewId");
864  if (!view_id->value.IsUint64()) {
865  FML_LOG(ERROR) << "Argument 'viewId' is not a int64";
866  return;
867  }
868  on_destroy_view_callback_(view_id->value.GetUint64());
869  } else if (method->value == "View.requestFocus") {
870  auto args_it = root.FindMember("args");
871  if (args_it == root.MemberEnd() || !args_it->value.IsObject()) {
872  FML_LOG(ERROR) << "No arguments found.";
873  return;
874  }
875  const auto& args = args_it->value;
876 
877  auto view_ref = args.FindMember("viewRef");
878  if (!view_ref->value.IsUint64()) {
879  FML_LOG(ERROR) << "Argument 'viewRef' is not a int64";
880  return;
881  }
882 
883  zx_handle_t handle = view_ref->value.GetUint64();
884  zx_handle_t out_handle;
885  zx_status_t status =
886  zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, &out_handle);
887  if (status != ZX_OK) {
888  FML_LOG(ERROR) << "Argument 'viewRef' is not valid";
889  return;
890  }
891  auto ref = fuchsia::ui::views::ViewRef({
892  .reference = zx::eventpair(out_handle),
893  });
894  focuser_->RequestFocus(
895  std::move(ref),
896  [view_ref = view_ref->value.GetUint64(),
897  message](fuchsia::ui::views::Focuser_RequestFocus_Result result) {
898  if (message->response().get()) {
899  int result_code =
900  result.is_err()
901  ? static_cast<
902  std::underlying_type_t<fuchsia::ui::views::Error>>(
903  result.err())
904  : 0;
905 
906  std::ostringstream out;
907  out << "[" << result_code << "]";
908  message->response()->Complete(
909  std::make_unique<fml::NonOwnedMapping>(
910  (const uint8_t*)out.str().c_str(), out.str().length()));
911  }
912  });
913  } else {
914  FML_DLOG(ERROR) << "Unknown " << message->channel() << " method "
915  << method->value.GetString();
916  }
917 }
918 
921  return std::make_unique<flutter::SmoothPointerDataDispatcher>(delegate);
922  };
923 }
924 
925 } // namespace flutter_runner
static constexpr char kKeyEventChannel[]
void SetInterfaceErrorHandler(fidl::InterfacePtr< T > &interface, std::string name)
G_BEGIN_DECLS FlValue * args
static constexpr char kFlutterPlatformChannel[]
#define DEBUG_CHECK(condition, tag, message)
Definition: inlines.h:19
#define TRACE_EVENT0(category_group, name)
Definition: trace_event.h:75
virtual void HandlePlatformMessage(fml::RefPtr< PlatformMessage > message)
Overridden by embedders to perform actions in response to platform messages sent from the framework t...
void DispatchPlatformMessage(fml::RefPtr< PlatformMessage > message)
Used by embedders to dispatch a platform message to a running root isolate hosted by the engine...
#define FML_DCHECK(condition)
Definition: logging.h:86
static flutter::PointerData::Change GetChangeFromPointerEventPhase(fuchsia::ui::input::PointerEventPhase phase)
static void OnViewDisconnected(scenic::ResourceId id)
Definition: scene_host.cc:146
fit::function< std::unique_ptr< flutter::Surface >()> OnCreateSurface
Definition: platform_view.h:31
FlMethodResponse GError ** error
static void OnViewConnected(scenic::ResourceId id)
Definition: scene_host.cc:138
const TaskRunners task_runners_
#define TRACE_FLOW_END(category, name, id)
Definition: trace_event.h:121
virtual void SetAccessibilityFeatures(int32_t flags)
Used by the embedder to specify the features to enable in the accessibility tree generated by the iso...
Definition: ref_ptr.h:252
The interface for Engine to implement.
virtual void PostTask(const fml::closure &task)
Definition: task_runner.cc:24
std::unordered_map< int32_t, SemanticsNode > SemanticsNodeUpdates
flutter::PointerDataDispatcherMaker GetDispatcherMaker() override
Returns a platform-specific PointerDataDispatcherMaker so the Engine can construct the PointerDataPac...
static flutter::PointerData::DeviceKind GetKindFromPointerType(fuchsia::ui::input::PointerEventType type)
#define FML_LOG(severity)
Definition: logging.h:65
std::function< std::unique_ptr< PointerDataDispatcher >(PointerDataDispatcher::Delegate &)> PointerDataDispatcherMaker
Signature for constructing PointerDataDispatcher.
static constexpr char kAccessibilityChannel[]
const std::vector< uint8_t > & data() const
static constexpr char kTextInputChannel[]
fit::function< void(int64_t)> OnDestroyView
Definition: platform_view.h:30
void DispatchPointerDataPacket(std::unique_ptr< PointerDataPacket > packet)
Dispatches pointer events from the embedder to the framework. Each pointer data packet may contain mu...
const std::string & channel() const
std::function< void()> closure
Definition: closure.h:14
virtual void SetSemanticsEnabled(bool enabled)
Used by embedder to notify the running isolate hosted by the engine on the UI thread that the accessi...
~PlatformView()
Destroys the platform view. The platform view is owned by the shell and will be destroyed by the same...
PlatformView(flutter::PlatformView::Delegate &delegate, std::string debug_label, fuchsia::ui::views::ViewRef view_ref, flutter::TaskRunners task_runners, std::shared_ptr< sys::ServiceDirectory > runner_services, fidl::InterfaceHandle< fuchsia::sys::ServiceProvider > parent_environment_service_provider, fidl::InterfaceRequest< fuchsia::ui::scenic::SessionListener > session_listener_request, fidl::InterfaceHandle< fuchsia::ui::views::Focuser > focuser, fit::closure on_session_listener_error_callback, OnEnableWireframe wireframe_enabled_callback, OnCreateView on_create_view_callback, OnUpdateView on_update_view_callback, OnDestroyView on_destroy_view_callback, OnCreateSurface on_create_surface_callback, std::shared_ptr< flutter::ExternalViewEmbedder > view_embedder, fml::TimeDelta vsync_offset, zx_handle_t vsync_event_handle)
FlutterPointerPhase phase
Definition: fl_view.cc:78
SemanticsAction action
fit::function< void(int64_t, bool, bool)> OnUpdateView
Definition: platform_view.h:29
void SetViewportMetrics(const ViewportMetrics &metrics)
Used by embedders to specify the updated viewport metrics. In response to this call, on the raster thread, the rasterizer may need to be reconfigured to the updated viewport dimensions. On the UI thread, the framework may need to start generating a new frame for the updated viewport metrics as well.
static trace_flow_id_t PointerTraceHACK(float fa, float fb)
void DispatchSemanticsAction(int32_t id, SemanticsAction action, std::vector< uint8_t > args)
Used by embedders to dispatch an accessibility action to a running isolate hosted by the engine...
GdkEventButton * event
Definition: fl_view.cc:62
fml::RefPtr< fml::TaskRunner > GetUITaskRunner() const
Definition: task_runners.cc:34
static constexpr char kFlutterPlatformViewsChannel[]
#define LOG_TAG
Definition: logging.h:11
const char * name
Definition: fuchsia.cc:50
fit::function< void(int64_t, bool, bool)> OnCreateView
Definition: platform_view.h:28
void SetSemanticsEnabled(bool enabled) override
Used by embedder to notify the running isolate hosted by the engine on the UI thread that the accessi...
static void OnViewStateChanged(scenic::ResourceId id, bool state)
Definition: scene_host.cc:154
#define FML_DLOG(severity)
Definition: logging.h:85
void DispatchSemanticsAction(int32_t node_id, flutter::SemanticsAction action) override
std::unordered_map< int32_t, CustomAccessibilityAction > CustomAccessibilityActionUpdates
Used to forward events from the platform view to interested subsystems. This forwarding is done by th...
Definition: platform_view.h:51
fit::function< void(bool)> OnEnableWireframe
Definition: platform_view.h:27
const fml::RefPtr< PlatformMessageResponse > & response() const