// Copyright (c) 2011 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/renderer/pepper/npapi_glue.h"

#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_util.h"
#include "content/renderer/pepper/host_array_buffer_var.h"
#include "content/renderer/pepper/host_globals.h"
#include "content/renderer/pepper/host_var_tracker.h"
#include "content/renderer/pepper/npobject_var.h"
#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
#include "content/renderer/pepper/plugin_module.h"
#include "content/renderer/pepper/plugin_object.h"
#include "ppapi/c/pp_var.h"
#include "third_party/npapi/bindings/npapi.h"
#include "third_party/npapi/bindings/npruntime.h"
#include "third_party/WebKit/public/web/WebBindings.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebElement.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebPluginContainer.h"
#include "v8/include/v8.h"

using ppapi::NPObjectVar;
using ppapi::PpapiGlobals;
using ppapi::StringVar;
using ppapi::Var;
using blink::WebArrayBuffer;
using blink::WebBindings;
using blink::WebFrame;
using blink::WebPluginContainer;

namespace content {

namespace {

const char kInvalidPluginValue[] = "Error: Plugin returned invalid value.";

PP_Var NPObjectToPPVarImpl(PepperPluginInstanceImpl* instance,
                           NPObject* object,
                           v8::Local<v8::Context> context) {
  DCHECK(object);
  if (context.IsEmpty())
    return PP_MakeUndefined();
  v8::Context::Scope context_scope(context);

  WebArrayBuffer buffer;
  // TODO(dmichael): Should I protect against duplicate Vars representing the
  // same array buffer? It's probably not worth the trouble, since it will only
  // affect in-process plugins.
  if (WebBindings::getArrayBuffer(object, &buffer)) {
    scoped_refptr<HostArrayBufferVar> buffer_var(
        new HostArrayBufferVar(buffer));
    return buffer_var->GetPPVar();
  }
  scoped_refptr<NPObjectVar> object_var(
      HostGlobals::Get()->host_var_tracker()->NPObjectVarForNPObject(
          instance->pp_instance(), object));
  if (!object_var.get()) {  // No object for this module yet, make a new one.
    object_var = new NPObjectVar(instance->pp_instance(), object);
  }
  return object_var->GetPPVar();
}


}  // namespace

// Utilities -------------------------------------------------------------------

bool PPVarToNPVariant(PP_Var var, NPVariant* result) {
  switch (var.type) {
    case PP_VARTYPE_UNDEFINED:
      VOID_TO_NPVARIANT(*result);
      break;
    case PP_VARTYPE_NULL:
      NULL_TO_NPVARIANT(*result);
      break;
    case PP_VARTYPE_BOOL:
      BOOLEAN_TO_NPVARIANT(var.value.as_bool, *result);
      break;
    case PP_VARTYPE_INT32:
      INT32_TO_NPVARIANT(var.value.as_int, *result);
      break;
    case PP_VARTYPE_DOUBLE:
      DOUBLE_TO_NPVARIANT(var.value.as_double, *result);
      break;
    case PP_VARTYPE_STRING: {
      StringVar* string = StringVar::FromPPVar(var);
      if (!string) {
        VOID_TO_NPVARIANT(*result);
        return false;
      }
      const std::string& value = string->value();
      char* c_string = static_cast<char*>(malloc(value.size()));
      memcpy(c_string, value.data(), value.size());
      STRINGN_TO_NPVARIANT(c_string, value.size(), *result);
      break;
    }
    case PP_VARTYPE_OBJECT: {
      scoped_refptr<NPObjectVar> object(NPObjectVar::FromPPVar(var));
      if (!object.get()) {
        VOID_TO_NPVARIANT(*result);
        return false;
      }
      OBJECT_TO_NPVARIANT(WebBindings::retainObject(object->np_object()),
                          *result);
      break;
    }
    // The following types are not supported for use with PPB_Var_Deprecated,
    // because PPB_Var_Deprecated is only for trusted plugins, and the trusted
    // plugins we have don't need these types. We can add support in the future
    // if it becomes necessary.
    case PP_VARTYPE_ARRAY:
    case PP_VARTYPE_DICTIONARY:
    case PP_VARTYPE_ARRAY_BUFFER:
    case PP_VARTYPE_RESOURCE:
      VOID_TO_NPVARIANT(*result);
      break;
  }
  return true;
}

PP_Var NPVariantToPPVar(PepperPluginInstanceImpl* instance,
                        const NPVariant* variant) {
  switch (variant->type) {
    case NPVariantType_Void:
      return PP_MakeUndefined();
    case NPVariantType_Null:
      return PP_MakeNull();
    case NPVariantType_Bool:
      return PP_MakeBool(PP_FromBool(NPVARIANT_TO_BOOLEAN(*variant)));
    case NPVariantType_Int32:
      return PP_MakeInt32(NPVARIANT_TO_INT32(*variant));
    case NPVariantType_Double:
      return PP_MakeDouble(NPVARIANT_TO_DOUBLE(*variant));
    case NPVariantType_String:
      return StringVar::StringToPPVar(
          NPVARIANT_TO_STRING(*variant).UTF8Characters,
          NPVARIANT_TO_STRING(*variant).UTF8Length);
    case NPVariantType_Object:
      return NPObjectToPPVar(instance, NPVARIANT_TO_OBJECT(*variant));
  }
  NOTREACHED();
  return PP_MakeUndefined();
}

NPIdentifier PPVarToNPIdentifier(PP_Var var) {
  switch (var.type) {
    case PP_VARTYPE_STRING: {
      StringVar* string = StringVar::FromPPVar(var);
      if (!string)
        return NULL;
      return WebBindings::getStringIdentifier(string->value().c_str());
    }
    case PP_VARTYPE_INT32:
      return WebBindings::getIntIdentifier(var.value.as_int);
    default:
      return NULL;
  }
}

PP_Var NPIdentifierToPPVar(NPIdentifier id) {
  const NPUTF8* string_value = NULL;
  int32_t int_value = 0;
  bool is_string = false;
  WebBindings::extractIdentifierData(id, string_value, int_value, is_string);
  if (is_string)
    return StringVar::StringToPPVar(string_value);

  return PP_MakeInt32(int_value);
}

PP_Var NPObjectToPPVar(PepperPluginInstanceImpl* instance, NPObject* object) {
  WebPluginContainer* container = instance->container();
  // It's possible that container() is NULL if the plugin has been removed from
  // the DOM (but the PluginInstance is not destroyed yet).
  if (!container)
    return PP_MakeUndefined();
  WebFrame* frame = container->element().document().frame();
  if (!frame)
    return PP_MakeUndefined();

  v8::HandleScope scope(instance->GetIsolate());
  v8::Local<v8::Context> context = frame->mainWorldScriptContext();
  return NPObjectToPPVarImpl(instance, object, context);
}

PP_Var NPObjectToPPVarForTest(PepperPluginInstanceImpl* instance,
                              NPObject* object) {
  v8::Isolate* test_isolate = v8::Isolate::New();
  PP_Var result = PP_MakeUndefined();
  {
    v8::HandleScope scope(test_isolate);
    v8::Isolate::Scope isolate_scope(test_isolate);
    v8::Local<v8::Context> context = v8::Context::New(test_isolate);
    result = NPObjectToPPVarImpl(instance, object, context);
  }
  test_isolate->Dispose();
  return result;
}

// PPResultAndExceptionToNPResult ----------------------------------------------

PPResultAndExceptionToNPResult::PPResultAndExceptionToNPResult(
    NPObject* object_var,
    NPVariant* np_result)
    : object_var_(object_var),
      np_result_(np_result),
      exception_(PP_MakeUndefined()),
      success_(false),
      checked_exception_(false) {
}

PPResultAndExceptionToNPResult::~PPResultAndExceptionToNPResult() {
  // The user should have called SetResult or CheckExceptionForNoResult
  // before letting this class go out of scope, or the exception will have
  // been lost.
  DCHECK(checked_exception_);

  PpapiGlobals::Get()->GetVarTracker()->ReleaseVar(exception_);
}

// Call this with the return value of the PPAPI function. It will convert
// the result to the NPVariant output parameter and pass any exception on to
// the JS engine. It will update the success flag and return it.
bool PPResultAndExceptionToNPResult::SetResult(PP_Var result) {
  DCHECK(!checked_exception_);  // Don't call more than once.
  DCHECK(np_result_);  // Should be expecting a result.

  checked_exception_ = true;

  if (has_exception()) {
    ThrowException();
    success_ = false;
  } else if (!PPVarToNPVariant(result, np_result_)) {
    WebBindings::setException(object_var_, kInvalidPluginValue);
    success_ = false;
  } else {
    success_ = true;
  }

  // No matter what happened, we need to release the reference to the
  // value passed in. On success, a reference to this value will be in
  // the np_result_.
  PpapiGlobals::Get()->GetVarTracker()->ReleaseVar(result);
  return success_;
}

// Call this after calling a PPAPI function that could have set the
// exception. It will pass the exception on to the JS engine and update
// the success flag.
//
// The success flag will be returned.
bool PPResultAndExceptionToNPResult::CheckExceptionForNoResult() {
  DCHECK(!checked_exception_);  // Don't call more than once.
  DCHECK(!np_result_);  // Can't have a result when doing this.

  checked_exception_ = true;

  if (has_exception()) {
    ThrowException();
    success_ = false;
    return false;
  }
  success_ = true;
  return true;
}

// Call this to ignore any exception. This prevents the DCHECK from failing
// in the destructor.
void PPResultAndExceptionToNPResult::IgnoreException() {
  checked_exception_ = true;
}

// Throws the current exception to JS. The exception must be set.
void PPResultAndExceptionToNPResult::ThrowException() {
  StringVar* string = StringVar::FromPPVar(exception_);
  if (string)
    WebBindings::setException(object_var_, string->value().c_str());
}

// PPVarArrayFromNPVariantArray ------------------------------------------------

PPVarArrayFromNPVariantArray::PPVarArrayFromNPVariantArray(
    PepperPluginInstanceImpl* instance,
    size_t size,
    const NPVariant* variants)
    : size_(size) {
  if (size_ > 0) {
    array_.reset(new PP_Var[size_]);
    for (size_t i = 0; i < size_; i++)
      array_[i] = NPVariantToPPVar(instance, &variants[i]);
  }
}

PPVarArrayFromNPVariantArray::~PPVarArrayFromNPVariantArray() {
  ppapi::VarTracker* var_tracker = PpapiGlobals::Get()->GetVarTracker();
  for (size_t i = 0; i < size_; i++)
    var_tracker->ReleaseVar(array_[i]);
}

// PPVarFromNPObject -----------------------------------------------------------

PPVarFromNPObject::PPVarFromNPObject(PepperPluginInstanceImpl* instance,
                                     NPObject* object)
    : var_(NPObjectToPPVar(instance, object)) {
}

PPVarFromNPObject::~PPVarFromNPObject() {
  PpapiGlobals::Get()->GetVarTracker()->ReleaseVar(var_);
}

// NPObjectAccessorWithIdentifier ----------------------------------------------

NPObjectAccessorWithIdentifier::NPObjectAccessorWithIdentifier(
    NPObject* object,
    NPIdentifier identifier,
    bool allow_integer_identifier)
    : object_(PluginObject::FromNPObject(object)),
      identifier_(PP_MakeUndefined()) {
  if (object_) {
    identifier_ = NPIdentifierToPPVar(identifier);
    if (identifier_.type == PP_VARTYPE_INT32 && !allow_integer_identifier)
      identifier_.type = PP_VARTYPE_UNDEFINED;  // Mark it invalid.
  }
}

NPObjectAccessorWithIdentifier::~NPObjectAccessorWithIdentifier() {
  PpapiGlobals::Get()->GetVarTracker()->ReleaseVar(identifier_);
}

// TryCatch --------------------------------------------------------------------

TryCatch::TryCatch(PP_Var* exception)
    : has_exception_(exception && exception->type != PP_VARTYPE_UNDEFINED),
      exception_(exception) {
  WebBindings::pushExceptionHandler(&TryCatch::Catch, this);
}

TryCatch::~TryCatch() {
  WebBindings::popExceptionHandler();
}

void TryCatch::SetException(const char* message) {
  if (!has_exception()) {
    has_exception_ = true;
    if (exception_) {
      *exception_ = ppapi::StringVar::StringToPPVar(message, strlen(message));
    }
  }
}

// static
void TryCatch::Catch(void* self, const char* message) {
  static_cast<TryCatch*>(self)->SetException(message);
}

}  // namespace content