| /* |
| * 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 "JSArrayBuffer.h" |
| #include "JSCCallbackFunction.h" |
| #include "JSCClassPrivate.h" |
| #include "JSCContextPrivate.h" |
| #include "JSCInlines.h" |
| #include "JSCValuePrivate.h" |
| #include "JSRetainPtr.h" |
| #include "JSTypedArray.h" |
| #include "LiteralParser.h" |
| #include "OpaqueJSString.h" |
| #include "TypedArrayType.h" |
| #include <gobject/gvaluecollector.h> |
| #include <wtf/glib/GRefPtr.h> |
| #include <wtf/glib/GUniquePtr.h> |
| #include <wtf/glib/WTFGType.h> |
| |
| /** |
| * 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()->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()->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(); |
| } |
| |
| struct ArrayBufferDeallocatorContext { |
| gpointer userData; |
| GDestroyNotify destroyNotify; |
| }; |
| WEBKIT_DEFINE_ASYNC_DATA_STRUCT(ArrayBufferDeallocatorContext) |
| |
| /** |
| * jsc_value_new_array_buffer: |
| * @context: A #JSCContext |
| * @data: Pointer to a region of memory. |
| * @size: Size in bytes of the memory region. |
| * @destroy_notify: (nullable): destroy notifier for @user_data. |
| * @user_data: (closure): user data. |
| * |
| * Creates a new %ArrayBuffer from existing @data in memory. |
| * |
| * The @data is not copied: while this allows sharing data with JavaScript |
| * efficiently, the caller must ensure that the memory region remains valid |
| * until the newly created object is released by JSC. |
| * |
| * Optionally, a @destroy_notify callback can be provided, which will be |
| * invoked with @user_data as parameter when the %ArrayBuffer object is |
| * released. This is intended to be used for freeing resources related to |
| * the memory region which contains the data: |
| * |
| * |[!<-- language="C" --> |
| * GMappedFile *f = g_mapped_file_new (file_path, TRUE, NULL); |
| * JSCValue *value = jsc_value_new_array_buffer (context, |
| * g_mapped_file_get_contents (f), g_mapped_file_get_length (f), |
| * (GDestroyNotify) g_mapped_file_unref, f); |
| * ]| |
| * |
| * Note that the @user_data can be the same value as @data: |
| * |
| * |[!<-- language="C" --> |
| * void *bytes = g_malloc0 (100); |
| * JSCValue *value = jsc_value_new_array_buffer (context, bytes, 100, g_free, bytes); |
| * ]| |
| * |
| * Returns: (transfer full) (nullable): A #JSCValue, or %NULL in case of exception. |
| * |
| * Since: 2.38 |
| */ |
| JSCValue* jsc_value_new_array_buffer(JSCContext* context, void* data, size_t length, GDestroyNotify destroyNotify, gpointer userData) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| |
| ArrayBufferDeallocatorContext* deallocatorContext = nullptr; |
| if (destroyNotify) { |
| deallocatorContext = createArrayBufferDeallocatorContext(); |
| deallocatorContext->destroyNotify = destroyNotify; |
| deallocatorContext->userData = userData; |
| } |
| |
| JSValueRef exception = nullptr; |
| auto* jsContext = jscContextGetJSContext(context); |
| auto* jsArrayBuffer = JSObjectMakeArrayBufferWithBytesNoCopy(jsContext, data, length, [](void*, void* deallocatorContext) { |
| if (deallocatorContext) { |
| auto* context = static_cast<ArrayBufferDeallocatorContext*>(deallocatorContext); |
| context->destroyNotify(context->userData); |
| destroyArrayBufferDeallocatorContext(context); |
| } |
| }, deallocatorContext, &exception); |
| |
| if (jscContextHandleExceptionIfNeeded(context, exception)) |
| return nullptr; |
| |
| return jscContextGetOrCreateValue(context, jsArrayBuffer).leakRef(); |
| } |
| |
| /** |
| * jsc_value_is_array_buffer: |
| * @value: A #JSCValue. |
| * |
| * Check whether the @value is an %ArrayBuffer. |
| * |
| * Returns: whether the value is an %ArrayBuffer |
| * |
| * Since: 2.38 |
| */ |
| gboolean jsc_value_is_array_buffer(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| using namespace JSC; |
| |
| JSGlobalObject* globalObject = toJS(jscContextGetJSContext(value->priv->context.get())); |
| VM& vm = globalObject->vm(); |
| JSLockHolder locker(vm); |
| |
| JSValue jsValue = toJS(globalObject, value->priv->jsValue); |
| if (!jsValue.isObject()) |
| return FALSE; |
| |
| return !!jsDynamicCast<JSArrayBuffer*>(jsValue.getObject()); |
| } |
| |
| /** |
| * jsc_value_array_buffer_get_data: |
| * @value: A #JSCValue |
| * @size: (nullable): location where to store the size of the memory region. |
| * |
| * Gets a pointer to memory that contains the array buffer data. |
| * |
| * Obtains a pointer to the memory region that holds the contents of the |
| * %ArrayBuffer; modifications done to the data will be visible to JavaScript |
| * code. If @size is not %NULL, the size in bytes of the memory region |
| * will also be stored in the pointed location. |
| * |
| * Note that the pointer returned by this function is not guaranteed to remain |
| * the same after calls to other JSC API functions. If you plan to access the |
| * data of the %ArrayBuffer later, you can keep a reference to the @value and |
| * obtain the data pointer at a later point. Keep in mind that if JavaScript |
| * code has a chance to run, for example due to main loop events that result |
| * in JSC being called, the contents of the memory region might be modified in |
| * the meantime. Consider taking a copy of the data and using the copy instead |
| * in asynchronous code. |
| * |
| * Returns: (transfer none): pointer to memory. |
| * |
| * Since: 2.38 |
| */ |
| gpointer jsc_value_array_buffer_get_data(JSCValue* value, gsize* size) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| |
| auto* jsContext = jscContextGetJSContext(value->priv->context.get()); |
| |
| JSValueRef exception = nullptr; |
| auto* jsObject = JSValueToObject(jsContext, value->priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return nullptr; |
| |
| void* data = JSObjectGetArrayBufferBytesPtr(jsContext, jsObject, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return nullptr; |
| |
| if (size) { |
| *size = JSObjectGetArrayBufferByteLength(jsContext, jsObject, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return nullptr; |
| } |
| |
| return data; |
| } |
| |
| /** |
| * jsc_value_array_buffer_get_size: |
| * @value: A #JSCValue |
| * |
| * Gets the size in bytes of the array buffer. |
| * |
| * Obtains the size in bytes of the memory region that holds the contents of |
| * an %ArrayBuffer. |
| * |
| * Returns: size, in bytes. |
| * |
| * Since: 2.38 |
| */ |
| gsize jsc_value_array_buffer_get_size(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), 0); |
| |
| auto* jsContext = jscContextGetJSContext(value->priv->context.get()); |
| |
| JSValueRef exception = nullptr; |
| auto* jsObject = JSValueToObject(jsContext, value->priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return 0; |
| |
| size_t size = JSObjectGetArrayBufferByteLength(jsContext, jsObject, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return 0; |
| |
| return size; |
| } |
| |
| /** |
| * JSCTypedArrayType: |
| * @JSC_TYPED_ARRAY_NONE: Not a typed array, or type unsupported. |
| * @JSC_TYPED_ARRAY_INT8: Array elements are 8-bit signed integers (int8_t). |
| * @JSC_TYPED_ARRAY_INT16: Array elements are 16-bit signed integers (int16_t). |
| * @JSC_TYPED_ARRAY_INT32: Array elements are 32-bit signed integers (int32_t). |
| * @JSC_TYPED_ARRAY_INT64: Array elements are 64-bit signed integers (int64_t). |
| * @JSC_TYPED_ARRAY_UINT8: Array elements are 8-bit unsigned integers (uint8_t). |
| * @JSC_TYPED_ARRAY_UINT8_CLAMPED: Array elements are 8-bit unsigned integers (uint8_t). |
| * @JSC_TYPED_ARRAY_UINT16: Array elements are 16-bit unsigned integers (uint16_t). |
| * @JSC_TYPED_ARRAY_UINT32: Array elements are 32-bit unsigned integers (uint32_t). |
| * @JSC_TYPED_ARRAY_UINT64: Array elements are 64-bit unsigned integers (uint64_t). |
| * @JSC_TYPED_ARRAY_FLOAT32: Array elements are 32-bit floating point numbers (float). |
| * @JSC_TYPED_ARRAY_FLOAT64: Array elements are 64-bit floating point numbers (double). |
| * |
| * Possible types of the elements contained in a typed array. |
| * |
| * Since: 2.38 |
| */ |
| |
| static JSTypedArrayType toTypedArrayType(JSCTypedArrayType type) |
| { |
| switch (type) { |
| case JSC_TYPED_ARRAY_NONE: |
| return kJSTypedArrayTypeNone; |
| case JSC_TYPED_ARRAY_INT8: |
| return kJSTypedArrayTypeInt8Array; |
| case JSC_TYPED_ARRAY_INT16: |
| return kJSTypedArrayTypeInt16Array; |
| case JSC_TYPED_ARRAY_INT32: |
| return kJSTypedArrayTypeInt32Array; |
| case JSC_TYPED_ARRAY_INT64: |
| return kJSTypedArrayTypeBigInt64Array; |
| case JSC_TYPED_ARRAY_UINT8: |
| return kJSTypedArrayTypeUint8Array; |
| case JSC_TYPED_ARRAY_UINT8_CLAMPED: |
| return kJSTypedArrayTypeUint8ClampedArray; |
| case JSC_TYPED_ARRAY_UINT16: |
| return kJSTypedArrayTypeUint16Array; |
| case JSC_TYPED_ARRAY_UINT32: |
| return kJSTypedArrayTypeUint32Array; |
| case JSC_TYPED_ARRAY_UINT64: |
| return kJSTypedArrayTypeBigUint64Array; |
| case JSC_TYPED_ARRAY_FLOAT32: |
| return kJSTypedArrayTypeFloat32Array; |
| case JSC_TYPED_ARRAY_FLOAT64: |
| return kJSTypedArrayTypeFloat64Array; |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| static JSC::TypedArrayType toJSCTypedArrayType(JSCTypedArrayType type) |
| { |
| switch (type) { |
| case JSC_TYPED_ARRAY_NONE: |
| return JSC::TypedArrayType::NotTypedArray; |
| case JSC_TYPED_ARRAY_INT8: |
| return JSC::TypedArrayType::TypeInt8; |
| case JSC_TYPED_ARRAY_INT16: |
| return JSC::TypedArrayType::TypeInt16; |
| case JSC_TYPED_ARRAY_INT32: |
| return JSC::TypedArrayType::TypeInt32; |
| case JSC_TYPED_ARRAY_INT64: |
| return JSC::TypedArrayType::TypeBigInt64; |
| case JSC_TYPED_ARRAY_UINT8: |
| return JSC::TypedArrayType::TypeUint8; |
| case JSC_TYPED_ARRAY_UINT8_CLAMPED: |
| return JSC::TypedArrayType::TypeUint8Clamped; |
| case JSC_TYPED_ARRAY_UINT16: |
| return JSC::TypedArrayType::TypeUint16; |
| case JSC_TYPED_ARRAY_UINT32: |
| return JSC::TypedArrayType::TypeUint32; |
| case JSC_TYPED_ARRAY_UINT64: |
| return JSC::TypedArrayType::TypeBigUint64; |
| case JSC_TYPED_ARRAY_FLOAT32: |
| return JSC::TypedArrayType::TypeFloat32; |
| case JSC_TYPED_ARRAY_FLOAT64: |
| return JSC::TypedArrayType::TypeFloat64; |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| /** |
| * jsc_value_new_typed_array: |
| * @context: a #JSCContext |
| * @type: the type of array elements |
| * @length: number of elements in the array |
| * |
| * Create a new typed array containing a given amount of elements. |
| * |
| * Create a #JSCValue referencing a new typed array with space for @length |
| * elements of a given @type. As all typed arrays must have an associated |
| * `ArrayBuffer`, a new one of suitable size will be allocated to store |
| * the elements, which will be initialized to zero. |
| * |
| * The @type must *not* be %JSC_TYPED_ARRAY_NONE. |
| * |
| * Returns: (transfer full): a #JSCValue |
| * |
| * Since: 2.38 |
| */ |
| JSCValue* jsc_value_new_typed_array(JSCContext* context, JSCTypedArrayType type, gsize count) |
| { |
| g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
| g_return_val_if_fail(type != JSC_TYPED_ARRAY_NONE, nullptr); |
| |
| auto* jsContext = jscContextGetJSContext(context); |
| |
| JSValueRef exception = nullptr; |
| auto* jsTypedArray = JSObjectMakeTypedArray(jsContext, toTypedArrayType(type), count, &exception); |
| if (jscContextHandleExceptionIfNeeded(context, exception)) |
| return nullptr; |
| |
| return jscContextGetOrCreateValue(context, jsTypedArray).leakRef(); |
| } |
| |
| /** |
| * jsc_value_new_typed_array_with_buffer: |
| * @array_buffer: a #JSCValue. |
| * @type: type of array elements. |
| * @offset: offset, in bytes. |
| * @length: number of array elements, or `-1`. |
| * |
| * Create a new typed array value with elements from an array buffer. |
| * |
| * Create a #JSCValue referencing a new typed array value containing |
| * elements of the given @type, where the elements are stored at the memory |
| * region represented by the @array_buffer. |
| * |
| * The @type must *not* be %JSC_TYPED_ARRAY_NONE. |
| * |
| * The @offset and @length parameters can be used to indicate which part of |
| * the array buffer can be accessed through the typed array. If both are |
| * omitted (passing zero as @offset, and `-1` as @length), the whole |
| * @array_buffer is exposed through the typed array. Omitting the @length |
| * with a non-zero @offset will expose the remainder of the @array_buffer |
| * starting at the indicated offset. |
| * |
| * Returns: (transfer full): a #JSCValue |
| * |
| * Since: 2.38 |
| */ |
| JSCValue* jsc_value_new_typed_array_with_buffer(JSCValue* arrayBuffer, JSCTypedArrayType type, gsize offset, gssize length) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(arrayBuffer), nullptr); |
| g_return_val_if_fail(jsc_value_is_array_buffer(arrayBuffer), nullptr); |
| g_return_val_if_fail(type != JSC_TYPED_ARRAY_NONE, nullptr); |
| g_return_val_if_fail(length >= -1, nullptr); |
| |
| auto* jsContext = jscContextGetJSContext(arrayBuffer->priv->context.get()); |
| |
| JSValueRef exception = nullptr; |
| auto* jsObject = JSValueToObject(jsContext, arrayBuffer->priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(arrayBuffer->priv->context.get(), exception)) |
| return nullptr; |
| |
| if (length < 0) { |
| const auto bufferByteSize = JSObjectGetArrayBufferByteLength(jsContext, jsObject, &exception); |
| if (jscContextHandleExceptionIfNeeded(arrayBuffer->priv->context.get(), exception)) |
| return nullptr; |
| |
| const auto remainingBytes = bufferByteSize - offset; |
| const auto elementByteSize = JSC::elementSize(toJSCTypedArrayType(type)); |
| if (remainingBytes >= elementByteSize) |
| length = remainingBytes / elementByteSize; |
| else |
| length = 0; |
| } |
| |
| auto* jsTypedArray = JSObjectMakeTypedArrayWithArrayBufferAndOffset(jsContext, toTypedArrayType(type), jsObject, offset, length, &exception); |
| if (jscContextHandleExceptionIfNeeded(arrayBuffer->priv->context.get(), exception)) |
| return nullptr; |
| |
| return jscContextGetOrCreateValue(arrayBuffer->priv->context.get(), jsTypedArray).leakRef(); |
| } |
| |
| /** |
| * jsc_value_is_typed_array: |
| * @value: a #JSCValue |
| * |
| * Determines whether a value is a typed array. |
| * |
| * Returns: Whether @value is a typed array. |
| * |
| * Since: 2.38 |
| */ |
| gboolean jsc_value_is_typed_array(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
| |
| return jsc_value_typed_array_get_type(value) != JSC_TYPED_ARRAY_NONE; |
| } |
| |
| /** |
| * jsc_value_typed_array_get_type: |
| * @value: a #JSCValue |
| * |
| * Gets the type of elements contained in a typed array. |
| * |
| * Returns: type of the elements, or %JSC_TYPED_ARRAY_NONE if @value is not a typed array. |
| * |
| * Since: 2.38 |
| */ |
| JSCTypedArrayType jsc_value_typed_array_get_type(JSCValue *value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), JSC_TYPED_ARRAY_NONE); |
| |
| JSC::JSGlobalObject* globalObject = toJS(jscContextGetJSContext(value->priv->context.get())); |
| JSC::VM& vm = globalObject->vm(); |
| JSC::JSLockHolder locker(vm); |
| |
| JSC::JSValue jsValue = toJS(globalObject, value->priv->jsValue); |
| if (!jsValue.isObject()) |
| return JSC_TYPED_ARRAY_NONE; |
| |
| switch (jsValue.getObject()->classInfo()->typedArrayStorageType) { |
| case JSC::TypeDataView: |
| case JSC::NotTypedArray: |
| return JSC_TYPED_ARRAY_NONE; |
| case JSC::TypeInt8: |
| return JSC_TYPED_ARRAY_INT8; |
| case JSC::TypeUint8: |
| return JSC_TYPED_ARRAY_UINT8; |
| case JSC::TypeUint8Clamped: |
| return JSC_TYPED_ARRAY_UINT8_CLAMPED; |
| case JSC::TypeInt16: |
| return JSC_TYPED_ARRAY_INT16; |
| case JSC::TypeUint16: |
| return JSC_TYPED_ARRAY_UINT16; |
| case JSC::TypeInt32: |
| return JSC_TYPED_ARRAY_INT32; |
| case JSC::TypeUint32: |
| return JSC_TYPED_ARRAY_UINT32; |
| case JSC::TypeFloat32: |
| return JSC_TYPED_ARRAY_FLOAT32; |
| case JSC::TypeFloat64: |
| return JSC_TYPED_ARRAY_FLOAT64; |
| case JSC::TypeBigInt64: |
| return JSC_TYPED_ARRAY_INT64; |
| case JSC::TypeBigUint64: |
| return JSC_TYPED_ARRAY_UINT64; |
| } |
| |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| /** |
| * jsc_value_typed_array_get_data: |
| * @value: a #JSCValue |
| * @length: (nullable) (out): location to return the number of elements contained |
| * |
| * Obtains a pointer to the memory region that holds the elements of the typed |
| * array; modifications done to them will be visible to JavaScript code. If |
| * @length is not %NULL, the number of elements contained in the typed array |
| * are also stored in the pointed location. |
| * |
| * The returned pointer needs to be casted to the appropriate type (see |
| * #JSCTypedArrayType), and has the `offset` over the underlying array |
| * buffer data applied—that is, points to the first element of the typed |
| * array: |
| * |
| * |[<!-- language="C" --> |
| * if (jsc_value_typed_array_get_type(value) != JSC_TYPED_ARRAY_UINT32) |
| * g_error ("Only arrays of uint32_t are supported"); |
| * |
| * gsize count = 0; |
| * uint32_t *elements = jsc_value_typed_array_get_contents (value, &count); |
| * for (gsize i = 0; i < count; i++) |
| * g_print ("index %zu, value %" PRIu32 "\n", i, elements[i]); |
| * ]| |
| * |
| * Note that the pointer returned by this function is not guaranteed to remain |
| * the same after calls to other JSC API functions. See |
| * jsc_value_array_buffer_get_data() for details. |
| * |
| * Returns: (transfer none): pointer to memory. |
| * |
| * Since: 2.38 |
| */ |
| gpointer jsc_value_typed_array_get_data(JSCValue* value, gsize* length) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| |
| auto* jsContext = jscContextGetJSContext(value->priv->context.get()); |
| |
| JSValueRef exception = nullptr; |
| auto* jsObject = JSValueToObject(jsContext, value->priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return nullptr; |
| |
| auto* bytes = JSObjectGetTypedArrayBytesPtr(jsContext, jsObject, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return nullptr; |
| |
| // Pointer may have an offset, which we want to return directly a pointer to the actual data. |
| const auto offset = JSObjectGetTypedArrayByteOffset(jsContext, jsObject, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return nullptr; |
| |
| if (length) { |
| const auto numElements = JSObjectGetTypedArrayLength(jsContext, jsObject, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return nullptr; |
| *length = numElements; |
| } |
| |
| return static_cast<uint8_t*>(bytes) + offset; |
| } |
| |
| /** |
| * jsc_value_typed_array_get_length: |
| * @value: a #JSCValue |
| * |
| * Gets the number of elements in a typed array. |
| * |
| * Returns: number of elements. |
| * |
| * Since: 2.38 |
| */ |
| gsize jsc_value_typed_array_get_length(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), 0); |
| |
| auto* jsContext = jscContextGetJSContext(value->priv->context.get()); |
| |
| JSValueRef exception = nullptr; |
| auto* jsObject = JSValueToObject(jsContext, value->priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return 0; |
| |
| const auto length = JSObjectGetTypedArrayLength(jsContext, jsObject, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return 0; |
| |
| return length; |
| } |
| |
| /** |
| * jsc_value_typed_array_get_size: |
| * @value: a #JSCValue |
| * |
| * Gets the size of a typed array. |
| * |
| * Returns: size, in bytes. |
| * |
| * Since: 2.38 |
| */ |
| gsize jsc_value_typed_array_get_size(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), 0); |
| |
| auto* jsContext = jscContextGetJSContext(value->priv->context.get()); |
| |
| JSValueRef exception = nullptr; |
| auto* jsObject = JSValueToObject(jsContext, value->priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return 0; |
| |
| const auto size = JSObjectGetTypedArrayByteLength(jsContext, jsObject, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return 0; |
| |
| return size; |
| } |
| |
| /** |
| * jsc_value_typed_array_get_offset: |
| * @value: A #JSCValue |
| * |
| * Gets the offset over the underlying array buffer data. |
| * |
| * Returns: offset, in bytes. |
| * |
| * Since: 2.38 |
| */ |
| gsize jsc_value_typed_array_get_offset(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), 0); |
| |
| auto* jsContext = jscContextGetJSContext(value->priv->context.get()); |
| |
| JSValueRef exception = nullptr; |
| auto* jsObject = JSValueToObject(jsContext, value->priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return 0; |
| |
| const auto offset = JSObjectGetTypedArrayByteOffset(jsContext, jsObject, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return 0; |
| |
| return offset; |
| } |
| |
| /** |
| * jsc_value_typed_array_get_buffer: |
| * @value: a #JSCValue |
| * |
| * Obtain the %ArrayBuffer for the memory region of the typed array elements. |
| * |
| * Returns: (transfer full): A #JSCValue |
| * |
| * Since: 2.38 |
| */ |
| JSCValue* jsc_value_typed_array_get_buffer(JSCValue* value) |
| { |
| g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
| |
| auto* jsContext = jscContextGetJSContext(value->priv->context.get()); |
| |
| JSValueRef exception = nullptr; |
| auto* jsObject = JSValueToObject(jsContext, value->priv->jsValue, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return nullptr; |
| |
| auto* jsArrayBuffer = JSObjectGetTypedArrayBuffer(jsContext, jsObject, &exception); |
| if (jscContextHandleExceptionIfNeeded(value->priv->context.get(), exception)) |
| return nullptr; |
| |
| return jscContextGetOrCreateValue(value->priv->context.get(), jsArrayBuffer).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; |
| } |