[gjs/ewlsh/text-encoding: 35/36] byteArray: Refactor functionality into Encoding




commit af32a164e74d1a628e51972ecce3ba38818dea5a
Author: Evan Welsh <contact evanwelsh com>
Date:   Sun Apr 25 13:14:58 2021 -0700

    byteArray: Refactor functionality into Encoding
    
    Copies common functionality from byteArray into a Encoding native
    module. This refactor is the basis for the WHATWG Encoding work.

 gjs/byteArray.cpp           | 249 ++---------------------------------------
 gjs/context.cpp             |   3 +
 gjs/text-encoding.cpp       | 262 ++++++++++++++++++++++++++++++++++++++++++++
 gjs/text-encoding.h         |  25 +++++
 meson.build                 |   1 +
 modules/script/byteArray.js |  50 ++++++++-
 6 files changed, 346 insertions(+), 244 deletions(-)
---
diff --git a/gjs/byteArray.cpp b/gjs/byteArray.cpp
index 4fe361d1..341df69e 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>
@@ -28,138 +26,17 @@
 #include "gjs/deprecation.h"
 #include "gjs/jsapi-util-args.h"
 #include "gjs/jsapi-util.h"
+#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);
-}
-
 static void bytes_unref_arraybuffer(void* contents [[maybe_unused]],
                                     void* user_data) {
     auto* gbytes = static_cast<GBytes*>(user_data);
     g_bytes_unref(gbytes);
 }
 
-GJS_JSAPI_RETURN_CONVENTION
-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,
-    // Make sure the bytes of the UTF-16 string are laid out in memory
-    // such that we can simply reinterpret_cast<char16_t> them.
-#if G_BYTE_ORDER == G_LITTLE_ENDIAN
-                                    "UTF-16LE",
-#else
-                                    "UTF-16BE",
-#endif
-                                    encoding, nullptr, /* bytes read */
-                                    &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);
-
-    // 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;
-
-    rval.setString(s);
-    return true;
-}
-
-/* implement toString() with an optional encoding arg */
-GJS_JSAPI_RETURN_CONVENTION
-static bool to_string_impl(JSContext* context, JS::HandleObject byte_array,
-                           const char* encoding, JS::MutableHandleValue rval) {
-    if (!JS_IsUint8Array(byte_array)) {
-        gjs_throw(context,
-                  "Argument to ByteArray.toString() must be a Uint8Array");
-        return false;
-    }
-
-    bool encoding_is_utf8;
-    uint8_t* data;
-
-    if (encoding) {
-        /* 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, "UTF-8") == 0);
-    } else {
-        encoding_is_utf8 = true;
-    }
-
-    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 (!encoding_is_utf8)
-        return to_string_impl_slow(context, data, len, encoding, rval);
-
-    // optimization, avoids iconv overhead and runs libmozjs hardwired
-    // utf8-to-utf16
-
-    // 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;
-    } else {
-        if (!gjs_string_from_utf8_n(context, reinterpret_cast<char*>(data), len,
-                                    rval))
-            return false;
-    }
-
-    uint8_t* current_data;
-    uint32_t current_len;
-    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(byte_array, &current_len, &ignore_val,
-                                   &current_data);
-
-    // Ensure the private data hasn't changed
-    if (current_len == len && current_data == data)
-        return true;
-
-    // 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);
-}
-
-GJS_JSAPI_RETURN_CONVENTION
-static bool to_string_func(JSContext* cx, unsigned argc, JS::Value* vp) {
-    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-    JS::UniqueChars encoding;
-    JS::RootedObject byte_array(cx);
-
-    if (!gjs_parse_call_args(cx, "toString", args, "o|s", "byteArray",
-                             &byte_array, "encoding", &encoding))
-        return false;
-
-    return to_string_impl(cx, byte_array, encoding.get(), args.rval());
-}
 
 /* Workaround to keep existing code compatible. This function is tacked onto
  * any Uint8Array instances created in situations where previously a ByteArray
@@ -180,130 +57,20 @@ static bool instance_to_string_func(JSContext* cx, unsigned argc,
 }
 
 GJS_JSAPI_RETURN_CONVENTION
-static bool
-to_gbytes_func(JSContext *context,
-               unsigned   argc,
-               JS::Value *vp)
-{
-    JS::CallArgs rec = JS::CallArgsFromVp(argc, vp);
-    GIBaseInfo *gbytes_info;
-    JS::RootedObject byte_array(context);
-
-    if (!gjs_parse_call_args(context, "toGBytes", rec, "o",
-                             "byteArray", &byte_array))
-        return false;
-
-    if (!JS_IsUint8Array(byte_array)) {
-        gjs_throw(context,
-                  "Argument to ByteArray.toGBytes() must be a Uint8Array");
-        return false;
-    }
-
-    GBytes* bytes = gjs_byte_array_get_bytes(byte_array);
-
-    g_irepository_require(nullptr, "GLib", "2.0", GIRepositoryLoadFlags(0),
-                          nullptr);
-    gbytes_info = g_irepository_find_by_gtype(NULL, G_TYPE_BYTES);
-    JSObject* ret_bytes_obj =
-        BoxedInstance::new_for_c_struct(context, gbytes_info, bytes);
-    g_bytes_unref(bytes);
-    if (!ret_bytes_obj)
-        return false;
-
-    rec.rval().setObject(*ret_bytes_obj);
-    return true;
-}
-
-/* fromString() function implementation */
-GJS_JSAPI_RETURN_CONVENTION
-static bool
-from_string_func(JSContext *context,
-                 unsigned   argc,
-                 JS::Value *vp)
-{
+static bool define_to_string_func(JSContext* context, unsigned argc, JS::Value* vp) {
     JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
-    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,
-                             "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
-
-        array_buffer =
-            JS::NewExternalArrayBuffer(context, bytes_written, encoded,
-                                       gfree_arraybuffer_contents, nullptr);
-    }
-
-    if (!array_buffer)
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS::RootedObject obj(context);
+    if (!gjs_parse_call_args(context, "defineToString", args, "o", "obj", &obj))
         return false;
-    obj = JS_NewUint8ArrayWithBuffer(context, array_buffer, 0, -1);
 
     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
     if (!JS_DefineFunctionById(context, obj, atoms.to_string(),
                                instance_to_string_func, 1, 0))
         return false;
 
-    argv.rval().setObject(*obj);
+    argv.rval().setUndefined();
     return true;
 }
 
@@ -393,10 +160,8 @@ GByteArray* gjs_byte_array_get_byte_array(JSObject* obj) {
 }
 
 static JSFunctionSpec gjs_byte_array_module_funcs[] = {
-    JS_FN("fromString", from_string_func, 2, 0),
     JS_FN("fromGBytes", from_gbytes_func, 1, 0),
-    JS_FN("toGBytes", to_gbytes_func, 1, 0),
-    JS_FN("toString", to_string_func, 2, 0),
+    JS_FN("defineToString", define_to_string_func, 1, 0),
     JS_FS_END};
 
 bool
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 59709572..c229dc53 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -76,6 +76,7 @@
 #include "gjs/objectbox.h"
 #include "gjs/profiler-private.h"
 #include "gjs/profiler.h"
+#include "gjs/text-encoding.h"
 #include "modules/modules.h"
 #include "util/log.h"
 
@@ -318,6 +319,8 @@ gjs_context_class_init(GjsContextClass *klass)
     }
 
     gjs_register_native_module("_byteArrayNative", gjs_define_byte_array_stuff);
+    gjs_register_native_module("_encodingNative",
+                               gjs_define_text_encoding_stuff);
     gjs_register_native_module("_gi", gjs_define_private_gi_stuff);
     gjs_register_native_module("gi", gjs_define_repo);
 
diff --git a/gjs/text-encoding.cpp b/gjs/text-encoding.cpp
new file mode 100644
index 00000000..5e706a83
--- /dev/null
+++ b/gjs/text-encoding.cpp
@@ -0,0 +1,262 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2010 litl, LLC
+// SPDX-FileCopyrightText: 2021 Evan Welsh
+
+#include <config.h>
+
+#include <stdint.h>
+#include <string.h>  // for strcmp, memchr, strlen
+
+#include <algorithm>
+#include <vector>
+
+#include <gio/gio.h>
+#include <girepository.h>
+#include <glib-object.h>
+#include <glib.h>
+
+#include <js/ArrayBuffer.h>
+#include <js/CallArgs.h>
+#include <js/CharacterEncoding.h>
+#include <js/GCAPI.h>  // for AutoCheckCannotGC
+#include <js/PropertySpec.h>
+#include <js/RootingAPI.h>
+#include <js/TypeDecls.h>
+#include <js/Utility.h>   // for UniqueChars
+#include <jsapi.h>        // for JS_DefineFunctionById, JS_DefineFun...
+#include <jsfriendapi.h>  // for JS_NewUint8ArrayWithBuffer, GetUint...
+
+#include "gi/boxed.h"
+#include "gjs/atoms.h"
+#include "gjs/context-private.h"
+#include "gjs/deprecation.h"
+#include "gjs/jsapi-util-args.h"
+#include "gjs/jsapi-util.h"
+#include "gjs/text-encoding.h"
+
+/* Callbacks to use with JS::NewExternalArrayBuffer() */
+
+static void gfree_arraybuffer_contents(void* contents, void*) {
+    g_free(contents);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+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,
+    // Make sure the bytes of the UTF-16 string are laid out in memory
+    // such that we can simply reinterpret_cast<char16_t> them.
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+                                    "UTF-16LE",
+#else
+                                    "UTF-16BE",
+#endif
+                                    encoding, nullptr, /* bytes read */
+                                    &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);
+
+    // 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;
+
+    rval.setString(s);
+    return true;
+}
+
+/* implement toString() with an optional encoding arg */
+GJS_JSAPI_RETURN_CONVENTION
+bool to_string_impl(JSContext* context, JS::HandleObject byte_array,
+                           const char* encoding, JS::MutableHandleValue rval) {
+    if (!JS_IsUint8Array(byte_array)) {
+        gjs_throw(context,
+                  "Argument to ByteArray.toString() must be a Uint8Array");
+        return false;
+    }
+
+    bool encoding_is_utf8;
+    uint8_t* data;
+
+    if (encoding) {
+        /* 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, "UTF-8") == 0);
+    } else {
+        encoding_is_utf8 = true;
+    }
+
+    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 (!encoding_is_utf8)
+        return to_string_impl_slow(context, data, len, encoding, rval);
+
+    // optimization, avoids iconv overhead and runs libmozjs hardwired
+    // utf8-to-utf16
+
+    // 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;
+    } else {
+        if (!gjs_string_from_utf8_n(context, reinterpret_cast<char*>(data), len,
+                                    rval))
+            return false;
+    }
+
+    uint8_t* current_data;
+    uint32_t current_len;
+    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(byte_array, &current_len, &ignore_val,
+                                   &current_data);
+
+    // Ensure the private data hasn't changed
+    if (current_len == len && current_data == data)
+        return true;
+
+    // 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);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool to_string_func(JSContext* cx, unsigned argc, JS::Value* vp) {
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS::UniqueChars encoding;
+    JS::RootedObject byte_array(cx);
+
+    if (!gjs_parse_call_args(cx, "toString", args, "o|s", "byteArray",
+                             &byte_array, "encoding", &encoding))
+        return false;
+
+    return to_string_impl(cx, byte_array, encoding.get(), args.rval());
+}
+
+
+/* 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);
+    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,
+                             "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
+
+        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);
+
+    argv.rval().setObject(*obj);
+    return true;
+}
+
+
+static JSFunctionSpec gjs_text_encoding_module_funcs[] = {
+    JS_FN("fromString", from_string_func, 2, 0),
+    JS_FN("toString", to_string_func, 2, 0),
+    JS_FS_END};
+
+bool gjs_define_text_encoding_stuff(JSContext* cx,
+                                    JS::MutableHandleObject module) {
+    module.set(JS_NewPlainObject(cx));
+    return JS_DefineFunctions(cx, module, gjs_text_encoding_module_funcs);
+}
diff --git a/gjs/text-encoding.h b/gjs/text-encoding.h
new file mode 100644
index 00000000..b389acf2
--- /dev/null
+++ b/gjs/text-encoding.h
@@ -0,0 +1,25 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Evan Welsh
+
+#ifndef GJS_TEXT_ENCODING_H_
+#define GJS_TEXT_ENCODING_H_
+
+#include <config.h>
+
+#include <stddef.h>  // for size_t
+
+#include <glib.h>
+
+#include <js/TypeDecls.h>
+
+#include "gjs/macros.h"
+
+[[nodiscard]] bool to_string_impl(JSContext* cx, JS::HandleObject uint8array,
+                                   const char* encoding,
+                                   JS::MutableHandleValue rval);
+
+[[nodiscard]] bool gjs_define_text_encoding_stuff(
+    JSContext* cx, JS::MutableHandleObject module);
+
+#endif  // GJS_TEXT_ENCODING_H_
diff --git a/meson.build b/meson.build
index d71c57a6..419ec0b3 100644
--- a/meson.build
+++ b/meson.build
@@ -404,6 +404,7 @@ libgjs_sources = [
     'gjs/native.cpp', 'gjs/native.h',
     'gjs/objectbox.cpp', 'gjs/objectbox.h',
     'gjs/profiler.cpp', 'gjs/profiler-private.h',
+    'gjs/text-encoding.cpp', 'gjs/text-encoding.h',
     'gjs/stack.cpp',
     'modules/console.cpp', 'modules/console.h',
     'modules/modules.cpp', 'modules/modules.h',
diff --git a/modules/script/byteArray.js b/modules/script/byteArray.js
index fad022d2..e0b650ac 100644
--- a/modules/script/byteArray.js
+++ b/modules/script/byteArray.js
@@ -2,15 +2,61 @@
 // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
 // SPDX-FileCopyrightText: 2017 Philip Chimento <philip chimento gmail com>
 
-/* eslint no-redeclare: ["error", { "builtinGlobals": false }] */  // for toString
-var {fromGBytes, fromString, toGBytes, toString} = imports._byteArrayNative;
+var {fromGBytes, defineToString} = imports._byteArrayNative;
+
+const Encoding = imports._encodingNative;
+const {GLib} = imports.gi;
 
 // For backwards compatibility
 
+/**
+ * @param {Iterable<number>} a an iterable to convert into a ByteArray wrapper
+ * @returns {ByteArray}
+ */
 function fromArray(a) {
     return new ByteArray(Uint8Array.from(a));
 }
 
+/**
+ * @param {Uint8Array} array the Uint8Array to convert to GLib.Bytes
+ * @returns {GLib.Bytes}
+ */
+function toGBytes(array) {
+    if (!(array instanceof Uint8Array))
+        throw new Error('Argument to ByteArray.toGBytes() must be a Uint8Array');
+
+    return new GLib.Bytes(array);
+}
+
+// Allow toString to be declared.
+
+/* eslint no-redeclare: ["error", { "builtinGlobals": false }] */
+
+/**
+ * @param {Uint8Array} array the byte array to decode into a string
+ * @param {string} [encoding] a text encoding tag
+ * @returns {string}
+ */
+function toString(array, encoding = 'utf-8') {
+    if (!(array instanceof Uint8Array))
+        throw new Error('Argument to ByteArray.toString() must be a Uint8Array');
+
+    return Encoding.toString(array, encoding);
+}
+
+/**
+ * @param {string} str the string to encode into bytes
+ * @param {string} [encoding] a text encoding tag
+ * @returns {Uint8Array}
+ */
+function fromString(str, encoding = 'utf-8') {
+    const array = Encoding.fromString(str, encoding);
+
+    defineToString(array);
+
+    return array;
+}
+
 var ByteArray = class ByteArray {
     constructor(arg = 0) {
         if (arg instanceof Uint8Array)


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