// Copyright (c) 2006-2008 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.
// This file contains definitions for CppBoundClass
// Here's the control flow of a JS method getting forwarded to a class.
// - Something calls our NPObject with a function like "Invoke".
// - CppNPObject's static invoke() function forwards it to its attached
// CppBoundClass's Invoke() method.
// - CppBoundClass has then overridden Invoke() to look up the function
// name in its internal map of methods, and then calls the appropriate
// method.
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/utf_string_conversions.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
#include "webkit/glue/cpp_bound_class.h"
using WebKit::WebBindings;
using WebKit::WebFrame;
namespace {
class CppVariantPropertyCallback : public CppBoundClass::PropertyCallback {
public:
CppVariantPropertyCallback(CppVariant* value) : value_(value) { }
virtual bool GetValue(CppVariant* value) {
value->Set(*value_);
return true;
}
virtual bool SetValue(const CppVariant& value) {
value_->Set(value);
return true;
}
private:
CppVariant* value_;
};
class GetterPropertyCallback : public CppBoundClass::PropertyCallback {
public:
GetterPropertyCallback(CppBoundClass::GetterCallback* callback)
: callback_(callback) { }
virtual bool GetValue(CppVariant* value) {
callback_->Run(value);
return true;
}
virtual bool SetValue(const CppVariant& value) {
return false;
}
private:
scoped_ptr<CppBoundClass::GetterCallback> callback_;
};
}
// Our special NPObject type. We extend an NPObject with a pointer to a
// CppBoundClass, which is just a C++ interface that we forward all NPObject
// callbacks to.
struct CppNPObject {
NPObject parent; // This must be the first field in the struct.
CppBoundClass* bound_class;
//
// All following objects and functions are static, and just used to interface
// with NPObject/NPClass.
//
// An NPClass associates static functions of CppNPObject with the
// function pointers used by the JS runtime.
static NPClass np_class_;
// Allocate a new NPObject with the specified class.
static NPObject* allocate(NPP npp, NPClass* aClass);
// Free an object.
static void deallocate(NPObject* obj);
// Returns true if the C++ class associated with this NPObject exposes the
// given property. Called by the JS runtime.
static bool hasProperty(NPObject *obj, NPIdentifier ident);
// Returns true if the C++ class associated with this NPObject exposes the
// given method. Called by the JS runtime.
static bool hasMethod(NPObject *obj, NPIdentifier ident);
// If the given method is exposed by the C++ class associated with this
// NPObject, invokes it with the given args and returns a result. Otherwise,
// returns "undefined" (in the JavaScript sense). Called by the JS runtime.
static bool invoke(NPObject *obj, NPIdentifier ident,
const NPVariant *args, uint32_t arg_count,
NPVariant *result);
// If the given property is exposed by the C++ class associated with this
// NPObject, returns its value. Otherwise, returns "undefined" (in the
// JavaScript sense). Called by the JS runtime.
static bool getProperty(NPObject *obj, NPIdentifier ident,
NPVariant *result);
// If the given property is exposed by the C++ class associated with this
// NPObject, sets its value. Otherwise, does nothing. Called by the JS
// runtime.
static bool setProperty(NPObject *obj, NPIdentifier ident,
const NPVariant *value);
};
// Build CppNPObject's static function pointers into an NPClass, for use
// in constructing NPObjects for the C++ classes.
NPClass CppNPObject::np_class_ = {
NP_CLASS_STRUCT_VERSION,
CppNPObject::allocate,
CppNPObject::deallocate,
/* NPInvalidateFunctionPtr */ NULL,
CppNPObject::hasMethod,
CppNPObject::invoke,
/* NPInvokeDefaultFunctionPtr */ NULL,
CppNPObject::hasProperty,
CppNPObject::getProperty,
CppNPObject::setProperty,
/* NPRemovePropertyFunctionPtr */ NULL
};
/* static */ NPObject* CppNPObject::allocate(NPP npp, NPClass* aClass) {
CppNPObject* obj = new CppNPObject;
// obj->parent will be initialized by the NPObject code calling this.
obj->bound_class = NULL;
return &obj->parent;
}
/* static */ void CppNPObject::deallocate(NPObject* np_obj) {
CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
delete obj;
}
/* static */ bool CppNPObject::hasMethod(NPObject* np_obj,
NPIdentifier ident) {
CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
return obj->bound_class->HasMethod(ident);
}
/* static */ bool CppNPObject::hasProperty(NPObject* np_obj,
NPIdentifier ident) {
CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
return obj->bound_class->HasProperty(ident);
}
/* static */ bool CppNPObject::invoke(NPObject* np_obj, NPIdentifier ident,
const NPVariant* args, uint32_t arg_count,
NPVariant* result) {
CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
return obj->bound_class->Invoke(ident, args, arg_count, result);
}
/* static */ bool CppNPObject::getProperty(NPObject* np_obj,
NPIdentifier ident,
NPVariant* result) {
CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
return obj->bound_class->GetProperty(ident, result);
}
/* static */ bool CppNPObject::setProperty(NPObject* np_obj,
NPIdentifier ident,
const NPVariant* value) {
CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
return obj->bound_class->SetProperty(ident, value);
}
CppBoundClass::CppBoundClass()
: bound_to_frame_(false) {
}
CppBoundClass::~CppBoundClass() {
for (MethodList::iterator i = methods_.begin(); i != methods_.end(); ++i)
delete i->second;
for (PropertyList::iterator i = properties_.begin(); i != properties_.end();
++i) {
delete i->second;
}
// Unregister ourselves if we were bound to a frame.
if (bound_to_frame_)
WebBindings::unregisterObject(NPVARIANT_TO_OBJECT(self_variant_));
}
bool CppBoundClass::HasMethod(NPIdentifier ident) const {
return (methods_.find(ident) != methods_.end());
}
bool CppBoundClass::HasProperty(NPIdentifier ident) const {
return (properties_.find(ident) != properties_.end());
}
bool CppBoundClass::Invoke(NPIdentifier ident,
const NPVariant* args,
size_t arg_count,
NPVariant* result) {
MethodList::const_iterator method = methods_.find(ident);
Callback* callback;
if (method == methods_.end()) {
if (fallback_callback_.get()) {
callback = fallback_callback_.get();
} else {
VOID_TO_NPVARIANT(*result);
return false;
}
} else {
callback = (*method).second;
}
// Build a CppArgumentList argument vector from the NPVariants coming in.
CppArgumentList cpp_args(arg_count);
for (size_t i = 0; i < arg_count; i++)
cpp_args[i].Set(args[i]);
CppVariant cpp_result;
callback->Run(cpp_args, &cpp_result);
cpp_result.CopyToNPVariant(result);
return true;
}
bool CppBoundClass::GetProperty(NPIdentifier ident, NPVariant* result) const {
PropertyList::const_iterator callback = properties_.find(ident);
if (callback == properties_.end()) {
VOID_TO_NPVARIANT(*result);
return false;
}
CppVariant cpp_value;
if (!callback->second->GetValue(&cpp_value))
return false;
cpp_value.CopyToNPVariant(result);
return true;
}
bool CppBoundClass::SetProperty(NPIdentifier ident,
const NPVariant* value) {
PropertyList::iterator callback = properties_.find(ident);
if (callback == properties_.end())
return false;
CppVariant cpp_value;
cpp_value.Set(*value);
return (*callback).second->SetValue(cpp_value);
}
void CppBoundClass::BindCallback(const std::string& name, Callback* callback) {
NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
MethodList::iterator old_callback = methods_.find(ident);
if (old_callback != methods_.end()) {
delete old_callback->second;
if (callback == NULL) {
methods_.erase(old_callback);
return;
}
}
methods_[ident] = callback;
}
void CppBoundClass::BindGetterCallback(const std::string& name,
GetterCallback* callback) {
PropertyCallback* property_callback = callback == NULL ?
NULL : new GetterPropertyCallback(callback);
BindProperty(name, property_callback);
}
void CppBoundClass::BindProperty(const std::string& name, CppVariant* prop) {
PropertyCallback* property_callback = prop == NULL ?
NULL : new CppVariantPropertyCallback(prop);
BindProperty(name, property_callback);
}
void CppBoundClass::BindProperty(const std::string& name,
PropertyCallback* callback) {
NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
PropertyList::iterator old_callback = properties_.find(ident);
if (old_callback != properties_.end()) {
delete old_callback->second;
if (callback == NULL) {
properties_.erase(old_callback);
return;
}
}
properties_[ident] = callback;
}
bool CppBoundClass::IsMethodRegistered(const std::string& name) const {
NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
MethodList::const_iterator callback = methods_.find(ident);
return (callback != methods_.end());
}
CppVariant* CppBoundClass::GetAsCppVariant() {
if (!self_variant_.isObject()) {
// Create an NPObject using our static NPClass. The first argument (a
// plugin's instance handle) is passed through to the allocate function
// directly, and we don't use it, so it's ok to be 0.
NPObject* np_obj = WebBindings::createObject(0, &CppNPObject::np_class_);
CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
obj->bound_class = this;
self_variant_.Set(np_obj);
WebBindings::releaseObject(np_obj); // CppVariant takes the reference.
}
DCHECK(self_variant_.isObject());
return &self_variant_;
}
void CppBoundClass::BindToJavascript(WebFrame* frame,
const std::string& classname) {
#if WEBKIT_USING_JSC
#error "This is not going to work anymore...but it's not clear what the solution is...or if it's still necessary."
JSC::JSLock lock(false);
#endif
// BindToWindowObject will take its own reference to the NPObject, and clean
// up after itself. It will also (indirectly) register the object with V8,
// so we must remember this so we can unregister it when we're destroyed.
frame->bindToWindowObject(ASCIIToUTF16(classname),
NPVARIANT_TO_OBJECT(*GetAsCppVariant()));
bound_to_frame_ = true;
}