/* Copyright (C) 2011 ProFUSION embedded systems This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #include "ewk_js.h" #if ENABLE(NETSCAPE_PLUGIN_API) #include "NP_jsobject.h" #include "Operations.h" #include "ewk_js_private.h" #include "ewk_private.h" #include "npruntime.h" #include "npruntime_impl.h" #include #define EINA_MAGIC_CHECK_OR_RETURN(o, ...) \ if (!EINA_MAGIC_CHECK(o, EWK_JS_OBJECT_MAGIC)) { \ EINA_MAGIC_FAIL(o, EWK_JS_OBJECT_MAGIC); \ return __VA_ARGS__; \ } struct _Ewk_JS_Class { const Ewk_JS_Class_Meta* meta; Eina_Hash* methods; // Key=NPIdentifier(name), value=pointer to meta->methods. Eina_Hash* properties; // Key=NPIdentifier(name), value=pointer to meta->properties. Ewk_JS_Default default_prop; }; static Eina_Bool ewk_js_npvariant_to_variant(Ewk_JS_Variant* data, const NPVariant* result); static Eina_Bool ewk_js_variant_to_npvariant(const Ewk_JS_Variant* data, NPVariant* result) { EINA_SAFETY_ON_NULL_RETURN_VAL(data, false); EINA_SAFETY_ON_NULL_RETURN_VAL(result, false); const char* string_value; switch (data->type) { case EWK_JS_VARIANT_VOID: VOID_TO_NPVARIANT(*result); break; case EWK_JS_VARIANT_NULL: NULL_TO_NPVARIANT(*result); break; case EWK_JS_VARIANT_INT32: INT32_TO_NPVARIANT(data->value.i, *result); break; case EWK_JS_VARIANT_DOUBLE: DOUBLE_TO_NPVARIANT(data->value.d, *result); break; case EWK_JS_VARIANT_STRING: string_value = eina_stringshare_add(data->value.s); if (string_value) STRINGZ_TO_NPVARIANT(string_value, *result); else return false; break; case EWK_JS_VARIANT_BOOL: BOOLEAN_TO_NPVARIANT(data->value.b, *result); break; case EWK_JS_VARIANT_OBJECT: OBJECT_TO_NPVARIANT(reinterpret_cast(data->value.o), *result); break; default: return false; } return true; } // These methods are used by NPAI, thats the reason to use bool instead of Eina_Bool. static bool ewk_js_property_has(NPObject* npObject, NPIdentifier name) { Ewk_JS_Object* object = reinterpret_cast(npObject); EINA_SAFETY_ON_NULL_RETURN_VAL(npObject, false); EINA_MAGIC_CHECK_OR_RETURN(object, false); if (!_NPN_IdentifierIsString(name)) { ERR("int NPIdentifier is not supported."); return false; } char* prop_name = _NPN_UTF8FromIdentifier(name); bool fail = eina_hash_find(object->properties, prop_name); // FIXME: should search methods too? free(prop_name); return fail; } static bool ewk_js_property_get(NPObject* npObject, NPIdentifier name, NPVariant* result) { Ewk_JS_Object* object = reinterpret_cast(npObject); Ewk_JS_Variant* value; Ewk_JS_Property* prop; bool fail = false; EINA_SAFETY_ON_NULL_RETURN_VAL(npObject, false); EINA_MAGIC_CHECK_OR_RETURN(object, false); if (!_NPN_IdentifierIsString(name)) { ERR("int NPIdentifier is not supported."); return false; } value = static_cast(malloc(sizeof(Ewk_JS_Variant))); if (!value) { ERR("Could not allocate memory for ewk_js_variant"); return false; } prop = static_cast(eina_hash_find(object->cls->properties, name)); if (prop && prop->get) { // Class has property and property has getter. fail = prop->get(object, prop->name, value); if (!fail) fail = ewk_js_variant_to_npvariant(value, result); } else if (object->cls->default_prop.get) { // Default getter exists. fail = object->cls->default_prop.get(object, prop->name, value); if (!fail) fail = ewk_js_variant_to_npvariant(value, result); } else { // Fallback to objects hash map. char* prop_name = _NPN_UTF8FromIdentifier(name); free(value); value = static_cast(eina_hash_find(object->properties, prop_name)); free(prop_name); if (value) return ewk_js_variant_to_npvariant(value, result); } free(value); return fail; } static bool ewk_js_property_set(NPObject* npObject, NPIdentifier name, const NPVariant* npValue) { Ewk_JS_Object* object = reinterpret_cast(npObject); Ewk_JS_Variant* value; Ewk_JS_Property* prop; bool fail = false; EINA_SAFETY_ON_NULL_RETURN_VAL(npObject, false); EINA_MAGIC_CHECK_OR_RETURN(object, false); if (!_NPN_IdentifierIsString(name)) { ERR("int NPIdentifier is not supported."); return fail; } value = static_cast(malloc(sizeof(Ewk_JS_Variant))); if (!value) { ERR("Could not allocate memory for ewk_js_variant"); return false; } ewk_js_npvariant_to_variant(value, npValue); char* prop_name = _NPN_UTF8FromIdentifier(name); prop = static_cast(eina_hash_find(object->cls->properties, prop_name)); if (prop && prop->set) fail = prop->set(object, prop->name, value); // Class has property and property has setter. else if (object->cls->default_prop.set) fail = object->cls->default_prop.set(object, prop_name, value); // Default getter exists. else { // Fallback to objects hash map. void* old = eina_hash_set(object->properties, prop_name, value); free(old); fail = true; } free(prop_name); return fail; } static bool ewk_js_property_remove(NPObject* npObject, NPIdentifier name) { Ewk_JS_Object* object = reinterpret_cast(npObject); Ewk_JS_Property* prop; bool fail = false; EINA_SAFETY_ON_NULL_RETURN_VAL(npObject, false); EINA_MAGIC_CHECK_OR_RETURN(object, false); if (!_NPN_IdentifierIsString(name)) { ERR("int NPIdentifier is not supported."); return fail; } char* prop_name = _NPN_UTF8FromIdentifier(name); prop = static_cast(eina_hash_find(object->cls->properties, prop_name)); if (prop && prop->del) fail = prop->del(object, prop->name); // Class has property and property has getter. else if (object->cls->default_prop.del) fail = object->cls->default_prop.del(object, prop_name); else fail = eina_hash_del(object->properties, prop_name, 0); free(prop_name); return fail; } static bool ewk_js_properties_enumerate(NPObject* npObject, NPIdentifier** value, uint32_t* count) { Ewk_JS_Object* object = reinterpret_cast(npObject); Eina_Iterator* it; char* key; int i = 0; EINA_SAFETY_ON_NULL_RETURN_VAL(npObject, false); EINA_MAGIC_CHECK_OR_RETURN(object, false); *count = eina_hash_population(object->properties); *value = static_cast(malloc(sizeof(NPIdentifier) * *count)); if (!*value) { ERR("Could not allocate memory for NPIdentifier"); return false; } it = eina_hash_iterator_key_new(object->properties); EINA_ITERATOR_FOREACH(it, key) (*value)[i++] = _NPN_GetStringIdentifier(key); eina_iterator_free(it); return true; } static bool ewk_js_method_has(NPObject* npObject, NPIdentifier name) { Ewk_JS_Object* object = reinterpret_cast(npObject); EINA_SAFETY_ON_NULL_RETURN_VAL(npObject, false); EINA_MAGIC_CHECK_OR_RETURN(object, false); if (!_NPN_IdentifierIsString(name)) { ERR("int NPIdentifier is not supported."); return false; } return eina_hash_find(object->cls->methods, name); // Returns pointer if found(true), 0(false) otherwise. } static bool ewk_js_method_invoke(NPObject* npObject, NPIdentifier name, const NPVariant* npArgs, uint32_t npArgCount, NPVariant* result) { Ewk_JS_Object* object = reinterpret_cast(npObject); Ewk_JS_Method* method; Ewk_JS_Variant* args; Ewk_JS_Variant* ret_val; EINA_SAFETY_ON_NULL_RETURN_VAL(npObject, false); EINA_MAGIC_CHECK_OR_RETURN(object, false); if (!_NPN_IdentifierIsString(name)) { ERR("int NPIdentifier is not supported."); return false; } method = static_cast(eina_hash_find(object->cls->methods, name)); if (!method) return false; args = static_cast(malloc(sizeof(Ewk_JS_Variant) *npArgCount)); if (!args) { ERR("Could not allocate memory for ewk_js_variant"); return false; } for (uint32_t i = 0; i < npArgCount; i++) ewk_js_npvariant_to_variant(&args[i], &npArgs[i]); ret_val = method->invoke(object, args, npArgCount); ewk_js_variant_to_npvariant(ret_val, result); ewk_js_variant_free(ret_val); return true; } static Eina_Bool ewk_js_npobject_property_get(Ewk_JS_Object* jsObject, const char* name, Ewk_JS_Variant* value) { NPIdentifier id = _NPN_GetStringIdentifier(name); NPVariant var; bool fail = _NPN_GetProperty(0, reinterpret_cast(jsObject), id, &var); if (!fail) fail = ewk_js_npvariant_to_variant(value, &var); return fail; } static Eina_Bool ewk_js_npobject_property_set(Ewk_JS_Object* jsObject, const char* name, const Ewk_JS_Variant* value) { NPIdentifier id = _NPN_GetStringIdentifier(name); NPVariant var; bool fail = ewk_js_variant_to_npvariant(value, &var); if (fail) fail = _NPN_SetProperty(0, reinterpret_cast(jsObject), id, &var); return fail; } static Eina_Bool ewk_js_npobject_property_del(Ewk_JS_Object* jsObject, const char* name) { NPIdentifier id = _NPN_GetStringIdentifier(name); return _NPN_RemoveProperty(0, reinterpret_cast(jsObject), id); } static void ewk_js_property_free(Ewk_JS_Property* prop) { free(const_cast(prop->name)); if (prop->value.type == EWK_JS_VARIANT_STRING) eina_stringshare_del(prop->value.value.s); else if (prop->value.type == EWK_JS_VARIANT_OBJECT) ewk_js_object_free(prop->value.value.o); free(prop); } /** * Create a Ewk_JS_Class to be used in @a ewk_js_object_new. * * @param meta @a Ewk_JS_Class_Meta that describes the class to be created. * * @return The Ewk_JS_Class created. */ Ewk_JS_Class* ewk_js_class_new(const Ewk_JS_Class_Meta* jsMetaClass) { Ewk_JS_Class* cls; EINA_SAFETY_ON_NULL_RETURN_VAL(jsMetaClass, 0); cls = static_cast(malloc(sizeof(Ewk_JS_Class))); if (!cls) { ERR("Could not allocate memory for ewk_js_class"); return 0; } cls->meta = jsMetaClass; cls->default_prop = cls->meta->default_prop; // Don't free methods since they point to meta class methods(will be freed when meta class is freed). cls->methods = eina_hash_pointer_new(0); for (int i = 0; cls->meta->methods && cls->meta->methods[i].name; i++) { NPIdentifier id = _NPN_GetStringIdentifier(cls->meta->methods[i].name); eina_hash_add(cls->methods, id, &cls->meta->methods[i]); } // Don't free properties since they point to cls->meta class properties(will be freed when cls->meta class is freed). cls->properties = eina_hash_pointer_new(0); for (int i = 0; cls->meta->properties && cls->meta->properties[i].name; i++) { NPIdentifier id = _NPN_GetStringIdentifier(cls->meta->properties[i].name); eina_hash_add(cls->properties, id, &cls->meta->properties[i]); } return cls; } /** * Release resources allocated by @a cls. * * @param cls @a Ewk_JS_Class to be released. */ void ewk_js_class_free(Ewk_JS_Class* jsClass) { EINA_SAFETY_ON_NULL_RETURN(jsClass); eina_hash_free(jsClass->methods); eina_hash_free(jsClass->properties); free(jsClass); } static NPClass EWK_NPCLASS = { NP_CLASS_STRUCT_VERSION, 0, // NPAllocateFunctionPtr 0, // NPDeallocateFunctionPtr 0, // NPInvalidateFunctionPtr ewk_js_method_has, // NPHasMethodFunctionPtr ewk_js_method_invoke, // NPInvokeFunctionPtr 0, // NPInvokeDefaultFunctionPtr ewk_js_property_has, // NPHasPropertyFunctionPtr ewk_js_property_get, // NPGetPropertyFunctionPtr ewk_js_property_set, // NPSetPropertyFunctionPtr ewk_js_property_remove, // NPRemovePropertyFunctionPtr ewk_js_properties_enumerate, // NPEnumerationFunctionPtr 0 // NPConstructFunction }; static Ewk_JS_Object* ewk_js_npobject_to_object(NPObject* npObject) { NPIdentifier* values; uint32_t np_props_count; Ewk_JS_Class* cls; Ewk_JS_Object* object; Eina_Iterator* it; Ewk_JS_Property* prop; JavaScriptObject* jso; if (EINA_MAGIC_CHECK(reinterpret_cast(npObject), EWK_JS_OBJECT_MAGIC)) return reinterpret_cast(npObject); if (!_NPN_Enumerate(0, npObject, &values, &np_props_count)) return 0; cls = static_cast(malloc(sizeof(Ewk_JS_Class))); if (!cls) { ERR("Could not allocate memory for ewk_js_class"); return 0; } cls->meta = 0; Ewk_JS_Default def = { ewk_js_npobject_property_set, ewk_js_npobject_property_get, ewk_js_npobject_property_del }; cls->default_prop = def; cls->methods = eina_hash_pointer_new(0); cls->properties = eina_hash_pointer_new(reinterpret_cast(ewk_js_property_free)); for (uint32_t i = 0; i < np_props_count; i++) { if (_NPN_HasProperty(0, npObject, values[i])) { NPVariant var; Ewk_JS_Property* prop = static_cast(calloc(sizeof(Ewk_JS_Property), 1)); if (!prop) { ERR("Could not allocate memory for ewk_js_property"); goto error; } _NPN_GetProperty(0, npObject, values[i], &var); ewk_js_npvariant_to_variant(&(prop->value), &var); prop->name = _NPN_UTF8FromIdentifier(values[i]); eina_hash_add(cls->properties, values[i], prop); } } // Can't use ewk_js_object_new(cls) because it expects cls->meta to exist. object = static_cast(malloc(sizeof(Ewk_JS_Object))); if (!object) { ERR("Could not allocate memory for ewk_js_object"); goto error; } free(values); EINA_MAGIC_SET(object, EWK_JS_OBJECT_MAGIC); object->name = 0; object->cls = cls; object->view = 0; jso = reinterpret_cast(npObject); if (!strcmp("Array", jso->imp->methodTable()->className(jso->imp).ascii().data())) object->type = EWK_JS_OBJECT_ARRAY; else if (!strcmp("Function", jso->imp->methodTable()->className(jso->imp).ascii().data())) object->type = EWK_JS_OBJECT_FUNCTION; else object->type = EWK_JS_OBJECT_OBJECT; if (eina_hash_population(cls->properties) < 25) object->properties = eina_hash_string_small_new(0); else object->properties = eina_hash_string_superfast_new(0); it = eina_hash_iterator_data_new(cls->properties); EINA_ITERATOR_FOREACH(it, prop) { const char* key = prop->name; Ewk_JS_Variant* value = &prop->value; eina_hash_add(object->properties, key, value); } eina_iterator_free(it); object->base = *reinterpret_cast(npObject); return object; error: ewk_js_class_free(cls); free(values); return 0; } static Eina_Bool ewk_js_npvariant_to_variant(Ewk_JS_Variant* data, const NPVariant* result) { EINA_SAFETY_ON_NULL_RETURN_VAL(data, false); EINA_SAFETY_ON_NULL_RETURN_VAL(result, false); switch (result->type) { case NPVariantType_Void: data->type = EWK_JS_VARIANT_VOID; data->value.o = 0; break; case NPVariantType_Null: data->type = EWK_JS_VARIANT_NULL; data->value.o = 0; break; case NPVariantType_Int32: data->type = EWK_JS_VARIANT_INT32; data->value.i = NPVARIANT_TO_INT32(*result); break; case NPVariantType_Double: data->type = EWK_JS_VARIANT_DOUBLE; data->value.d = NPVARIANT_TO_DOUBLE(*result); break; case NPVariantType_String: data->value.s = eina_stringshare_add_length(NPVARIANT_TO_STRING(*result).UTF8Characters, NPVARIANT_TO_STRING(*result).UTF8Length); data->type = EWK_JS_VARIANT_STRING; break; case NPVariantType_Bool: data->type = EWK_JS_VARIANT_BOOL; data->value.b = NPVARIANT_TO_BOOLEAN(*result); break; case NPVariantType_Object: data->type = EWK_JS_VARIANT_OBJECT; data->value.o = ewk_js_npobject_to_object(NPVARIANT_TO_OBJECT(*result)); break; default: return false; } return true; } #endif // ENABLE(NETSCAPE_PLUGIN_API) Ewk_JS_Object* ewk_js_object_new(const Ewk_JS_Class_Meta* jsMetaClass) { #if ENABLE(NETSCAPE_PLUGIN_API) Ewk_JS_Object* object; EINA_SAFETY_ON_NULL_RETURN_VAL(jsMetaClass, 0); object = static_cast(malloc(sizeof(Ewk_JS_Object))); if (!object) { ERR("Could not allocate memory for ewk_js_object"); return 0; } EINA_MAGIC_SET(object, EWK_JS_OBJECT_MAGIC); object->cls = ewk_js_class_new(jsMetaClass); object->view = 0; object->name = 0; object->type = EWK_JS_OBJECT_OBJECT; if (eina_hash_population(object->cls->properties) < 25) object->properties = eina_hash_string_small_new(reinterpret_cast(ewk_js_variant_free)); else object->properties = eina_hash_string_superfast_new(reinterpret_cast(ewk_js_variant_free)); for (int i = 0; object->cls->meta->properties && object->cls->meta->properties[i].name; i++) { Ewk_JS_Property prop = object->cls->meta->properties[i]; const char* key = object->cls->meta->properties[i].name; Ewk_JS_Variant* value = static_cast(malloc(sizeof(Ewk_JS_Variant))); if (!value) { ERR("Could not allocate memory for ewk_js_variant"); goto error; } if (prop.get) prop.get(object, key, value); else { value->type = prop.value.type; switch (value->type) { case EWK_JS_VARIANT_VOID: case EWK_JS_VARIANT_NULL: value->value.o = 0; break; case EWK_JS_VARIANT_STRING: value->value.s = eina_stringshare_add(prop.value.value.s); break; case EWK_JS_VARIANT_BOOL: value->value.b = prop.value.value.b; break; case EWK_JS_VARIANT_INT32: value->value.i = prop.value.value.i; break; case EWK_JS_VARIANT_DOUBLE: value->value.d = prop.value.value.d; break; case EWK_JS_VARIANT_OBJECT: value->value.o = prop.value.value.o; break; } } eina_hash_add(object->properties, key, value); } object->base.object.referenceCount = 1; object->base.object._class = &EWK_NPCLASS; return object; error: ewk_js_object_free(object); return 0; #else UNUSED_PARAM(jsMetaClass); return 0; #endif } void ewk_js_object_free(Ewk_JS_Object* jsObject) { #if ENABLE(NETSCAPE_PLUGIN_API) EINA_SAFETY_ON_NULL_RETURN(jsObject); EINA_MAGIC_CHECK_OR_RETURN(jsObject); Eina_Bool script_obj = !jsObject->cls->meta; eina_hash_free(jsObject->properties); eina_stringshare_del(jsObject->name); ewk_js_class_free(const_cast(jsObject->cls)); EINA_MAGIC_SET(jsObject, EINA_MAGIC_NONE); if (script_obj) free(jsObject); #else UNUSED_PARAM(jsObject); #endif } Evas_Object* ewk_js_object_view_get(const Ewk_JS_Object* jsObject) { #if ENABLE(NETSCAPE_PLUGIN_API) EINA_SAFETY_ON_NULL_RETURN_VAL(jsObject, 0); EINA_MAGIC_CHECK_OR_RETURN(jsObject, 0); return jsObject->view; #else UNUSED_PARAM(jsObject); return 0; #endif } Eina_Hash* ewk_js_object_properties_get(const Ewk_JS_Object* jsObject) { #if ENABLE(NETSCAPE_PLUGIN_API) EINA_SAFETY_ON_NULL_RETURN_VAL(jsObject, 0); EINA_MAGIC_CHECK_OR_RETURN(jsObject, 0); return jsObject->properties; #else UNUSED_PARAM(jsObject); return 0; #endif } const char* ewk_js_object_name_get(const Ewk_JS_Object* jsObject) { #if ENABLE(NETSCAPE_PLUGIN_API) EINA_SAFETY_ON_NULL_RETURN_VAL(jsObject, 0); EINA_MAGIC_CHECK_OR_RETURN(jsObject, 0); return jsObject->name; #else UNUSED_PARAM(jsObject); return 0; #endif } Eina_Bool ewk_js_object_invoke(Ewk_JS_Object* jsObject, Ewk_JS_Variant* args, int argCount, Ewk_JS_Variant* result) { #if ENABLE(NETSCAPE_PLUGIN_API) NPVariant* np_args; NPVariant np_result; bool fail = false; EINA_MAGIC_CHECK_OR_RETURN(jsObject, false); if (ewk_js_object_type_get(jsObject) != EWK_JS_OBJECT_FUNCTION) return false; if (argCount) EINA_SAFETY_ON_NULL_RETURN_VAL(args, false); np_args = static_cast(malloc(sizeof(NPVariant) *argCount)); if (!np_args) { ERR("Could not allocate memory to method arguments"); return false; } for (int i = 0; i < argCount; i++) if (!ewk_js_variant_to_npvariant(&args[i], &np_args[i])) goto end; if (!(fail = _NPN_InvokeDefault(0, reinterpret_cast(jsObject), np_args, argCount, &np_result))) goto end; if (result) fail = ewk_js_npvariant_to_variant(result, &np_result); end: free(np_args); return fail; #else UNUSED_PARAM(jsObject); UNUSED_PARAM(args); UNUSED_PARAM(argCount); UNUSED_PARAM(result); return false; #endif } Ewk_JS_Object_Type ewk_js_object_type_get(Ewk_JS_Object* jsObject) { #if ENABLE(NETSCAPE_PLUGIN_API) EINA_SAFETY_ON_NULL_RETURN_VAL(jsObject, EWK_JS_OBJECT_OBJECT); EINA_MAGIC_CHECK_OR_RETURN(jsObject, EWK_JS_OBJECT_OBJECT); return jsObject->type; #else UNUSED_PARAM(jsObject); return EWK_JS_OBJECT_INVALID; #endif } void ewk_js_object_type_set(Ewk_JS_Object* jsObject, Ewk_JS_Object_Type type) { #if ENABLE(NETSCAPE_PLUGIN_API) EINA_SAFETY_ON_NULL_RETURN(jsObject); EINA_MAGIC_CHECK_OR_RETURN(jsObject); jsObject->type = type; #else UNUSED_PARAM(jsObject); UNUSED_PARAM(type); #endif } void ewk_js_variant_free(Ewk_JS_Variant* jsVariant) { #if ENABLE(NETSCAPE_PLUGIN_API) EINA_SAFETY_ON_NULL_RETURN(jsVariant); if (jsVariant->type == EWK_JS_VARIANT_STRING) eina_stringshare_del(jsVariant->value.s); else if (jsVariant->type == EWK_JS_VARIANT_OBJECT) ewk_js_object_free(jsVariant->value.o); free(jsVariant); #else UNUSED_PARAM(jsVariant); #endif } void ewk_js_variant_array_free(Ewk_JS_Variant* jsVariant, int count) { #if ENABLE(NETSCAPE_PLUGIN_API) EINA_SAFETY_ON_NULL_RETURN(jsVariant); for (int i = 0; i < count; i++) { if (jsVariant[i].type == EWK_JS_VARIANT_STRING) eina_stringshare_del(jsVariant[i].value.s); else if (jsVariant[i].type == EWK_JS_VARIANT_OBJECT) ewk_js_object_free(jsVariant[i].value.o); } free(jsVariant); #else UNUSED_PARAM(jsVariant); UNUSED_PARAM(count); #endif }