// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef EXTENSIONS_RENDERER_ONE_TIME_MESSAGE_HANDLER_H_
#define EXTENSIONS_RENDERER_ONE_TIME_MESSAGE_HANDLER_H_

#include <memory>
#include <string>

#include "base/memory/weak_ptr.h"
#include "v8/include/v8.h"

namespace gin {
class Arguments;
}

namespace extensions {
class NativeExtensionBindingsSystem;
class ScriptContext;
struct Message;
struct MessageTarget;
struct PortId;

// A class for handling one-time message communication, including
// runtime.sendMessage and extension.sendRequest. These methods use the same
// underlying architecture as long-lived port-based communications (like
// runtime.connect), but are exposed through a simpler API.
// A basic flow will be from an "opener" (the original sender) and a "receiver"
// (the event listener), which will be in two separate contexts (and potentially
// renderer processes). The flow is outlined below:
//
// chrome.runtime.sendMessage(  // initiates the sendMessage flow, triggering
//                              // SendMessage().
//     {foo: bar},              // The data sent with SendMessage().
//     function() { ... });     // The response callback in SendMessage().
//
// This creates a new opener port in the context, and posts a message to it
// with the data. The browser then dispatches this to other renderers.
//
// In another context, we have:
// chrome.runtime.onMessage.addListener(function(message, sender, reply) {
//   ...
//   reply(...);
// });
//
// When the renderer receives the connection message, we will create a
// new receiver port in this context via AddReceiver().
// When the message comes in, we reply with DeliverMessage() to the receiver's
// port ID.
// If the receiver replies via the reply callback, it will send a new message
// back along the port to the browser. The browser then sends this message back
// to the opener's renderer, where it is delivered via DeliverMessage().
//
// This concludes the one-time message flow.
class OneTimeMessageHandler {
 public:
  explicit OneTimeMessageHandler(
      NativeExtensionBindingsSystem* bindings_system);
  ~OneTimeMessageHandler();

  // Returns true if the given context has a port with the specified id.
  bool HasPort(ScriptContext* script_context, const PortId& port_id);

  // Initiates a flow to send a message from the given |script_context|.
  void SendMessage(ScriptContext* script_context,
                   const PortId& new_port_id,
                   const MessageTarget& target_id,
                   const std::string& method_name,
                   bool include_tls_channel_id,
                   const Message& message,
                   v8::Local<v8::Function> response_callback);

  // Adds a receiving port port to the given |script_context| in preparation
  // for receiving a message to post to the onMessage event.
  void AddReceiver(ScriptContext* script_context,
                   const PortId& target_port_id,
                   v8::Local<v8::Object> sender,
                   const std::string& event_name);

  // Delivers a message to the port, either the event listener or in response
  // to the sender, if one exists with the specified |target_port_id|. Returns
  // true if a message was delivered (i.e., an open channel existed), and false
  // otherwise.
  bool DeliverMessage(ScriptContext* script_context,
                      const Message& message,
                      const PortId& target_port_id);

  // Disconnects the port in the context, if one exists with the specified
  // |target_port_id|. Returns true if a port was disconnected (i.e., an open
  // channel existed), and false otherwise.
  bool Disconnect(ScriptContext* script_context,
                  const PortId& port_id,
                  const std::string& error_message);

 private:
  // Helper methods to deliver a message to an opener/receiver.
  bool DeliverMessageToReceiver(ScriptContext* script_context,
                                const Message& message,
                                const PortId& target_port_id);
  bool DeliverReplyToOpener(ScriptContext* script_context,
                            const Message& message,
                            const PortId& target_port_id);

  // Helper methods to disconnect an opener/receiver.
  bool DisconnectReceiver(ScriptContext* script_context, const PortId& port_id);
  bool DisconnectOpener(ScriptContext* script_context,
                        const PortId& port_id,
                        const std::string& error_message);

  // Triggered when a receiver responds to a message.
  void OnOneTimeMessageResponse(const PortId& port_id,
                                gin::Arguments* arguments);

  // Triggered when the callback to reply is garbage collected.
  void OnResponseCallbackCollected(ScriptContext* script_context,
                                   const PortId& port_id);

  // Called when the messaging event has been dispatched with the result of the
  // listeners.
  void OnEventFired(const PortId& port_id,
                    v8::Local<v8::Context> context,
                    v8::MaybeLocal<v8::Value> result);

  // The associated bindings system. Outlives this object.
  NativeExtensionBindingsSystem* const bindings_system_;

  base::WeakPtrFactory<OneTimeMessageHandler> weak_factory_;

  DISALLOW_COPY_AND_ASSIGN(OneTimeMessageHandler);
};

}  // namespace extensions

#endif  // EXTENSIONS_RENDERER_ONE_TIME_MESSAGE_HANDLER_H_
