// Copyright 2013 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 "content/child/npapi/npobject_stub.h"

#include "build/build_config.h"
#include "content/child/npapi/np_channel_base.h"
#include "content/child/npapi/npobject_util.h"
#include "content/child/plugin_messages.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "third_party/WebKit/public/web/WebBindings.h"
#include "third_party/npapi/bindings/npapi.h"
#include "third_party/npapi/bindings/npruntime.h"

#if defined(OS_WIN)
#include "base/command_line.h"
#include "content/common/plugin_constants_win.h"
#endif

using blink::WebBindings;

namespace content {

NPObjectStub::NPObjectStub(
    NPObject* npobject,
    NPChannelBase* channel,
    int route_id,
    int render_view_id,
    const GURL& page_url)
    : npobject_(npobject),
      channel_(channel),
      route_id_(route_id),
      render_view_id_(render_view_id),
      page_url_(page_url) {
  channel_->AddMappingForNPObjectStub(route_id, npobject);
  channel_->AddRoute(route_id, this, this);

  // We retain the object just as PluginHost does if everything was in-process.
  WebBindings::retainObject(npobject_);
}

NPObjectStub::~NPObjectStub() {
  channel_->RemoveRoute(route_id_);
  DCHECK(!npobject_);
}

void NPObjectStub::DeleteSoon() {
  if (npobject_) {
    channel_->RemoveMappingForNPObjectStub(route_id_, npobject_);

    // We need to NULL npobject_ prior to calling releaseObject() to avoid
    // problems with re-entrancy. See http://crbug.com/94179#c17 for more
    // details on how this can happen.
    NPObject* npobject = npobject_;
    npobject_ = NULL;

    WebBindings::releaseObject(npobject);

    base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
  }
}

bool NPObjectStub::Send(IPC::Message* msg) {
  return channel_->Send(msg);
}

NPObject* NPObjectStub::GetUnderlyingNPObject() {
  return npobject_;
}

IPC::Listener* NPObjectStub::GetChannelListener() {
  return static_cast<IPC::Listener*>(this);
}

bool NPObjectStub::OnMessageReceived(const IPC::Message& msg) {
  GetContentClient()->SetActiveURL(page_url_);
  if (!npobject_) {
    if (msg.is_sync()) {
      // The object could be garbage because the frame has gone away, so
      // just send an error reply to the caller.
      IPC::Message* reply = IPC::SyncMessage::GenerateReply(&msg);
      reply->set_reply_error();
      Send(reply);
    }

    return true;
  }

  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(NPObjectStub, msg)
    IPC_MESSAGE_HANDLER_DELAY_REPLY(NPObjectMsg_Release, OnRelease);
    IPC_MESSAGE_HANDLER(NPObjectMsg_HasMethod, OnHasMethod);
    IPC_MESSAGE_HANDLER_DELAY_REPLY(NPObjectMsg_Invoke, OnInvoke);
    IPC_MESSAGE_HANDLER(NPObjectMsg_HasProperty, OnHasProperty);
    IPC_MESSAGE_HANDLER(NPObjectMsg_GetProperty, OnGetProperty);
    IPC_MESSAGE_HANDLER_DELAY_REPLY(NPObjectMsg_SetProperty, OnSetProperty);
    IPC_MESSAGE_HANDLER(NPObjectMsg_RemoveProperty, OnRemoveProperty);
    IPC_MESSAGE_HANDLER(NPObjectMsg_Invalidate, OnInvalidate);
    IPC_MESSAGE_HANDLER(NPObjectMsg_Enumeration, OnEnumeration);
    IPC_MESSAGE_HANDLER_DELAY_REPLY(NPObjectMsg_Construct, OnConstruct);
    IPC_MESSAGE_HANDLER_DELAY_REPLY(NPObjectMsg_Evaluate, OnEvaluate);
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  DCHECK(handled);
  return handled;
}

void NPObjectStub::OnChannelError() {
  DeleteSoon();
}

void NPObjectStub::OnRelease(IPC::Message* reply_msg) {
  Send(reply_msg);
  DeleteSoon();
}

void NPObjectStub::OnHasMethod(const NPIdentifier_Param& name,
                               bool* result) {
  NPIdentifier id = CreateNPIdentifier(name);
  // If we're in the plugin process, then the stub is holding onto an NPObject
  // from the plugin, so all function calls on it need to go through the
  // functions in NPClass.  If we're in the renderer process, then we just call
  // the NPN_ functions.
  if (IsPluginProcess()) {
    if (npobject_->_class->hasMethod) {
      *result = npobject_->_class->hasMethod(npobject_, id);
    } else {
      *result = false;
    }
  } else {
    *result = WebBindings::hasMethod(0, npobject_, id);
  }
}

void NPObjectStub::OnInvoke(bool is_default,
                            const NPIdentifier_Param& method,
                            const std::vector<NPVariant_Param>& args,
                            IPC::Message* reply_msg) {
  bool return_value = false;
  NPVariant_Param result_param;
  NPVariant result_var;

  VOID_TO_NPVARIANT(result_var);
  result_param.type = NPVARIANT_PARAM_VOID;

  int arg_count = static_cast<int>(args.size());
  NPVariant* args_var = new NPVariant[arg_count];
  for (int i = 0; i < arg_count; ++i) {
    if (!CreateNPVariant(args[i],
                         channel_.get(),
                         &(args_var[i]),
                         render_view_id_,
                         page_url_)) {
      NPObjectMsg_Invoke::WriteReplyParams(
          reply_msg, result_param, return_value);
      channel_->Send(reply_msg);
      delete[] args_var;
      return;
    }
  }

  if (is_default) {
    if (IsPluginProcess()) {
      if (npobject_->_class->invokeDefault) {
        return_value = npobject_->_class->invokeDefault(
            npobject_, args_var, arg_count, &result_var);
      } else {
        return_value = false;
      }
    } else {
      return_value = WebBindings::invokeDefault(
          0, npobject_, args_var, arg_count, &result_var);
    }
  } else {
    NPIdentifier id = CreateNPIdentifier(method);
    if (IsPluginProcess()) {
      if (npobject_->_class->invoke) {
        return_value = npobject_->_class->invoke(
            npobject_, id, args_var, arg_count, &result_var);
      } else {
        return_value = false;
      }
    } else {
      return_value = WebBindings::invoke(
          0, npobject_, id, args_var, arg_count, &result_var);
    }
  }

  for (int i = 0; i < arg_count; ++i)
    WebBindings::releaseVariantValue(&(args_var[i]));

  delete[] args_var;

  CreateNPVariantParam(result_var,
                       channel_.get(),
                       &result_param,
                       true,
                       render_view_id_,
                       page_url_);
  NPObjectMsg_Invoke::WriteReplyParams(reply_msg, result_param, return_value);
  channel_->Send(reply_msg);
}

void NPObjectStub::OnHasProperty(const NPIdentifier_Param& name,
                                 bool* result) {
  NPIdentifier id = CreateNPIdentifier(name);
  if (IsPluginProcess()) {
    if (npobject_->_class->hasProperty) {
      *result = npobject_->_class->hasProperty(npobject_, id);
    } else {
      *result = false;
    }
  } else {
    *result = WebBindings::hasProperty(0, npobject_, id);
  }
}

void NPObjectStub::OnGetProperty(const NPIdentifier_Param& name,
                                 NPVariant_Param* property,
                                 bool* result) {
  NPVariant result_var;
  VOID_TO_NPVARIANT(result_var);
  NPIdentifier id = CreateNPIdentifier(name);

  if (IsPluginProcess()) {
    if (npobject_->_class->getProperty) {
      *result = npobject_->_class->getProperty(npobject_, id, &result_var);
    } else {
      *result = false;
    }
  } else {
    *result = WebBindings::getProperty(0, npobject_, id, &result_var);
  }

  CreateNPVariantParam(
      result_var, channel_.get(), property, true, render_view_id_, page_url_);
}

void NPObjectStub::OnSetProperty(const NPIdentifier_Param& name,
                                 const NPVariant_Param& property,
                                 IPC::Message* reply_msg) {
  bool result = false;
  NPIdentifier id = CreateNPIdentifier(name);
  NPVariant property_var;
  if (!CreateNPVariant(property,
                       channel_.get(),
                       &property_var,
                       render_view_id_,
                       page_url_)) {
    NPObjectMsg_SetProperty::WriteReplyParams(reply_msg, result);
    channel_->Send(reply_msg);
    return;
  }

  if (IsPluginProcess()) {
    if (npobject_->_class->setProperty) {
#if defined(OS_WIN)
      static base::FilePath plugin_path =
          base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
              switches::kPluginPath);
      static std::wstring filename = base::ToLowerASCII(
          plugin_path.BaseName().value());
      static NPIdentifier fullscreen =
          WebBindings::getStringIdentifier("fullScreen");
      if (filename == kNewWMPPlugin && id == fullscreen) {
        // Workaround for bug 15985, which is if Flash causes WMP to go
        // full screen a deadlock can occur when WMP calls SetFocus.
        NPObjectMsg_SetProperty::WriteReplyParams(reply_msg, true);
        Send(reply_msg);
        reply_msg = NULL;
      }
#endif
      result = npobject_->_class->setProperty(npobject_, id, &property_var);
    } else {
      result = false;
    }
  } else {
    result = WebBindings::setProperty(0, npobject_, id, &property_var);
  }

  WebBindings::releaseVariantValue(&property_var);

  if (reply_msg) {
    NPObjectMsg_SetProperty::WriteReplyParams(reply_msg, result);
    Send(reply_msg);
  }
}

void NPObjectStub::OnRemoveProperty(const NPIdentifier_Param& name,
                                    bool* result) {
  NPIdentifier id = CreateNPIdentifier(name);
  if (IsPluginProcess()) {
    if (npobject_->_class->removeProperty) {
      *result = npobject_->_class->removeProperty(npobject_, id);
    } else {
      *result = false;
    }
  } else {
    *result = WebBindings::removeProperty(0, npobject_, id);
  }
}

void NPObjectStub::OnInvalidate() {
  if (!IsPluginProcess()) {
    NOTREACHED() << "Should only be called on NPObjects in the plugin";
    return;
  }

  if (!npobject_->_class->invalidate)
    return;

  npobject_->_class->invalidate(npobject_);
}

void NPObjectStub::OnEnumeration(std::vector<NPIdentifier_Param>* value,
                                 bool* result) {
  NPIdentifier* value_np = NULL;
  unsigned int count = 0;
  if (!IsPluginProcess()) {
    *result = WebBindings::enumerate(0, npobject_, &value_np, &count);
  } else {
    if (npobject_->_class->structVersion < NP_CLASS_STRUCT_VERSION_ENUM ||
        !npobject_->_class->enumerate) {
      *result = false;
      return;
    }

    *result = npobject_->_class->enumerate(npobject_, &value_np, &count);
  }

  if (!*result)
    return;

  for (unsigned int i = 0; i < count; ++i) {
    NPIdentifier_Param param;
    CreateNPIdentifierParam(value_np[i], &param);
    value->push_back(param);
  }

  free(value_np);
}

void NPObjectStub::OnConstruct(const std::vector<NPVariant_Param>& args,
                               IPC::Message* reply_msg) {
  bool return_value = false;
  NPVariant_Param result_param;
  NPVariant result_var;

  VOID_TO_NPVARIANT(result_var);

  int arg_count = static_cast<int>(args.size());
  NPVariant* args_var = new NPVariant[arg_count];
  for (int i = 0; i < arg_count; ++i) {
    if (!CreateNPVariant(args[i],
                         channel_.get(),
                         &(args_var[i]),
                         render_view_id_,
                         page_url_)) {
      NPObjectMsg_Invoke::WriteReplyParams(
          reply_msg, result_param, return_value);
      channel_->Send(reply_msg);
      delete[] args_var;
      return;
    }
  }

  if (IsPluginProcess()) {
    if (npobject_->_class->structVersion >= NP_CLASS_STRUCT_VERSION_CTOR &&
        npobject_->_class->construct) {
      return_value = npobject_->_class->construct(
          npobject_, args_var, arg_count, &result_var);
    } else {
      return_value = false;
    }
  } else {
    return_value = WebBindings::construct(
        0, npobject_, args_var, arg_count, &result_var);
  }

  for (int i = 0; i < arg_count; ++i)
    WebBindings::releaseVariantValue(&(args_var[i]));

  delete[] args_var;

  CreateNPVariantParam(result_var,
                       channel_.get(),
                       &result_param,
                       true,
                       render_view_id_,
                       page_url_);
  NPObjectMsg_Invoke::WriteReplyParams(reply_msg, result_param, return_value);
  channel_->Send(reply_msg);
}

void NPObjectStub::OnEvaluate(const std::string& script,
                              bool popups_allowed,
                              IPC::Message* reply_msg) {
  if (IsPluginProcess()) {
    NOTREACHED() << "Should only be called on NPObjects in the renderer";
    return;
  }

  NPVariant result_var;
  NPString script_string;
  script_string.UTF8Characters = script.c_str();
  script_string.UTF8Length = static_cast<unsigned int>(script.length());

  bool return_value = WebBindings::evaluateHelper(0, popups_allowed, npobject_,
                                                  &script_string, &result_var);

  NPVariant_Param result_param;
  CreateNPVariantParam(result_var,
                       channel_.get(),
                       &result_param,
                       true,
                       render_view_id_,
                       page_url_);
  NPObjectMsg_Evaluate::WriteReplyParams(reply_msg, result_param, return_value);
  channel_->Send(reply_msg);
}

}  // namespace content
