// Copyright (c) 2009 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.

#include "chrome/plugin/plugin_channel.h"

#include "base/command_line.h"
#include "base/lock.h"
#include "base/process_util.h"
#include "base/string_util.h"
#include "base/waitable_event.h"
#include "build/build_config.h"
#include "chrome/common/child_process.h"
#include "chrome/common/plugin_messages.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/plugin/plugin_thread.h"
#include "chrome/plugin/webplugin_delegate_stub.h"
#include "chrome/plugin/webplugin_proxy.h"

#if defined(OS_POSIX)
#include "ipc/ipc_channel_posix.h"
#endif

class PluginReleaseTask : public Task {
 public:
  void Run() {
    ChildProcess::current()->ReleaseProcess();
  }
};

// How long we wait before releasing the plugin process.
static const int kPluginReleaseTimeMS = 10000;


// If a sync call to the renderer results in a modal dialog, we need to have a
// way to know so that we can run a nested message loop to simulate what would
// happen in a single process browser and avoid deadlock.
class PluginChannel::MessageFilter : public IPC::ChannelProxy::MessageFilter {
 public:
  MessageFilter() : channel_(NULL) { }
  ~MessageFilter() {
    // Clean up in case of renderer crash.
    for (ModalDialogEventMap::iterator i = modal_dialog_event_map_.begin();
        i != modal_dialog_event_map_.end(); ++i) {
      delete i->second.event;
    }
  }

  base::WaitableEvent* GetModalDialogEvent(
      gfx::NativeViewId containing_window) {
    AutoLock auto_lock(modal_dialog_event_map_lock_);
    if (!modal_dialog_event_map_.count(containing_window)) {
      NOTREACHED();
      return NULL;
    }

    return modal_dialog_event_map_[containing_window].event;
  }

  // Decrement the ref count associated with the modal dialog event for the
  // given tab.
  void ReleaseModalDialogEvent(gfx::NativeViewId containing_window) {
    AutoLock auto_lock(modal_dialog_event_map_lock_);
    if (!modal_dialog_event_map_.count(containing_window)) {
      NOTREACHED();
      return;
    }

    if (--(modal_dialog_event_map_[containing_window].refcount))
      return;

    // Delete the event when the stack unwinds as it could be in use now.
    MessageLoop::current()->DeleteSoon(
        FROM_HERE, modal_dialog_event_map_[containing_window].event);
    modal_dialog_event_map_.erase(containing_window);
  }

  bool Send(IPC::Message* message) {
    // Need this function for the IPC_MESSAGE_HANDLER_DELAY_REPLY macro.
    return channel_->Send(message);
  }

 private:
  void OnFilterAdded(IPC::Channel* channel) { channel_ = channel; }

  bool OnMessageReceived(const IPC::Message& message) {
    IPC_BEGIN_MESSAGE_MAP(PluginChannel::MessageFilter, message)
      IPC_MESSAGE_HANDLER_DELAY_REPLY(PluginMsg_Init, OnInit)
      IPC_MESSAGE_HANDLER(PluginMsg_SignalModalDialogEvent,
                          OnSignalModalDialogEvent)
      IPC_MESSAGE_HANDLER(PluginMsg_ResetModalDialogEvent,
                          OnResetModalDialogEvent)
    IPC_END_MESSAGE_MAP()
    return message.type() == PluginMsg_SignalModalDialogEvent::ID ||
           message.type() == PluginMsg_ResetModalDialogEvent::ID;
  }

  void OnInit(const PluginMsg_Init_Params& params, IPC::Message* reply_msg) {
    AutoLock auto_lock(modal_dialog_event_map_lock_);
    if (modal_dialog_event_map_.count(params.containing_window)) {
      modal_dialog_event_map_[params.containing_window].refcount++;
      return;
    }

    WaitableEventWrapper wrapper;
    wrapper.event = new base::WaitableEvent(true, false);
    wrapper.refcount = 1;
    modal_dialog_event_map_[params.containing_window] = wrapper;
  }

  void OnSignalModalDialogEvent(gfx::NativeViewId containing_window) {
    AutoLock auto_lock(modal_dialog_event_map_lock_);
    if (modal_dialog_event_map_.count(containing_window))
      modal_dialog_event_map_[containing_window].event->Signal();
  }

  void OnResetModalDialogEvent(gfx::NativeViewId containing_window) {
    AutoLock auto_lock(modal_dialog_event_map_lock_);
    if (modal_dialog_event_map_.count(containing_window))
      modal_dialog_event_map_[containing_window].event->Reset();
  }

  struct WaitableEventWrapper {
    base::WaitableEvent* event;
    int refcount;  // There could be multiple plugin instances per tab.
  };
  typedef std::map<gfx::NativeViewId, WaitableEventWrapper> ModalDialogEventMap;
  ModalDialogEventMap modal_dialog_event_map_;
  Lock modal_dialog_event_map_lock_;

  IPC::Channel* channel_;
};


PluginChannel* PluginChannel::GetPluginChannel(int renderer_id,
                                               MessageLoop* ipc_message_loop) {
  // Map renderer ID to a (single) channel to that process.
  std::string channel_name = StringPrintf(
      "%d.r%d", base::GetCurrentProcId(), renderer_id);

  PluginChannel* channel =
      static_cast<PluginChannel*>(PluginChannelBase::GetChannel(
          channel_name,
          IPC::Channel::MODE_SERVER,
          ClassFactory,
          ipc_message_loop,
          false));

  if (channel)
    channel->renderer_id_ = renderer_id;

  return channel;
}

PluginChannel::PluginChannel()
    : renderer_handle_(0),
      renderer_id_(-1),
#if defined(OS_POSIX)
      renderer_fd_(-1),
#endif
      in_send_(0),
      off_the_record_(false),
      filter_(new MessageFilter()) {
  SendUnblockingOnlyDuringDispatch();
  ChildProcess::current()->AddRefProcess();
  const CommandLine* command_line = CommandLine::ForCurrentProcess();
  log_messages_ = command_line->HasSwitch(switches::kLogPluginMessages);
}

PluginChannel::~PluginChannel() {
  if (renderer_handle_)
    base::CloseProcessHandle(renderer_handle_);
#if defined(OS_POSIX)
  // If we still have the renderer FD, close it.
  if (renderer_fd_ != -1) {
    close(renderer_fd_);
  }
#endif
  MessageLoop::current()->PostDelayedTask(FROM_HERE, new PluginReleaseTask(),
                                          kPluginReleaseTimeMS);
}

bool PluginChannel::Send(IPC::Message* msg) {
  in_send_++;
  if (log_messages_) {
    LOG(INFO) << "sending message @" << msg << " on channel @" << this
              << " with type " << msg->type();
  }
  bool result = PluginChannelBase::Send(msg);
  in_send_--;
  return result;
}

void PluginChannel::OnMessageReceived(const IPC::Message& msg) {
  if (log_messages_) {
    LOG(INFO) << "received message @" << &msg << " on channel @" << this
              << " with type " << msg.type();
  }
  PluginChannelBase::OnMessageReceived(msg);
}

void PluginChannel::OnControlMessageReceived(const IPC::Message& msg) {
  IPC_BEGIN_MESSAGE_MAP(PluginChannel, msg)
    IPC_MESSAGE_HANDLER(PluginMsg_CreateInstance, OnCreateInstance)
    IPC_MESSAGE_HANDLER_DELAY_REPLY(PluginMsg_DestroyInstance,
                                    OnDestroyInstance)
    IPC_MESSAGE_HANDLER(PluginMsg_GenerateRouteID, OnGenerateRouteID)
    IPC_MESSAGE_UNHANDLED_ERROR()
  IPC_END_MESSAGE_MAP()
}

void PluginChannel::OnCreateInstance(const std::string& mime_type,
                                     int* instance_id) {
  *instance_id = GenerateRouteID();
  scoped_refptr<WebPluginDelegateStub> stub = new WebPluginDelegateStub(
      mime_type, *instance_id, this);
  AddRoute(*instance_id, stub, false);
  plugin_stubs_.push_back(stub);
}

void PluginChannel::OnDestroyInstance(int instance_id,
                                      IPC::Message* reply_msg) {
  for (size_t i = 0; i < plugin_stubs_.size(); ++i) {
    if (plugin_stubs_[i]->instance_id() == instance_id) {
      scoped_refptr<MessageFilter> filter(filter_);
      gfx::NativeViewId window =
          plugin_stubs_[i]->webplugin()->containing_window();
      plugin_stubs_.erase(plugin_stubs_.begin() + i);
      Send(reply_msg);
      RemoveRoute(instance_id);
      // NOTE: *this* might be deleted as a result of calling RemoveRoute.
      // Don't release the modal dialog event right away, but do it after the
      // stack unwinds since the plugin can be destroyed later if it's in use
      // right now.
      MessageLoop::current()->PostNonNestableTask(FROM_HERE, NewRunnableMethod(
          filter.get(), &MessageFilter::ReleaseModalDialogEvent, window));
      return;
    }
  }

  NOTREACHED() << "Couldn't find WebPluginDelegateStub to destroy";
}

void PluginChannel::OnGenerateRouteID(int* route_id) {
  *route_id = GenerateRouteID();
}

int PluginChannel::GenerateRouteID() {
  static int last_id = 0;
  return ++last_id;
}

base::WaitableEvent* PluginChannel::GetModalDialogEvent(
    gfx::NativeViewId containing_window) {
  return filter_->GetModalDialogEvent(containing_window);
}

void PluginChannel::OnChannelConnected(int32 peer_pid) {
  base::ProcessHandle handle;
  if (!base::OpenProcessHandle(peer_pid, &handle)) {
    NOTREACHED();
  }
  renderer_handle_ = handle;
  PluginChannelBase::OnChannelConnected(peer_pid);
}

void PluginChannel::OnChannelError() {
  base::CloseProcessHandle(renderer_handle_);
  renderer_handle_ = 0;
  PluginChannelBase::OnChannelError();
  CleanUp();
}

void PluginChannel::CleanUp() {
  // We need to clean up the stubs so that they call NPPDestroy.  This will
  // also lead to them releasing their reference on this object so that it can
  // be deleted.
  for (size_t i = 0; i < plugin_stubs_.size(); ++i)
    RemoveRoute(plugin_stubs_[i]->instance_id());

  // Need to addref this object temporarily because otherwise removing the last
  // stub will cause the destructor of this object to be called, however at
  // that point plugin_stubs_ will have one element and its destructor will be
  // called twice.
  scoped_refptr<PluginChannel> me(this);

  plugin_stubs_.clear();
}

bool PluginChannel::Init(MessageLoop* ipc_message_loop, bool create_pipe_now) {
#if defined(OS_POSIX)
  // This gets called when the PluginChannel is initially created. At this
  // point, create the socketpair and assign the plugin side FD to the channel
  // name. Keep the renderer side FD as a member variable in the PluginChannel
  // to be able to transmit it through IPC.
  int plugin_fd;
  IPC::SocketPair(&plugin_fd, &renderer_fd_);
  IPC::AddChannelSocket(channel_name(), plugin_fd);
#endif
  if (!PluginChannelBase::Init(ipc_message_loop, create_pipe_now))
    return false;

  channel_->AddFilter(filter_.get());
  return true;
}