1/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 * Copyright (C) 2009 Pawel Hajdan (phajdan.jr@chromium.org)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32// This file contains definitions for CppBoundClass
33
34// Here's the control flow of a JS method getting forwarded to a class.
35// - Something calls our NPObject with a function like "Invoke".
36// - CppNPObject's static invoke() function forwards it to its attached
37//   CppBoundClass's invoke() method.
38// - CppBoundClass has then overridden invoke() to look up the function
39//   name in its internal map of methods, and then calls the appropriate
40//   method.
41
42#include "config.h"
43#include "CppBoundClass.h"
44
45#include "WebBindings.h"
46#include "WebFrame.h"
47#include "WebString.h"
48#include <wtf/Assertions.h>
49#include <wtf/OwnPtr.h>
50
51using namespace WebKit;
52using namespace std;
53
54class CppVariantPropertyCallback : public CppBoundClass::PropertyCallback {
55public:
56    CppVariantPropertyCallback(CppVariant* value) : m_value(value) { }
57
58    virtual bool getValue(CppVariant* value)
59    {
60        value->set(*m_value);
61        return true;
62    }
63
64    virtual bool setValue(const CppVariant& value)
65    {
66        m_value->set(value);
67        return true;
68    }
69
70private:
71    CppVariant* m_value;
72};
73
74class GetterPropertyCallback : public CppBoundClass::PropertyCallback {
75public:
76    GetterPropertyCallback(CppBoundClass::GetterCallback* callback)
77        : m_callback(callback) { }
78
79    virtual bool getValue(CppVariant* value)
80    {
81        m_callback->run(value);
82        return true;
83    }
84
85    virtual bool setValue(const CppVariant& value) { return false; }
86
87private:
88    OwnPtr<CppBoundClass::GetterCallback> m_callback;
89};
90
91// Our special NPObject type.  We extend an NPObject with a pointer to a
92// CppBoundClass, which is just a C++ interface that we forward all NPObject
93// callbacks to.
94struct CppNPObject {
95    NPObject parent; // This must be the first field in the struct.
96    CppBoundClass* boundClass;
97
98    //
99    // All following objects and functions are static, and just used to interface
100    // with NPObject/NPClass.
101    //
102
103    // An NPClass associates static functions of CppNPObject with the
104    // function pointers used by the JS runtime.
105    static NPClass npClass;
106
107    // Allocate a new NPObject with the specified class.
108    static NPObject* allocate(NPP, NPClass*);
109
110    // Free an object.
111    static void deallocate(NPObject*);
112
113    // Returns true if the C++ class associated with this NPObject exposes the
114    // given property.  Called by the JS runtime.
115    static bool hasProperty(NPObject*, NPIdentifier);
116
117    // Returns true if the C++ class associated with this NPObject exposes the
118    // given method.  Called by the JS runtime.
119    static bool hasMethod(NPObject*, NPIdentifier);
120
121    // If the given method is exposed by the C++ class associated with this
122    // NPObject, invokes it with the given arguments and returns a result.  Otherwise,
123    // returns "undefined" (in the JavaScript sense).  Called by the JS runtime.
124    static bool invoke(NPObject*, NPIdentifier,
125                       const NPVariant* arguments, uint32_t argumentCount,
126                       NPVariant* result);
127
128    // If the given property is exposed by the C++ class associated with this
129    // NPObject, returns its value.  Otherwise, returns "undefined" (in the
130    // JavaScript sense).  Called by the JS runtime.
131    static bool getProperty(NPObject*, NPIdentifier, NPVariant* result);
132
133    // If the given property is exposed by the C++ class associated with this
134    // NPObject, sets its value.  Otherwise, does nothing. Called by the JS
135    // runtime.
136    static bool setProperty(NPObject*, NPIdentifier, const NPVariant* value);
137};
138
139// Build CppNPObject's static function pointers into an NPClass, for use
140// in constructing NPObjects for the C++ classes.
141NPClass CppNPObject::npClass = {
142    NP_CLASS_STRUCT_VERSION,
143    CppNPObject::allocate,
144    CppNPObject::deallocate,
145    /* NPInvalidateFunctionPtr */ 0,
146    CppNPObject::hasMethod,
147    CppNPObject::invoke,
148    /* NPInvokeDefaultFunctionPtr */ 0,
149    CppNPObject::hasProperty,
150    CppNPObject::getProperty,
151    CppNPObject::setProperty,
152    /* NPRemovePropertyFunctionPtr */ 0
153};
154
155NPObject* CppNPObject::allocate(NPP npp, NPClass* aClass)
156{
157    CppNPObject* obj = new CppNPObject;
158    // obj->parent will be initialized by the NPObject code calling this.
159    obj->boundClass = 0;
160    return &obj->parent;
161}
162
163void CppNPObject::deallocate(NPObject* npObj)
164{
165    CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
166    delete obj;
167}
168
169bool CppNPObject::hasMethod(NPObject* npObj, NPIdentifier ident)
170{
171    CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
172    return obj->boundClass->hasMethod(ident);
173}
174
175bool CppNPObject::hasProperty(NPObject* npObj, NPIdentifier ident)
176{
177    CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
178    return obj->boundClass->hasProperty(ident);
179}
180
181bool CppNPObject::invoke(NPObject* npObj, NPIdentifier ident,
182                         const NPVariant* arguments, uint32_t argumentCount,
183                         NPVariant* result)
184{
185    CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
186    return obj->boundClass->invoke(ident, arguments, argumentCount, result);
187}
188
189bool CppNPObject::getProperty(NPObject* npObj, NPIdentifier ident, NPVariant* result)
190{
191    CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
192    return obj->boundClass->getProperty(ident, result);
193}
194
195bool CppNPObject::setProperty(NPObject* npObj, NPIdentifier ident, const NPVariant* value)
196{
197    CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
198    return obj->boundClass->setProperty(ident, value);
199}
200
201CppBoundClass::~CppBoundClass()
202{
203    for (MethodList::iterator i = m_methods.begin(); i != m_methods.end(); ++i)
204        delete i->second;
205
206    for (PropertyList::iterator i = m_properties.begin(); i != m_properties.end(); ++i)
207        delete i->second;
208
209    // Unregister ourselves if we were bound to a frame.
210    if (m_boundToFrame)
211        WebBindings::unregisterObject(NPVARIANT_TO_OBJECT(m_selfVariant));
212}
213
214bool CppBoundClass::hasMethod(NPIdentifier ident) const
215{
216    return m_methods.find(ident) != m_methods.end();
217}
218
219bool CppBoundClass::hasProperty(NPIdentifier ident) const
220{
221    return m_properties.find(ident) != m_properties.end();
222}
223
224bool CppBoundClass::invoke(NPIdentifier ident,
225                           const NPVariant* arguments,
226                           size_t argumentCount,
227                           NPVariant* result) {
228    MethodList::const_iterator end = m_methods.end();
229    MethodList::const_iterator method = m_methods.find(ident);
230    Callback* callback;
231    if (method == end) {
232        if (!m_fallbackCallback.get()) {
233            VOID_TO_NPVARIANT(*result);
234            return false;
235        }
236        callback = m_fallbackCallback.get();
237    } else
238        callback = (*method).second;
239
240    // Build a CppArgumentList argument vector from the NPVariants coming in.
241    CppArgumentList cppArguments(argumentCount);
242    for (size_t i = 0; i < argumentCount; i++)
243        cppArguments[i].set(arguments[i]);
244
245    CppVariant cppResult;
246    callback->run(cppArguments, &cppResult);
247
248    cppResult.copyToNPVariant(result);
249    return true;
250}
251
252bool CppBoundClass::getProperty(NPIdentifier ident, NPVariant* result) const
253{
254    PropertyList::const_iterator callback = m_properties.find(ident);
255    if (callback == m_properties.end()) {
256        VOID_TO_NPVARIANT(*result);
257        return false;
258    }
259
260    CppVariant cppValue;
261    if (!callback->second->getValue(&cppValue))
262        return false;
263    cppValue.copyToNPVariant(result);
264    return true;
265}
266
267bool CppBoundClass::setProperty(NPIdentifier ident, const NPVariant* value)
268{
269    PropertyList::iterator callback = m_properties.find(ident);
270    if (callback == m_properties.end())
271        return false;
272
273    CppVariant cppValue;
274    cppValue.set(*value);
275    return (*callback).second->setValue(cppValue);
276}
277
278void CppBoundClass::bindCallback(const string& name, Callback* callback)
279{
280    NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
281    MethodList::iterator oldCallback = m_methods.find(ident);
282    if (oldCallback != m_methods.end()) {
283        delete oldCallback->second;
284        if (!callback) {
285            m_methods.remove(oldCallback);
286            return;
287        }
288    }
289
290    m_methods.set(ident, callback);
291}
292
293void CppBoundClass::bindGetterCallback(const string& name, GetterCallback* callback)
294{
295    PropertyCallback* propertyCallback = callback ? new GetterPropertyCallback(callback) : 0;
296    bindProperty(name, propertyCallback);
297}
298
299void CppBoundClass::bindProperty(const string& name, CppVariant* prop)
300{
301    PropertyCallback* propertyCallback = prop ? new CppVariantPropertyCallback(prop) : 0;
302    bindProperty(name, propertyCallback);
303}
304
305void CppBoundClass::bindProperty(const string& name, PropertyCallback* callback)
306{
307    NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
308    PropertyList::iterator oldCallback = m_properties.find(ident);
309    if (oldCallback != m_properties.end()) {
310        delete oldCallback->second;
311        if (!callback) {
312            m_properties.remove(oldCallback);
313            return;
314        }
315    }
316
317    m_properties.set(ident, callback);
318}
319
320bool CppBoundClass::isMethodRegistered(const string& name) const
321{
322    NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
323    MethodList::const_iterator callback = m_methods.find(ident);
324    return callback != m_methods.end();
325}
326
327CppVariant* CppBoundClass::getAsCppVariant()
328{
329    if (!m_selfVariant.isObject()) {
330        // Create an NPObject using our static NPClass.  The first argument (a
331        // plugin's instance handle) is passed through to the allocate function
332        // directly, and we don't use it, so it's ok to be 0.
333        NPObject* npObj = WebBindings::createObject(0, &CppNPObject::npClass);
334        CppNPObject* obj = reinterpret_cast<CppNPObject*>(npObj);
335        obj->boundClass = this;
336        m_selfVariant.set(npObj);
337        WebBindings::releaseObject(npObj); // CppVariant takes the reference.
338    }
339    ASSERT(m_selfVariant.isObject());
340    return &m_selfVariant;
341}
342
343void CppBoundClass::bindToJavascript(WebFrame* frame, const WebString& classname)
344{
345    // BindToWindowObject will take its own reference to the NPObject, and clean
346    // up after itself.  It will also (indirectly) register the object with V8,
347    // so we must remember this so we can unregister it when we're destroyed.
348    frame->bindToWindowObject(classname, NPVARIANT_TO_OBJECT(*getAsCppVariant()));
349    m_boundToFrame = true;
350}
351