/* * Copyright 2009, Google Inc. * All rights reserved. * * 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. */ // Code relating to interoperation of V8 JavaScript engine with NPAPI. // Tests are in o3d/tests/selenium/tests/v8.html. They can be run // by opening the web page in a browser or as part of the selenium tests. #include #include #include #include "base/scoped_ptr.h" #include "plugin/cross/np_v8_bridge.h" using v8::AccessorInfo; using v8::Arguments; using v8::Array; using v8::Context; using v8::DontDelete; using v8::DontEnum; using v8::External; using v8::Function; using v8::FunctionTemplate; using v8::HandleScope; using v8::Int32; using v8::Integer; using v8::Local; using v8::Message; using v8::Null; using v8::Number; using v8::Object; using v8::ObjectTemplate; using v8::Persistent; using v8::PropertyAttribute; using v8::ReadOnly; using v8::Script; using v8::TryCatch; using v8::Undefined; using v8::Value; namespace o3d { // Only used during debugging. Type "o3d::DebugV8String(a.val_)" in the // watch window to get the string representation of a V8 object. const char* DebugV8String(Value* value) { static char buffer[4096]; if (value == NULL) { ::base::snprintf(buffer, sizeof(buffer), ""); } else { value->ToString()->WriteUtf8(buffer); } return buffer; } namespace { // The indices of the internal fields of a V8 proxy for an NPObject. enum { // Pointer to the bridge that created the proxy. V8_NP_OBJECT_BRIDGE, // Pointer to the wrapped NPObject. V8_NP_OBJECT_WRAPPED, V8_NP_OBJECT_NUM_INTERNAL_FIELDS }; // The name of the "hidden" property in a V8 non-proxy object that contains // an External that points to the NPObject proxy for it. The property does // not exist if there is no associated NPObject proxy. const char* const kInternalProperty = "internal_property_"; // Convert an NPIdentifier (NULL, string or integer) to a V8 value. Local NPToV8Identifier(NPIdentifier np_identifier) { if (np_identifier == NULL) { return Local(); } else if (NPN_IdentifierIsString(np_identifier)) { NPUTF8* utf8_name = NPN_UTF8FromIdentifier(np_identifier); Local v8_identifier = v8::String::New(utf8_name); NPN_MemFree(utf8_name); return v8_identifier; } else { return Integer::New(NPN_IntFromIdentifier(np_identifier)); } } // Convert a V8 value (empty, string or integer) into an NPIdentifier. NPIdentifier V8ToNPIdentifier(v8::Handle v8_identifier) { if (v8_identifier.IsEmpty()) { return NULL; } else if (v8_identifier->IsNumber()) { return NPN_GetIntIdentifier(v8_identifier->Int32Value()); } else if (v8_identifier->IsString()) { return NPN_GetStringIdentifier( *v8::String::Utf8Value(v8_identifier->ToString())); } else { return NULL; } } } // namespace anonymous // The class of NPObject proxies that wrap V8 objects. These field the NPAPI // functions and translate them into V8 calls. class NPV8Object : public NPObject { public: static NPObjectPtr Create(NPV8Bridge* bridge, Local v8_object) { NPObjectPtr np_object = NPObjectPtr::AttachToReturned( static_cast(NPN_CreateObject(bridge->npp(), &np_class_))); np_object->v8_object_ = Persistent::New(v8_object); np_object->bridge_ = bridge; return np_object; } v8::Handle v8_object() const { return v8_object_; } // Drop references between NPObject and V8 object. Must be called before the // NPObject is destroyed so V8 can garbage collect the associated V8 object. void UnlinkFromV8() { HandleScope handle_scope; if (!v8_object_.IsEmpty()) { v8_object_->DeleteHiddenValue(v8::String::NewSymbol(kInternalProperty)); v8_object_.Dispose(); v8_object_.Clear(); } } static NPClass np_class_; private: NPV8Object() : bridge_(NULL) { } static NPObject* Allocate(NPP npp, NPClass* np_class) { v8::Locker locker; NPV8Object* np_v8_object = new NPV8Object(); np_v8_object->bridge_ = NULL; return np_v8_object; } static void Deallocate(NPObject* np_object) { v8::Locker locker; NPV8Object* np_v8_object = static_cast (np_object); // Uncomment this line to see objects with a non-zero reference // count being deallocated. For example, Firefox does this when unloading // the plugin. // DCHECK_EQ(0, np_v8_object_map->referenceCount); np_v8_object->UnlinkFromV8(); delete np_v8_object; } static void Invalidate(NPObject* np_object) { v8::Locker locker; NPV8Object* np_v8_object = static_cast (np_object); np_v8_object->bridge_ = NULL; np_v8_object->UnlinkFromV8(); } static bool HasMethod(NPObject* np_object, NPIdentifier np_name) { v8::Locker locker; NPV8Object* np_v8_object = static_cast (np_object); NPV8Bridge* bridge = np_v8_object->bridge_; if (bridge == NULL) return false; HandleScope handle_scope; Context::Scope scope(bridge->script_context()); TryCatch tryCatch; v8::Handle v8_object = np_v8_object->v8_object_; if (v8_object.IsEmpty()) return false; Local v8_name = NPToV8Identifier(np_name); Local value = v8_object->Get(v8_name); if (tryCatch.HasCaught()) { bridge->ReportV8Exception(tryCatch); return false; } // Returns true iff the object has a property with the given name and // the object assigned to the property is a function. This works for V8 // functions and assigned browser JavaScript functions (because their // proxies are created from FunctionTemplates so V8 considers them to be // functions). return !value.IsEmpty() && value->IsFunction(); } // Called when a method is invoked through "obj.m(...)". static bool Invoke(NPObject* np_object, NPIdentifier np_name, const NPVariant* np_args, uint32_t numArgs, NPVariant* result) { v8::Locker locker; // This works around a bug in Chrome: // http://code.google.com/p/chromium/issues/detail?id=5110 // NPN_InvokeDefault is transformed into a call to Invoke on the plugin with // a null method name identifier. if (np_name == NULL) { return InvokeDefault(np_object, np_args, numArgs, result); } NPV8Object* np_v8_object = static_cast (np_object); NPV8Bridge* bridge = np_v8_object->bridge_; if (bridge == NULL) return false; HandleScope handle_scope; Context::Scope scope(bridge->script_context()); TryCatch tryCatch; v8::Handle v8_object = np_v8_object->v8_object_; if (v8_object.IsEmpty()) return false; Local v8_name = NPToV8Identifier(np_name); Local value = v8_object->Get(v8_name); if (value.IsEmpty() || !value->IsFunction()) return false; Local function = Local::Cast(value); std::vector > v8_args(numArgs); for (uint32_t i = 0; i != numArgs; ++i) { v8_args[i] = bridge->NPToV8Variant(np_args[i]); } *result = bridge->V8ToNPVariant( function->Call(v8_object, numArgs, numArgs == 0 ? NULL : &v8_args.front())); if (tryCatch.HasCaught()) { bridge->ReportV8Exception(tryCatch); return false; } return true; } // Called when an object is called as a function "f(...)". static bool InvokeDefault(NPObject* np_object, const NPVariant* np_args, uint32_t numArgs, NPVariant* result) { v8::Locker locker; NPV8Object* np_v8_object = static_cast (np_object); NPV8Bridge* bridge = np_v8_object->bridge_; if (bridge == NULL) return false; HandleScope handle_scope; Context::Scope scope(bridge->script_context()); TryCatch tryCatch; v8::Handle v8_object = np_v8_object->v8_object_; if (v8_object.IsEmpty()) return false; if (!v8_object->IsFunction()) return false; v8::Handle function = v8::Handle::Cast(v8_object); std::vector > v8_args(numArgs); for (uint32_t i = 0; i != numArgs; ++i) { v8_args[i] = bridge->NPToV8Variant(np_args[i]); } *result = bridge->V8ToNPVariant( function->Call(v8_object, numArgs, numArgs == 0 ? NULL : &v8_args.front())); if (tryCatch.HasCaught()) { bridge->ReportV8Exception(tryCatch); return false; } return true; } // Called when an object is called as a constructor "new C(...)". static bool Construct(NPObject* np_object, const NPVariant* np_args, uint32_t numArgs, NPVariant* result) { v8::Locker locker; NPV8Object* np_v8_object = static_cast (np_object); NPV8Bridge* bridge = np_v8_object->bridge_; if (bridge == NULL) return false; HandleScope handle_scope; Context::Scope scope(bridge->script_context()); TryCatch tryCatch; v8::Handle v8_object = np_v8_object->v8_object_; if (v8_object.IsEmpty()) return false; if (!v8_object->IsFunction()) return false; v8::Handle function = v8::Handle::Cast(v8_object); std::vector > v8_args(numArgs); for (uint32_t i = 0; i != numArgs; ++i) { v8_args[i] = bridge->NPToV8Variant(np_args[i]); } Local v8_result = function->NewInstance( numArgs, numArgs == 0 ? NULL : &v8_args.front()); if (v8_result.IsEmpty()) return false; *result = bridge->V8ToNPVariant(v8_result); if (tryCatch.HasCaught()) { bridge->ReportV8Exception(tryCatch); return false; } return true; } static bool HasProperty(NPObject* np_object, NPIdentifier np_name) { v8::Locker locker; NPV8Object* np_v8_object = static_cast (np_object); NPV8Bridge* bridge = np_v8_object->bridge_; if (bridge == NULL) return false; HandleScope handle_scope; Context::Scope scope(bridge->script_context()); v8::Handle v8_object = np_v8_object->v8_object_; if (v8_object.IsEmpty()) return false; // This is a better approach than the one below. It allows functions // to be retreived as first class objects. Unfortunately we can't // support this yet because of a Chrome bug: // http://code.google.com/p/chromium/issues/detail?id=5742 // if (NPN_IdentifierIsString(np_name)) { // Local v8_name = Local::Cast( // NPToV8Identifier(np_name)); // return v8_object->Has(v8_name); // } else { // return v8_object->Has(NPN_IntFromIdentifier(np_name)); // } // Instead hide properties with function type. This ensures that Chrome // will invoke them with Invoke rather than InvokeDefault. The problem // with InvokeDefault is it doesn't tell us what "this" should be // bound to, whereas Invoke does. Local v8_name = NPToV8Identifier(np_name); if (NPN_IdentifierIsString(np_name)) { if (!v8_object->Has(v8_name->ToString())) { return false; } } else { if (!v8_object->Has(NPN_IntFromIdentifier(np_name))) { return false; } } Local v8_property_value = v8_object->Get(v8_name); if (v8_property_value->IsFunction()) { return false; } return true; } static bool GetProperty(NPObject* np_object, NPIdentifier np_name, NPVariant* result) { v8::Locker locker; NPV8Object* np_v8_object = static_cast (np_object); NPV8Bridge* bridge = np_v8_object->bridge_; if (bridge == NULL) return false; HandleScope handle_scope; Context::Scope scope(bridge->script_context()); TryCatch tryCatch; v8::Handle v8_object = np_v8_object->v8_object_; if (v8_object.IsEmpty()) return false; Local v8_name = NPToV8Identifier(np_name); Local v8_property_value = v8_object->Get(v8_name); if (tryCatch.HasCaught()) { bridge->ReportV8Exception(tryCatch); return false; } // See comment in HasProperty. Do not return properties that are // functions. It will prevent Chrome from invoking them as methods. if (v8_property_value.IsEmpty() || v8_property_value->IsFunction()) return false; *result = bridge->V8ToNPVariant(v8_property_value); return true; } static bool SetProperty(NPObject* np_object, NPIdentifier np_name, const NPVariant* np_value) { v8::Locker locker; NPV8Object* np_v8_object = static_cast (np_object); NPV8Bridge* bridge = np_v8_object->bridge_; if (bridge == NULL) return false; HandleScope handle_scope; Context::Scope scope(bridge->script_context()); TryCatch tryCatch; v8::Handle v8_object = np_v8_object->v8_object_; if (v8_object.IsEmpty()) return false; Local v8_name = NPToV8Identifier(np_name); bool success = v8_object->Set(v8_name, bridge->NPToV8Variant(*np_value)); if (tryCatch.HasCaught()) { bridge->ReportV8Exception(tryCatch); return false; } return success; } static bool RemoveProperty(NPObject* np_object, NPIdentifier np_name) { v8::Locker locker; NPV8Object* np_v8_object = static_cast (np_object); NPV8Bridge* bridge = np_v8_object->bridge_; if (bridge == NULL) return false; HandleScope handle_scope; Context::Scope scope(bridge->script_context()); TryCatch tryCatch; v8::Handle v8_object = np_v8_object->v8_object_; if (v8_object.IsEmpty()) return false; bool success; if (NPN_IdentifierIsString(np_name)) { NPUTF8* utf8_name = NPN_UTF8FromIdentifier(np_name); Local v8_name = v8::String::New(utf8_name); NPN_MemFree(utf8_name); success = v8_object->Delete(v8_name); } else { success = v8_object->Delete(NPN_IntFromIdentifier(np_name)); } if (tryCatch.HasCaught()) { bridge->ReportV8Exception(tryCatch); return false; } return success; } static bool Enumerate(NPObject* np_object, NPIdentifier** np_names, uint32_t* numNames) { v8::Locker locker; NPV8Object* np_v8_object = static_cast (np_object); NPV8Bridge* bridge = np_v8_object->bridge_; if (bridge == NULL) return false; HandleScope handle_scope; Context::Scope scope(bridge->script_context()); v8::Handle v8_object = np_v8_object->v8_object_; if (v8_object.IsEmpty()) return false; Local v8_names = v8_object->GetPropertyNames(); // Due to a bug in Chrome, need to filter out any properties that // are functions. See comment in HasProperty. int num_non_function_properties = 0; int length = v8_names->Length(); for (int i = 0; i < length; ++i) { Local v8_property_value = v8_object->Get(v8_names->Get(Int32::New(i))); if (!v8_property_value->IsFunction()) { ++num_non_function_properties; } } *numNames = num_non_function_properties; *np_names = static_cast ( NPN_MemAlloc(num_non_function_properties * sizeof(NPIdentifier))); int j = 0; for (uint32_t i = 0; i != v8_names->Length(); ++i) { Local v8_name = v8_names->Get(Int32::New(i)); Local v8_property_value = v8_object->Get(v8_name); if (!v8_property_value->IsFunction()) { (*np_names)[j++] = V8ToNPIdentifier(v8_name); } } return true; } NPV8Bridge* bridge_; AutoV8Persistent v8_object_; DISALLOW_COPY_AND_ASSIGN(NPV8Object); }; NPClass NPV8Object::np_class_ = { NP_CLASS_STRUCT_VERSION, Allocate, Deallocate, Invalidate, HasMethod, Invoke, InvokeDefault, HasProperty, GetProperty, SetProperty, RemoveProperty, Enumerate, Construct }; NPV8Bridge::NPV8Bridge(ServiceLocator* service_locator, NPP npp) : service_locator_(service_locator), error_status_(service_locator), npp_(npp) { np_name_identifier_ = NPN_GetStringIdentifier("name"); np_call_identifier_ = NPN_GetStringIdentifier("call"); np_length_identifier_ = NPN_GetStringIdentifier("length"); np_proxy_identifier_ = NPN_GetStringIdentifier("npv8_proxy_"); } NPV8Bridge::~NPV8Bridge() { v8::Locker locker; // Do not call weak reference callback after the bridge is destroyed // because the callbacks assume it exists. The only purpose of the callback // is to remove the corresponding object entry from the NP-V8 object map // and its about to get cleared anyway. for (NPV8ObjectMap::iterator it = np_v8_object_map_.begin(); it != np_v8_object_map_.end(); ++it) { it->second.ClearWeak(); } } NPObjectPtr NPV8Bridge::NPEvaluateObject(const char* script) { NPString np_script = { script, strlen(script) }; NPVariant np_variant; NPObjectPtr np_result; if (NPN_Evaluate(npp_, global_np_object_.Get(), &np_script, &np_variant)) { if (NPVARIANT_IS_OBJECT(np_variant)) { np_result = NPObjectPtr(NPVARIANT_TO_OBJECT(np_variant)); } NPN_ReleaseVariantValue(&np_variant); } return np_result; } namespace { // Create code that looks like this: // (function(func, protoArray) { // return function() { // switch (arguments.length) { // case 0: // return func.call(this); // case 1: // return func.call(this, // arguments[0]); // case 2: // return func.call(this, // arguments[0], // arguments[1]); // ... // default: // var args = protoArray.slice(); // for (var i = 0; i < arguments.length; ++i) { // args[i] = arguments[i]; // } // return func.apply(this, args); // } // }; // }) String MakeWrapFunctionScript() { std::ostringstream code; code << "(function(func, protoArray) {"; code << " return function() {"; code << " switch (arguments.length) {"; for (int i = 0; i <= 10; ++i) { code << " case " << i << ": return func.call(this"; for (int j = 0; j < i; ++j) { code << ", arguments[" << j << "]"; } code << ");"; } code << " default:"; code << " var args = protoArray.slice();"; code << " for (var i = 0; i < arguments.length; ++i) {"; code << " args.push(arguments[i]);"; code << " }"; code << " return func.apply(this, args);"; code << " }"; code << " };"; code << "})"; return code.str(); } } // namespace anonymous void NPV8Bridge::Initialize(const NPObjectPtr& global_np_object) { v8::Locker locker; HandleScope handle_scope; global_np_object_ = global_np_object; // This template is used for V8 proxies of NPObjects. v8_np_constructor_template_ = Persistent::New( FunctionTemplate::New()); InitializeV8ObjectTemplate(v8_np_constructor_template_->InstanceTemplate()); // This template is used for the global V8 object. Local v8_global_template = FunctionTemplate::New(); InitializeV8ObjectTemplate(v8_global_template->PrototypeTemplate()); script_context_ = Context::New(NULL, v8_global_template->InstanceTemplate()); Context::Scope scope(script_context_); // Give the global object a prototype that allows V8 to access global // variables in another JavaScript environemnt over NPAPI. Local v8_global_prototype = Local::Cast(script_context_->Global()->GetPrototype()); Local v8_global_prototype2 = Local::Cast(v8_global_prototype->GetPrototype()); global_prototype_ = Persistent::New(v8_global_prototype2); NPToV8Object(v8_global_prototype2, global_np_object); function_map_ = Persistent::New(Object::New()); // Create a browser JavaScript function that can later be called to get the // type of an object (as the browser sees it). This is useful for determining // whether an object received over NPAPI is a function (which means its // proxy must be created from a FunctionTemplate rather than an // ObjectTemplate). static const char kIsFunctionScript[] = "(function(obj) { return obj instanceof Function; })"; np_is_function_function_ = NPEvaluateObject(kIsFunctionScript); // Create a browser JavaScript function that can later be used to enumerate // the properties of an object. This is used as a fallback if NPN_Evaluate // is not implemented by the browser (like Firefox 2) and the enumerate // callback is not implemented by the NPObject. static const char kEnumerateScript[] = "(function(object) {" " var properties = [];" " for (var property in object) {" " if (object.hasOwnProperty(property)) {" " properties[properties.length++] = property;" " }" " }" " return properties;" "})"; np_enumerate_function_ = NPEvaluateObject(kEnumerateScript); // Create a browser JavaScript function that can later be used to create // a wrapper around an V8 function proxy, making it appear to be a real // browser function. np_wrap_function_function_ = NPEvaluateObject( MakeWrapFunctionScript().c_str()); // Create an NPObject proxy for a V8 array. This is for the browser to use as // a prototype for creating new V8 arrays with slice(). np_empty_array_ = V8ToNPObject(v8::Array::New(0)); } void NPV8Bridge::ReleaseNPObjects() { v8::Locker locker; np_v8_object_map_.clear(); np_construct_functions_.clear(); global_np_object_.Clear(); np_is_function_function_.Clear(); np_enumerate_function_.Clear(); np_wrap_function_function_.Clear(); np_empty_array_.Clear(); } v8::Handle NPV8Bridge::script_context() { return script_context_; } bool NPV8Bridge::Evaluate(const NPVariant* np_args, int numArgs, NPVariant* np_result) { v8::Locker locker; HandleScope handle_scope; Context::Scope scope(script_context_); Local v8_code; if (numArgs == 1) { v8_code = NPToV8Variant(np_args[0]); } else { return false; } if (v8_code.IsEmpty() || !v8_code->IsString()) return false; TryCatch tryCatch; Local v8_code_string = v8_code->ToString(); Local