[gjs/ewlsh/text-encoding: 3/6] modules: Support zero-terminated and fixed length encoding.




commit 81f265e52af73776cfe3ae178c9378ccf9833277
Author: Evan Welsh <contact evanwelsh com>
Date:   Mon Jul 5 21:25:20 2021 -0700

    modules: Support zero-terminated and fixed length encoding.

 gjs/byteArray.cpp         | 127 +++++--------------
 gjs/jsapi-util-string.cpp |  39 +++++-
 gjs/jsapi-util.h          |   3 +
 gjs/text-encoding.cpp     | 312 +++++++++++++++++++++++++++++++++++++---------
 gjs/text-encoding.h       |  15 ++-
 5 files changed, 336 insertions(+), 160 deletions(-)
---
diff --git a/gjs/byteArray.cpp b/gjs/byteArray.cpp
index 63a2f17d..a5979df0 100644
--- a/gjs/byteArray.cpp
+++ b/gjs/byteArray.cpp
@@ -5,7 +5,6 @@
 #include <config.h>
 
 #include <stdint.h>
-#include <string.h>  // for strcmp, memchr, strlen
 
 #include <girepository.h>
 #include <glib-object.h>
@@ -13,7 +12,6 @@
 
 #include <js/ArrayBuffer.h>
 #include <js/CallArgs.h>
-#include <js/GCAPI.h>  // for AutoCheckCannotGC
 #include <js/PropertySpec.h>
 #include <js/RootingAPI.h>
 #include <js/TypeDecls.h>
@@ -31,11 +29,7 @@
 #include "gjs/text-encoding.h"
 #include "util/misc.h"  // for _gjs_memdup2
 
-/* Callbacks to use with JS::NewExternalArrayBuffer() */
-
-static void gfree_arraybuffer_contents(void* contents, void*) {
-    g_free(contents);
-}
+// Callback to use with JS::NewExternalArrayBuffer()
 
 static void bytes_unref_arraybuffer(void* contents [[maybe_unused]],
                                     void* user_data) {
@@ -53,7 +47,15 @@ static bool to_string_func(JSContext* cx, unsigned argc, JS::Value* vp) {
                              &byte_array, "encoding", &encoding))
         return false;
 
-    return bytearray_to_string(cx, byte_array, encoding.get(), args.rval());
+    const char* actual_encoding = encoding ? encoding.get() : "utf-8";
+    JS::RootedString str(
+        cx, gjs_decode_from_uint8array(cx, byte_array, actual_encoding,
+                                       GjsStringTermination::ZERO_TERMINATED));
+    if (!str)
+        return false;
+
+    args.rval().setString(str);
+    return true;
 }
 
 /* Workaround to keep existing code compatible. This function is tacked onto
@@ -71,7 +73,15 @@ static bool instance_to_string_func(JSContext* cx, unsigned argc,
     if (!gjs_parse_call_args(cx, "toString", args, "|s", "encoding", &encoding))
         return false;
 
-    return bytearray_to_string(cx, this_obj, encoding.get(), args.rval());
+    const char* actual_encoding = encoding ? encoding.get() : "utf-8";
+    JS::RootedString str(
+        cx, gjs_decode_from_uint8array(cx, this_obj, actual_encoding,
+                                       GjsStringTermination::ZERO_TERMINATED));
+    if (!str)
+        return false;
+
+    args.rval().setString(str);
+    return true;
 }
 
 GJS_JSAPI_RETURN_CONVENTION
@@ -83,101 +93,22 @@ static bool define_legacy_tostring(JSContext* cx, JS::HandleObject array) {
 
 /* fromString() function implementation */
 GJS_JSAPI_RETURN_CONVENTION
-static bool
-from_string_func(JSContext *context,
-                 unsigned   argc,
-                 JS::Value *vp)
-{
-    JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
+static bool from_string_func(JSContext* cx, unsigned argc, JS::Value* vp) {
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS::RootedString str(cx);
     JS::UniqueChars encoding;
-    JS::UniqueChars utf8;
-    bool encoding_is_utf8;
-    JS::RootedObject obj(context), array_buffer(context);
-
-    if (!gjs_parse_call_args(context, "fromString", argv, "s|s",
-                             "string", &utf8,
+    if (!gjs_parse_call_args(cx, "fromString", args, "S|s", "string", &str,
                              "encoding", &encoding))
         return false;
 
-    if (argc > 1) {
-        /* maybe we should be smarter about utf8 synonyms here.
-         * doesn't matter much though. encoding_is_utf8 is
-         * just an optimization anyway.
-         */
-        encoding_is_utf8 = (strcmp(encoding.get(), "UTF-8") == 0);
-    } else {
-        encoding_is_utf8 = true;
-    }
-
-    if (encoding_is_utf8) {
-        /* optimization? avoids iconv overhead and runs
-         * libmozjs hardwired utf16-to-utf8.
-         */
-        size_t len = strlen(utf8.get());
-        array_buffer =
-            JS::NewArrayBufferWithContents(context, len, utf8.release());
-    } else {
-        JSString *str = argv[0].toString();  /* Rooted by argv */
-        GError *error = NULL;
-        char *encoded = NULL;
-        gsize bytes_written;
-
-        /* Scope for AutoCheckCannotGC, will crash if a GC is triggered
-         * while we are using the string's chars */
-        {
-            JS::AutoCheckCannotGC nogc;
-            size_t len;
-
-            if (JS_StringHasLatin1Chars(str)) {
-                const JS::Latin1Char *chars =
-                    JS_GetLatin1StringCharsAndLength(context, nogc, str, &len);
-                if (chars == NULL)
-                    return false;
-
-                encoded = g_convert((char *) chars, len,
-                                    encoding.get(),  // to_encoding
-                                    "LATIN1",  /* from_encoding */
-                                    NULL,  /* bytes read */
-                                    &bytes_written, &error);
-            } else {
-                const char16_t *chars =
-                    JS_GetTwoByteStringCharsAndLength(context, nogc, str, &len);
-                if (chars == NULL)
-                    return false;
-
-                encoded = g_convert((char *) chars, len * 2,
-                                    encoding.get(),  // to_encoding
-                                    "UTF-16",  /* from_encoding */
-                                    NULL,  /* bytes read */
-                                    &bytes_written, &error);
-            }
-        }
-
-        if (!encoded)
-            return gjs_throw_gerror_message(context, error);  // frees GError
-
-        if (bytes_written == 0) {
-            g_free(encoded);
-            JS::RootedObject empty_array(context, JS_NewUint8Array(context, 0));
-            if (!empty_array || !define_legacy_tostring(context, empty_array))
-                return false;
-
-            argv.rval().setObject(*empty_array);
-            return true;
-        }
-
-        array_buffer =
-            JS::NewExternalArrayBuffer(context, bytes_written, encoded,
-                                       gfree_arraybuffer_contents, nullptr);
-    }
-
-    if (!array_buffer)
-        return false;
-    obj = JS_NewUint8ArrayWithBuffer(context, array_buffer, 0, -1);
-    if (!obj || !define_legacy_tostring(context, obj))
+    const char* actual_encoding = encoding ? encoding.get() : "utf-8";
+    JS::RootedObject uint8array(
+        cx, gjs_encode_to_uint8array(cx, str, actual_encoding,
+                                     GjsStringTermination::ZERO_TERMINATED));
+    if (!uint8array || !define_legacy_tostring(cx, uint8array))
         return false;
 
-    argv.rval().setObject(*obj);
+    args.rval().setObject(*uint8array);
     return true;
 }
 
diff --git a/gjs/jsapi-util-string.cpp b/gjs/jsapi-util-string.cpp
index e5de20c2..51ca5f9c 100644
--- a/gjs/jsapi-util-string.cpp
+++ b/gjs/jsapi-util-string.cpp
@@ -19,7 +19,6 @@
 #include <js/BigInt.h>
 #include <js/CharacterEncoding.h>
 #include <js/Class.h>
-#include <js/ComparisonOperators.h>
 #include <js/GCAPI.h>  // for AutoCheckCannotGC
 #include <js/Id.h>     // for JSID_IS_STRING...
 #include <js/Promise.h>
@@ -92,6 +91,44 @@ JS::UniqueChars gjs_string_to_utf8(JSContext* cx, const JS::Value value) {
     return JS_EncodeStringToUTF8(cx, str);
 }
 
+/**
+ * gjs_string_to_utf8_n:
+ * @param cx: the current #JSContext
+ * @param str: a handle to a JSString
+ * @param output a pointer to a JS::UniqueChars
+ * @param output_len a pointer for the length of output
+ *
+ * @brief Converts a JSString to UTF-8 and puts the char array in #output and
+ * its length in #output_len.
+ *
+ * This function handles the boilerblate for unpacking a JSString, determining its
+ * length, and returning the appropriate JS::UniqueChars. This function should generally
+ * be preferred over using JS::DeflateStringToUTF8Buffer directly as it correctly
+ * handles allocation in a JS_Free compatible manner.
+ */
+bool gjs_string_to_utf8_n(JSContext* cx, JS::HandleString str, JS::UniqueChars* output,
+                          size_t* output_len) {
+    JSLinearString* linear = JS_EnsureLinearString(cx, str);
+    if (!linear)
+        return false;
+
+    size_t length = JS::GetDeflatedUTF8StringLength(linear);
+    char* bytes = js_pod_arena_malloc<char>(js::StringBufferArena, length + 1);
+    if (!bytes)
+        return false;
+
+    // Append a zero-terminator to the string.
+    bytes[length] = '\0';
+
+    size_t deflated_length [[maybe_unused]] =
+        JS::DeflateStringToUTF8Buffer(linear, mozilla::Span(bytes, length));
+    g_assert(deflated_length == length);
+
+    *output_len = length;
+    *output = JS::UniqueChars(bytes);
+    return true;
+}
+
 bool
 gjs_string_from_utf8(JSContext             *context,
                      const char            *utf8_string,
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index e1f41e5a..ec648347 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -451,6 +451,9 @@ void gjs_warning_reporter(JSContext*, JSErrorReport* report);
 GJS_JSAPI_RETURN_CONVENTION
 JS::UniqueChars gjs_string_to_utf8(JSContext* cx, const JS::Value string_val);
 GJS_JSAPI_RETURN_CONVENTION
+bool gjs_string_to_utf8_n(JSContext* cx, JS::HandleString str, JS::UniqueChars* output,
+                          size_t* output_len);
+GJS_JSAPI_RETURN_CONVENTION
 bool gjs_string_from_utf8(JSContext             *context,
                           const char            *utf8_string,
                           JS::MutableHandleValue value_p);
diff --git a/gjs/text-encoding.cpp b/gjs/text-encoding.cpp
index a77bd19f..03600778 100644
--- a/gjs/text-encoding.cpp
+++ b/gjs/text-encoding.cpp
@@ -35,6 +35,20 @@
 #include "gjs/jsapi-util.h"
 #include "gjs/text-encoding.h"
 
+// Callback to use with JS::NewExternalArrayBuffer()
+
+static void gfree_arraybuffer_contents(void* contents, void*) {
+    g_free(contents);
+}
+
+static std::nullptr_t gjs_throw_type_error_from_gerror(JSContext* cx,
+                                                       GError* error) {
+    g_return_val_if_fail(error, nullptr);
+    gjs_throw_custom(cx, JSProto_TypeError, nullptr, "%s", error->message);
+    g_error_free(error);
+    return nullptr;
+}
+
 // UTF16_CODESET is used to encode and decode UTF-16 buffers with
 // iconv. To ensure the output of iconv is laid out in memory correctly
 // we have to use UTF-16LE on little endian systems and UTF-16BE on big
@@ -47,6 +61,29 @@ static const char* UTF16_CODESET = "UTF-16LE";
 static const char* UTF16_CODESET = "UTF-16BE";
 #endif
 
+GJS_JSAPI_RETURN_CONVENTION
+static JSString* gjs_decode_from_uint8array_slow(JSContext* cx, uint8_t* input,
+                                                 uint32_t input_len,
+                                                 const char* encoding) {
+    size_t bytes_written, bytes_read;
+    GError* error = nullptr;
+
+    GjsAutoChar bytes =
+        g_convert(reinterpret_cast<const char*>(input), input_len,
+                  UTF16_CODESET, encoding, &bytes_read, &bytes_written, &error);
+
+    if (error)
+        return gjs_throw_type_error_from_gerror(cx, error);
+
+    // bytes_written should be bytes in a UTF-16 string so should be a
+    // multiple of 2
+    g_assert((bytes_written % 2) == 0);
+
+    // Cast g_convert's output to char16_t and copy the data.
+    const char16_t* unicode_bytes = reinterpret_cast<char16_t*>(bytes.get());
+    return JS_NewUCStringCopyN(cx, unicode_bytes, bytes_written / 2);
+}
+
 [[nodiscard]] static bool is_utf8_label(const char* encoding) {
     // We could be smarter about utf8 synonyms here.
     // For now, we handle any casing and trailing/leading
@@ -63,71 +100,83 @@ static const char* UTF16_CODESET = "UTF-16BE";
            g_ascii_strcasecmp(stripped, "utf8") == 0;
 }
 
-GJS_JSAPI_RETURN_CONVENTION
-static bool to_string_impl_slow(JSContext* cx, uint8_t* data, uint32_t len,
-                                const char* encoding,
-                                JS::MutableHandleValue rval) {
-    size_t bytes_written;
-    GError* error = nullptr;
-    GjsAutoChar u16_str =
-        g_convert(reinterpret_cast<char*>(data), len, UTF16_CODESET, encoding,
-                  /* bytes_read = */ nullptr, &bytes_written, &error);
-    if (!u16_str)
-        return gjs_throw_gerror_message(cx, error);  // frees GError
-
-    // bytes_written should be bytes in a UTF-16 string so should be a multiple
-    // of 2
-    g_assert((bytes_written % 2) == 0);
+// Finds the length of a given data array, stopping at the first 0 byte.
+template <class T, class L>
+[[nodiscard]] static L zero_terminated_length(const T* data, L len) {
+    if (!data || len == 0)
+        return 0;
 
-    // g_convert 0-terminates the string, although the 0 isn't included in
-    // bytes_written
-    JSString* s =
-        JS_NewUCStringCopyZ(cx, reinterpret_cast<char16_t*>(u16_str.get()));
-    if (!s)
-        return false;
+    const T* start = data;
+    auto* found = static_cast<const T*>(
+        std::memchr(start, '\0', static_cast<size_t>(len)));
 
-    rval.setString(s);
-    return true;
+    // If a null byte was not found, return the passed length.
+    if (!found)
+        return len;
+
+    return std::distance(start, found);
 }
 
-// implement ByteArray.toString() with an optional encoding arg
-bool bytearray_to_string(JSContext* context, JS::HandleObject byte_array,
-                         const char* encoding, JS::MutableHandleValue rval) {
+// decode() function implementation
+JSString* gjs_decode_from_uint8array(JSContext* cx, JS::HandleObject byte_array,
+                                     const char* encoding,
+                                     GjsStringTermination string_termination) {
+    g_assert(encoding && "encoding must be non-null");
+
     if (!JS_IsUint8Array(byte_array)) {
-        gjs_throw(context,
-                  "Argument to ByteArray.toString() must be a Uint8Array");
-        return false;
+        gjs_throw(cx, "Argument to decode() must be a Uint8Array");
+        return nullptr;
     }
 
-    bool encoding_is_utf8 = true;
-    if (encoding)
-        encoding_is_utf8 = is_utf8_label(encoding);
-
     uint8_t* data;
+    // len should be size_t but SpiderMonkey defines it differently in mozjs78
     uint32_t len;
     bool is_shared_memory;
     js::GetUint8ArrayLengthAndData(byte_array, &len, &is_shared_memory, &data);
 
-    if (len == 0) {
-        rval.setString(JS_GetEmptyString(context));
-        return true;
-    }
+    // If the desired behavior is zero-terminated, calculate the
+    // zero-terminated length of the given data.
+    if (len && string_termination == GjsStringTermination::ZERO_TERMINATED)
+        len = zero_terminated_length(data, len);
+
+    // If the calculated length is 0 we can just return an empty string.
+    if (len == 0)
+        return JS_GetEmptyString(cx);
 
+    // Optimization, only use glib's iconv-based converters if we're dealing
+    // with a non-UTF8 encoding. SpiderMonkey has highly optimized UTF-8 decoder
+    // and encoders.
+    bool encoding_is_utf8 = is_utf8_label(encoding);
     if (!encoding_is_utf8)
-        return to_string_impl_slow(context, data, len, encoding, rval);
+        return gjs_decode_from_uint8array_slow(cx, data, len, encoding);
 
-    // optimization, avoids iconv overhead and runs libmozjs hardwired
-    // utf8-to-utf16
+    JS::RootedString decoded(cx);
+    JS::UTF8Chars chars(reinterpret_cast<char*>(data), len);
+    JS::RootedString str(cx, JS_NewStringCopyUTF8N(cx, chars));
 
-    // If there are any 0 bytes, including the terminating byte, stop at the
-    // first one
-    if (data[len - 1] == 0 || memchr(data, 0, len)) {
-        if (!gjs_string_from_utf8(context, reinterpret_cast<char*>(data), rval))
-            return false;
+    // If an exception occurred, we need to check if the
+    // exception was an InternalError. Unfortunately,
+    // SpiderMonkey's decoder can throw InternalError for some
+    // invalid UTF-8 sources, we have to convert this into a
+    // TypeError to match the Encoding specification.
+    if (str) {
+        decoded.set(str);
     } else {
-        if (!gjs_string_from_utf8_n(context, reinterpret_cast<char*>(data), len,
-                                    rval))
-            return false;
+        JS::RootedValue exc(cx);
+        if (!JS_GetPendingException(cx, &exc) || !exc.isObject())
+            return nullptr;
+
+        JS::RootedObject exc_obj(cx, &exc.toObject());
+        const JSClass* internal_error =
+            js::ProtoKeyToClass(JSProto_InternalError);
+        if (JS_InstanceOf(cx, exc_obj, internal_error, nullptr)) {
+            // Clear the existing exception.
+            JS_ClearPendingException(cx);
+            gjs_throw_custom(cx, JSProto_TypeError, nullptr,
+                             "The provided encoded data was not valid UTF-8");
+        }
+
+        return nullptr;
     }
 
     uint8_t* current_data;
@@ -135,23 +184,168 @@ bool bytearray_to_string(JSContext* context, JS::HandleObject byte_array,
     bool ignore_val;
 
     // If a garbage collection occurs between when we call
-    // js::GetUint8ArrayLengthAndData and return from gjs_string_from_utf8, a
-    // use-after-free corruption can occur if the garbage collector shifts the
-    // location of the Uint8Array's private data. To mitigate this we call
-    // js::GetUint8ArrayLengthAndData again and then compare if the length and
-    // pointer are still the same. If the pointers differ, we use the slow path
-    // to ensure no data corruption occurred. The shared-ness of an array cannot
-    // change between calls, so we ignore it.
+    // js::GetUint8ArrayLengthAndData and return from
+    // gjs_decode_from_uint8array, a use-after-free corruption can occur if the
+    // garbage collector shifts the location of the Uint8Array's private data.
+    // To mitigate this we call js::GetUint8ArrayLengthAndData again and then
+    // compare if the length and pointer are still the same. If the pointers
+    // differ, we use the slow path to ensure no data corruption occurred. The
+    // shared-ness of an array cannot change between calls, so we ignore it.
     js::GetUint8ArrayLengthAndData(byte_array, &current_len, &ignore_val,
                                    &current_data);
 
     // Ensure the private data hasn't changed
-    if (current_len == len && current_data == data)
-        return true;
+    if (current_data == data)
+        return decoded;
+
+    g_assert(current_len == len &&
+             "Garbage collection should not affect data length.");
 
     // This was the UTF-8 optimized path, so we explicitly pass the encoding
-    return to_string_impl_slow(context, current_data, current_len, "UTF-8",
-                               rval);
+    return gjs_decode_from_uint8array_slow(cx, current_data, current_len,
+                                           "UTF-8");
+}
+
+// encode() function implementation
+JSObject* gjs_encode_to_uint8array(JSContext* cx, JS::HandleString str,
+                                   const char* encoding,
+                                   GjsStringTermination string_termination) {
+    JS::RootedObject array_buffer(cx);
+
+    bool encoding_is_utf8 = is_utf8_label(encoding);
+    if (encoding_is_utf8) {
+        JS::UniqueChars utf8;
+        size_t utf8_len;
+
+        if (!gjs_string_to_utf8_n(cx, str, &utf8, &utf8_len))
+            return nullptr;
+
+        if (string_termination == GjsStringTermination::ZERO_TERMINATED) {
+            // strlen is safe because gjs_string_to_utf8_n returns
+            // a null-terminated string.
+            utf8_len = strlen(utf8.get());
+        }
+
+        array_buffer = JS::NewArrayBufferWithContents(cx, utf8_len, utf8.get());
+
+        // array_buffer only assumes ownership if the call succeeded,
+        // if array_buffer assumes ownership we must release our ownership
+        // without freeing the data.
+        if (array_buffer) [[maybe_unused]]
+            char* data = utf8.release();
+    } else {
+        GError* error = nullptr;
+        GjsAutoChar encoded = nullptr;
+        size_t bytes_written;
+
+        /* Scope for AutoCheckCannotGC, will crash if a GC is triggered
+         * while we are using the string's chars */
+        {
+            JS::AutoCheckCannotGC nogc;
+            size_t len;
+
+            if (JS_StringHasLatin1Chars(str)) {
+                const JS::Latin1Char* chars =
+                    JS_GetLatin1StringCharsAndLength(cx, nogc, str, &len);
+                if (!chars)
+                    return nullptr;
+
+                encoded = g_convert(reinterpret_cast<const char*>(chars), len,
+                                    encoding,  // to_encoding
+                                    "LATIN1",  // from_encoding
+                                    nullptr,   // bytes_read
+                                    &bytes_written, &error);
+            } else {
+                const char16_t* chars =
+                    JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &len);
+                if (!chars)
+                    return nullptr;
+
+                encoded =
+                    g_convert(reinterpret_cast<const char*>(chars), len * 2,
+                              encoding,  // to_encoding
+                              "UTF-16",  // from_encoding
+                              nullptr,   // bytes_read
+                              &bytes_written, &error);
+            }
+        }
+
+        if (!encoded)
+            return gjs_throw_type_error_from_gerror(cx, error);  // frees GError
+
+        if (string_termination == GjsStringTermination::ZERO_TERMINATED) {
+            bytes_written =
+                zero_terminated_length(encoded.get(), bytes_written);
+        }
+
+        if (bytes_written == 0)
+            return JS_NewUint8Array(cx, 0);
+
+        array_buffer =
+            JS::NewExternalArrayBuffer(cx, bytes_written, encoded.release(),
+                                       gfree_arraybuffer_contents, nullptr);
+    }
+
+    if (!array_buffer)
+        return nullptr;
+
+    return JS_NewUint8ArrayWithBuffer(cx, array_buffer, 0, -1);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_encode_into_uint8array(JSContext* cx, JS::HandleString str,
+                                       JS::HandleObject uint8array,
+                                       JS::MutableHandleValue rval) {
+    if (!JS_IsUint8Array(uint8array)) {
+        gjs_throw_custom(cx, JSProto_TypeError, nullptr,
+                         "Argument to encodeInto() must be a Uint8Array");
+        return false;
+    }
+
+    uint32_t len = JS_GetTypedArrayByteLength(uint8array);
+    bool shared = JS_GetTypedArraySharedness(uint8array);
+
+    if (shared) {
+        gjs_throw(cx, "Cannot encode data into shared memory.");
+        return false;
+    }
+
+    mozilla::Maybe<mozilla::Tuple<size_t, size_t>> results;
+
+    {
+        JS::AutoCheckCannotGC nogc(cx);
+        uint8_t* data = JS_GetUint8ArrayData(uint8array, &shared, nogc);
+
+        // We already checked for sharedness with JS_GetTypedArraySharedness
+        g_assert(!shared);
+
+        results = JS_EncodeStringToUTF8BufferPartial(
+            cx, str, mozilla::AsWritableChars(mozilla::Span(data, len)));
+    }
+
+    if (!results) {
+        JS_ReportOutOfMemory(cx);
+        return false;
+    }
+
+    size_t read, written;
+    mozilla::Tie(read, written) = *results;
+
+    g_assert(written <= len);
+
+    JS::RootedObject result(cx, JS_NewPlainObject(cx));
+    if (!result)
+        return false;
+
+    JS::RootedValue v_read(cx, JS::NumberValue(read)),
+        v_written(cx, JS::NumberValue(written));
+
+    if (!JS_SetProperty(cx, result, "read", v_read) ||
+        !JS_SetProperty(cx, result, "written", v_written))
+        return false;
+
+    rval.setObject(*result);
+    return true;
 }
 
 static JSFunctionSpec gjs_text_encoding_module_funcs[] = {JS_FS_END};
diff --git a/gjs/text-encoding.h b/gjs/text-encoding.h
index e9425392..eee174bb 100644
--- a/gjs/text-encoding.h
+++ b/gjs/text-encoding.h
@@ -14,9 +14,20 @@
 
 #include "gjs/macros.h"
 
+enum class GjsStringTermination {
+    ZERO_TERMINATED,
+    EXPLICIT_LENGTH,
+};
+
+GJS_JSAPI_RETURN_CONVENTION
+JSString* gjs_decode_from_uint8array(JSContext* cx, JS::HandleObject uint8array,
+                                     const char* encoding,
+                                     GjsStringTermination string_termination);
+
 GJS_JSAPI_RETURN_CONVENTION
-bool bytearray_to_string(JSContext* cx, JS::HandleObject uint8array,
-                         const char* encoding, JS::MutableHandleValue rval);
+JSObject* gjs_encode_to_uint8array(JSContext* cx, JS::HandleString str,
+                                   const char* encoding,
+                                   GjsStringTermination string_termination);
 
 GJS_JSAPI_RETURN_CONVENTION
 bool gjs_define_text_encoding_stuff(JSContext* cx,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]