1// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This file contains definitions for CppBoundClass
6
7// Here's the control flow of a JS method getting forwarded to a class.
8// - Something calls our NPObject with a function like "Invoke".
9// - CppNPObject's static invoke() function forwards it to its attached
10//   CppBoundClass's Invoke() method.
11// - CppBoundClass has then overridden Invoke() to look up the function
12//   name in its internal map of methods, and then calls the appropriate
13//   method.
14
15#include "base/compiler_specific.h"
16#include "base/logging.h"
17#include "base/utf_string_conversions.h"
18#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h"
19#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
20#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
21#include "webkit/glue/cpp_bound_class.h"
22
23using WebKit::WebBindings;
24using WebKit::WebFrame;
25
26namespace {
27
28class CppVariantPropertyCallback : public CppBoundClass::PropertyCallback {
29 public:
30  CppVariantPropertyCallback(CppVariant* value) : value_(value) { }
31
32  virtual bool GetValue(CppVariant* value) {
33    value->Set(*value_);
34    return true;
35  }
36  virtual bool SetValue(const CppVariant& value) {
37    value_->Set(value);
38    return true;
39  }
40
41 private:
42  CppVariant* value_;
43};
44
45class GetterPropertyCallback : public CppBoundClass::PropertyCallback {
46public:
47  GetterPropertyCallback(CppBoundClass::GetterCallback* callback)
48      : callback_(callback) { }
49
50  virtual bool GetValue(CppVariant* value) {
51    callback_->Run(value);
52    return true;
53  }
54
55  virtual bool SetValue(const CppVariant& value) {
56    return false;
57  }
58
59private:
60  scoped_ptr<CppBoundClass::GetterCallback> callback_;
61};
62
63}
64
65// Our special NPObject type.  We extend an NPObject with a pointer to a
66// CppBoundClass, which is just a C++ interface that we forward all NPObject
67// callbacks to.
68struct CppNPObject {
69  NPObject parent;  // This must be the first field in the struct.
70  CppBoundClass* bound_class;
71
72  //
73  // All following objects and functions are static, and just used to interface
74  // with NPObject/NPClass.
75  //
76
77  // An NPClass associates static functions of CppNPObject with the
78  // function pointers used by the JS runtime.
79  static NPClass np_class_;
80
81  // Allocate a new NPObject with the specified class.
82  static NPObject* allocate(NPP npp, NPClass* aClass);
83
84  // Free an object.
85  static void deallocate(NPObject* obj);
86
87  // Returns true if the C++ class associated with this NPObject exposes the
88  // given property.  Called by the JS runtime.
89  static bool hasProperty(NPObject *obj, NPIdentifier ident);
90
91  // Returns true if the C++ class associated with this NPObject exposes the
92  // given method.  Called by the JS runtime.
93  static bool hasMethod(NPObject *obj, NPIdentifier ident);
94
95  // If the given method is exposed by the C++ class associated with this
96  // NPObject, invokes it with the given args and returns a result.  Otherwise,
97  // returns "undefined" (in the JavaScript sense).  Called by the JS runtime.
98  static bool invoke(NPObject *obj, NPIdentifier ident,
99                     const NPVariant *args, uint32_t arg_count,
100                     NPVariant *result);
101
102  // If the given property is exposed by the C++ class associated with this
103  // NPObject, returns its value.  Otherwise, returns "undefined" (in the
104  // JavaScript sense).  Called by the JS runtime.
105  static bool getProperty(NPObject *obj, NPIdentifier ident,
106                          NPVariant *result);
107
108  // If the given property is exposed by the C++ class associated with this
109  // NPObject, sets its value.  Otherwise, does nothing. Called by the JS
110  // runtime.
111  static bool setProperty(NPObject *obj, NPIdentifier ident,
112                          const NPVariant *value);
113};
114
115// Build CppNPObject's static function pointers into an NPClass, for use
116// in constructing NPObjects for the C++ classes.
117NPClass CppNPObject::np_class_ = {
118  NP_CLASS_STRUCT_VERSION,
119  CppNPObject::allocate,
120  CppNPObject::deallocate,
121  /* NPInvalidateFunctionPtr */ NULL,
122  CppNPObject::hasMethod,
123  CppNPObject::invoke,
124  /* NPInvokeDefaultFunctionPtr */ NULL,
125  CppNPObject::hasProperty,
126  CppNPObject::getProperty,
127  CppNPObject::setProperty,
128  /* NPRemovePropertyFunctionPtr */ NULL
129};
130
131/* static */ NPObject* CppNPObject::allocate(NPP npp, NPClass* aClass) {
132  CppNPObject* obj = new CppNPObject;
133  // obj->parent will be initialized by the NPObject code calling this.
134  obj->bound_class = NULL;
135  return &obj->parent;
136}
137
138/* static */ void CppNPObject::deallocate(NPObject* np_obj) {
139  CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
140  delete obj;
141}
142
143/* static */ bool CppNPObject::hasMethod(NPObject* np_obj,
144                                         NPIdentifier ident) {
145  CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
146  return obj->bound_class->HasMethod(ident);
147}
148
149/* static */ bool CppNPObject::hasProperty(NPObject* np_obj,
150                                           NPIdentifier ident) {
151  CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
152  return obj->bound_class->HasProperty(ident);
153}
154
155/* static */ bool CppNPObject::invoke(NPObject* np_obj, NPIdentifier ident,
156                                      const NPVariant* args, uint32_t arg_count,
157                                      NPVariant* result) {
158  CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
159  return obj->bound_class->Invoke(ident, args, arg_count, result);
160}
161
162/* static */ bool CppNPObject::getProperty(NPObject* np_obj,
163                                           NPIdentifier ident,
164                                           NPVariant* result) {
165  CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
166  return obj->bound_class->GetProperty(ident, result);
167}
168
169/* static */ bool CppNPObject::setProperty(NPObject* np_obj,
170                                           NPIdentifier ident,
171                                           const NPVariant* value) {
172  CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
173  return obj->bound_class->SetProperty(ident, value);
174}
175
176CppBoundClass::CppBoundClass()
177    : bound_to_frame_(false) {
178}
179
180CppBoundClass::~CppBoundClass() {
181  for (MethodList::iterator i = methods_.begin(); i != methods_.end(); ++i)
182    delete i->second;
183
184  for (PropertyList::iterator i = properties_.begin(); i != properties_.end();
185      ++i) {
186    delete i->second;
187  }
188
189  // Unregister ourselves if we were bound to a frame.
190  if (bound_to_frame_)
191    WebBindings::unregisterObject(NPVARIANT_TO_OBJECT(self_variant_));
192}
193
194bool CppBoundClass::HasMethod(NPIdentifier ident) const {
195  return (methods_.find(ident) != methods_.end());
196}
197
198bool CppBoundClass::HasProperty(NPIdentifier ident) const {
199  return (properties_.find(ident) != properties_.end());
200}
201
202bool CppBoundClass::Invoke(NPIdentifier ident,
203                              const NPVariant* args,
204                              size_t arg_count,
205                              NPVariant* result) {
206  MethodList::const_iterator method = methods_.find(ident);
207  Callback* callback;
208  if (method == methods_.end()) {
209    if (fallback_callback_.get()) {
210      callback = fallback_callback_.get();
211    } else {
212      VOID_TO_NPVARIANT(*result);
213      return false;
214    }
215  } else {
216    callback = (*method).second;
217  }
218
219  // Build a CppArgumentList argument vector from the NPVariants coming in.
220  CppArgumentList cpp_args(arg_count);
221  for (size_t i = 0; i < arg_count; i++)
222    cpp_args[i].Set(args[i]);
223
224  CppVariant cpp_result;
225  callback->Run(cpp_args, &cpp_result);
226
227  cpp_result.CopyToNPVariant(result);
228  return true;
229}
230
231bool CppBoundClass::GetProperty(NPIdentifier ident, NPVariant* result) const {
232  PropertyList::const_iterator callback = properties_.find(ident);
233  if (callback == properties_.end()) {
234    VOID_TO_NPVARIANT(*result);
235    return false;
236  }
237
238  CppVariant cpp_value;
239  if (!callback->second->GetValue(&cpp_value))
240    return false;
241  cpp_value.CopyToNPVariant(result);
242  return true;
243}
244
245bool CppBoundClass::SetProperty(NPIdentifier ident,
246                                const NPVariant* value) {
247  PropertyList::iterator callback = properties_.find(ident);
248  if (callback == properties_.end())
249    return false;
250
251  CppVariant cpp_value;
252  cpp_value.Set(*value);
253  return (*callback).second->SetValue(cpp_value);
254}
255
256void CppBoundClass::BindCallback(const std::string& name, Callback* callback) {
257  NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
258  MethodList::iterator old_callback = methods_.find(ident);
259  if (old_callback != methods_.end()) {
260    delete old_callback->second;
261    if (callback == NULL) {
262      methods_.erase(old_callback);
263      return;
264    }
265  }
266
267  methods_[ident] = callback;
268}
269
270void CppBoundClass::BindGetterCallback(const std::string& name,
271                                       GetterCallback* callback) {
272  PropertyCallback* property_callback = callback == NULL ?
273      NULL : new GetterPropertyCallback(callback);
274
275  BindProperty(name, property_callback);
276}
277
278void CppBoundClass::BindProperty(const std::string& name, CppVariant* prop) {
279  PropertyCallback* property_callback = prop == NULL ?
280      NULL : new CppVariantPropertyCallback(prop);
281
282  BindProperty(name, property_callback);
283}
284
285void CppBoundClass::BindProperty(const std::string& name,
286                                 PropertyCallback* callback) {
287  NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
288  PropertyList::iterator old_callback = properties_.find(ident);
289  if (old_callback != properties_.end()) {
290    delete old_callback->second;
291    if (callback == NULL) {
292      properties_.erase(old_callback);
293      return;
294    }
295  }
296
297  properties_[ident] = callback;
298}
299
300bool CppBoundClass::IsMethodRegistered(const std::string& name) const {
301  NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str());
302  MethodList::const_iterator callback = methods_.find(ident);
303  return (callback != methods_.end());
304}
305
306CppVariant* CppBoundClass::GetAsCppVariant() {
307  if (!self_variant_.isObject()) {
308    // Create an NPObject using our static NPClass.  The first argument (a
309    // plugin's instance handle) is passed through to the allocate function
310    // directly, and we don't use it, so it's ok to be 0.
311    NPObject* np_obj = WebBindings::createObject(0, &CppNPObject::np_class_);
312    CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj);
313    obj->bound_class = this;
314    self_variant_.Set(np_obj);
315    WebBindings::releaseObject(np_obj);  // CppVariant takes the reference.
316  }
317  DCHECK(self_variant_.isObject());
318  return &self_variant_;
319}
320
321void CppBoundClass::BindToJavascript(WebFrame* frame,
322                                     const std::string& classname) {
323#if WEBKIT_USING_JSC
324#error "This is not going to work anymore...but it's not clear what the solution is...or if it's still necessary."
325  JSC::JSLock lock(false);
326#endif
327
328  // BindToWindowObject will take its own reference to the NPObject, and clean
329  // up after itself.  It will also (indirectly) register the object with V8,
330  // so we must remember this so we can unregister it when we're destroyed.
331  frame->bindToWindowObject(ASCIIToUTF16(classname),
332                            NPVARIANT_TO_OBJECT(*GetAsCppVariant()));
333  bound_to_frame_ = true;
334}
335