// 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/plugin_object.h"

#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "content/renderer/pepper/npapi_glue.h"
#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
#include "content/renderer/pepper/plugin_module.h"
#include "ppapi/c/dev/ppb_var_deprecated.h"
#include "ppapi/c/dev/ppp_class_deprecated.h"
#include "ppapi/c/pp_resource.h"
#include "ppapi/c/pp_var.h"
#include "ppapi/shared_impl/ppapi_globals.h"
#include "ppapi/shared_impl/resource_tracker.h"
#include "ppapi/shared_impl/var.h"
#include "ppapi/shared_impl/var_tracker.h"
#include "third_party/WebKit/public/web/WebBindings.h"
#include "third_party/npapi/bindings/npapi.h"
#include "third_party/npapi/bindings/npruntime.h"

using ppapi::PpapiGlobals;
using ppapi::StringVar;
using ppapi::Var;
using blink::WebBindings;

namespace content {

namespace {

const char kInvalidValueException[] = "Error: Invalid value";

// NPObject implementation in terms of PPP_Class_Deprecated --------------------

NPObject* WrapperClass_Allocate(NPP npp, NPClass* unused) {
  return PluginObject::AllocateObjectWrapper();
}

void WrapperClass_Deallocate(NPObject* np_object) {
  PluginObject* plugin_object = PluginObject::FromNPObject(np_object);
  if (!plugin_object)
    return;
  plugin_object->ppp_class()->Deallocate(plugin_object->ppp_class_data());
  delete plugin_object;
}

void WrapperClass_Invalidate(NPObject* object) {
}

bool WrapperClass_HasMethod(NPObject* object, NPIdentifier method_name) {
  NPObjectAccessorWithIdentifier accessor(object, method_name, false);
  if (!accessor.is_valid())
    return false;

  PPResultAndExceptionToNPResult result_converter(
      accessor.object()->GetNPObject(), NULL);
  bool rv = accessor.object()->ppp_class()->HasMethod(
      accessor.object()->ppp_class_data(), accessor.identifier(),
      result_converter.exception());
  result_converter.CheckExceptionForNoResult();
  return rv;
}

bool WrapperClass_Invoke(NPObject* object, NPIdentifier method_name,
                         const NPVariant* argv, uint32_t argc,
                         NPVariant* result) {
  NPObjectAccessorWithIdentifier accessor(object, method_name, false);
  if (!accessor.is_valid())
    return false;

  PPResultAndExceptionToNPResult result_converter(
      accessor.object()->GetNPObject(), result);
  PPVarArrayFromNPVariantArray args(accessor.object()->instance(),
                                    argc, argv);

  // For the OOP plugin case we need to grab a reference on the plugin module
  // object to ensure that it is not destroyed courtsey an incoming
  // ExecuteScript call which destroys the plugin module and in turn the
  // dispatcher.
  scoped_refptr<PluginModule> ref(accessor.object()->instance()->module());

  return result_converter.SetResult(accessor.object()->ppp_class()->Call(
      accessor.object()->ppp_class_data(), accessor.identifier(),
      argc, args.array(), result_converter.exception()));
}

bool WrapperClass_InvokeDefault(NPObject* np_object, const NPVariant* argv,
                                uint32_t argc, NPVariant* result) {
  PluginObject* obj = PluginObject::FromNPObject(np_object);
  if (!obj)
    return false;

  PPVarArrayFromNPVariantArray args(obj->instance(), argc, argv);
  PPResultAndExceptionToNPResult result_converter(obj->GetNPObject(), result);

  // For the OOP plugin case we need to grab a reference on the plugin module
  // object to ensure that it is not destroyed courtsey an incoming
  // ExecuteScript call which destroys the plugin module and in turn the
  // dispatcher.
  scoped_refptr<PluginModule> ref(obj->instance()->module());

  result_converter.SetResult(obj->ppp_class()->Call(
      obj->ppp_class_data(), PP_MakeUndefined(), argc, args.array(),
      result_converter.exception()));
  return result_converter.success();
}

bool WrapperClass_HasProperty(NPObject* object, NPIdentifier property_name) {
  NPObjectAccessorWithIdentifier accessor(object, property_name, true);
  if (!accessor.is_valid())
    return false;

  PPResultAndExceptionToNPResult result_converter(
      accessor.object()->GetNPObject(), NULL);
  bool rv = accessor.object()->ppp_class()->HasProperty(
      accessor.object()->ppp_class_data(), accessor.identifier(),
      result_converter.exception());
  result_converter.CheckExceptionForNoResult();
  return rv;
}

bool WrapperClass_GetProperty(NPObject* object, NPIdentifier property_name,
                              NPVariant* result) {
  NPObjectAccessorWithIdentifier accessor(object, property_name, true);
  if (!accessor.is_valid())
    return false;

  PPResultAndExceptionToNPResult result_converter(
      accessor.object()->GetNPObject(), result);
  return result_converter.SetResult(accessor.object()->ppp_class()->GetProperty(
      accessor.object()->ppp_class_data(), accessor.identifier(),
      result_converter.exception()));
}

bool WrapperClass_SetProperty(NPObject* object, NPIdentifier property_name,
                              const NPVariant* value) {
  NPObjectAccessorWithIdentifier accessor(object, property_name, true);
  if (!accessor.is_valid())
    return false;

  PPResultAndExceptionToNPResult result_converter(
      accessor.object()->GetNPObject(), NULL);
  PP_Var value_var = NPVariantToPPVar(accessor.object()->instance(), value);
  accessor.object()->ppp_class()->SetProperty(
      accessor.object()->ppp_class_data(), accessor.identifier(), value_var,
      result_converter.exception());
  PpapiGlobals::Get()->GetVarTracker()->ReleaseVar(value_var);
  return result_converter.CheckExceptionForNoResult();
}

bool WrapperClass_RemoveProperty(NPObject* object, NPIdentifier property_name) {
  NPObjectAccessorWithIdentifier accessor(object, property_name, true);
  if (!accessor.is_valid())
    return false;

  PPResultAndExceptionToNPResult result_converter(
      accessor.object()->GetNPObject(), NULL);
  accessor.object()->ppp_class()->RemoveProperty(
      accessor.object()->ppp_class_data(), accessor.identifier(),
      result_converter.exception());
  return result_converter.CheckExceptionForNoResult();
}

bool WrapperClass_Enumerate(NPObject* object, NPIdentifier** values,
                            uint32_t* count) {
  *values = NULL;
  *count = 0;
  PluginObject* obj = PluginObject::FromNPObject(object);
  if (!obj)
    return false;

  uint32_t property_count = 0;
  PP_Var* properties = NULL;  // Must be freed!
  PPResultAndExceptionToNPResult result_converter(obj->GetNPObject(), NULL);
  obj->ppp_class()->GetAllPropertyNames(obj->ppp_class_data(),
                                        &property_count, &properties,
                                        result_converter.exception());

  // Convert the array of PP_Var to an array of NPIdentifiers. If any
  // conversions fail, we will set the exception.
  if (!result_converter.has_exception()) {
    if (property_count > 0) {
      *values = static_cast<NPIdentifier*>(
          malloc(sizeof(NPIdentifier) * property_count));
      *count = 0;  // Will be the number of items successfully converted.
      for (uint32_t i = 0; i < property_count; ++i) {
        if (!((*values)[i] = PPVarToNPIdentifier(properties[i]))) {
          // Throw an exception for the failed convertion.
          *result_converter.exception() =
              StringVar::StringToPPVar(kInvalidValueException);
          break;
        }
        (*count)++;
      }

      if (result_converter.has_exception()) {
        // We don't actually have to free the identifiers we converted since
        // all identifiers leak anyway :( .
        free(*values);
        *values = NULL;
        *count = 0;
      }
    }
  }

  // This will actually throw the exception, either from GetAllPropertyNames,
  // or if anything was set during the conversion process.
  result_converter.CheckExceptionForNoResult();

  // Release the PP_Var that the plugin allocated. On success, they will all
  // be converted to NPVariants, and on failure, we want them to just go away.
  ppapi::VarTracker* var_tracker = PpapiGlobals::Get()->GetVarTracker();
  for (uint32_t i = 0; i < property_count; ++i)
    var_tracker->ReleaseVar(properties[i]);
  free(properties);
  return result_converter.success();
}

bool WrapperClass_Construct(NPObject* object, const NPVariant* argv,
                            uint32_t argc, NPVariant* result) {
  PluginObject* obj = PluginObject::FromNPObject(object);
  if (!obj)
    return false;

  PPVarArrayFromNPVariantArray args(obj->instance(), argc, argv);
  PPResultAndExceptionToNPResult result_converter(obj->GetNPObject(), result);
  return result_converter.SetResult(obj->ppp_class()->Construct(
      obj->ppp_class_data(), argc, args.array(),
      result_converter.exception()));
}

const NPClass wrapper_class = {
  NP_CLASS_STRUCT_VERSION,
  WrapperClass_Allocate,
  WrapperClass_Deallocate,
  WrapperClass_Invalidate,
  WrapperClass_HasMethod,
  WrapperClass_Invoke,
  WrapperClass_InvokeDefault,
  WrapperClass_HasProperty,
  WrapperClass_GetProperty,
  WrapperClass_SetProperty,
  WrapperClass_RemoveProperty,
  WrapperClass_Enumerate,
  WrapperClass_Construct
};

}  // namespace

// PluginObject ----------------------------------------------------------------

struct PluginObject::NPObjectWrapper : public NPObject {
  // Points to the var object that owns this wrapper. This value may be NULL
  // if there is no var owning this wrapper. This can happen if the plugin
  // releases all references to the var, but a reference to the underlying
  // NPObject is still held by script on the page.
  PluginObject* obj;
};

PluginObject::PluginObject(PepperPluginInstanceImpl* instance,
                           NPObjectWrapper* object_wrapper,
                           const PPP_Class_Deprecated* ppp_class,
                           void* ppp_class_data)
    : instance_(instance),
      object_wrapper_(object_wrapper),
      ppp_class_(ppp_class),
      ppp_class_data_(ppp_class_data) {
  // Make the object wrapper refer back to this class so our NPObject
  // implementation can call back into the Pepper layer.
  object_wrapper_->obj = this;
  instance_->AddPluginObject(this);
}

PluginObject::~PluginObject() {
  // The wrapper we made for this NPObject may still have a reference to it
  // from JavaScript, so we clear out its ObjectVar back pointer which will
  // cause all calls "up" to the plugin to become NOPs. Our ObjectVar base
  // class will release our reference to the object, which may or may not
  // delete the NPObject.
  DCHECK(object_wrapper_->obj == this);
  object_wrapper_->obj = NULL;
  instance_->RemovePluginObject(this);
}

PP_Var PluginObject::Create(PepperPluginInstanceImpl* instance,
                            const PPP_Class_Deprecated* ppp_class,
                            void* ppp_class_data) {
  // This will internally end up calling our AllocateObjectWrapper via the
  // WrapperClass_Allocated function which will have created an object wrapper
  // appropriate for this class (derived from NPObject).
  NPObjectWrapper* wrapper = static_cast<NPObjectWrapper*>(
      WebBindings::createObject(instance->instanceNPP(),
      const_cast<NPClass*>(&wrapper_class)));

  // This object will register itself both with the NPObject and with the
  // PluginModule. The NPObject will normally handle its lifetime, and it
  // will get deleted in the destroy method. It may also get deleted when the
  // plugin module is deallocated.
  new PluginObject(instance, wrapper, ppp_class, ppp_class_data);

  // We can just use a normal ObjectVar to refer to this object from the
  // plugin. It will hold a ref to the underlying NPObject which will in turn
  // hold our pluginObject.
  PP_Var obj_var(NPObjectToPPVar(instance, wrapper));

  // Note that the ObjectVar constructor incremented the reference count, and so
  // did WebBindings::createObject above. Now that the PP_Var has taken
  // ownership, we need to release to balance out the createObject reference
  // count bump.
  WebBindings::releaseObject(wrapper);
  return obj_var;
}

NPObject* PluginObject::GetNPObject() const {
  return object_wrapper_;
}

// static
bool PluginObject::IsInstanceOf(NPObject* np_object,
                                const PPP_Class_Deprecated* ppp_class,
                                void** ppp_class_data) {
  // Validate that this object is implemented by our wrapper class before
  // trying to get the PluginObject.
  if (np_object->_class != &wrapper_class)
    return false;

  PluginObject* plugin_object = FromNPObject(np_object);
  if (!plugin_object)
    return false;  // Object is no longer alive.

  if (plugin_object->ppp_class() != ppp_class)
    return false;
  if (ppp_class_data)
    *ppp_class_data = plugin_object->ppp_class_data();
  return true;
}

// static
PluginObject* PluginObject::FromNPObject(NPObject* object) {
  return static_cast<NPObjectWrapper*>(object)->obj;
}

// static
NPObject* PluginObject::AllocateObjectWrapper() {
  NPObjectWrapper* wrapper = new NPObjectWrapper;
  memset(wrapper, 0, sizeof(NPObjectWrapper));
  return wrapper;
}

}  // namespace content