Diff
Modified: trunk/Source/_javascript_Core/API/glib/JSCValue.cpp (291193 => 291194)
--- trunk/Source/_javascript_Core/API/glib/JSCValue.cpp 2022-03-11 22:45:35 UTC (rev 291193)
+++ trunk/Source/_javascript_Core/API/glib/JSCValue.cpp 2022-03-11 22:55:46 UTC (rev 291194)
@@ -22,6 +22,7 @@
#include "APICast.h"
#include "APIUtils.h"
+#include "JSArrayBuffer.h"
#include "JSCCallbackFunction.h"
#include "JSCClassPrivate.h"
#include "JSCContextPrivate.h"
@@ -28,6 +29,7 @@
#include "JSCInlines.h"
#include "JSCValuePrivate.h"
#include "JSRetainPtr.h"
+#include "JSTypedArray.h"
#include "LiteralParser.h"
#include "OpaqueJSString.h"
#include <gobject/gvaluecollector.h>
@@ -1461,7 +1463,184 @@
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*>(vm, 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 = "" 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 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;
+}
+
+/**
* jsc_value_new_from_json:
* @context: a #JSCContext
* @json: the JSON string to be parsed
Modified: trunk/Source/_javascript_Core/API/glib/JSCValue.h (291193 => 291194)
--- trunk/Source/_javascript_Core/API/glib/JSCValue.h 2022-03-11 22:45:35 UTC (rev 291193)
+++ trunk/Source/_javascript_Core/API/glib/JSCValue.h 2022-03-11 22:55:46 UTC (rev 291194)
@@ -246,8 +246,24 @@
jsc_value_function_callv (JSCValue *value,
guint n_parameters,
JSCValue **parameters) G_GNUC_WARN_UNUSED_RESULT;
+JSC_API JSCValue *
+jsc_value_new_array_buffer (JSCContext *context,
+ gpointer data,
+ gsize size,
+ GDestroyNotify destroy_notify,
+ gpointer user_data);
JSC_API gboolean
+jsc_value_is_array_buffer (JSCValue *value);
+
+JSC_API gpointer
+jsc_value_array_buffer_get_data (JSCValue *value,
+ gsize *size);
+
+JSC_API gsize
+jsc_value_array_buffer_get_size (JSCValue *value);
+
+JSC_API gboolean
jsc_value_is_constructor (JSCValue *value);
JSC_API JSCValue *
Modified: trunk/Source/_javascript_Core/API/glib/docs/jsc-glib-4.0-sections.txt (291193 => 291194)
--- trunk/Source/_javascript_Core/API/glib/docs/jsc-glib-4.0-sections.txt 2022-03-11 22:45:35 UTC (rev 291193)
+++ trunk/Source/_javascript_Core/API/glib/docs/jsc-glib-4.0-sections.txt 2022-03-11 22:55:46 UTC (rev 291194)
@@ -110,6 +110,10 @@
jsc_value_is_function
jsc_value_function_call
jsc_value_function_callv
+jsc_value_new_array_buffer
+jsc_value_is_array_buffer
+jsc_value_array_buffer_get_data
+jsc_value_array_buffer_get_size
jsc_value_is_constructor
jsc_value_constructor_call
jsc_value_constructor_callv
Modified: trunk/Source/_javascript_Core/ChangeLog (291193 => 291194)
--- trunk/Source/_javascript_Core/ChangeLog 2022-03-11 22:45:35 UTC (rev 291193)
+++ trunk/Source/_javascript_Core/ChangeLog 2022-03-11 22:55:46 UTC (rev 291194)
@@ -1,3 +1,23 @@
+2022-03-11 Adrian Perez de Castro <ape...@igalia.com>
+
+ [GLib] Expose ArrayBuffer in the public API
+ https://bugs.webkit.org/show_bug.cgi?id=237088
+
+ Reviewed by Carlos Garcia Campos.
+
+ This adds a set of new functions to operate on JSCValue objects which refer to array
+ buffers in the JS side of the world. This allows sharing chunks of memory buffers
+ efficiently with native code, without needing to copy nor encode data back and forth.
+
+ * API/glib/JSCValue.cpp:
+ (jscArrayBufferDeallocate):
+ (jsc_value_new_array_buffer):
+ (jsc_value_is_array_buffer):
+ (jsc_value_array_buffer_get_data):
+ (jsc_value_array_buffer_get_length):
+ * API/glib/JSCValue.h:
+ * API/glib/docs/jsc-glib-4.0-sections.txt:
+
2022-03-11 Mikhail R. Gadelha <mikh...@igalia.com>
Debug build failure after r246172: ASSERT_UNDER_CONSTEXPR_CONTEXT should work in constexpr contexts
Modified: trunk/Tools/ChangeLog (291193 => 291194)
--- trunk/Tools/ChangeLog 2022-03-11 22:45:35 UTC (rev 291193)
+++ trunk/Tools/ChangeLog 2022-03-11 22:55:46 UTC (rev 291194)
@@ -1,3 +1,18 @@
+2022-03-11 Adrian Perez de Castro <ape...@igalia.com>
+
+ [GLib] Expose ArrayBuffer in the public API
+ https://bugs.webkit.org/show_bug.cgi?id=237088
+
+ Reviewed by Carlos Garcia Campos.
+
+ This adds a set of new functions to operate on JSCValue objects which refer to array
+ buffers in the JS side of the world. This allows sharing chunks of memory buffers
+ efficiently with native code, without needing to copy nor encode data back and forth.
+
+ * TestWebKitAPI/Tests/_javascript_Core/glib/TestJSC.cpp: Added test for array buffers.
+ (testJSCArrayBuffer):
+ (main):
+
2022-03-11 Jonathan Bedard <jbed...@apple.com>
[Merge-Queue] Add queue triggered by label addition
Modified: trunk/Tools/TestWebKitAPI/Tests/_javascript_Core/glib/TestJSC.cpp (291193 => 291194)
--- trunk/Tools/TestWebKitAPI/Tests/_javascript_Core/glib/TestJSC.cpp 2022-03-11 22:45:35 UTC (rev 291193)
+++ trunk/Tools/TestWebKitAPI/Tests/_javascript_Core/glib/TestJSC.cpp 2022-03-11 22:55:46 UTC (rev 291194)
@@ -2822,6 +2822,155 @@
}
}
+static void testJSCArrayBuffer()
+{
+ {
+ LeakChecker checker;
+ GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
+ checker.watch(context.get());
+ ExceptionHandler exceptionHandler(context.get());
+
+ GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(),
+ "const arr = new Uint8Array(5);"
+ "arr[0] = 0x73; arr[1] = 0x70; arr[2] = 0x61; arr[3] = 0x6D; arr[4] = 0x00;"
+ "arr.buffer;",
+ -1));
+ checker.watch(result.get());
+
+ g_assert_true(jsc_value_is_array_buffer(result.get()));
+
+ gsize byteCount = 0;
+ auto* data = "" &byteCount));
+ g_assert_cmpuint(jsc_value_array_buffer_get_size(result.get()), ==, 5);
+ g_assert_cmpuint(byteCount, ==, 5);
+ g_assert_cmpint(data[4], ==, '\0');
+ g_assert_cmpstr(data, ==, "spam");
+
+ snprintf(data, byteCount, "Yay!");
+
+ result = adoptGRef(jsc_context_evaluate(context.get(), "arr[0] == 0x59 && arr[1] == 0x61 && arr[2] == 0x79 && arr[3] == 0x21 && arr[4] == 0x00;", -1));
+ checker.watch(result.get());
+
+ g_assert_true(jsc_value_is_boolean(result.get()));
+ g_assert_true(jsc_value_to_boolean(result.get()));
+ }
+
+ {
+ LeakChecker checker;
+ GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
+ checker.watch(context.get());
+ ExceptionHandler exceptionHandler(context.get());
+
+ char data[100];
+ snprintf(data, sizeof(data), "Hello, JS!");
+
+ GRefPtr<JSCValue> value = adoptGRef(jsc_value_new_array_buffer(context.get(), data, 100, nullptr, nullptr));
+ checker.watch(value.get());
+ g_assert_true(jsc_value_is_array_buffer(value.get()));
+
+ g_assert_cmpuint(jsc_value_array_buffer_get_size(value.get()), ==, 100);
+
+ jsc_context_set_value(context.get(), "data", value.get());
+
+ GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "(new Uint8Array(data))[2]", -1));
+ checker.watch(result.get());
+
+ g_assert_true(jsc_value_is_number(result.get()));
+ g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 'l');
+
+ result = adoptGRef(jsc_context_evaluate(context.get(), "(new Uint8Array(data))[0] = 65;", -1));
+ checker.watch(result.get());
+
+ g_assert_true(jsc_value_is_number(result.get()));
+ g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 'A');
+ g_assert_cmpint(data[0], ==, 'A');
+ g_assert_cmpint(static_cast<const char*>(jsc_value_array_buffer_get_data(value.get(), nullptr))[0], ==, 'A');
+ }
+
+ {
+ LeakChecker checker;
+ GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
+ checker.watch(context.get());
+ ExceptionHandler exceptionHandler(context.get());
+
+ char data[] = "spam and eggs";
+ bool destroyNotifyCalled = false;
+
+ GRefPtr<JSCValue> value = adoptGRef(jsc_value_new_array_buffer(context.get(), data, strlen(data), [](gpointer data) {
+ *static_cast<bool*>(data) = true;
+ }, &destroyNotifyCalled));
+ checker.watch(value.get());
+
+ g_assert_true(jsc_value_is_array_buffer(value.get()));
+
+ jsc_context_set_value(context.get(), "data", value.get());
+ value = nullptr;
+
+ jscContextGarbageCollect(context.get());
+ g_assert_false(destroyNotifyCalled);
+
+ value = adoptGRef(jsc_context_get_value(context.get(), "data"));
+ checker.watch(value.get());
+ g_assert_true(jsc_value_is_array_buffer(value.get()));
+
+ value = adoptGRef(jsc_value_new_undefined(context.get()));
+ checker.watch(value.get());
+ jsc_context_set_value(context.get(), "data", value.get());
+
+ jscContextGarbageCollect(context.get());
+ g_assert_true(destroyNotifyCalled);
+ }
+
+ {
+ LeakChecker checker;
+ GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
+ checker.watch(context.get());
+ ExceptionHandler exceptionHandler(context.get());
+
+ struct HeapData {
+ char data { 0x42 };
+ GRefPtr<GFile> object { adoptGRef(g_file_new_for_path("/")) };
+ };
+
+ auto* data = "" HeapData;
+ checker.watch(data->object.get());
+ GRefPtr<JSCValue> value = adoptGRef(jsc_value_new_array_buffer(context.get(), data, sizeof(HeapData), [](gpointer data) {
+ delete static_cast<HeapData*>(data);
+ }, data));
+ checker.watch(value.get());
+ jsc_context_set_value(context.get(), "data", value.get());
+
+ GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "(new Uint8Array(data))[0];", -1));
+ g_assert_true(jsc_value_is_number(result.get()));
+ g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 0x42);
+
+ value = adoptGRef(jsc_value_new_undefined(context.get()));
+ checker.watch(value.get());
+
+ jsc_context_set_value(context.get(), "data", value.get());
+ jscContextGarbageCollect(context.get());
+ }
+
+ {
+ LeakChecker checker;
+ GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
+ checker.watch(context.get());
+ ExceptionHandler exceptionHandler(context.get());
+
+ GRefPtr<JSCValue> value = adoptGRef(jsc_context_evaluate(context.get(), "(new Uint8Array(0)).buffer;", -1));
+ checker.watch(value.get());
+
+ g_assert_true(jsc_value_is_array_buffer(value.get()));
+ g_assert_cmpuint(jsc_value_array_buffer_get_size(value.get()), ==, 0);
+
+ value = adoptGRef(jsc_value_new_array_buffer(context.get(), nullptr, 0, nullptr, nullptr));
+ checker.watch(value.get());
+
+ g_assert_true(jsc_value_is_array_buffer(value.get()));
+ g_assert_cmpuint(jsc_value_array_buffer_get_size(value.get()), ==, 0);
+ }
+}
+
typedef struct {
Foo parent;
int bar;
@@ -4182,6 +4331,7 @@
g_test_add_func("/jsc/function", testJSCFunction);
g_test_add_func("/jsc/object", testJSCObject);
g_test_add_func("/jsc/class", testJSCClass);
+ g_test_add_func("/jsc/array-buffer", testJSCArrayBuffer);
g_test_add_func("/jsc/prototypes", testJSCPrototypes);
g_test_add_func("/jsc/exceptions", testJSCExceptions);
g_test_add_func("/jsc/promises", testJSCPromises);