| // Copyright 2018 The Fuchsia 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 <sstream> |
| |
| #include "flutter/lib/ui/window/pointer_data.h" |
| #include "lib/component/cpp/connect.h" |
| #include "lib/ui/gfx/cpp/math.h" |
| #include "rapidjson/document.h" |
| #include "rapidjson/stringbuffer.h" |
| #include "rapidjson/writer.h" |
| #include "vsync_waiter.h" |
| |
| namespace flutter { |
| |
| constexpr char kFlutterPlatformChannel[] = "flutter/platform"; |
| constexpr char kTextInputChannel[] = "flutter/textinput"; |
| constexpr char kKeyEventChannel[] = "flutter/keyevent"; |
| constexpr char kAccessibilityChannel[] = "flutter/accessibility"; |
| |
| // FL(77): Terminate engine if Fuchsia system FIDL connections have error. |
| template <class T> |
| void SetInterfaceErrorHandler(fidl::InterfacePtr<T>& interface, |
| std::string name) { |
| interface.set_error_handler([name](zx_status_t status) { |
| FML_LOG(ERROR) << "Interface error on: " << name; |
| }); |
| } |
| template <class T> |
| void SetInterfaceErrorHandler(fidl::Binding<T>& binding, std::string name) { |
| binding.set_error_handler([name](zx_status_t status) { |
| FML_LOG(ERROR) << "Interface error on: " << name; |
| }); |
| } |
| |
| PlatformView::PlatformView( |
| PlatformView::Delegate& delegate, std::string debug_label, |
| blink::TaskRunners task_runners, |
| fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> |
| parent_environment_service_provider_handle, |
| fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener> |
| session_listener_request, |
| fit::closure session_listener_error_callback, |
| OnMetricsUpdate session_metrics_did_change_callback, |
| OnSizeChangeHint session_size_change_hint_callback, |
| #ifndef SCENIC_VIEWS2 |
| fidl::InterfaceHandle<fuchsia::ui::viewsv1::ViewManager> |
| view_manager_handle, |
| zx::eventpair view_token, zx::eventpair export_token, |
| #endif |
| fidl::InterfaceHandle<fuchsia::modular::ContextWriter> |
| accessibility_context_writer, |
| zx_handle_t vsync_event_handle) |
| : shell::PlatformView(delegate, std::move(task_runners)), |
| debug_label_(std::move(debug_label)), |
| session_listener_binding_(this, std::move(session_listener_request)), |
| session_listener_error_callback_( |
| std::move(session_listener_error_callback)), |
| metrics_changed_callback_(std::move(session_metrics_did_change_callback)), |
| size_change_hint_callback_(std::move(session_size_change_hint_callback)), |
| #ifndef SCENIC_VIEWS2 |
| view_manager_(view_manager_handle.Bind()), |
| view_listener_(this), |
| #endif |
| ime_client_(this), |
| context_writer_bridge_(std::move(accessibility_context_writer)), |
| semantics_bridge_(this, &metrics_), |
| surface_(std::make_unique<Surface>(debug_label_)), |
| vsync_event_handle_(vsync_event_handle) { |
| // Register all error handlers. |
| SetInterfaceErrorHandler(session_listener_binding_, "SessionListener"); |
| #ifndef SCENIC_VIEWS2 |
| SetInterfaceErrorHandler(view_manager_, "View Manager"); |
| SetInterfaceErrorHandler(view_, "View"); |
| #endif |
| SetInterfaceErrorHandler(ime_, "Input Method Editor"); |
| SetInterfaceErrorHandler(text_sync_service_, "Text Sync Service"); |
| SetInterfaceErrorHandler(clipboard_, "Clipboard"); |
| SetInterfaceErrorHandler(parent_environment_service_provider_, |
| "Parent Environment Service Provider"); |
| |
| #ifndef SCENIC_VIEWS2 |
| // Create the view. |
| view_manager_->CreateView2(view_.NewRequest(), // view |
| std::move(view_token), // view token |
| view_listener_.NewBinding(), // view listener |
| std::move(export_token), // export token |
| debug_label_ // diagnostic label |
| ); |
| |
| // Get the view container. This will need to be returned to the isolate |
| // configurator so that it can setup Mozart bindings later. |
| view_->GetContainer(view_container_.NewRequest()); |
| #endif |
| |
| // Access the clipboard. |
| parent_environment_service_provider_ = |
| parent_environment_service_provider_handle.Bind(); |
| component::ConnectToService(parent_environment_service_provider_.get(), |
| clipboard_.NewRequest()); |
| |
| component::ConnectToService(parent_environment_service_provider_.get(), |
| text_sync_service_.NewRequest()); |
| |
| // Finally! Register the native platform message handlers. |
| RegisterPlatformMessageHandlers(); |
| |
| // TODO(SCN-975): Re-enable. |
| // view_->GetToken(std::bind(&PlatformView::ConnectSemanticsProvider, this, |
| // std::placeholders::_1)); |
| } |
| |
| PlatformView::~PlatformView() = default; |
| |
| #ifndef SCENIC_VIEWS2 |
| void PlatformView::OfferServiceProvider( |
| fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> service_provider, |
| fidl::VectorPtr<fidl::StringPtr> services) { |
| view_->OfferServiceProvider(std::move(service_provider), std::move(services)); |
| } |
| #endif |
| |
| void PlatformView::RegisterPlatformMessageHandlers() { |
| platform_message_handlers_[kFlutterPlatformChannel] = |
| std::bind(&PlatformView::HandleFlutterPlatformChannelPlatformMessage, // |
| this, // |
| std::placeholders::_1); |
| platform_message_handlers_[kTextInputChannel] = |
| std::bind(&PlatformView::HandleFlutterTextInputChannelPlatformMessage, // |
| this, // |
| std::placeholders::_1); |
| platform_message_handlers_[kAccessibilityChannel] = |
| std::bind(&PlatformView::HandleAccessibilityChannelPlatformMessage, // |
| this, // |
| std::placeholders::_1); |
| } |
| |
| #ifndef SCENIC_VIEWS2 |
| fidl::InterfaceHandle<fuchsia::ui::viewsv1::ViewContainer> |
| PlatformView::TakeViewContainer() { |
| return std::move(view_container_); |
| } |
| #endif |
| |
| #ifndef SCENIC_VIEWS2 |
| // |fuchsia::ui::viewsv1::ViewListener| |
| void PlatformView::OnPropertiesChanged( |
| fuchsia::ui::viewsv1::ViewProperties properties, |
| OnPropertiesChangedCallback callback) { |
| if (properties.view_layout) { |
| UpdateViewportMetrics(*properties.view_layout); |
| } |
| callback(); |
| } |
| #else |
| void PlatformView::OnPropertiesChanged( |
| const fuchsia::ui::gfx::ViewProperties& view_properties) { |
| fuchsia::ui::gfx::BoundingBox layout_box = |
| scenic::ViewPropertiesLayoutBox(view_properties); |
| |
| fuchsia::ui::gfx::vec3 logical_size = |
| scenic::Max(layout_box.max - layout_box.min, 0.f); |
| |
| metrics_.size.width = logical_size.x; |
| metrics_.size.height = logical_size.y; |
| metrics_.padding.left = view_properties.inset_from_min.x; |
| metrics_.padding.top = view_properties.inset_from_min.y; |
| metrics_.padding.right = view_properties.inset_from_max.x; |
| metrics_.padding.bottom = view_properties.inset_from_max.y; |
| |
| FlushViewportMetrics(); |
| } |
| #endif |
| |
| // TODO(SCN-975): Re-enable. |
| // void PlatformView::ConnectSemanticsProvider( |
| // fuchsia::ui::viewsv1token::ViewToken token) { |
| // semantics_bridge_.SetupEnvironment( |
| // token.value, parent_environment_service_provider_.get()); |
| // } |
| |
| #ifndef SCENIC_VIEWS2 |
| void PlatformView::UpdateViewportMetrics( |
| const fuchsia::ui::viewsv1::ViewLayout& layout) { |
| metrics_.size.width = layout.size.width; |
| metrics_.size.height = layout.size.height; |
| metrics_.padding.left = layout.inset.left; |
| metrics_.padding.top = layout.inset.top; |
| metrics_.padding.right = layout.inset.right; |
| metrics_.padding.bottom = layout.inset.bottom; |
| |
| FlushViewportMetrics(); |
| } |
| #endif |
| |
| void PlatformView::UpdateViewportMetrics( |
| const fuchsia::ui::gfx::Metrics& metrics) { |
| metrics_.scale = metrics.scale_x; |
| |
| FlushViewportMetrics(); |
| } |
| |
| void PlatformView::FlushViewportMetrics() { |
| const auto scale = metrics_.scale; |
| |
| SetViewportMetrics({ |
| scale, // device_pixel_ratio |
| metrics_.size.width * scale, // physical_width |
| metrics_.size.height * scale, // physical_height |
| metrics_.padding.top * scale, // physical_padding_top |
| metrics_.padding.right * scale, // physical_padding_right |
| metrics_.padding.bottom * scale, // physical_padding_bottom |
| metrics_.padding.left * scale, // physical_padding_left |
| metrics_.view_inset.top * scale, // physical_view_inset_top |
| metrics_.view_inset.right * scale, // physical_view_inset_right |
| metrics_.view_inset.bottom * scale, // physical_view_inset_bottom |
| metrics_.view_inset.left * scale // physical_view_inset_left |
| }); |
| } |
| |
| // |fuchsia::ui::input::InputMethodEditorClient| |
| void PlatformView::DidUpdateState( |
| fuchsia::ui::input::TextInputState state, |
| std::unique_ptr<fuchsia::ui::input::InputEvent> input_event) { |
| rapidjson::Document document; |
| auto& allocator = document.GetAllocator(); |
| rapidjson::Value encoded_state(rapidjson::kObjectType); |
| encoded_state.AddMember("text", state.text.get(), allocator); |
| encoded_state.AddMember("selectionBase", state.selection.base, allocator); |
| encoded_state.AddMember("selectionExtent", state.selection.extent, allocator); |
| switch (state.selection.affinity) { |
| case fuchsia::ui::input::TextAffinity::UPSTREAM: |
| encoded_state.AddMember("selectionAffinity", |
| rapidjson::Value("TextAffinity.upstream"), |
| allocator); |
| break; |
| case fuchsia::ui::input::TextAffinity::DOWNSTREAM: |
| encoded_state.AddMember("selectionAffinity", |
| rapidjson::Value("TextAffinity.downstream"), |
| allocator); |
| break; |
| } |
| encoded_state.AddMember("selectionIsDirectional", true, allocator); |
| encoded_state.AddMember("composingBase", state.composing.start, allocator); |
| encoded_state.AddMember("composingExtent", state.composing.end, allocator); |
| |
| rapidjson::Value args(rapidjson::kArrayType); |
| args.PushBack(current_text_input_client_, allocator); |
| args.PushBack(encoded_state, allocator); |
| |
| document.SetObject(); |
| document.AddMember("method", |
| rapidjson::Value("TextInputClient.updateEditingState"), |
| allocator); |
| document.AddMember("args", args, allocator); |
| |
| rapidjson::StringBuffer buffer; |
| rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
| document.Accept(writer); |
| |
| const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString()); |
| DispatchPlatformMessage(fml::MakeRefCounted<blink::PlatformMessage>( |
| kTextInputChannel, // channel |
| std::vector<uint8_t>(data, data + buffer.GetSize()), // message |
| nullptr) // response |
| ); |
| last_text_state_ = |
| std::make_unique<fuchsia::ui::input::TextInputState>(state); |
| |
| // Handle keyboard input events for HID keys only. |
| // TODO(SCN-1189): Are we done here? |
| if (input_event && input_event->keyboard().hid_usage != 0) { |
| OnHandleKeyboardEvent(input_event->keyboard()); |
| } |
| } |
| |
| // |fuchsia::ui::input::InputMethodEditorClient| |
| void PlatformView::OnAction(fuchsia::ui::input::InputMethodAction action) { |
| rapidjson::Document document; |
| auto& allocator = document.GetAllocator(); |
| |
| rapidjson::Value args(rapidjson::kArrayType); |
| args.PushBack(current_text_input_client_, allocator); |
| |
| // Done is currently the only text input action defined by Flutter. |
| args.PushBack("TextInputAction.done", allocator); |
| |
| document.SetObject(); |
| document.AddMember( |
| "method", rapidjson::Value("TextInputClient.performAction"), allocator); |
| document.AddMember("args", args, allocator); |
| |
| rapidjson::StringBuffer buffer; |
| rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
| document.Accept(writer); |
| |
| const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString()); |
| DispatchPlatformMessage(fml::MakeRefCounted<blink::PlatformMessage>( |
| kTextInputChannel, // channel |
| std::vector<uint8_t>(data, data + buffer.GetSize()), // message |
| nullptr) // response |
| ); |
| } |
| |
| void PlatformView::OnScenicError(fidl::StringPtr error) { |
| FML_LOG(ERROR) << "Session error: " << error; |
| session_listener_error_callback_(); |
| } |
| |
| void PlatformView::OnScenicEvent( |
| fidl::VectorPtr<fuchsia::ui::scenic::Event> events) { |
| for (const auto& event : *events) { |
| switch (event.Which()) { |
| case fuchsia::ui::scenic::Event::Tag::kGfx: |
| switch (event.gfx().Which()) { |
| case fuchsia::ui::gfx::Event::Tag::kMetrics: { |
| if (event.gfx().metrics().metrics != scenic_metrics_) { |
| scenic_metrics_ = std::move(event.gfx().metrics().metrics); |
| metrics_changed_callback_(scenic_metrics_); |
| UpdateViewportMetrics(scenic_metrics_); |
| } |
| break; |
| } |
| case fuchsia::ui::gfx::Event::Tag::kSizeChangeHint: { |
| size_change_hint_callback_( |
| event.gfx().size_change_hint().width_change_factor, |
| event.gfx().size_change_hint().height_change_factor); |
| break; |
| } |
| case fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged: { |
| #ifdef SCENIC_VIEWS2 |
| OnPropertiesChanged( |
| std::move(event.gfx().view_properties_changed().properties)); |
| #endif |
| break; |
| } |
| case fuchsia::ui::gfx::Event::Tag::kImportUnbound: |
| FML_LOG(WARNING) |
| << "Flutter PlatformView::OnScenicEvent: Unhandled GFX event " |
| "(fuchsia.ui.gfx.ImportUnboundEvent)."; |
| break; |
| case fuchsia::ui::gfx::Event::Tag::kViewConnected: |
| FML_LOG(WARNING) |
| << "Flutter PlatformView::OnScenicEvent: Unhandled GFX event " |
| "(fuchsia.ui.gfx.ViewConnectedEvent)."; |
| break; |
| case fuchsia::ui::gfx::Event::Tag::kViewDisconnected: |
| FML_LOG(WARNING) |
| << "Flutter PlatformView::OnScenicEvent: Unhandled GFX event " |
| "(fuchsia.ui.gfx.ViewDisconnectedEvent)."; |
| break; |
| case fuchsia::ui::gfx::Event::Tag::kViewHolderDisconnected: |
| FML_LOG(WARNING) |
| << "Flutter PlatformView::OnScenicEvent: Unhandled GFX event " |
| "(fuchsia.ui.gfx.ViewHolderDisconnectedEvent)."; |
| break; |
| case fuchsia::ui::gfx::Event::Tag::kViewAttachedToScene: |
| FML_LOG(WARNING) |
| << "Flutter PlatformView::OnScenicEvent: Unhandled GFX event " |
| "(fuchsia.ui.gfx.ViewAttachedToScene)."; |
| break; |
| case fuchsia::ui::gfx::Event::Tag::kViewDetachedFromScene: |
| FML_LOG(WARNING) |
| << "Flutter PlatformView::OnScenicEvent: Unhandled GFX event " |
| "(fuchsia.ui.gfx.ViewDetachedFromScene)."; |
| break; |
| case fuchsia::ui::gfx::Event::Tag::kViewStateChanged: |
| FML_LOG(WARNING) |
| << "Flutter PlatformView::OnScenicEvent: Unhandled GFX event " |
| "(fuchsia.ui.gfx.ViewStateChanged)."; |
| break; |
| case fuchsia::ui::gfx::Event::Tag::Invalid: |
| FXL_DCHECK(false) |
| << "Flutter PlatformView::OnScenicEvent: Got an invalid GFX " |
| "event."; |
| break; |
| } |
| break; |
| case fuchsia::ui::scenic::Event::Tag::kInput: |
| switch (event.input().Which()) { |
| case fuchsia::ui::input::InputEvent::Tag::kFocus: { |
| OnHandleFocusEvent(event.input().focus()); |
| break; |
| } |
| case fuchsia::ui::input::InputEvent::Tag::kPointer: { |
| OnHandlePointerEvent(event.input().pointer()); |
| break; |
| } |
| case fuchsia::ui::input::InputEvent::Tag::kKeyboard: { |
| OnHandleKeyboardEvent(event.input().keyboard()); |
| break; |
| } |
| default: { |
| FML_LOG(WARNING) << "Flutter PlatformView::OnScenicEvent: " |
| "Unhandled input event."; |
| } |
| } |
| break; |
| default: { |
| FML_LOG(WARNING) |
| << "Flutter PlatformView::OnScenicEvent: Unhandled Scenic event."; |
| } |
| } |
| } |
| } |
| |
| static blink::PointerData::Change GetChangeFromPointerEventPhase( |
| fuchsia::ui::input::PointerEventPhase phase) { |
| switch (phase) { |
| case fuchsia::ui::input::PointerEventPhase::ADD: |
| return blink::PointerData::Change::kAdd; |
| case fuchsia::ui::input::PointerEventPhase::HOVER: |
| return blink::PointerData::Change::kHover; |
| case fuchsia::ui::input::PointerEventPhase::DOWN: |
| return blink::PointerData::Change::kDown; |
| case fuchsia::ui::input::PointerEventPhase::MOVE: |
| return blink::PointerData::Change::kMove; |
| case fuchsia::ui::input::PointerEventPhase::UP: |
| return blink::PointerData::Change::kUp; |
| case fuchsia::ui::input::PointerEventPhase::REMOVE: |
| return blink::PointerData::Change::kRemove; |
| case fuchsia::ui::input::PointerEventPhase::CANCEL: |
| return blink::PointerData::Change::kCancel; |
| default: |
| return blink::PointerData::Change::kCancel; |
| } |
| } |
| |
| static blink::PointerData::DeviceKind GetKindFromPointerType( |
| fuchsia::ui::input::PointerEventType type) { |
| switch (type) { |
| case fuchsia::ui::input::PointerEventType::TOUCH: |
| return blink::PointerData::DeviceKind::kTouch; |
| case fuchsia::ui::input::PointerEventType::MOUSE: |
| return blink::PointerData::DeviceKind::kMouse; |
| default: |
| return blink::PointerData::DeviceKind::kTouch; |
| } |
| } |
| |
| bool PlatformView::OnHandlePointerEvent( |
| const fuchsia::ui::input::PointerEvent& pointer) { |
| blink::PointerData pointer_data; |
| 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_data.physical_x = pointer.x * metrics_.scale; |
| pointer_data.physical_y = pointer.y * metrics_.scale; |
| // Buttons are single bit values starting with kMousePrimaryButton = 1. |
| pointer_data.buttons = static_cast<uint64_t>(pointer.buttons); |
| |
| switch (pointer_data.change) { |
| case blink::PointerData::Change::kDown: |
| down_pointers_.insert(pointer_data.device); |
| break; |
| case blink::PointerData::Change::kCancel: |
| case blink::PointerData::Change::kUp: |
| down_pointers_.erase(pointer_data.device); |
| break; |
| case blink::PointerData::Change::kMove: |
| if (down_pointers_.count(pointer_data.device) == 0) { |
| pointer_data.change = blink::PointerData::Change::kHover; |
| } |
| break; |
| case blink::PointerData::Change::kAdd: |
| if (down_pointers_.count(pointer_data.device) != 0) { |
| FML_DLOG(ERROR) << "Received add event for down pointer."; |
| } |
| break; |
| case blink::PointerData::Change::kRemove: |
| if (down_pointers_.count(pointer_data.device) != 0) { |
| FML_DLOG(ERROR) << "Received remove event for down pointer."; |
| } |
| break; |
| case blink::PointerData::Change::kHover: |
| if (down_pointers_.count(pointer_data.device) != 0) { |
| FML_DLOG(ERROR) << "Received hover event for down pointer."; |
| } |
| break; |
| } |
| |
| auto packet = std::make_unique<blink::PointerDataPacket>(1); |
| packet->SetPointerData(0, pointer_data); |
| DispatchPointerDataPacket(std::move(packet)); |
| return true; |
| } |
| |
| bool PlatformView::OnHandleKeyboardEvent( |
| const fuchsia::ui::input::KeyboardEvent& keyboard) { |
| const char* type = nullptr; |
| if (keyboard.phase == fuchsia::ui::input::KeyboardEventPhase::PRESSED) { |
| type = "keydown"; |
| } else if (keyboard.phase == fuchsia::ui::input::KeyboardEventPhase::REPEAT) { |
| type = "keydown"; // TODO change this to keyrepeat |
| } else if (keyboard.phase == |
| fuchsia::ui::input::KeyboardEventPhase::RELEASED) { |
| type = "keyup"; |
| } |
| |
| if (type == nullptr) { |
| FML_DLOG(ERROR) << "Unknown key event phase."; |
| return false; |
| } |
| |
| rapidjson::Document document; |
| auto& allocator = document.GetAllocator(); |
| document.SetObject(); |
| document.AddMember("type", rapidjson::Value(type, strlen(type)), allocator); |
| document.AddMember("keymap", rapidjson::Value("fuchsia"), allocator); |
| document.AddMember("hidUsage", keyboard.hid_usage, allocator); |
| document.AddMember("codePoint", keyboard.code_point, allocator); |
| document.AddMember("modifiers", keyboard.modifiers, allocator); |
| rapidjson::StringBuffer buffer; |
| rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
| document.Accept(writer); |
| |
| const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString()); |
| DispatchPlatformMessage(fml::MakeRefCounted<blink::PlatformMessage>( |
| kKeyEventChannel, // channel |
| std::vector<uint8_t>(data, data + buffer.GetSize()), // data |
| nullptr) // response |
| ); |
| |
| return true; |
| } |
| |
| bool PlatformView::OnHandleFocusEvent( |
| const fuchsia::ui::input::FocusEvent& focus) { |
| // Ensure last_text_state_ is set to make sure Flutter actually wants an IME. |
| if (focus.focused && last_text_state_ != nullptr) { |
| ActivateIme(); |
| return true; |
| } else if (!focus.focused) { |
| DeactivateIme(); |
| return true; |
| } |
| return false; |
| } |
| |
| void PlatformView::ActivateIme() { |
| FXL_DCHECK(last_text_state_); |
| |
| text_sync_service_->GetInputMethodEditor( |
| fuchsia::ui::input::KeyboardType::TEXT, // keyboard type |
| fuchsia::ui::input::InputMethodAction::DONE, // input method action |
| *last_text_state_, // initial state |
| ime_client_.NewBinding(), // client |
| ime_.NewRequest() // editor |
| ); |
| } |
| |
| void PlatformView::DeactivateIme() { |
| if (ime_) { |
| text_sync_service_->HideKeyboard(); |
| ime_ = nullptr; |
| } |
| if (ime_client_.is_bound()) { |
| ime_client_.Unbind(); |
| } |
| } |
| |
| // |shell::PlatformView| |
| std::unique_ptr<shell::VsyncWaiter> PlatformView::CreateVSyncWaiter() { |
| return std::make_unique<flutter::VsyncWaiter>( |
| debug_label_, vsync_event_handle_, task_runners_); |
| } |
| |
| // |shell::PlatformView| |
| std::unique_ptr<shell::Surface> PlatformView::CreateRenderingSurface() { |
| // This platform does not repeatly lose and gain a surface connection. So the |
| // surface is setup once during platform view setup and and returned to the |
| // shell on the initial (and only) |NotifyCreated| call. |
| return std::move(surface_); |
| } |
| |
| // |shell::PlatformView| |
| void PlatformView::HandlePlatformMessage( |
| fml::RefPtr<blink::PlatformMessage> message) { |
| if (!message) { |
| return; |
| } |
| auto found = platform_message_handlers_.find(message->channel()); |
| if (found == platform_message_handlers_.end()) { |
| FML_DLOG(ERROR) |
| << "Platform view received message on channel '" << message->channel() |
| << "' with no registed handler. And empty response will be generated. " |
| "Please implement the native message handler."; |
| PlatformView::HandlePlatformMessage(std::move(message)); |
| return; |
| } |
| found->second(std::move(message)); |
| } |
| |
| // |shell::PlatformView| |
| void PlatformView::UpdateSemantics( |
| blink::SemanticsNodeUpdates update, |
| blink::CustomAccessibilityActionUpdates actions) { |
| // TODO(MI4-1262): Figure out if the context_writer_bridge should be removed |
| // as it is unused. |
| // context_writer_bridge_.UpdateSemantics(update); |
| semantics_bridge_.UpdateSemantics(update); |
| } |
| |
| // Channel handler for kAccessibilityChannel |
| void PlatformView::HandleAccessibilityChannelPlatformMessage( |
| fml::RefPtr<blink::PlatformMessage> message) { |
| FML_DCHECK(message->channel() == kAccessibilityChannel); |
| } |
| |
| // Channel handler for kFlutterPlatformChannel |
| void PlatformView::HandleFlutterPlatformChannelPlatformMessage( |
| fml::RefPtr<blink::PlatformMessage> message) { |
| FML_DCHECK(message->channel() == kFlutterPlatformChannel); |
| const auto& data = message->data(); |
| rapidjson::Document document; |
| document.Parse(reinterpret_cast<const char*>(data.data()), data.size()); |
| if (document.HasParseError() || !document.IsObject()) { |
| return; |
| } |
| |
| auto root = document.GetObject(); |
| auto method = root.FindMember("method"); |
| if (method == root.MemberEnd() || !method->value.IsString()) { |
| return; |
| } |
| |
| fml::RefPtr<blink::PlatformMessageResponse> response = message->response(); |
| if (method->value == "Clipboard.setData") { |
| auto text = root["args"]["text"].GetString(); |
| clipboard_->Push(text); |
| response->CompleteEmpty(); |
| } else if (method->value == "Clipboard.getData") { |
| clipboard_->Peek([response](fidl::StringPtr text) { |
| rapidjson::StringBuffer json_buffer; |
| rapidjson::Writer<rapidjson::StringBuffer> writer(json_buffer); |
| writer.StartArray(); |
| writer.StartObject(); |
| writer.Key("text"); |
| writer.String(text.get()); |
| writer.EndObject(); |
| writer.EndArray(); |
| std::string result = json_buffer.GetString(); |
| response->Complete(std::make_unique<fml::DataMapping>( |
| std::vector<uint8_t>{result.begin(), result.end()})); |
| }); |
| } else { |
| response->CompleteEmpty(); |
| } |
| } |
| |
| // Channel handler for kTextInputChannel |
| void PlatformView::HandleFlutterTextInputChannelPlatformMessage( |
| fml::RefPtr<blink::PlatformMessage> message) { |
| FML_DCHECK(message->channel() == kTextInputChannel); |
| const auto& data = message->data(); |
| rapidjson::Document document; |
| document.Parse(reinterpret_cast<const char*>(data.data()), data.size()); |
| if (document.HasParseError() || !document.IsObject()) { |
| return; |
| } |
| auto root = document.GetObject(); |
| auto method = root.FindMember("method"); |
| if (method == root.MemberEnd() || !method->value.IsString()) { |
| return; |
| } |
| |
| if (method->value == "TextInput.show") { |
| if (ime_) { |
| text_sync_service_->ShowKeyboard(); |
| } |
| } else if (method->value == "TextInput.hide") { |
| if (ime_) { |
| text_sync_service_->HideKeyboard(); |
| } |
| } else if (method->value == "TextInput.setClient") { |
| current_text_input_client_ = 0; |
| DeactivateIme(); |
| auto args = root.FindMember("args"); |
| if (args == root.MemberEnd() || !args->value.IsArray() || |
| args->value.Size() != 2) |
| return; |
| const auto& configuration = args->value[1]; |
| if (!configuration.IsObject()) { |
| return; |
| } |
| // TODO(abarth): Read the keyboard type from the configuration. |
| current_text_input_client_ = args->value[0].GetInt(); |
| |
| auto initial_text_input_state = fuchsia::ui::input::TextInputState{}; |
| initial_text_input_state.text = ""; |
| last_text_state_ = std::make_unique<fuchsia::ui::input::TextInputState>( |
| initial_text_input_state); |
| ActivateIme(); |
| } else if (method->value == "TextInput.setEditingState") { |
| if (ime_) { |
| auto args_it = root.FindMember("args"); |
| if (args_it == root.MemberEnd() || !args_it->value.IsObject()) { |
| return; |
| } |
| const auto& args = args_it->value; |
| fuchsia::ui::input::TextInputState state; |
| state.text = ""; |
| // TODO(abarth): Deserialize state. |
| auto text = args.FindMember("text"); |
| if (text != args.MemberEnd() && text->value.IsString()) |
| state.text = text->value.GetString(); |
| auto selection_base = args.FindMember("selectionBase"); |
| if (selection_base != args.MemberEnd() && selection_base->value.IsInt()) |
| state.selection.base = selection_base->value.GetInt(); |
| auto selection_extent = args.FindMember("selectionExtent"); |
| if (selection_extent != args.MemberEnd() && |
| selection_extent->value.IsInt()) |
| state.selection.extent = selection_extent->value.GetInt(); |
| auto selection_affinity = args.FindMember("selectionAffinity"); |
| if (selection_affinity != args.MemberEnd() && |
| selection_affinity->value.IsString() && |
| selection_affinity->value == "TextAffinity.upstream") |
| state.selection.affinity = fuchsia::ui::input::TextAffinity::UPSTREAM; |
| else |
| state.selection.affinity = fuchsia::ui::input::TextAffinity::DOWNSTREAM; |
| // We ignore selectionIsDirectional because that concept doesn't exist on |
| // Fuchsia. |
| auto composing_base = args.FindMember("composingBase"); |
| if (composing_base != args.MemberEnd() && composing_base->value.IsInt()) |
| state.composing.start = composing_base->value.GetInt(); |
| auto composing_extent = args.FindMember("composingExtent"); |
| if (composing_extent != args.MemberEnd() && |
| composing_extent->value.IsInt()) |
| state.composing.end = composing_extent->value.GetInt(); |
| ime_->SetState(std::move(state)); |
| } |
| } else if (method->value == "TextInput.clearClient") { |
| current_text_input_client_ = 0; |
| last_text_state_ = nullptr; |
| DeactivateIme(); |
| } else { |
| FML_DLOG(ERROR) << "Unknown " << message->channel() << " method " |
| << method->value.GetString(); |
| } |
| } |
| |
| } // namespace flutter |