Title: [291194] trunk
Revision
291194
Author
ape...@igalia.com
Date
2022-03-11 14:55:46 -0800 (Fri, 11 Mar 2022)

Log Message

[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.

Source/_javascript_Core:

* 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:

Tools:

* TestWebKitAPI/Tests/_javascript_Core/glib/TestJSC.cpp: Added test for array buffers.
(testJSCArrayBuffer):
(main):

Modified Paths

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);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to