blob: f357bd25751ce8e8f7a61ed065baea0e9a34d9c7 [file] [log] [blame]
/*
* 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(&parameter, 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(), &parameter, &exception);
g_value_unset(&parameter);
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;
}