| /* |
| * Copyright (C) 2018 Igalia S.L. |
| * |
| * 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 "JSCValue.h" |
| |
| #include "APICast.h" |
| #include "APIUtils.h" |
| #include "JSCCallbackFunction.h" |
| #include "JSCClassPrivate.h" |
| #include "JSCContextPrivate.h" |
| #include "JSCInlines.h" |
| #include "JSCValuePrivate.h" |
| #include "JSRetainPtr.h" |
| #include "LiteralParser.h" |
| #include "OpaqueJSString.h" |
| #include <gobject/gvaluecollector.h> |
| #include <wtf/glib/GRefPtr.h> |
| #include <wtf/glib/GUniquePtr.h> |
| #include <wtf/glib/WTFGType.h> |
| |
| /** |
| * SECTION: JSCValue |
| * @short_description: JavaScript value |
| * @title: JSCValue |
| * @see_also: JSCContext |
| * |
| * JSCValue represents a reference to a value in a #JSCContext. The JSCValue |
| * protects the referenced value from being garbage collected. |
| */ |
| |
| enum { |
| PROP_0, |
| |
| PROP_CONTEXT, |
| }; |
| |
| struct _JSCValuePrivate { |
| GRefPtr<JSCContext> context; |
| JSValueRef jsValue; |
| }; |
| |
| WEBKIT_DEFINE_TYPE(JSCValue, jsc_value, G_TYPE_OBJECT) |
| |
| static void jscValueGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* paramSpec) |
| { |
| JSCValuePrivate* priv = JSC_VALUE(object)->priv; |
| |
| switch (propID) { |
| case PROP_CONTEXT: |
| g_value_set_object(value, priv->context.get()); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec); |
| } |
| } |
| |
| static void jscValueSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* paramSpec) |
| { |
| JSCValuePrivate* priv = JSC_VALUE(object)->priv; |
| |
| switch (propID) { |
| case PROP_CONTEXT: |
| priv->context = JSC_CONTEXT(g_value_get_object(value)); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec); |
| } |
| } |
| |
| static void jscValueDispose(GObject* object) |
| { |
| JSCValuePrivate* priv = JSC_VALUE(object)->priv; |
| |
| if (priv->context) { |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| |
| JSValueUnprotect(jsContext, priv->jsValue); |
| jscContextValueDestroyed(priv->context.get(), priv->jsValue); |
| priv->jsValue = nullptr; |
| priv->context = nullptr; |
| } |
| |
| G_OBJECT_CLASS(jsc_value_parent_class)->dispose(object); |
| } |
| |
| static void jsc_value_class_init(JSCValueClass* klass) |
| { |
| GObjectClass* objClass = G_OBJECT_CLASS(klass); |
| |
| objClass->get_property = jscValueGetProperty; |
| objClass->set_property = jscValueSetProperty; |
| objClass->dispose = jscValueDispose; |
| |
| /** |
| * JSCValue:context: |
| * |
| * The #JSCContext in which the value was created. |
| */ |
| g_object_class_install_property(objClass, |
| PROP_CONTEXT, |
| g_param_spec_object( |
| "context", |
| "JSCContext", |
| "JSC Context", |
| JSC_TYPE_CONTEXT, |
| static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY))); |
| } |
| |
| JSValueRef jscValueGetJSValue(JSCValue* value) |
| { |
| return value->priv->jsValue; |
| } |
| |
| JSCValue* jscValueCreate(JSCContext* context, JSValueRef jsValue) |
| { |
| auto* value = JSC_VALUE(g_object_new(JSC_TYPE_VALUE, "context", context, nullptr)); |
| JSValueProtect(jscContextGetJSContext(context), jsValue); |
| value->priv->jsValue = jsValue; |
| return value; |
| } |
| |
| /** |
| * jsc_value_get_context: |
| * @value: a #JSCValue |
| * |
| * Get the #JSCContext in which @value was created. |
| * |
| * Returns: (transfer none): the #JSCValue context. |
| */ |
| JSCContext* jsc_value_get_context(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| |
| return value->priv->context.get(); |
| } |
| |
| /** |
| * jsc_value_new_undefined: |
| * @context: a #JSCContext |
| * |
| * Create a new #JSCValue referencing <function>undefined</function> in @context. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_undefined(JSCContext* context) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| |
| return jscContextGetOrCreateValue(context, JSValueMakeUndefined(jscContextGetJSContext(context))).leakRef(); |
| } |
| |
| /** |
| * jsc_value_is_undefined: |
| * @value: a #JSCValue |
| * |
| * Get whether the value referenced by @value is <function>undefined</function>. |
| * |
| * Returns: whether the value is undefined. |
| */ |
| gboolean jsc_value_is_undefined(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| return JSValueIsUndefined(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
| } |
| |
| /** |
| * jsc_value_new_null: |
| * @context: a #JSCContext |
| * |
| * Create a new #JSCValue referencing <function>null</function> in @context. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_null(JSCContext* context) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| |
| return jscContextGetOrCreateValue(context, JSValueMakeNull(jscContextGetJSContext(context))).leakRef(); |
| } |
| |
| /** |
| * jsc_value_is_null: |
| * @value: a #JSCValue |
| * |
| * Get whether the value referenced by @value is <function>null</function>. |
| * |
| * Returns: whether the value is null. |
| */ |
| gboolean jsc_value_is_null(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| return JSValueIsNull(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
| } |
| |
| /** |
| * jsc_value_new_number: |
| * @context: a #JSCContext |
| * @number: a number |
| * |
| * Create a new #JSCValue from @number. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_number(JSCContext* context, double number) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| |
| return jscContextGetOrCreateValue(context, JSValueMakeNumber(jscContextGetJSContext(context), number)).leakRef(); |
| } |
| |
| /** |
| * jsc_value_is_number: |
| * @value: a #JSCValue |
| * |
| * Get whether the value referenced by @value is a number. |
| * |
| * Returns: whether the value is a number. |
| */ |
| gboolean jsc_value_is_number(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| return JSValueIsNumber(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
| } |
| |
| /** |
| * jsc_value_to_double: |
| * @value: a #JSCValue |
| * |
| * Convert @value to a double. |
| * |
| * Returns: a #gdouble result of the conversion. |
| */ |
| double jsc_value_to_double(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), std::numeric_limits<double>::quiet_NaN()); |
| |
| JSCValuePrivate* priv = value->priv; |
| JSValueRef exception = nullptr; |
| auto result = JSValueToNumber(jscContextGetJSContext(priv->context.get()), priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return std::numeric_limits<double>::quiet_NaN(); |
| |
| return result; |
| } |
| |
| /** |
| * jsc_value_to_int32: |
| * @value: a #JSCValue |
| * |
| * Convert @value to a #gint32. |
| * |
| * Returns: a #gint32 result of the conversion. |
| */ |
| gint32 jsc_value_to_int32(JSCValue* value) |
| { |
| return JSC::toInt32(jsc_value_to_double(value)); |
| } |
| |
| /** |
| * jsc_value_new_boolean: |
| * @context: a #JSCContext |
| * @value: a #gboolean |
| * |
| * Create a new #JSCValue from @value |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_boolean(JSCContext* context, gboolean value) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| |
| return jscContextGetOrCreateValue(context, JSValueMakeBoolean(jscContextGetJSContext(context), value)).leakRef(); |
| } |
| |
| /** |
| * jsc_value_is_boolean: |
| * @value: a #JSCValue |
| * |
| * Get whether the value referenced by @value is a boolean. |
| * |
| * Returns: whether the value is a boolean. |
| */ |
| gboolean jsc_value_is_boolean(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| return JSValueIsBoolean(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
| } |
| |
| /** |
| * jsc_value_to_boolean: |
| * @value: a #JSCValue |
| * |
| * Convert @value to a boolean. |
| * |
| * Returns: a #gboolean result of the conversion. |
| */ |
| gboolean jsc_value_to_boolean(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| return JSValueToBoolean(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
| } |
| |
| /** |
| * jsc_value_new_string: |
| * @context: a #JSCContext |
| * @string: (nullable): a null-terminated string |
| * |
| * Create a new #JSCValue from @string. If you need to create a #JSCValue from a |
| * string containing null characters, use jsc_value_new_string_from_bytes() instead. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_string(JSCContext* context, const char* string) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| |
| JSValueRef jsStringValue; |
| if (string) { |
| JSRetainPtr<JSStringRef> jsString(Adopt, JSStringCreateWithUTF8CString(string)); |
| jsStringValue = JSValueMakeString(jscContextGetJSContext(context), jsString.get()); |
| } else |
| jsStringValue = JSValueMakeString(jscContextGetJSContext(context), nullptr); |
| return jscContextGetOrCreateValue(context, jsStringValue).leakRef(); |
| } |
| |
| /** |
| * jsc_value_new_string_from_bytes: |
| * @context: a #JSCContext |
| * @bytes: (nullable): a #GBytes |
| * |
| * Create a new #JSCValue from @bytes. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_string_from_bytes(JSCContext* context, GBytes* bytes) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| |
| if (!bytes) |
| return jsc_value_new_string(context, nullptr); |
| |
| gsize dataSize; |
| const auto* data = static_cast<const char*>(g_bytes_get_data(bytes, &dataSize)); |
| auto string = String::fromUTF8(data, dataSize); |
| JSRetainPtr<JSStringRef> jsString(Adopt, OpaqueJSString::tryCreate(WTFMove(string)).leakRef()); |
| return jscContextGetOrCreateValue(context, JSValueMakeString(jscContextGetJSContext(context), jsString.get())).leakRef(); |
| } |
| |
| /** |
| * jsc_value_is_string: |
| * @value: a #JSCValue |
| * |
| * Get whether the value referenced by @value is a string |
| * |
| * Returns: whether the value is a string |
| */ |
| gboolean jsc_value_is_string(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| return JSValueIsString(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
| } |
| |
| /** |
| * jsc_value_to_string: |
| * @value: a #JSCValue |
| * |
| * Convert @value to a string. Use jsc_value_to_string_as_bytes() instead, if you need to |
| * handle strings containing null characters. |
| * |
| * Returns: (transfer full): a null-terminated string result of the conversion. |
| */ |
| char* jsc_value_to_string(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| JSValueRef exception = nullptr; |
| JSRetainPtr<JSStringRef> jsString(Adopt, JSValueToStringCopy(jscContextGetJSContext(priv->context.get()), priv->jsValue, &exception)); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return nullptr; |
| |
| if (!jsString) |
| return nullptr; |
| |
| size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString.get()); |
| auto* string = static_cast<char*>(g_malloc(maxSize)); |
| if (!JSStringGetUTF8CString(jsString.get(), string, maxSize)) { |
| g_free(string); |
| return nullptr; |
| } |
| |
| return string; |
| } |
| |
| /** |
| * jsc_value_to_string_as_bytes: |
| * @value: a #JSCValue |
| * |
| * Convert @value to a string and return the results as #GBytes. This is needed |
| * to handle strings with null characters. |
| * |
| * Returns: (transfer full): a #GBytes with the result of the conversion. |
| */ |
| GBytes* jsc_value_to_string_as_bytes(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| JSValueRef exception = nullptr; |
| JSRetainPtr<JSStringRef> jsString(Adopt, JSValueToStringCopy(jscContextGetJSContext(priv->context.get()), priv->jsValue, &exception)); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return nullptr; |
| |
| if (!jsString) |
| return nullptr; |
| |
| size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString.get()); |
| if (maxSize == 1) |
| return g_bytes_new_static("", 0); |
| |
| auto* string = static_cast<char*>(fastMalloc(maxSize)); |
| auto stringSize = JSStringGetUTF8CString(jsString.get(), string, maxSize); |
| if (!stringSize) { |
| fastFree(string); |
| return nullptr; |
| } |
| |
| // Ignore the null character added by JSStringGetUTF8CString. |
| return g_bytes_new_with_free_func(string, stringSize - 1, fastFree, string); |
| } |
| |
| /** |
| * jsc_value_new_array: (skip) |
| * @context: a #JSCContext |
| * @first_item_type: #GType of first item, or %G_TYPE_NONE |
| * @...: value of the first item, followed optionally by more type/value pairs, followed by %G_TYPE_NONE. |
| * |
| * Create a new #JSCValue referencing an array with the given items. If @first_item_type |
| * is %G_TYPE_NONE an empty array is created. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_array(JSCContext* context, GType firstItemType, ...) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| |
| auto* jsContext = jscContextGetJSContext(context); |
| JSC::JSGlobalObject* globalObject = toJS(jsContext); |
| JSC::JSLockHolder locker(globalObject); |
| |
| JSValueRef exception = nullptr; |
| auto* jsArray = JSObjectMakeArray(jsContext, 0, nullptr, &exception); |
| if (jscContextHandleExceptionIfNeeded(context, exception)) |
| return nullptr; |
| |
| auto* jsArrayObject = JSValueToObject(jsContext, jsArray, &exception); |
| if (jscContextHandleExceptionIfNeeded(context, exception)) |
| return nullptr; |
| |
| unsigned index = 0; |
| va_list args; |
| va_start(args, firstItemType); |
| GType itemType = firstItemType; |
| while (itemType != G_TYPE_NONE) { |
| GValue item; |
| GUniqueOutPtr<char> error; |
| G_VALUE_COLLECT_INIT(&item, itemType, args, G_VALUE_NOCOPY_CONTENTS, &error.outPtr()); |
| if (error) { |
| exception = toRef(JSC::createTypeError(globalObject, makeString("failed to collect array item: ", error.get()))); |
| jscContextHandleExceptionIfNeeded(context, exception); |
| jsArray = nullptr; |
| break; |
| } |
| |
| auto* jsValue = jscContextGValueToJSValue(context, &item, &exception); |
| g_value_unset(&item); |
| if (jscContextHandleExceptionIfNeeded(context, exception)) { |
| jsArray = nullptr; |
| break; |
| } |
| |
| JSObjectSetPropertyAtIndex(jsContext, jsArrayObject, index, jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(context, exception)) { |
| jsArray = nullptr; |
| break; |
| } |
| |
| itemType = va_arg(args, GType); |
| index++; |
| } |
| va_end(args); |
| |
| return jsArray ? jscContextGetOrCreateValue(context, jsArray).leakRef() : nullptr; |
| } |
| |
| /** |
| * jsc_value_new_array_from_garray: |
| * @context: a #JSCContext |
| * @array: (nullable) (element-type JSCValue): a #GPtrArray |
| * |
| * Create a new #JSCValue referencing an array with the items from @array. If @array |
| * is %NULL or empty a new empty array will be created. Elements of @array should be |
| * pointers to a #JSCValue. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_array_from_garray(JSCContext* context, GPtrArray* gArray) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| |
| if (!gArray || !gArray->len) |
| return jsc_value_new_array(context, G_TYPE_NONE); |
| |
| JSValueRef exception = nullptr; |
| auto* jsArray = jscContextGArrayToJSArray(context, gArray, &exception); |
| if (jscContextHandleExceptionIfNeeded(context, exception)) |
| return nullptr; |
| |
| return jscContextGetOrCreateValue(context, jsArray).leakRef(); |
| } |
| |
| /** |
| * jsc_value_new_array_from_strv: |
| * @context: a #JSCContext |
| * @strv: (array zero-terminated=1) (element-type utf8): a %NULL-terminated array of strings |
| * |
| * Create a new #JSCValue referencing an array of strings with the items from @strv. If @array |
| * is %NULL or empty a new empty array will be created. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_array_from_strv(JSCContext* context, const char* const* strv) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| |
| auto strvLength = strv ? g_strv_length(const_cast<char**>(strv)) : 0; |
| if (!strvLength) |
| return jsc_value_new_array(context, G_TYPE_NONE); |
| |
| GRefPtr<GPtrArray> gArray = adoptGRef(g_ptr_array_new_full(strvLength, g_object_unref)); |
| for (unsigned i = 0; i < strvLength; i++) |
| g_ptr_array_add(gArray.get(), jsc_value_new_string(context, strv[i])); |
| |
| return jsc_value_new_array_from_garray(context, gArray.get()); |
| } |
| |
| /** |
| * jsc_value_is_array: |
| * @value: a #JSCValue |
| * |
| * Get whether the value referenced by @value is an array. |
| * |
| * Returns: whether the value is an array. |
| */ |
| gboolean jsc_value_is_array(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| return JSValueIsArray(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
| } |
| |
| /** |
| * jsc_value_new_object: |
| * @context: a #JSCContext |
| * @instance: (nullable) (transfer full): an object instance or %NULL |
| * @jsc_class: (nullable): the #JSCClass of @instance |
| * |
| * Create a new #JSCValue from @instance. If @instance is %NULL a new empty object is created. |
| * When @instance is provided, @jsc_class must be provided too. @jsc_class takes ownership of |
| * @instance that will be freed by the #GDestroyNotify passed to jsc_context_register_class(). |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_object(JSCContext* context, gpointer instance, JSCClass* jscClass) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| g_return_val_if_fail(!instance || JSC_IS_CLASS(jscClass), nullptr); |
| |
| return jscContextGetOrCreateValue(context, instance ? toRef(jscClassGetOrCreateJSWrapper(jscClass, context, instance)) : JSObjectMake(jscContextGetJSContext(context), nullptr, nullptr)).leakRef(); |
| } |
| |
| /** |
| * jsc_value_is_object: |
| * @value: a #JSCValue |
| * |
| * Get whether the value referenced by @value is an object. |
| * |
| * Returns: whether the value is an object. |
| */ |
| gboolean jsc_value_is_object(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| return JSValueIsObject(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
| } |
| |
| /** |
| * jsc_value_object_is_instance_of: |
| * @value: a #JSCValue |
| * @name: a class name |
| * |
| * Get whether the value referenced by @value is an instance of class @name. |
| * |
| * Returns: whether the value is an object instance of class @name. |
| */ |
| gboolean jsc_value_object_is_instance_of(JSCValue* value, const char* name) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| g_return_val_if_fail(name, FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| // We use evaluate here and not get_value because classes are not necessarily a property of the global object. |
| // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-global-environment-records |
| GRefPtr<JSCValue> constructor = adoptGRef(jsc_context_evaluate(priv->context.get(), name, -1)); |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, constructor->priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return FALSE; |
| |
| gboolean returnValue = JSValueIsInstanceOfConstructor(jsContext, priv->jsValue, object, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return FALSE; |
| |
| return returnValue; |
| } |
| |
| /** |
| * jsc_value_object_set_property: |
| * @value: a #JSCValue |
| * @name: the property name |
| * @property: the #JSCValue to set |
| * |
| * Set @property with @name on @value. |
| */ |
| void jsc_value_object_set_property(JSCValue* value, const char* name, JSCValue* property) |
| { |
| g_return_if_fail(JSC_IS_VALUE(value)); |
| g_return_if_fail(name); |
| g_return_if_fail(JSC_IS_VALUE(property)); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return; |
| |
| JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString(name)); |
| JSObjectSetProperty(jsContext, object, propertyName.get(), property->priv->jsValue, kJSPropertyAttributeNone, &exception); |
| jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
| } |
| |
| /** |
| * jsc_value_object_get_property: |
| * @value: a #JSCValue |
| * @name: the property name |
| * |
| * Get property with @name from @value. |
| * |
| * Returns: (transfer full): the property #JSCValue. |
| */ |
| JSCValue* jsc_value_object_get_property(JSCValue* value, const char* name) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| g_return_val_if_fail(name, nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString(name)); |
| JSValueRef result = JSObjectGetProperty(jsContext, object, propertyName.get(), &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| return jscContextGetOrCreateValue(priv->context.get(), result).leakRef(); |
| } |
| |
| /** |
| * jsc_value_object_set_property_at_index: |
| * @value: a #JSCValue |
| * @index: the property index |
| * @property: the #JSCValue to set |
| * |
| * Set @property at @index on @value. |
| */ |
| void jsc_value_object_set_property_at_index(JSCValue* value, unsigned index, JSCValue* property) |
| { |
| g_return_if_fail(JSC_IS_VALUE(value)); |
| g_return_if_fail(JSC_IS_VALUE(property)); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return; |
| |
| JSObjectSetPropertyAtIndex(jsContext, object, index, property->priv->jsValue, &exception); |
| jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
| } |
| |
| /** |
| * jsc_value_object_get_property_at_index: |
| * @value: a #JSCValue |
| * @index: the property index |
| * |
| * Get property at @index from @value. |
| * |
| * Returns: (transfer full): the property #JSCValue. |
| */ |
| JSCValue* jsc_value_object_get_property_at_index(JSCValue* value, unsigned index) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| JSValueRef result = JSObjectGetPropertyAtIndex(jsContext, object, index, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| return jscContextGetOrCreateValue(priv->context.get(), result).leakRef(); |
| } |
| |
| /** |
| * jsc_value_object_has_property: |
| * @value: a #JSCValue |
| * @name: the property name |
| * |
| * Get whether @value has property with @name. |
| * |
| * Returns: %TRUE if @value has a property with @name, or %FALSE otherwise |
| */ |
| gboolean jsc_value_object_has_property(JSCValue* value, const char* name) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| g_return_val_if_fail(name, FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return FALSE; |
| |
| JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString(name)); |
| return JSObjectHasProperty(jsContext, object, propertyName.get()); |
| } |
| |
| /** |
| * jsc_value_object_delete_property: |
| * @value: a #JSCValue |
| * @name: the property name |
| * |
| * Try to delete property with @name from @value. This function will return %FALSE if |
| * the property was defined without %JSC_VALUE_PROPERTY_CONFIGURABLE flag. |
| * |
| * Returns: %TRUE if the property was deleted, or %FALSE otherwise. |
| */ |
| gboolean jsc_value_object_delete_property(JSCValue* value, const char* name) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| g_return_val_if_fail(name, FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return FALSE; |
| |
| JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString(name)); |
| gboolean result = JSObjectDeleteProperty(jsContext, object, propertyName.get(), &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return FALSE; |
| |
| return result; |
| } |
| |
| /** |
| * jsc_value_object_enumerate_properties: |
| * @value: a #JSCValue |
| * |
| * Get the list of property names of @value. Only properties defined with %JSC_VALUE_PROPERTY_ENUMERABLE |
| * flag will be collected. |
| * |
| * Returns: (array zero-terminated=1) (transfer full) (nullable): a %NULL-terminated array of strings containing the |
| * property names, or %NULL if @value doesn't have enumerable properties. Use g_strfreev() to free. |
| */ |
| char** jsc_value_object_enumerate_properties(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return nullptr; |
| |
| auto* propertiesArray = JSObjectCopyPropertyNames(jsContext, object); |
| if (!propertiesArray) |
| return nullptr; |
| |
| auto propertiesArraySize = JSPropertyNameArrayGetCount(propertiesArray); |
| if (!propertiesArraySize) { |
| JSPropertyNameArrayRelease(propertiesArray); |
| return nullptr; |
| } |
| |
| auto* result = static_cast<char**>(g_new0(char*, propertiesArraySize + 1)); |
| for (unsigned i = 0; i < propertiesArraySize; ++i) { |
| auto* jsString = JSPropertyNameArrayGetNameAtIndex(propertiesArray, i); |
| size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString); |
| auto* string = static_cast<char*>(g_malloc(maxSize)); |
| JSStringGetUTF8CString(jsString, string, maxSize); |
| result[i] = string; |
| } |
| JSPropertyNameArrayRelease(propertiesArray); |
| |
| return result; |
| } |
| |
| static JSValueRef jsObjectCall(JSGlobalContextRef jsContext, JSObjectRef function, JSC::JSCCallbackFunction::Type functionType, JSObjectRef thisObject, const Vector<JSValueRef>& arguments, JSValueRef* exception) |
| { |
| switch (functionType) { |
| case JSC::JSCCallbackFunction::Type::Constructor: |
| return JSObjectCallAsConstructor(jsContext, function, arguments.size(), arguments.data(), exception); |
| case JSC::JSCCallbackFunction::Type::Method: |
| ASSERT(thisObject); |
| FALLTHROUGH; |
| case JSC::JSCCallbackFunction::Type::Function: |
| return JSObjectCallAsFunction(jsContext, function, thisObject, arguments.size(), arguments.data(), exception); |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| static GRefPtr<JSCValue> jscValueCallFunction(JSCValue* value, JSObjectRef function, JSC::JSCCallbackFunction::Type functionType, JSObjectRef thisObject, GType firstParameterType, va_list args) |
| { |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSC::JSGlobalObject* globalObject = toJS(jsContext); |
| JSC::JSLockHolder locker(globalObject); |
| |
| JSValueRef exception = nullptr; |
| Vector<JSValueRef> arguments; |
| GType parameterType = firstParameterType; |
| while (parameterType != G_TYPE_NONE) { |
| GValue parameter; |
| GUniqueOutPtr<char> error; |
| G_VALUE_COLLECT_INIT(¶meter, parameterType, args, G_VALUE_NOCOPY_CONTENTS, &error.outPtr()); |
| if (error) { |
| exception = toRef(JSC::createTypeError(globalObject, makeString("failed to collect function paramater: ", error.get()))); |
| jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
| return adoptGRef(jsc_value_new_undefined(priv->context.get())); |
| } |
| |
| auto* jsValue = jscContextGValueToJSValue(priv->context.get(), ¶meter, &exception); |
| g_value_unset(¶meter); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jscContextGetOrCreateValue(priv->context.get(), jsValue); |
| |
| arguments.append(jsValue); |
| parameterType = va_arg(args, GType); |
| } |
| |
| auto result = jsObjectCall(jsContext, function, functionType, thisObject, arguments, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return adoptGRef(jsc_value_new_undefined(priv->context.get())); |
| |
| return jscContextGetOrCreateValue(priv->context.get(), result); |
| } |
| |
| /** |
| * jsc_value_object_invoke_method: (skip) |
| * @value: a #JSCValue |
| * @name: the method name |
| * @first_parameter_type: #GType of first parameter, or %G_TYPE_NONE |
| * @...: value of the first parameter, followed optionally by more type/value pairs, followed by %G_TYPE_NONE |
| * |
| * Invoke method with @name on object referenced by @value, passing the given parameters. If |
| * @first_parameter_type is %G_TYPE_NONE no parameters will be passed to the method. |
| * The object instance will be handled automatically even when the method is a custom one |
| * registered with jsc_class_add_method(), so it should never be passed explicitly as parameter |
| * of this function. |
| * |
| * This function always returns a #JSCValue, in case of void methods a #JSCValue referencing |
| * <function>undefined</function> is returned. |
| * |
| * Returns: (transfer full): a #JSCValue with the return value of the method. |
| */ |
| JSCValue* jsc_value_object_invoke_method(JSCValue* value, const char* name, GType firstParameterType, ...) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| g_return_val_if_fail(name, nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| JSRetainPtr<JSStringRef> methodName(Adopt, JSStringCreateWithUTF8CString(name)); |
| JSValueRef functionValue = JSObjectGetProperty(jsContext, object, methodName.get(), &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| JSObjectRef function = JSValueToObject(jsContext, functionValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| va_list args; |
| va_start(args, firstParameterType); |
| auto result = jscValueCallFunction(value, function, JSC::JSCCallbackFunction::Type::Method, object, firstParameterType, args); |
| va_end(args); |
| |
| return result.leakRef(); |
| } |
| |
| /** |
| * jsc_value_object_invoke_methodv: (rename-to jsc_value_object_invoke_method) |
| * @value: a #JSCValue |
| * @name: the method name |
| * @n_parameters: the number of parameters |
| * @parameters: (nullable) (array length=n_parameters) (element-type JSCValue): the #JSCValue<!-- -->s to pass as parameters to the method, or %NULL |
| * |
| * Invoke method with @name on object referenced by @value, passing the given @parameters. If |
| * @n_parameters is 0 no parameters will be passed to the method. |
| * The object instance will be handled automatically even when the method is a custom one |
| * registered with jsc_class_add_method(), so it should never be passed explicitly as parameter |
| * of this function. |
| * |
| * This function always returns a #JSCValue, in case of void methods a #JSCValue referencing |
| * <function>undefined</function> is returned. |
| * |
| * Returns: (transfer full): a #JSCValue with the return value of the method. |
| */ |
| JSCValue* jsc_value_object_invoke_methodv(JSCValue* value, const char* name, unsigned parametersCount, JSCValue** parameters) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| g_return_val_if_fail(name, nullptr); |
| g_return_val_if_fail(!parametersCount || parameters, nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| JSRetainPtr<JSStringRef> methodName(Adopt, JSStringCreateWithUTF8CString(name)); |
| JSValueRef functionValue = JSObjectGetProperty(jsContext, object, methodName.get(), &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| JSObjectRef function = JSValueToObject(jsContext, functionValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| Vector<JSValueRef> arguments; |
| if (parametersCount) { |
| arguments.reserveInitialCapacity(parametersCount); |
| for (unsigned i = 0; i < parametersCount; ++i) |
| arguments.uncheckedAppend(jscValueGetJSValue(parameters[i])); |
| } |
| |
| auto result = jsObjectCall(jsContext, function, JSC::JSCCallbackFunction::Type::Method, object, arguments, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| return jscContextGetOrCreateValue(priv->context.get(), result).leakRef(); |
| } |
| |
| /** |
| * JSCValuePropertyFlags: |
| * @JSC_VALUE_PROPERTY_CONFIGURABLE: the type of the property descriptor may be changed and the |
| * property may be deleted from the corresponding object. |
| * @JSC_VALUE_PROPERTY_ENUMERABLE: the property shows up during enumeration of the properties on |
| * the corresponding object. |
| * @JSC_VALUE_PROPERTY_WRITABLE: the value associated with the property may be changed with an |
| * assignment operator. This doesn't have any effect when passed to jsc_value_object_define_property_accessor(). |
| * |
| * Flags used when defining properties with jsc_value_object_define_property_data() and |
| * jsc_value_object_define_property_accessor(). |
| */ |
| |
| /** |
| * jsc_value_object_define_property_data: |
| * @value: a #JSCValue |
| * @property_name: the name of the property to define |
| * @flags: #JSCValuePropertyFlags |
| * @property_value: (nullable): the default property value |
| * |
| * Define or modify a property with @property_name in object referenced by @value. This is equivalent to |
| * JavaScript <function>Object.defineProperty()</function> when used with a data descriptor. |
| */ |
| void jsc_value_object_define_property_data(JSCValue* value, const char* propertyName, JSCValuePropertyFlags flags, JSCValue* propertyValue) |
| { |
| g_return_if_fail(JSC_IS_VALUE(value)); |
| g_return_if_fail(propertyName); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSC::JSGlobalObject* globalObject = toJS(jsContext); |
| JSC::VM& vm = globalObject->vm(); |
| JSC::JSLockHolder locker(vm); |
| auto scope = DECLARE_CATCH_SCOPE(vm); |
| |
| JSC::JSValue jsValue = toJS(globalObject, priv->jsValue); |
| JSC::JSObject* object = jsValue.toObject(globalObject); |
| JSValueRef exception = nullptr; |
| if (handleExceptionIfNeeded(scope, jsContext, &exception) == ExceptionStatus::DidThrow) { |
| jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
| return; |
| } |
| |
| auto name = OpaqueJSString::tryCreate(String::fromUTF8(propertyName)); |
| if (!name) |
| return; |
| |
| JSC::PropertyDescriptor descriptor; |
| descriptor.setValue(toJS(globalObject, propertyValue->priv->jsValue)); |
| descriptor.setEnumerable(flags & JSC_VALUE_PROPERTY_ENUMERABLE); |
| descriptor.setConfigurable(flags & JSC_VALUE_PROPERTY_CONFIGURABLE); |
| descriptor.setWritable(flags & JSC_VALUE_PROPERTY_WRITABLE); |
| object->methodTable(vm)->defineOwnProperty(object, globalObject, name->identifier(&vm), descriptor, true); |
| if (handleExceptionIfNeeded(scope, jsContext, &exception) == ExceptionStatus::DidThrow) { |
| jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
| return; |
| } |
| } |
| |
| static void jscValueObjectDefinePropertyAccessor(JSCValue* value, const char* propertyName, JSCValuePropertyFlags flags, GType propertyType, JSC::JSCCallbackFunction::Type functionType, GCallback getter, GCallback setter, gpointer userData, GDestroyNotify destroyNotify) |
| { |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSC::JSGlobalObject* globalObject = toJS(jsContext); |
| JSC::VM& vm = globalObject->vm(); |
| JSC::JSLockHolder locker(vm); |
| auto scope = DECLARE_CATCH_SCOPE(vm); |
| |
| JSC::JSValue jsValue = toJS(globalObject, priv->jsValue); |
| JSC::JSObject* object = jsValue.toObject(globalObject); |
| JSValueRef exception = nullptr; |
| if (handleExceptionIfNeeded(scope, jsContext, &exception) == ExceptionStatus::DidThrow) { |
| jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
| return; |
| } |
| |
| auto name = OpaqueJSString::tryCreate(String::fromUTF8(propertyName)); |
| if (!name) |
| return; |
| |
| JSC::PropertyDescriptor descriptor; |
| descriptor.setEnumerable(flags & JSC_VALUE_PROPERTY_ENUMERABLE); |
| descriptor.setConfigurable(flags & JSC_VALUE_PROPERTY_CONFIGURABLE); |
| if (getter) { |
| GRefPtr<GClosure> closure; |
| if (functionType == JSC::JSCCallbackFunction::Type::Function && userData) |
| closure = adoptGRef(g_cclosure_new_swap(getter, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
| else |
| closure = adoptGRef(g_cclosure_new(getter, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
| auto function = JSC::JSCCallbackFunction::create(vm, globalObject, "get"_s, functionType, nullptr, WTFMove(closure), propertyType, Vector<GType> { }); |
| descriptor.setGetter(function); |
| } |
| if (setter) { |
| GRefPtr<GClosure> closure = adoptGRef(g_cclosure_new(setter, userData, getter ? nullptr : reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
| auto function = JSC::JSCCallbackFunction::create(vm, globalObject, "set"_s, functionType, nullptr, WTFMove(closure), G_TYPE_NONE, Vector<GType> { propertyType }); |
| descriptor.setSetter(function); |
| } |
| object->methodTable(vm)->defineOwnProperty(object, globalObject, name->identifier(&vm), descriptor, true); |
| if (handleExceptionIfNeeded(scope, jsContext, &exception) == ExceptionStatus::DidThrow) { |
| jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
| return; |
| } |
| } |
| |
| /** |
| * jsc_value_object_define_property_accessor: |
| * @value: a #JSCValue |
| * @property_name: the name of the property to define |
| * @flags: #JSCValuePropertyFlags |
| * @property_type: the #GType of the property |
| * @getter: (scope async) (nullable): a #GCallback to be called to get the property value |
| * @setter: (scope async) (nullable): a #GCallback to be called to set the property value |
| * @user_data: (closure): user data to pass to @getter and @setter |
| * @destroy_notify: (nullable): destroy notifier for @user_data |
| * |
| * Define or modify a property with @property_name in object referenced by @value. When the |
| * property value needs to be getted or set, @getter and @setter callbacks will be called. |
| * When the property is cleared in the #JSCClass context, @destroy_notify is called with |
| * @user_data as parameter. This is equivalent to JavaScript <function>Object.defineProperty()</function> |
| * when used with an accessor descriptor. |
| * |
| * Note that the value returned by @getter must be fully transferred. In case of boxed types, you could use |
| * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
| * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
| * with jsc_value_new_object() that receives the copy as instance parameter. |
| * |
| * Note that @getter and @setter are called as functions and not methods, so they don't receive an instance as |
| * first parameter. Use jsc_class_add_property() if you want to add property accessor invoked as a method. |
| */ |
| void jsc_value_object_define_property_accessor(JSCValue* value, const char* propertyName, JSCValuePropertyFlags flags, GType propertyType, GCallback getter, GCallback setter, gpointer userData, GDestroyNotify destroyNotify) |
| { |
| g_return_if_fail(JSC_IS_VALUE(value)); |
| g_return_if_fail(propertyName); |
| g_return_if_fail(propertyType != G_TYPE_INVALID && propertyType != G_TYPE_NONE); |
| g_return_if_fail(getter || setter); |
| |
| jscValueObjectDefinePropertyAccessor(value, propertyName, flags, propertyType, JSC::JSCCallbackFunction::Type::Function, getter, setter, userData, destroyNotify); |
| } |
| |
| void jscValueAddPropertyAccessor(JSCValue* value, const char* propertyName, GType propertyType, GCallback getter, GCallback setter, gpointer userData, GDestroyNotify destroyNotify) |
| { |
| jscValueObjectDefinePropertyAccessor(value, propertyName, JSC_VALUE_PROPERTY_CONFIGURABLE, propertyType, JSC::JSCCallbackFunction::Type::Method, getter, setter, userData, destroyNotify); |
| } |
| |
| static GRefPtr<JSCValue> jscValueFunctionCreate(JSCContext* context, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, std::optional<Vector<GType>>&& parameters) |
| { |
| GRefPtr<GClosure> closure; |
| // If the function doesn't have arguments, we need to swap the fake instance and user data to ensure |
| // user data is the first parameter and fake instance ignored. |
| if (parameters && parameters->isEmpty() && userData) |
| closure = adoptGRef(g_cclosure_new_swap(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
| else |
| closure = adoptGRef(g_cclosure_new(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
| JSC::JSGlobalObject* globalObject = toJS(jscContextGetJSContext(context)); |
| JSC::VM& vm = globalObject->vm(); |
| JSC::JSLockHolder locker(vm); |
| auto* functionObject = toRef(JSC::JSCCallbackFunction::create(vm, globalObject, name ? String::fromUTF8(name) : "anonymous"_s, |
| JSC::JSCCallbackFunction::Type::Function, nullptr, WTFMove(closure), returnType, WTFMove(parameters))); |
| return jscContextGetOrCreateValue(context, functionObject); |
| } |
| |
| /** |
| * jsc_value_new_function: (skip) |
| * @context: a #JSCContext: |
| * @name: (nullable): the function name or %NULL |
| * @callback: (scope async): a #GCallback. |
| * @user_data: (closure): user data to pass to @callback. |
| * @destroy_notify: (nullable): destroy notifier for @user_data |
| * @return_type: the #GType of the function return value, or %G_TYPE_NONE if the function is void. |
| * @n_params: the number of parameter types to follow or 0 if the function doesn't receive parameters. |
| * @...: a list of #GType<!-- -->s, one for each parameter. |
| * |
| * Create a function in @context. If @name is %NULL an anonymous function will be created. |
| * When the function is called by JavaScript or jsc_value_function_call(), @callback is called |
| * receiving the function parameters and then @user_data as last parameter. When the function is |
| * cleared in @context, @destroy_notify is called with @user_data as parameter. |
| * |
| * Note that the value returned by @callback must be fully transferred. In case of boxed types, you could use |
| * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
| * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
| * with jsc_value_new_object() that receives the copy as instance parameter. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_function(JSCContext* context, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned paramCount, ...) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| g_return_val_if_fail(callback, nullptr); |
| |
| va_list args; |
| va_start(args, paramCount); |
| Vector<GType> parameters; |
| if (paramCount) { |
| parameters.reserveInitialCapacity(paramCount); |
| for (unsigned i = 0; i < paramCount; ++i) |
| parameters.uncheckedAppend(va_arg(args, GType)); |
| } |
| va_end(args); |
| |
| return jscValueFunctionCreate(context, name, callback, userData, destroyNotify, returnType, WTFMove(parameters)).leakRef(); |
| } |
| |
| /** |
| * jsc_value_new_functionv: (rename-to jsc_value_new_function) |
| * @context: a #JSCContext |
| * @name: (nullable): the function name or %NULL |
| * @callback: (scope async): a #GCallback. |
| * @user_data: (closure): user data to pass to @callback. |
| * @destroy_notify: (nullable): destroy notifier for @user_data |
| * @return_type: the #GType of the function return value, or %G_TYPE_NONE if the function is void. |
| * @n_parameters: the number of parameters |
| * @parameter_types: (nullable) (array length=n_parameters) (element-type GType): a list of #GType<!-- -->s, one for each parameter, or %NULL |
| * |
| * Create a function in @context. If @name is %NULL an anonymous function will be created. |
| * When the function is called by JavaScript or jsc_value_function_call(), @callback is called |
| * receiving the function parameters and then @user_data as last parameter. When the function is |
| * cleared in @context, @destroy_notify is called with @user_data as parameter. |
| * |
| * Note that the value returned by @callback must be fully transferred. In case of boxed types, you could use |
| * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
| * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
| * with jsc_value_new_object() that receives the copy as instance parameter. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_functionv(JSCContext* context, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned parametersCount, GType *parameterTypes) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| g_return_val_if_fail(callback, nullptr); |
| g_return_val_if_fail(!parametersCount || parameterTypes, nullptr); |
| |
| Vector<GType> parameters; |
| if (parametersCount) { |
| parameters.reserveInitialCapacity(parametersCount); |
| for (unsigned i = 0; i < parametersCount; ++i) |
| parameters.uncheckedAppend(parameterTypes[i]); |
| } |
| |
| return jscValueFunctionCreate(context, name, callback, userData, destroyNotify, returnType, WTFMove(parameters)).leakRef(); |
| } |
| |
| /** |
| * jsc_value_new_function_variadic: |
| * @context: a #JSCContext |
| * @name: (nullable): the function name or %NULL |
| * @callback: (scope async): a #GCallback. |
| * @user_data: (closure): user data to pass to @callback. |
| * @destroy_notify: (nullable): destroy notifier for @user_data |
| * @return_type: the #GType of the function return value, or %G_TYPE_NONE if the function is void. |
| * |
| * Create a function in @context. If @name is %NULL an anonymous function will be created. |
| * When the function is called by JavaScript or jsc_value_function_call(), @callback is called |
| * receiving an #GPtrArray of #JSCValue<!-- -->s with the arguments and then @user_data as last parameter. |
| * When the function is cleared in @context, @destroy_notify is called with @user_data as parameter. |
| * |
| * Note that the value returned by @callback must be fully transferred. In case of boxed types, you could use |
| * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
| * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
| * with jsc_value_new_object() that receives the copy as instance parameter. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| */ |
| JSCValue* jsc_value_new_function_variadic(JSCContext* context, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| g_return_val_if_fail(callback, nullptr); |
| |
| return jscValueFunctionCreate(context, name, callback, userData, destroyNotify, returnType, std::nullopt).leakRef(); |
| } |
| |
| /** |
| * jsc_value_is_function: |
| * @value: a #JSCValue |
| * |
| * Get whether the value referenced by @value is a function |
| * |
| * Returns: whether the value is a function. |
| */ |
| gboolean jsc_value_is_function(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
| return !exception ? JSObjectIsFunction(jsContext, object) : FALSE; |
| } |
| |
| /** |
| * jsc_value_function_call: (skip) |
| * @value: a #JSCValue |
| * @first_parameter_type: #GType of first parameter, or %G_TYPE_NONE |
| * @...: value of the first parameter, followed optionally by more type/value pairs, followed by %G_TYPE_NONE |
| * |
| * Call function referenced by @value, passing the given parameters. If @first_parameter_type |
| * is %G_TYPE_NONE no parameters will be passed to the function. |
| * |
| * This function always returns a #JSCValue, in case of void functions a #JSCValue referencing |
| * <function>undefined</function> is returned |
| * |
| * Returns: (transfer full): a #JSCValue with the return value of the function. |
| */ |
| JSCValue* jsc_value_function_call(JSCValue* value, GType firstParameterType, ...) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef function = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| va_list args; |
| va_start(args, firstParameterType); |
| auto result = jscValueCallFunction(value, function, JSC::JSCCallbackFunction::Type::Function, nullptr, firstParameterType, args); |
| va_end(args); |
| |
| return result.leakRef(); |
| } |
| |
| /** |
| * jsc_value_function_callv: (rename-to jsc_value_function_call) |
| * @value: a #JSCValue |
| * @n_parameters: the number of parameters |
| * @parameters: (nullable) (array length=n_parameters) (element-type JSCValue): the #JSCValue<!-- -->s to pass as parameters to the function, or %NULL |
| * |
| * Call function referenced by @value, passing the given @parameters. If @n_parameters |
| * is 0 no parameters will be passed to the function. |
| * |
| * This function always returns a #JSCValue, in case of void functions a #JSCValue referencing |
| * <function>undefined</function> is returned |
| * |
| * Returns: (transfer full): a #JSCValue with the return value of the function. |
| */ |
| JSCValue* jsc_value_function_callv(JSCValue* value, unsigned parametersCount, JSCValue** parameters) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| g_return_val_if_fail(!parametersCount || parameters, nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef function = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| Vector<JSValueRef> arguments; |
| if (parametersCount) { |
| arguments.reserveInitialCapacity(parametersCount); |
| for (unsigned i = 0; i < parametersCount; ++i) |
| arguments.uncheckedAppend(jscValueGetJSValue(parameters[i])); |
| } |
| |
| auto result = jsObjectCall(jsContext, function, JSC::JSCCallbackFunction::Type::Function, nullptr, arguments, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| return jscContextGetOrCreateValue(priv->context.get(), result).leakRef(); |
| } |
| |
| /** |
| * jsc_value_is_constructor: |
| * @value: a #JSCValue |
| * |
| * Get whether the value referenced by @value is a constructor. |
| * |
| * Returns: whether the value is a constructor. |
| */ |
| gboolean jsc_value_is_constructor(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
| return !exception ? JSObjectIsConstructor(jsContext, object) : FALSE; |
| } |
| |
| /** |
| * jsc_value_constructor_call: (skip) |
| * @value: a #JSCValue |
| * @first_parameter_type: #GType of first parameter, or %G_TYPE_NONE |
| * @...: value of the first parameter, followed optionally by more type/value pairs, followed by %G_TYPE_NONE |
| * |
| * Invoke <function>new</function> with constructor referenced by @value. If @first_parameter_type |
| * is %G_TYPE_NONE no parameters will be passed to the constructor. |
| * |
| * Returns: (transfer full): a #JSCValue referencing the newly created object instance. |
| */ |
| JSCValue* jsc_value_constructor_call(JSCValue* value, GType firstParameterType, ...) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef function = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| va_list args; |
| va_start(args, firstParameterType); |
| auto result = jscValueCallFunction(value, function, JSC::JSCCallbackFunction::Type::Constructor, nullptr, firstParameterType, args); |
| va_end(args); |
| |
| return result.leakRef(); |
| } |
| |
| /** |
| * jsc_value_constructor_callv: (rename-to jsc_value_constructor_call) |
| * @value: a #JSCValue |
| * @n_parameters: the number of parameters |
| * @parameters: (nullable) (array length=n_parameters) (element-type JSCValue): the #JSCValue<!-- -->s to pass as parameters to the constructor, or %NULL |
| * |
| * Invoke <function>new</function> with constructor referenced by @value. If @n_parameters |
| * is 0 no parameters will be passed to the constructor. |
| * |
| * Returns: (transfer full): a #JSCValue referencing the newly created object instance. |
| */ |
| JSCValue* jsc_value_constructor_callv(JSCValue* value, unsigned parametersCount, JSCValue** parameters) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| g_return_val_if_fail(!parametersCount || parameters, nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| auto* jsContext = jscContextGetJSContext(priv->context.get()); |
| JSValueRef exception = nullptr; |
| JSObjectRef function = JSValueToObject(jsContext, priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| Vector<JSValueRef> arguments; |
| if (parametersCount) { |
| arguments.reserveInitialCapacity(parametersCount); |
| for (unsigned i = 0; i < parametersCount; ++i) |
| arguments.uncheckedAppend(jscValueGetJSValue(parameters[i])); |
| } |
| |
| auto result = jsObjectCall(jsContext, function, JSC::JSCCallbackFunction::Type::Constructor, nullptr, arguments, &exception); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return jsc_value_new_undefined(priv->context.get()); |
| |
| return jscContextGetOrCreateValue(priv->context.get(), result).leakRef(); |
| } |
| |
| /** |
| * jsc_value_new_from_json: |
| * @context: a #JSCContext |
| * @json: the JSON string to be parsed |
| * |
| * Create a new #JSCValue referencing a new value created by parsing @json. |
| * |
| * Returns: (transfer full): a #JSCValue. |
| * |
| * Since: 2.28 |
| */ |
| JSCValue* jsc_value_new_from_json(JSCContext* context, const char* json) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| |
| if (!json) |
| return jsc_value_new_null(context); |
| |
| auto* jsContext = jscContextGetJSContext(context); |
| JSC::JSGlobalObject* globalObject = toJS(jsContext); |
| JSC::JSLockHolder locker(globalObject); |
| |
| JSValueRef exception = nullptr; |
| JSC::JSValue jsValue; |
| String jsonString = String::fromUTF8(json); |
| if (jsonString.is8Bit()) { |
| JSC::LiteralParser<LChar> jsonParser(globalObject, jsonString.characters8(), jsonString.length(), JSC::StrictJSON); |
| jsValue = jsonParser.tryLiteralParse(); |
| if (!jsValue) |
| exception = toRef(JSC::createSyntaxError(globalObject, jsonParser.getErrorMessage())); |
| } else { |
| JSC::LiteralParser<UChar> jsonParser(globalObject, jsonString.characters16(), jsonString.length(), JSC::StrictJSON); |
| jsValue = jsonParser.tryLiteralParse(); |
| if (!jsValue) |
| exception = toRef(JSC::createSyntaxError(globalObject, jsonParser.getErrorMessage())); |
| } |
| |
| if (exception) { |
| jscContextHandleExceptionIfNeeded(context, exception); |
| return nullptr; |
| } |
| |
| return jsValue ? jscContextGetOrCreateValue(context, toRef(globalObject, jsValue)).leakRef() : nullptr; |
| } |
| |
| /** |
| * jsc_value_to_json: |
| * @value: a #JSCValue |
| * @indent: The number of spaces to indent when nesting. |
| * |
| * Create a JSON string of @value serialization. If @indent is 0, the resulting JSON will |
| * not contain newlines. The size of the indent is clamped to 10 spaces. |
| * |
| * Returns: (transfer full): a null-terminated JSON string with serialization of @value |
| * |
| * Since: 2.28 |
| */ |
| char* jsc_value_to_json(JSCValue* value, unsigned indent) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| |
| JSCValuePrivate* priv = value->priv; |
| JSValueRef exception = nullptr; |
| JSRetainPtr<JSStringRef> jsJSON(Adopt, JSValueCreateJSONString(jscContextGetJSContext(priv->context.get()), priv->jsValue, indent, &exception)); |
| if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
| return nullptr; |
| |
| if (!jsJSON) |
| return nullptr; |
| |
| size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsJSON.get()); |
| auto* json = static_cast<char*>(g_malloc(maxSize)); |
| if (!JSStringGetUTF8CString(jsJSON.get(), json, maxSize)) { |
| g_free(json); |
| return nullptr; |
| } |
| |
| return json; |
| } |