| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "fuchsia_web/webengine/browser/message_port.h" |
| |
| #include <fuchsia/mem/cpp/fidl.h> |
| #include <lib/fidl/cpp/binding.h> |
| #include <lib/fit/function.h> |
| #include <lib/fpromise/result.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/containers/circular_deque.h" |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/fuchsia/mem_buffer_util.h" |
| #include "base/functional/bind.h" |
| #include "base/stl_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/single_thread_task_runner.h" |
| namespace { |
| |
| using BlinkMessage = blink::WebMessagePort::Message; |
| |
| // Converts a fuchsia::web::WebMessage to a BlinkMessage. |
| // An empty result indicates that conversion was successful. |
| // Data validation errors are returned as a FrameError. |
| std::optional<fuchsia::web::FrameError> BlinkMessageFromFidl( |
| fuchsia::web::WebMessage fidl_message, |
| BlinkMessage* blink_message) { |
| if (!fidl_message.has_data()) { |
| return fuchsia::web::FrameError::NO_DATA_IN_MESSAGE; |
| } |
| |
| std::optional<std::u16string> data_utf16 = |
| base::ReadUTF8FromVMOAsUTF16(fidl_message.data()); |
| if (!data_utf16) { |
| return fuchsia::web::FrameError::BUFFER_NOT_UTF8; |
| } |
| blink_message->data = std::move(*data_utf16); |
| |
| if (fidl_message.has_outgoing_transfer() && |
| fidl_message.has_incoming_transfer()) { |
| DLOG(WARNING) << "WebMessage may only have incoming or outgoing transfer."; |
| return fuchsia::web::FrameError::INTERNAL_ERROR; |
| } |
| if (fidl_message.has_outgoing_transfer()) { |
| for (fuchsia::web::OutgoingTransferable& transferrable : |
| *fidl_message.mutable_outgoing_transfer()) { |
| if (!transferrable.is_message_port()) |
| return fuchsia::web::FrameError::INTERNAL_ERROR; |
| blink_message->ports.push_back( |
| BlinkMessagePortFromFidl(std::move(transferrable.message_port()))); |
| } |
| } else if (fidl_message.has_incoming_transfer()) { |
| for (fuchsia::web::IncomingTransferable& transferrable : |
| *fidl_message.mutable_incoming_transfer()) { |
| if (!transferrable.is_message_port()) |
| return fuchsia::web::FrameError::INTERNAL_ERROR; |
| blink_message->ports.push_back( |
| BlinkMessagePortFromFidl(std::move(transferrable.message_port()))); |
| } |
| } |
| |
| return std::nullopt; |
| } |
| |
| // Defines a MessagePortAdapter, which translates and routes messages between a |
| // FIDL MessagePort and a blink::WebMessagePort. Every MessagePortAdapter has |
| // exactly one FIDL MessagePort and one blink::WebMessagePort. |
| // |
| // MessagePortAdapter instances are self-managed; they destroy themselves when |
| // the connection is terminated from either the Blink or FIDL side. |
| class MessagePortAdapter : public blink::WebMessagePort::MessageReceiver { |
| public: |
| MessagePortAdapter(const MessagePortAdapter&) = delete; |
| MessagePortAdapter& operator=(const MessagePortAdapter&) = delete; |
| |
| protected: |
| explicit MessagePortAdapter(blink::WebMessagePort blink_port) |
| : blink_port_(std::move(blink_port)) { |
| blink_port_.SetReceiver(this, |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| } |
| |
| ~MessagePortAdapter() override = default; |
| |
| // Deletes |this|, implicitly disconnecting the FIDL and Blink ports. |
| void Destroy() { delete this; } |
| |
| // Sends a message to |blink_port_|. |
| void SendBlinkMessage(BlinkMessage message) { |
| CHECK(blink_port_.PostMessage(std::move(message))); |
| } |
| |
| // Called when a Blink message was received through |blink_port_|. |
| virtual void DeliverMessageToFidl() = 0; |
| |
| // Returns the next messagefrom Blink, or an empty value if there |
| // are no more messages in the incoming queue. |
| std::optional<fuchsia::web::WebMessage> GetNextBlinkMessage() { |
| if (message_queue_.empty()) |
| return std::nullopt; |
| |
| return std::move(message_queue_.front()); |
| } |
| |
| void OnDeliverMessageToFidlComplete() { |
| DCHECK(!message_queue_.empty()); |
| message_queue_.pop_front(); |
| } |
| |
| private: |
| // blink::WebMessagePort::MessageReceiver implementation: |
| bool OnMessage(BlinkMessage message) override { |
| std::optional<fuchsia::web::WebMessage> message_converted = |
| FidlWebMessageFromBlink(std::move(message), |
| TransferableHostType::kLocal); |
| if (!message_converted) { |
| DLOG(ERROR) << "Couldn't decode WebMessage from blink::WebMessagePort."; |
| Destroy(); |
| return false; |
| } |
| message_queue_.emplace_back(std::move(*message_converted)); |
| |
| // Start draining the queue if it was empty beforehand. |
| if (message_queue_.size() == 1u) |
| DeliverMessageToFidl(); |
| |
| return true; |
| } |
| |
| // blink::WebMessagePort::MessageReceiver implementation: |
| void OnPipeError() override { Destroy(); } |
| |
| base::circular_deque<fuchsia::web::WebMessage> message_queue_; |
| blink::WebMessagePort blink_port_; |
| }; |
| |
| // Binds a handle to a remote MessagePort to a blink::WebMessagePort. |
| class FidlMessagePortClientAdapter : public MessagePortAdapter { |
| public: |
| FidlMessagePortClientAdapter( |
| blink::WebMessagePort blink_port, |
| fidl::InterfaceHandle<fuchsia::web::MessagePort> fidl_port) |
| : MessagePortAdapter(std::move(blink_port)), port_(fidl_port.Bind()) { |
| ReadMessageFromFidl(); |
| |
| port_.set_error_handler([this](zx_status_t status) { |
| ZX_LOG_IF(ERROR, |
| status != ZX_ERR_PEER_CLOSED && status != ZX_ERR_CANCELED, |
| status) |
| << " MessagePort disconnected."; |
| Destroy(); |
| }); |
| } |
| |
| FidlMessagePortClientAdapter(const FidlMessagePortClientAdapter&) = delete; |
| FidlMessagePortClientAdapter& operator=(const FidlMessagePortClientAdapter&) = |
| delete; |
| |
| fidl::InterfaceRequest<fuchsia::web::MessagePort> NewRequest() { |
| return port_.NewRequest(); |
| } |
| |
| private: |
| ~FidlMessagePortClientAdapter() override = default; |
| |
| void ReadMessageFromFidl() { |
| port_->ReceiveMessage(fit::bind_member( |
| this, &FidlMessagePortClientAdapter::OnMessageReceived)); |
| } |
| |
| void OnMessageReceived(fuchsia::web::WebMessage message) { |
| BlinkMessage blink_message; |
| std::optional<fuchsia::web::FrameError> result = |
| BlinkMessageFromFidl(std::move(message), &blink_message); |
| if (result) { |
| LOG(WARNING) << "Received bad message, error: " |
| << static_cast<int32_t>(*result); |
| Destroy(); |
| return; |
| } |
| |
| SendBlinkMessage(std::move(blink_message)); |
| |
| ReadMessageFromFidl(); |
| } |
| |
| void OnMessagePosted(fuchsia::web::MessagePort_PostMessage_Result result) { |
| if (result.is_err()) { |
| LOG(ERROR) << "PostMessage failed, reason: " |
| << static_cast<int32_t>(result.err()); |
| Destroy(); |
| return; |
| } |
| |
| DeliverMessageToFidl(); |
| } |
| |
| // MessagePortAdapter implementation. |
| void DeliverMessageToFidl() override { |
| std::optional<fuchsia::web::WebMessage> message = GetNextBlinkMessage(); |
| if (!message) |
| return; |
| |
| port_->PostMessage( |
| std::move(*message), |
| fit::bind_member(this, &FidlMessagePortClientAdapter::OnMessagePosted)); |
| |
| OnDeliverMessageToFidlComplete(); |
| } |
| |
| fuchsia::web::MessagePortPtr port_; |
| }; |
| |
| // Binds a MessagePort FIDL service from a blink::WebMessagePort. |
| class FidlMessagePortServerAdapter : public fuchsia::web::MessagePort, |
| public MessagePortAdapter { |
| public: |
| explicit FidlMessagePortServerAdapter(blink::WebMessagePort blink_port) |
| : MessagePortAdapter(std::move(blink_port)), binding_(this) { |
| binding_.set_error_handler([this](zx_status_t status) { |
| ZX_LOG_IF(ERROR, |
| status != ZX_ERR_PEER_CLOSED && status != ZX_ERR_CANCELED, |
| status) |
| << " MessagePort disconnected."; |
| Destroy(); |
| }); |
| } |
| |
| FidlMessagePortServerAdapter( |
| blink::WebMessagePort blink_port, |
| fidl::InterfaceRequest<fuchsia::web::MessagePort> request) |
| : FidlMessagePortServerAdapter(std::move(blink_port)) { |
| binding_.Bind(std::move(request)); |
| } |
| |
| FidlMessagePortServerAdapter(const FidlMessagePortServerAdapter&) = delete; |
| FidlMessagePortServerAdapter& operator=(const FidlMessagePortServerAdapter&) = |
| delete; |
| |
| fidl::InterfaceHandle<fuchsia::web::MessagePort> NewBinding() { |
| return binding_.NewBinding(); |
| } |
| |
| private: |
| ~FidlMessagePortServerAdapter() override = default; |
| |
| // MessagePortAdapter implementation. |
| void DeliverMessageToFidl() override { |
| // Do nothing if the client hasn't requested a read, or if there's nothing |
| // to read. |
| if (!pending_receive_message_callback_) |
| return; |
| |
| std::optional<fuchsia::web::WebMessage> message = GetNextBlinkMessage(); |
| if (!message) |
| return; |
| |
| pending_receive_message_callback_(std::move(*message)); |
| pending_receive_message_callback_ = {}; |
| OnDeliverMessageToFidlComplete(); |
| } |
| |
| // fuchsia::web::MessagePort implementation. |
| void PostMessage(fuchsia::web::WebMessage message, |
| PostMessageCallback callback) override { |
| BlinkMessage blink_message; |
| std::optional<fuchsia::web::FrameError> status = |
| BlinkMessageFromFidl(std::move(message), &blink_message); |
| |
| if (status) { |
| LOG(ERROR) << "Error when reading message from FIDL: " |
| << static_cast<int32_t>(*status); |
| Destroy(); |
| return; |
| } |
| |
| SendBlinkMessage(std::move(blink_message)); |
| callback(fpromise::ok()); |
| } |
| |
| void ReceiveMessage(ReceiveMessageCallback callback) override { |
| if (pending_receive_message_callback_) { |
| LOG(WARNING) |
| << "ReceiveMessage called multiple times without acknowledgement."; |
| Destroy(); |
| return; |
| } |
| pending_receive_message_callback_ = std::move(callback); |
| DeliverMessageToFidl(); |
| } |
| |
| PostMessageCallback post_message_ack_; |
| ReceiveMessageCallback pending_receive_message_callback_; |
| fidl::Binding<fuchsia::web::MessagePort> binding_; |
| }; |
| |
| fidl::InterfaceRequest<fuchsia::web::MessagePort> |
| RemoteFidlMessagePortFromBlink(blink::WebMessagePort blink_port) { |
| fidl::InterfaceHandle<fuchsia::web::MessagePort> fidl_handle; |
| auto request = fidl_handle.NewRequest(); |
| new FidlMessagePortClientAdapter(std::move(blink_port), |
| std::move(fidl_handle)); |
| return request; |
| } |
| |
| } // namespace |
| |
| // Methods for constructing MessagePortAdapters for various port types and |
| // origins. The adapters manage their own lifetimes and will self-delete when |
| // either endpoint of their channels are disconnected. |
| |
| blink::WebMessagePort BlinkMessagePortFromFidl( |
| fidl::InterfaceRequest<fuchsia::web::MessagePort> fidl_port) { |
| auto port_pair = blink::WebMessagePort::CreatePair(); |
| new FidlMessagePortServerAdapter(std::move(port_pair.first), |
| std::move(fidl_port)); |
| return std::move(port_pair.second); |
| } |
| |
| blink::WebMessagePort BlinkMessagePortFromFidl( |
| fidl::InterfaceHandle<fuchsia::web::MessagePort> fidl_port) { |
| auto port_pair = blink::WebMessagePort::CreatePair(); |
| new FidlMessagePortClientAdapter(std::move(port_pair.first), |
| std::move(fidl_port)); |
| return std::move(port_pair.second); |
| } |
| |
| fidl::InterfaceHandle<fuchsia::web::MessagePort> FidlMessagePortFromBlink( |
| blink::WebMessagePort blink_port) { |
| auto* adapter = new FidlMessagePortServerAdapter(std::move(blink_port)); |
| return adapter->NewBinding(); |
| } |
| |
| std::optional<fuchsia::web::WebMessage> FidlWebMessageFromBlink( |
| BlinkMessage blink_message, |
| TransferableHostType port_type) { |
| fuchsia::web::WebMessage fidl_message; |
| |
| if (!blink_message.ports.empty()) { |
| switch (port_type) { |
| case TransferableHostType::kLocal: |
| for (blink::WebMessagePort& port : blink_message.ports) { |
| fuchsia::web::IncomingTransferable incoming; |
| incoming.set_message_port(FidlMessagePortFromBlink(std::move(port))); |
| fidl_message.mutable_incoming_transfer()->push_back( |
| std::move(incoming)); |
| } |
| break; |
| case TransferableHostType::kRemote: |
| for (blink::WebMessagePort& port : blink_message.ports) { |
| fuchsia::web::OutgoingTransferable outgoing; |
| outgoing.set_message_port( |
| RemoteFidlMessagePortFromBlink(std::move(port))); |
| fidl_message.mutable_outgoing_transfer()->push_back( |
| std::move(outgoing)); |
| } |
| break; |
| } |
| blink_message.ports.clear(); |
| } |
| |
| std::u16string data_utf16 = std::move(blink_message.data); |
| std::string data_utf8; |
| if (!base::UTF16ToUTF8(data_utf16.data(), data_utf16.size(), &data_utf8)) |
| return std::nullopt; |
| |
| base::STLClearObject(&data_utf16); |
| |
| constexpr char kBufferVmoName[] = "cr-web-message-from-blink"; |
| fuchsia::mem::Buffer data_buffer = |
| base::MemBufferFromString(data_utf8, kBufferVmoName); |
| if (!data_buffer.vmo) |
| return std::nullopt; |
| |
| fidl_message.set_data(std::move(data_buffer)); |
| return fidl_message; |
| } |