910e62b5创建于 1月15日历史提交
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef EXTENSIONS_RENDERER_API_MESSAGING_NATIVE_RENDERER_MESSAGING_SERVICE_H_
#define EXTENSIONS_RENDERER_API_MESSAGING_NATIVE_RENDERER_MESSAGING_SERVICE_H_

#include <string>

#include "base/memory/raw_ptr.h"
#include "base/memory/safe_ref.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/mojom/message_port.mojom.h"
#include "extensions/renderer/api/messaging/gin_port.h"
#include "extensions/renderer/api/messaging/one_time_message_handler.h"
#include "extensions/renderer/bindings/api_binding_types.h"
#include "gin/handle.h"
#include "mojo/public/cpp/bindings/associated_receiver_set.h"
#include "v8/include/v8-forward.h"

namespace content {
class RenderFrame;
}

namespace extensions {

namespace mojom {
enum class ChannelType;
enum class SerializationFormat;
}  // namespace mojom

class NativeExtensionBindingsSystem;
class ScriptContextSetIterable;
class Message;
struct MessageTarget;
struct PortId;

// The messaging service to handle dispatching extension messages and connection
// events to different contexts.
// This primarily handles long-lived port-based communications (like
// runtime.connect). A basic flow will create an "opener" port and one ore more
// "receiver" ports in different contexts (and possibly processes). This class
// manages the communication with the browser to forward these messages along.
// From JavaScript, a basic flow would be:
// // page1.js
// var port = chrome.runtime.connect();
// port.onMessage.addListener(function() { <handle message> });
// port.postMessage('hi!');
// <eventually> port.disconnect();
// // page2.js
// chrome.runtime.onConnect.addListener(function(port) {
//   port.onMessage.addListener(function() { <handle message> });
//   port.postMessage('hey!');
// });
// This causes the following steps in the messaging service:
// Connection:
// * connect() triggers OpenChannelToExtension, which notifies the browser of
//   a new connection.
// * The browser dispatches OnConnect messages to different renderers. If a
//   renderer has a listener, it will respond with an OpenMessagePort message.
//   If no renderer has a listener, the browser will close the port.
// Message Posting
// * Calls to postMessage() trigger a PostMessageToPort messge to the browser.
// * The browser sends a DeliverMessage message to listening renderers. These
//   then dispatch the onMessage event to listeners.
// Disconnecting
// * disconnect() calls result in sending a CloseMessagePort message to the
//   browser.
// * The browser then sends a DispatchOnDisconnect message to other renderers,
//   which triggers the onDisconnect() event.
// TODO(devlin): This is a pretty large comment for a class, and it documents
// browser/renderer interaction. I wonder if this would be better in a
// messaging.md document?
class NativeRendererMessagingService : public GinPort::Delegate {
 public:
  explicit NativeRendererMessagingService(
      NativeExtensionBindingsSystem* bindings_system);

  NativeRendererMessagingService(const NativeRendererMessagingService&) =
      delete;
  NativeRendererMessagingService& operator=(
      const NativeRendererMessagingService&) = delete;

  ~NativeRendererMessagingService() override;

  using ConnectCallback = base::OnceCallback<void(bool success)>;
  using TabConnectionInfo = mojom::TabConnectionInfo;
  using ExternalConnectionInfo = mojom::ExternalConnectionInfo;
  // Dispatches the onConnect content script messaging event to some contexts
  // in `context_set`. If `restrict_to_render_frame` is specified, only contexts
  // in that render frame will receive the message.
  void DispatchOnConnect(
      ScriptContextSetIterable* context_set,
      const PortId& target_port_id,
      mojom::ChannelType channel_type,
      const std::string& channel_name,
      const TabConnectionInfo& tab_info,
      const ExternalConnectionInfo& external_info,
      mojo::PendingAssociatedReceiver<extensions::mojom::MessagePort> port,
      mojo::PendingAssociatedRemote<extensions::mojom::MessagePortHost>
          port_host,
      content::RenderFrame* restrict_to_render_frame,
      ConnectCallback);

  // Delivers a message sent using content script messaging to some of the
  // contexts in `bindings_context_set`. If `restrict_to_render_frame` is
  // specified, only contexts in that render view will receive the message.
  void DeliverMessage(ScriptContextSetIterable* context_set,
                      const PortId& target_port_id,
                      const Message& message,
                      content::RenderFrame* restrict_to_render_frame);

  // Dispatches the onDisconnect event in response to the channel being closed.
  void DispatchOnDisconnect(ScriptContextSetIterable* context_set,
                            const PortId& port_id,
                            const std::string& error_message,
                            content::RenderFrame* restrict_to_render_frame);

  // Creates and opens a new message port in the specified context.
  GinPort* Connect(ScriptContext* script_context,
                               const MessageTarget& target,
                               const std::string& name,
                               mojom::SerializationFormat format);

  // Sends a one-time message, as is used by runtime.sendMessage. Returns a
  // Promise if used in a promise based API call, otherwise returns an empty
  // v8::Local<>.
  v8::Local<v8::Promise> SendOneTimeMessage(
      ScriptContext* script_context,
      const MessageTarget& target,
      mojom::ChannelType channel_type,
      const Message& message,
      binding::AsyncResponseType async_type,
      v8::Local<v8::Function> response_callback);

  // GinPort::Delegate:
  void PostMessageToPort(v8::Local<v8::Context> context,
                         const PortId& port_id,
                         std::unique_ptr<Message> message) override;
  void ClosePort(v8::Local<v8::Context> context,
                 const PortId& port_id) override;

  GinPort* CreatePortForTesting(
      ScriptContext* script_context,
      const std::string& channel_name,
      const mojom::ChannelType channel_type,
      const PortId& port_id,
      mojo::PendingAssociatedRemote<mojom::MessagePort>& message_port_remote,
      mojo::PendingAssociatedReceiver<mojom::MessagePortHost>&
          message_port_host_receiver);
  GinPort* GetPortForTesting(ScriptContext* script_context,
                                         const PortId& port_id);
  bool HasPortForTesting(ScriptContext* script_context, const PortId& port_id);

  void BindPortForTesting(
      ScriptContext* script_context,
      const PortId& port_id,
      mojo::PendingAssociatedRemote<mojom::MessagePort>& message_port_remote,
      mojo::PendingAssociatedReceiver<mojom::MessagePortHost>&
          message_port_host_receiver);

  // Closes the message port with the given `port_id` in the given
  // `script_context`. If `close_channel` is true, the entire communication
  // channel is torn down, which also disconnects the port on the other side.
  void CloseMessagePort(ScriptContext* script_context,
                        const PortId& port_id,
                        bool close_channel);

  // Same as above, but it allows passing an error message that will be provided
  // to the message port opener.
  void CloseMessagePort(ScriptContext* script_context,
                        const PortId& port_id,
                        bool close_channel,
                        const std::optional<std::string>& error_message);

  // Returns the associated MessagePortHost. This method asserts that it
  // exists.
  mojom::MessagePortHost* GetMessagePortHost(ScriptContext* script_context,
                                             const PortId& port_id);
  // Similar to `GetMessagePortHost` but will return null if the port
  // no longer exists.
  mojom::MessagePortHost* GetMessagePortHostIfExists(
      ScriptContext* script_context,
      const PortId& port_id);

 private:
  class MessagePortScope;
  class RenderFrameMessagePorts;
  friend class OneTimeMessageHandlerTest;

  // Helpers for the public methods to perform the action in a single
  // ScriptContext.
  void DispatchOnConnectToScriptContext(const PortId& target_port_id,
                                        mojom::ChannelType channel_type,
                                        const std::string& channel_name,
                                        const TabConnectionInfo& source,
                                        const ExternalConnectionInfo& info,
                                        bool* port_created,
                                        ScriptContext* script_context);
  void DeliverMessageToScriptContext(const Message& message,
                                     const PortId& target_port_id,
                                     ScriptContext* script_context);
  void DeliverMessageToWorker(const Message& message,
                              const PortId& target_port_id,
                              ScriptContext* script_context);
  void DeliverMessageToBackgroundPage(const Message& message,
                                      const PortId& target_port_id,
                                      ScriptContext* script_context);
  void DispatchOnDisconnectToScriptContext(const PortId& port_id,
                                           const std::string& error_message,
                                           ScriptContext* script_context);

  // Returns true if the given `script_context` has a port with the given
  // `port_id`.
  bool ContextHasMessagePort(ScriptContext* script_context,
                             const PortId& port_id);

  // Dispatches the onConnect event to listeners in the given `script_context`.
  void DispatchOnConnectToListeners(ScriptContext* script_context,
                                    const PortId& target_port_id,
                                    const ExtensionId& target_extension_id,
                                    mojom::ChannelType channel_type,
                                    const std::string& channel_name,
                                    const TabConnectionInfo& source,
                                    const ExternalConnectionInfo& info,
                                    const std::string& event_name);

  // Dispatches the onMessage event to listeners in the given `script_context`.
  // This will only be called if the context has a port with the given id.
  void DispatchOnMessageToListeners(ScriptContext* script_context,
                                    const Message& message,
                                    const PortId& target_port_id);

  // Dispatches the onDisconnect event to listeners in the given
  // `script_context`. This will only be called if the context has a port
  // with the given id.
  void DispatchOnDisconnectToListeners(ScriptContext* script_context,
                                       const PortId& port_id,
                                       const std::string& error);

  // Creates a new port in the given context, with the specified `channel_name`
  // and `port_id`. Assumes no such port exists.
  GinPort* CreatePort(ScriptContext* script_context,
                                  const std::string& channel_name,
                                  const mojom::ChannelType channel_type,
                                  const PortId& port_id);

  // Returns the port with the given `port_id` in the given `script_context`;
  // requires that such a port exists.
  GinPort* GetPort(ScriptContext* script_context,
                               const PortId& port_id);

  MessagePortScope* GetMessagePortScope(content::RenderFrame* render_frame);
  base::SafeRef<NativeRendererMessagingService> AsSafeRef();

  // The associated bindings system; guaranteed to outlive this object.
  const raw_ptr<NativeExtensionBindingsSystem> bindings_system_;

  OneTimeMessageHandler one_time_message_handler_;
  std::unique_ptr<MessagePortScope> default_scope_;
  base::WeakPtrFactory<NativeRendererMessagingService> weak_ptr_factory_{this};
};

}  // namespace extensions

#endif  // EXTENSIONS_RENDERER_API_MESSAGING_NATIVE_RENDERER_MESSAGING_SERVICE_H_