/* * Copyright (C) 2006, 2007, 2008, 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. */ #include "config.h" #include "bindings/v8/V8NPObject.h" #include "V8HTMLAppletElement.h" #include "V8HTMLEmbedElement.h" #include "V8HTMLObjectElement.h" #include "bindings/v8/NPV8Object.h" #include "bindings/v8/UnsafePersistent.h" #include "bindings/v8/V8Binding.h" #include "bindings/v8/V8NPUtils.h" #include "bindings/v8/V8ObjectConstructor.h" #include "bindings/v8/npruntime_impl.h" #include "bindings/v8/npruntime_priv.h" #include "core/html/HTMLPlugInElement.h" #include "wtf/OwnPtr.h" namespace WebCore { enum InvokeFunctionType { InvokeMethod = 1, InvokeConstruct = 2, InvokeDefault = 3 }; struct IdentifierRep { int number() const { return m_isString ? 0 : m_value.m_number; } const char* string() const { return m_isString ? m_value.m_string : 0; } union { const char* m_string; int m_number; } m_value; bool m_isString; }; // FIXME: need comments. // Params: holder could be HTMLEmbedElement or NPObject static void npObjectInvokeImpl(const v8::FunctionCallbackInfo& info, InvokeFunctionType functionId) { NPObject* npObject; WrapperWorldType currentWorldType = worldType(info.GetIsolate()); // These three types are subtypes of HTMLPlugInElement. if (V8HTMLAppletElement::hasInstance(info.Holder(), info.GetIsolate(), currentWorldType) || V8HTMLEmbedElement::hasInstance(info.Holder(), info.GetIsolate(), currentWorldType) || V8HTMLObjectElement::hasInstance(info.Holder(), info.GetIsolate(), currentWorldType)) { // The holder object is a subtype of HTMLPlugInElement. HTMLPlugInElement* element; if (V8HTMLAppletElement::hasInstance(info.Holder(), info.GetIsolate(), currentWorldType)) element = V8HTMLAppletElement::toNative(info.Holder()); else if (V8HTMLEmbedElement::hasInstance(info.Holder(), info.GetIsolate(), currentWorldType)) element = V8HTMLEmbedElement::toNative(info.Holder()); else element = V8HTMLObjectElement::toNative(info.Holder()); if (RefPtr > wrapper = element->pluginWrapper()) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::HandleScope handleScope(isolate); npObject = v8ObjectToNPObject(wrapper->newLocal(isolate)); } else npObject = 0; } else { // The holder object is not a subtype of HTMLPlugInElement, it must be an NPObject which has three // internal fields. if (info.Holder()->InternalFieldCount() != npObjectInternalFieldCount) { throwError(v8ReferenceError, "NPMethod called on non-NPObject", info.GetIsolate()); return; } npObject = v8ObjectToNPObject(info.Holder()); } // Verify that our wrapper wasn't using a NPObject which has already been deleted. if (!npObject || !_NPN_IsAlive(npObject)) { throwError(v8ReferenceError, "NPObject deleted", info.GetIsolate()); return; } // Wrap up parameters. int numArgs = info.Length(); OwnPtr npArgs = adoptArrayPtr(new NPVariant[numArgs]); for (int i = 0; i < numArgs; i++) convertV8ObjectToNPVariant(info[i], npObject, &npArgs[i], info.GetIsolate()); NPVariant result; VOID_TO_NPVARIANT(result); bool retval = true; switch (functionId) { case InvokeMethod: if (npObject->_class->invoke) { v8::Handle functionName = v8::Handle::Cast(info.Data()); NPIdentifier identifier = getStringIdentifier(functionName); retval = npObject->_class->invoke(npObject, identifier, npArgs.get(), numArgs, &result); } break; case InvokeConstruct: if (npObject->_class->construct) retval = npObject->_class->construct(npObject, npArgs.get(), numArgs, &result); break; case InvokeDefault: if (npObject->_class->invokeDefault) retval = npObject->_class->invokeDefault(npObject, npArgs.get(), numArgs, &result); break; default: break; } if (!retval) throwError(v8GeneralError, "Error calling method on NPObject.", info.GetIsolate()); for (int i = 0; i < numArgs; i++) _NPN_ReleaseVariantValue(&npArgs[i]); // Unwrap return values. v8::Handle returnValue; if (_NPN_IsAlive(npObject)) returnValue = convertNPVariantToV8Object(&result, npObject, info.GetIsolate()); _NPN_ReleaseVariantValue(&result); v8SetReturnValue(info, returnValue); } void npObjectMethodHandler(const v8::FunctionCallbackInfo& info) { return npObjectInvokeImpl(info, InvokeMethod); } void npObjectInvokeDefaultHandler(const v8::FunctionCallbackInfo& info) { if (info.IsConstructCall()) { npObjectInvokeImpl(info, InvokeConstruct); return; } npObjectInvokeImpl(info, InvokeDefault); } class V8NPTemplateMap { public: // NPIdentifier is PrivateIdentifier*. typedef HashMap > MapType; UnsafePersistent get(PrivateIdentifier* key) { return m_map.get(key); } void set(PrivateIdentifier* key, v8::Handle handle) { ASSERT(!m_map.contains(key)); v8::Persistent wrapper(m_isolate, handle); wrapper.SetWeak(key, &setWeakCallback); m_map.set(key, UnsafePersistent(wrapper)); } static V8NPTemplateMap& sharedInstance(v8::Isolate* isolate) { DEFINE_STATIC_LOCAL(V8NPTemplateMap, map, (isolate)); ASSERT(isolate == map.m_isolate); return map; } private: explicit V8NPTemplateMap(v8::Isolate* isolate) : m_isolate(isolate) { } void clear(PrivateIdentifier* key) { MapType::iterator it = m_map.find(key); ASSERT_WITH_SECURITY_IMPLICATION(it != m_map.end()); it->value.dispose(); m_map.remove(it); } static void setWeakCallback(const v8::WeakCallbackData& data) { V8NPTemplateMap::sharedInstance(data.GetIsolate()).clear(data.GetParameter()); } MapType m_map; v8::Isolate* m_isolate; }; static v8::Handle npObjectGetProperty(v8::Local self, NPIdentifier identifier, v8::Local key, v8::Isolate* isolate) { NPObject* npObject = v8ObjectToNPObject(self); // Verify that our wrapper wasn't using a NPObject which // has already been deleted. if (!npObject || !_NPN_IsAlive(npObject)) return throwError(v8ReferenceError, "NPObject deleted", isolate); if (npObject->_class->hasProperty && npObject->_class->getProperty && npObject->_class->hasProperty(npObject, identifier)) { if (!_NPN_IsAlive(npObject)) return throwError(v8ReferenceError, "NPObject deleted", isolate); NPVariant result; VOID_TO_NPVARIANT(result); if (!npObject->_class->getProperty(npObject, identifier, &result)) return v8Undefined(); v8::Handle returnValue; if (_NPN_IsAlive(npObject)) returnValue = convertNPVariantToV8Object(&result, npObject, isolate); _NPN_ReleaseVariantValue(&result); return returnValue; } if (!_NPN_IsAlive(npObject)) return throwError(v8ReferenceError, "NPObject deleted", isolate); if (key->IsString() && npObject->_class->hasMethod && npObject->_class->hasMethod(npObject, identifier)) { if (!_NPN_IsAlive(npObject)) return throwError(v8ReferenceError, "NPObject deleted", isolate); PrivateIdentifier* id = static_cast(identifier); UnsafePersistent functionTemplate = V8NPTemplateMap::sharedInstance(isolate).get(id); // FunctionTemplate caches function for each context. v8::Local v8Function; // Cache templates using identifier as the key. if (functionTemplate.isEmpty()) { // Create a new template. v8::Local temp = v8::FunctionTemplate::New(isolate); temp->SetCallHandler(npObjectMethodHandler, key); V8NPTemplateMap::sharedInstance(isolate).set(id, temp); v8Function = temp->GetFunction(); } else { v8Function = functionTemplate.newLocal(isolate)->GetFunction(); } v8Function->SetName(v8::Handle::Cast(key)); return v8Function; } return v8Undefined(); } void npObjectNamedPropertyGetter(v8::Local name, const v8::PropertyCallbackInfo& info) { NPIdentifier identifier = getStringIdentifier(name); v8SetReturnValue(info, npObjectGetProperty(info.Holder(), identifier, name, info.GetIsolate())); } void npObjectIndexedPropertyGetter(uint32_t index, const v8::PropertyCallbackInfo& info) { NPIdentifier identifier = _NPN_GetIntIdentifier(index); v8SetReturnValue(info, npObjectGetProperty(info.Holder(), identifier, v8::Number::New(info.GetIsolate(), index), info.GetIsolate())); } void npObjectGetNamedProperty(v8::Local self, v8::Local name, const v8::PropertyCallbackInfo& info) { NPIdentifier identifier = getStringIdentifier(name); v8SetReturnValue(info, npObjectGetProperty(self, identifier, name, info.GetIsolate())); } void npObjectGetIndexedProperty(v8::Local self, uint32_t index, const v8::PropertyCallbackInfo& info) { NPIdentifier identifier = _NPN_GetIntIdentifier(index); v8SetReturnValue(info, npObjectGetProperty(self, identifier, v8::Number::New(info.GetIsolate(), index), info.GetIsolate())); } void npObjectQueryProperty(v8::Local name, const v8::PropertyCallbackInfo& info) { NPIdentifier identifier = getStringIdentifier(name); if (npObjectGetProperty(info.Holder(), identifier, name, info.GetIsolate()).IsEmpty()) return; v8SetReturnValueInt(info, 0); } static v8::Handle npObjectSetProperty(v8::Local self, NPIdentifier identifier, v8::Local value, v8::Isolate* isolate) { NPObject* npObject = v8ObjectToNPObject(self); // Verify that our wrapper wasn't using a NPObject which has already been deleted. if (!npObject || !_NPN_IsAlive(npObject)) { throwError(v8ReferenceError, "NPObject deleted", isolate); return value; // Intercepted, but an exception was thrown. } if (npObject->_class->hasProperty && npObject->_class->setProperty && npObject->_class->hasProperty(npObject, identifier)) { if (!_NPN_IsAlive(npObject)) return throwError(v8ReferenceError, "NPObject deleted", isolate); NPVariant npValue; VOID_TO_NPVARIANT(npValue); convertV8ObjectToNPVariant(value, npObject, &npValue, isolate); bool success = npObject->_class->setProperty(npObject, identifier, &npValue); _NPN_ReleaseVariantValue(&npValue); if (success) return value; // Intercept the call. } return v8Undefined(); } void npObjectNamedPropertySetter(v8::Local name, v8::Local value, const v8::PropertyCallbackInfo& info) { NPIdentifier identifier = getStringIdentifier(name); v8SetReturnValue(info, npObjectSetProperty(info.Holder(), identifier, value, info.GetIsolate())); } void npObjectIndexedPropertySetter(uint32_t index, v8::Local value, const v8::PropertyCallbackInfo& info) { NPIdentifier identifier = _NPN_GetIntIdentifier(index); v8SetReturnValue(info, npObjectSetProperty(info.Holder(), identifier, value, info.GetIsolate())); } void npObjectSetNamedProperty(v8::Local self, v8::Local name, v8::Local value, const v8::PropertyCallbackInfo& info) { NPIdentifier identifier = getStringIdentifier(name); v8SetReturnValue(info, npObjectSetProperty(self, identifier, value, info.GetIsolate())); } void npObjectSetIndexedProperty(v8::Local self, uint32_t index, v8::Local value, const v8::PropertyCallbackInfo& info) { NPIdentifier identifier = _NPN_GetIntIdentifier(index); v8SetReturnValue(info, npObjectSetProperty(self, identifier, value, info.GetIsolate())); } void npObjectPropertyEnumerator(const v8::PropertyCallbackInfo& info, bool namedProperty) { NPObject* npObject = v8ObjectToNPObject(info.Holder()); // Verify that our wrapper wasn't using a NPObject which // has already been deleted. if (!npObject || !_NPN_IsAlive(npObject)) throwError(v8ReferenceError, "NPObject deleted", info.GetIsolate()); if (NP_CLASS_STRUCT_VERSION_HAS_ENUM(npObject->_class) && npObject->_class->enumerate) { uint32_t count; NPIdentifier* identifiers; if (npObject->_class->enumerate(npObject, &identifiers, &count)) { v8::Handle properties = v8::Array::New(info.GetIsolate(), count); for (uint32_t i = 0; i < count; ++i) { IdentifierRep* identifier = static_cast(identifiers[i]); if (namedProperty) properties->Set(v8::Integer::New(i, info.GetIsolate()), v8AtomicString(info.GetIsolate(), identifier->string())); else properties->Set(v8::Integer::New(i, info.GetIsolate()), v8::Integer::New(identifier->number(), info.GetIsolate())); } v8SetReturnValue(info, properties); return; } } } void npObjectNamedPropertyEnumerator(const v8::PropertyCallbackInfo& info) { npObjectPropertyEnumerator(info, true); } void npObjectIndexedPropertyEnumerator(const v8::PropertyCallbackInfo& info) { npObjectPropertyEnumerator(info, false); } static DOMWrapperMap& staticNPObjectMap() { DEFINE_STATIC_LOCAL(DOMWrapperMap, npObjectMap, (v8::Isolate::GetCurrent())); return npObjectMap; } template<> inline void DOMWrapperMap::setWeakCallback(const v8::WeakCallbackData >& data) { NPObject* npObject = static_cast(toNative(data.GetValue())); ASSERT(npObject); ASSERT(staticNPObjectMap().containsKeyAndValue(npObject, data.GetValue())); // Must remove from our map before calling _NPN_ReleaseObject(). _NPN_ReleaseObject can // call forgetV8ObjectForNPObject, which uses the table as well. staticNPObjectMap().removeAndDispose(npObject); if (_NPN_IsAlive(npObject)) _NPN_ReleaseObject(npObject); } v8::Local createV8ObjectForNPObject(NPObject* object, NPObject* root, v8::Isolate* isolate) { static v8::Eternal npObjectDesc; ASSERT(isolate->InContext()); // If this is a v8 object, just return it. V8NPObject* v8NPObject = npObjectToV8NPObject(object); if (v8NPObject) return v8::Local::New(isolate, v8NPObject->v8Object); // If we've already wrapped this object, just return it. v8::Handle wrapper = staticNPObjectMap().newLocal(object, isolate); if (!wrapper.IsEmpty()) return wrapper; // FIXME: we should create a Wrapper type as a subclass of JSObject. It has two internal fields, field 0 is the wrapped // pointer, and field 1 is the type. There should be an api function that returns unused type id. The same Wrapper type // can be used by DOM bindings. if (npObjectDesc.IsEmpty()) { v8::Local templ = v8::FunctionTemplate::New(isolate); templ->InstanceTemplate()->SetInternalFieldCount(npObjectInternalFieldCount); templ->InstanceTemplate()->SetNamedPropertyHandler(npObjectNamedPropertyGetter, npObjectNamedPropertySetter, npObjectQueryProperty, 0, npObjectNamedPropertyEnumerator); templ->InstanceTemplate()->SetIndexedPropertyHandler(npObjectIndexedPropertyGetter, npObjectIndexedPropertySetter, 0, 0, npObjectIndexedPropertyEnumerator); templ->InstanceTemplate()->SetCallAsFunctionHandler(npObjectInvokeDefaultHandler); npObjectDesc.Set(isolate, templ); } // FIXME: Move staticNPObjectMap() to DOMDataStore. // Use V8DOMWrapper::createWrapper() and // V8DOMWrapper::associateObjectWithWrapper() // to create a wrapper object. v8::Handle v8Function = npObjectDesc.Get(isolate)->GetFunction(); v8::Local value = V8ObjectConstructor::newInstance(v8Function); if (value.IsEmpty()) return value; V8DOMWrapper::setNativeInfo(value, npObjectTypeInfo(), object); // KJS retains the object as part of its wrapper (see Bindings::CInstance). _NPN_RetainObject(object); _NPN_RegisterObject(object, root); WrapperConfiguration configuration = buildWrapperConfiguration(object, WrapperConfiguration::Dependent); staticNPObjectMap().set(object, value, configuration); ASSERT(V8DOMWrapper::maybeDOMWrapper(value)); return value; } void forgetV8ObjectForNPObject(NPObject* object) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::HandleScope scope(isolate); v8::Handle wrapper = staticNPObjectMap().newLocal(object, isolate); if (!wrapper.IsEmpty()) { V8DOMWrapper::clearNativeInfo(wrapper, npObjectTypeInfo()); staticNPObjectMap().removeAndDispose(object); _NPN_ReleaseObject(object); } } } // namespace WebCore