/*
* Copyright (C) 2010 Google Inc. All rights reserved.
* Copyright (C) 2009 Pawel Hajdan (phajdan.jr@chromium.org)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// 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 "config.h"
#include "CppBoundClass.h"
#include "WebBindings.h"
#include "WebFrame.h"
#include "WebString.h"
#include <wtf/Assertions.h>
#include <wtf/OwnPtr.h>
using namespace WebKit;
using namespace std;
class CppVariantPropertyCallback : public CppBoundClass::PropertyCallback {
public:
CppVariantPropertyCallback(CppVariant* value) : m_value(value) { }
virtual bool getValue(CppVariant* value)
{
value->set(*m_value);
return true;
}
virtual bool setValue(const CppVariant& value)
{
m_value->set(value);
return true;
}
private:
CppVariant* m_value;
};
class GetterPropertyCallback : public CppBoundClass::PropertyCallback {
public:
GetterPropertyCallback(CppBoundClass::GetterCallback* callback)
: m_callback(callback) { }
virtual bool getValue(CppVariant* value)
{
m_callback->run(value);
return true;
}
virtual bool setValue(const CppVariant& value) { return false; }
private:
OwnPtr<CppBoundClass::GetterCallback> m_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* boundClass;
//
// 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 npClass;
// Allocate a new NPObject with the specified class.
static NPObject* allocate(NPP, NPClass*);
// Free an object.
static void deallocate(NPObject*);
// Returns true if the C++ class associated with this NPObject exposes the
// given property. Called by the JS runtime.
static bool hasProperty(NPObject*, NPIdentifier);
// Returns true if the C++ class associated with this NPObject exposes the
// given method. Called by the JS runtime.
static bool hasMethod(NPObject*, NPIdentifier);
// If the given method is exposed by the C++ class associated with this
// NPObject, invokes it with the given arguments and returns a result. Otherwise,
// returns "undefined" (in the JavaScript sense). Called by the JS runtime.
static bool invoke(NPObject*, NPIdentifier,
const NPVariant* arguments, uint32_t argumentCount,
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*, NPIdentifier, 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*, NPIdentifier, const NPVariant* value);
};
// Build CppNPObject's static function pointers into an NPClass, for use
// in constructing NPObjects for the C++ classes.
NPClass CppNPObject::npClass = {
NP_CLASS_STRUCT_VERSION,
CppNPObject::allocate,
CppNPObject::deallocate,
/* NPInvalidateFunctionPtr */ 0,
CppNPObject::hasMethod,
CppNPObject::invoke,
/* NPInvokeDefaultFunctionPtr */ 0,
CppNPObject::hasProperty,
CppNPObject::getProperty,
CppNPObject::setProperty,
/* NPRemovePropertyFunctionPtr */ 0
};
NPObject* CppNPObject::allocate(NPP npp, NPClass* aClass)
{
CppNPObject* obj = new CppNPObject;
// obj->parent will be initialized by the NPObject code calling this.
obj->boundClass = 0;
return &obj->parent;
}
void CppNPObject::deallocate(NPObject* npObj)
{
CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
delete obj;
}
bool CppNPObject::hasMethod(NPObject* npObj, NPIdentifier ident)
{
CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
return obj->boundClass->hasMethod(ident);
}
bool CppNPObject::hasProperty(NPObject* npObj, NPIdentifier ident)
{
CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
return obj->boundClass->hasProperty(ident);
}
bool CppNPObject::invoke(NPObject* npObj, NPIdentifier ident,
const NPVariant* arguments, uint32_t argumentCount,
NPVariant* result)
{
CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
return obj->boundClass->invoke(ident, arguments, argumentCount, result);
}
bool CppNPObject::getProperty(NPObject* npObj, NPIdentifier ident, NPVariant* result)
{
CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
return obj->boundClass->getProperty(ident, result);
}
bool CppNPObject::setProperty(NPObject* npObj, NPIdentifier ident, const NPVariant* value)
{
CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
return obj->boundClass->setProperty(ident, value);
}
CppBoundClass::~CppBoundClass()
{
for (MethodList::iterator i = m_methods.begin(); i != m_methods.end(); ++i)
delete i->second;
for (PropertyList::iterator i = m_properties.begin(); i != m_properties.end(); ++i)
delete i->second;
// Unregister ourselves if we were bound to a frame.
if (m_boundToFrame)
WebBindings::unregisterObject(NPVARIANT_TO_OBJECT(m_selfVariant));
}
bool CppBoundClass::hasMethod(NPIdentifier ident) const
{
return m_methods.find(ident) != m_methods.end();
}
bool CppBoundClass::hasProperty(NPIdentifier ident) const
{
return m_properties.find(ident) != m_properties.end();
}
bool CppBoundClass::invoke(NPIdentifier ident,
const NPVariant* arguments,
size_t argumentCount,
NPVariant* result) {
MethodList::const_iterator end = m_methods.end();
MethodList::const_iterator method = m_methods.find(ident);
Callback* callback;
if (method == end) {
if (!m_fallbackCallback.get()) {
VOID_TO_NPVARIANT(*result);
return false;
}
callback = m_fallbackCallback.get();
} else
callback = (*method).second;
// Build a CppArgumentList argument vector from the NPVariants coming in.
CppArgumentList cppArguments(argumentCount);
for (size_t i = 0; i < argumentCount; i++)
cppArguments[i].set(arguments[i]);
CppVariant cppResult;
callback->run(cppArguments, &cppResult);
cppResult.copyToNPVariant(result);
return true;
}
bool CppBoundClass::getProperty(NPIdentifier ident, NPVariant* result) const
{
PropertyList::const_iterator callback = m_properties.find(ident);
if (callback == m_properties.end()) {
VOID_TO_NPVARIANT(*result);
return false;
}
CppVariant cppValue;
if (!callback->second->getValue(&cppValue))
return false;
cppValue.copyToNPVariant(result);
return true;
}
bool CppBoundClass::setProperty(NPIdentifier ident, const NPVariant* value)
{
PropertyList::iterator callback = m_properties.find(ident);
if (callback == m_properties.end())
return false;
CppVariant cppValue;
cppValue.set(*value);
return (*callback).second->setValue(cppValue);
}
void CppBoundClass::bindCallback(const string& name, Callback* callback)
{
NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
MethodList::iterator oldCallback = m_methods.find(ident);
if (oldCallback != m_methods.end()) {
delete oldCallback->second;
if (!callback) {
m_methods.remove(oldCallback);
return;
}
}
m_methods.set(ident, callback);
}
void CppBoundClass::bindGetterCallback(const string& name, GetterCallback* callback)
{
PropertyCallback* propertyCallback = callback ? new GetterPropertyCallback(callback) : 0;
bindProperty(name, propertyCallback);
}
void CppBoundClass::bindProperty(const string& name, CppVariant* prop)
{
PropertyCallback* propertyCallback = prop ? new CppVariantPropertyCallback(prop) : 0;
bindProperty(name, propertyCallback);
}
void CppBoundClass::bindProperty(const string& name, PropertyCallback* callback)
{
NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
PropertyList::iterator oldCallback = m_properties.find(ident);
if (oldCallback != m_properties.end()) {
delete oldCallback->second;
if (!callback) {
m_properties.remove(oldCallback);
return;
}
}
m_properties.set(ident, callback);
}
bool CppBoundClass::isMethodRegistered(const string& name) const
{
NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
MethodList::const_iterator callback = m_methods.find(ident);
return callback != m_methods.end();
}
CppVariant* CppBoundClass::getAsCppVariant()
{
if (!m_selfVariant.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* npObj = WebBindings::createObject(0, &CppNPObject::npClass);
CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
obj->boundClass = this;
m_selfVariant.set(npObj);
WebBindings::releaseObject(npObj); // CppVariant takes the reference.
}
ASSERT(m_selfVariant.isObject());
return &m_selfVariant;
}
void CppBoundClass::bindToJavascript(WebFrame* frame, const WebString& classname)
{
// 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(classname, NPVARIANT_TO_OBJECT(*getAsCppVariant()));
m_boundToFrame = true;
}