[njs] QuickJS: added TextDecoder and TextEncoder.
noreply at nginx.com
noreply at nginx.com
Thu Jan 23 00:06:02 UTC 2025
details: https://github.com/nginx/njs/commit/447d66d41d41504db976e900d94e75a90d388265
branches: master
commit: 447d66d41d41504db976e900d94e75a90d388265
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Fri, 10 Jan 2025 23:20:36 -0800
description:
QuickJS: added TextDecoder and TextEncoder.
---
src/qjs.c | 568 +++++++++++++++++++++++++++++++++++++++++++++++
src/qjs.h | 11 +-
src/qjs_buffer.c | 4 +-
src/test/njs_unit_test.c | 122 ----------
test/text_decoder.t.js | 151 +++++++++++++
test/text_encoder.t.js | 87 ++++++++
6 files changed, 814 insertions(+), 129 deletions(-)
diff --git a/src/qjs.c b/src/qjs.c
index 487a03fc..e21e6568 100644
--- a/src/qjs.c
+++ b/src/qjs.c
@@ -19,6 +19,26 @@ typedef struct {
} qjs_signal_entry_t;
+typedef enum {
+ QJS_ENCODING_UTF8,
+} qjs_encoding_t;
+
+
+typedef struct {
+ qjs_encoding_t encoding;
+ int fatal;
+ int ignore_bom;
+
+ njs_unicode_decode_t ctx;
+} qjs_text_decoder_t;
+
+
+typedef struct {
+ njs_str_t name;
+ qjs_encoding_t encoding;
+} qjs_encoding_label_t;
+
+
extern char **environ;
@@ -32,6 +52,26 @@ static JSValue qjs_process_kill(JSContext *ctx, JSValueConst this_val,
static JSValue qjs_process_pid(JSContext *ctx, JSValueConst this_val);
static JSValue qjs_process_ppid(JSContext *ctx, JSValueConst this_val);
+static int qjs_add_intrinsic_text_decoder(JSContext *cx, JSValueConst global);
+static JSValue qjs_text_decoder_to_string_tag(JSContext *ctx,
+ JSValueConst this_val);
+static JSValue qjs_text_decoder_decode(JSContext *ctx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue qjs_text_decoder_encoding(JSContext *ctx, JSValueConst this_val);
+static JSValue qjs_text_decoder_fatal(JSContext *ctx, JSValueConst this_val);
+static JSValue qjs_text_decoder_ignore_bom(JSContext *ctx,
+ JSValueConst this_val);
+static void qjs_text_decoder_finalizer(JSRuntime *rt, JSValue val);
+
+static int qjs_add_intrinsic_text_encoder(JSContext *cx, JSValueConst global);
+static JSValue qjs_text_encoder_to_string_tag(JSContext *ctx,
+ JSValueConst this_val);
+static JSValue qjs_text_encoder_encode(JSContext *ctx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue qjs_text_encoder_encode_into(JSContext *ctx,
+ JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue qjs_text_encoder_encoding(JSContext *ctx, JSValueConst this_val);
+
/* P1990 signals from `man 7 signal` are supported */
static qjs_signal_entry_t qjs_signals_table[] = {
@@ -58,10 +98,35 @@ static qjs_signal_entry_t qjs_signals_table[] = {
};
+static qjs_encoding_label_t qjs_encoding_labels[] =
+{
+ { njs_str("utf-8"), QJS_ENCODING_UTF8 },
+ { njs_str("utf8") , QJS_ENCODING_UTF8 },
+ { njs_null_str, 0 }
+};
+
+
static const JSCFunctionListEntry qjs_global_proto[] = {
JS_CGETSET_DEF("njs", qjs_njs_getter, NULL),
};
+static const JSCFunctionListEntry qjs_text_decoder_proto[] = {
+ JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_text_decoder_to_string_tag,
+ NULL),
+ JS_CFUNC_DEF("decode", 1, qjs_text_decoder_decode),
+ JS_CGETSET_DEF("encoding", qjs_text_decoder_encoding, NULL),
+ JS_CGETSET_DEF("fatal", qjs_text_decoder_fatal, NULL),
+ JS_CGETSET_DEF("ignoreBOM", qjs_text_decoder_ignore_bom, NULL),
+};
+
+static const JSCFunctionListEntry qjs_text_encoder_proto[] = {
+ JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_text_encoder_to_string_tag,
+ NULL),
+ JS_CFUNC_DEF("encode", 1, qjs_text_encoder_encode),
+ JS_CFUNC_DEF("encodeInto", 1, qjs_text_encoder_encode_into),
+ JS_CGETSET_DEF("encoding", qjs_text_encoder_encoding, NULL),
+};
+
static const JSCFunctionListEntry qjs_njs_proto[] = {
JS_CGETSET_DEF("[Symbol.toStringTag]", qjs_njs_to_string_tag, NULL),
JS_PROP_STRING_DEF("version", NJS_VERSION, JS_PROP_C_W_E),
@@ -80,6 +145,12 @@ static const JSCFunctionListEntry qjs_process_proto[] = {
};
+static JSClassDef qjs_text_decoder_class = {
+ "TextDecoder",
+ .finalizer = qjs_text_decoder_finalizer,
+};
+
+
JSContext *
qjs_new_context(JSRuntime *rt, qjs_module_t **addons)
{
@@ -121,6 +192,14 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons)
global_obj = JS_GetGlobalObject(ctx);
+ if (qjs_add_intrinsic_text_decoder(ctx, global_obj) < 0) {
+ return NULL;
+ }
+
+ if (qjs_add_intrinsic_text_encoder(ctx, global_obj) < 0) {
+ return NULL;
+ }
+
JS_SetPropertyFunctionList(ctx, global_obj, qjs_global_proto,
njs_nitems(qjs_global_proto));
@@ -393,6 +472,495 @@ qjs_process_object(JSContext *ctx, int argc, const char **argv)
}
+static int
+qjs_text_decoder_encoding_arg(JSContext *cx, int argc, JSValueConst *argv,
+ qjs_text_decoder_t *td)
+{
+ njs_str_t str;
+ qjs_encoding_label_t *label;
+
+ if (argc < 1) {
+ td->encoding = QJS_ENCODING_UTF8;
+ return 0;
+ }
+
+ str.start = (u_char *) JS_ToCStringLen(cx, &str.length, argv[0]);
+ if (str.start == NULL) {
+ JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ for (label = &qjs_encoding_labels[0]; label->name.length != 0; label++) {
+ if (njs_strstr_eq(&str, &label->name)) {
+ td->encoding = label->encoding;
+ JS_FreeCString(cx, (char *) str.start);
+ return 0;
+ }
+ }
+
+ JS_ThrowTypeError(cx, "The \"%.*s\" encoding is not supported",
+ (int) str.length, str.start);
+ JS_FreeCString(cx, (char *) str.start);
+
+ return -1;
+}
+
+
+static int
+qjs_text_decoder_options(JSContext *cx, int argc, JSValueConst *argv,
+ qjs_text_decoder_t *td)
+{
+ JSValue val;
+
+ if (argc < 2) {
+ td->fatal = 0;
+ td->ignore_bom = 0;
+
+ return 0;
+ }
+
+ val = JS_GetPropertyStr(cx, argv[1], "fatal");
+ if (JS_IsException(val)) {
+ return -1;
+ }
+
+ td->fatal = JS_ToBool(cx, val);
+ JS_FreeValue(cx, val);
+
+ val = JS_GetPropertyStr(cx, argv[1], "ignoreBOM");
+ if (JS_IsException(val)) {
+ return -1;
+ }
+
+ td->ignore_bom = JS_ToBool(cx, val);
+ JS_FreeValue(cx, val);
+
+ return 0;
+}
+
+
+static JSValue
+qjs_text_decoder_ctor(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ JSValue obj;
+ qjs_text_decoder_t *td;
+
+ obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_TEXT_DECODER);
+ if (JS_IsException(obj)) {
+ return JS_EXCEPTION;
+ }
+
+ td = js_mallocz(cx, sizeof(qjs_text_decoder_t));
+ if (td == NULL) {
+ JS_ThrowOutOfMemory(cx);
+ JS_FreeValue(cx, obj);
+ return JS_EXCEPTION;
+ }
+
+ if (qjs_text_decoder_encoding_arg(cx, argc, argv, td) < 0) {
+ js_free(cx, td);
+ JS_FreeValue(cx, obj);
+ return JS_EXCEPTION;
+ }
+
+ if (qjs_text_decoder_options(cx, argc, argv, td) < 0) {
+ js_free(cx, td);
+ JS_FreeValue(cx, obj);
+ return JS_EXCEPTION;
+ }
+
+ njs_utf8_decode_init(&td->ctx);
+
+ JS_SetOpaque(obj, td);
+
+ return obj;
+}
+
+
+static int
+qjs_add_intrinsic_text_decoder(JSContext *cx, JSValueConst global)
+{
+ JSValue ctor, proto;
+
+ if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_TEXT_DECODER,
+ &qjs_text_decoder_class) < 0)
+ {
+ return -1;
+ }
+
+ proto = JS_NewObject(cx);
+ if (JS_IsException(proto)) {
+ return -1;
+ }
+
+ JS_SetPropertyFunctionList(cx, proto, qjs_text_decoder_proto,
+ njs_nitems(qjs_text_decoder_proto));
+
+ JS_SetClassProto(cx, QJS_CORE_CLASS_ID_TEXT_DECODER, proto);
+
+ ctor = JS_NewCFunction2(cx, qjs_text_decoder_ctor, "TextDecoder", 2,
+ JS_CFUNC_constructor, 0);
+ if (JS_IsException(ctor)) {
+ return -1;
+ }
+
+ JS_SetConstructor(cx, ctor, proto);
+
+ return JS_SetPropertyStr(cx, global, "TextDecoder", ctor);
+}
+
+
+static JSValue
+qjs_text_decoder_to_string_tag(JSContext *ctx, JSValueConst this_val)
+{
+ return JS_NewString(ctx, "TextDecoder");
+}
+
+
+static JSValue
+qjs_text_decoder_decode(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ int stream;
+ size_t size;
+ u_char *dst;
+ JSValue ret;
+ ssize_t length;
+ njs_str_t data;
+ const u_char *end;
+ qjs_text_decoder_t *td;
+ njs_unicode_decode_t ctx;
+
+ td = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_TEXT_DECODER);
+ if (td == NULL) {
+ return JS_ThrowInternalError(cx, "'this' is not a TextDecoder");
+ }
+
+ ret = qjs_typed_array_data(cx, argv[0], &data);
+ if (JS_IsException(ret)) {
+ return ret;
+ }
+
+ stream = 0;
+
+ if (argc > 1) {
+ ret = JS_GetPropertyStr(cx, argv[1], "stream");
+ if (JS_IsException(ret)) {
+ return JS_EXCEPTION;
+ }
+
+ stream = JS_ToBool(cx, ret);
+ JS_FreeValue(cx, ret);
+ }
+
+ ctx = td->ctx;
+ end = data.start + data.length;
+
+ if (data.start != NULL && !td->ignore_bom) {
+ data.start += njs_utf8_bom(data.start, end);
+ }
+
+ length = njs_utf8_stream_length(&ctx, data.start, end - data.start, !stream,
+ td->fatal, &size);
+
+ if (length == -1) {
+ return JS_ThrowTypeError(cx, "The encoded data was not valid");
+ }
+
+ dst = js_malloc(cx, size + 1);
+ if (dst == NULL) {
+ JS_ThrowOutOfMemory(cx);
+ return JS_EXCEPTION;
+ }
+
+ (void) njs_utf8_stream_encode(&td->ctx, data.start, end, dst, !stream, 0);
+
+ ret = JS_NewStringLen(cx, (const char *) dst, size);
+ js_free(cx, dst);
+
+ if (!stream) {
+ njs_utf8_decode_init(&td->ctx);
+ }
+
+ return ret;
+}
+
+
+static JSValue
+qjs_text_decoder_encoding(JSContext *ctx, JSValueConst this_val)
+{
+ qjs_text_decoder_t *td;
+
+ td = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_TEXT_DECODER);
+ if (td == NULL) {
+ return JS_ThrowInternalError(ctx, "'this' is not a TextDecoder");
+ }
+
+ switch (td->encoding) {
+ case QJS_ENCODING_UTF8:
+ return JS_NewString(ctx, "utf-8");
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+qjs_text_decoder_fatal(JSContext *ctx, JSValueConst this_val)
+{
+ qjs_text_decoder_t *td;
+
+ td = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_TEXT_DECODER);
+ if (td == NULL) {
+ return JS_ThrowInternalError(ctx, "'this' is not a TextDecoder");
+ }
+
+ return JS_NewBool(ctx, td->fatal);
+}
+
+
+static JSValue
+qjs_text_decoder_ignore_bom(JSContext *ctx, JSValueConst this_val)
+{
+ qjs_text_decoder_t *td;
+
+ td = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_TEXT_DECODER);
+ if (td == NULL) {
+ return JS_ThrowInternalError(ctx, "'this' is not a TextDecoder");
+ }
+
+ return JS_NewBool(ctx, td->ignore_bom);
+}
+
+
+static void
+qjs_text_decoder_finalizer(JSRuntime *rt, JSValue val)
+{
+ qjs_text_decoder_t *td;
+
+ td = JS_GetOpaque(val, QJS_CORE_CLASS_ID_TEXT_DECODER);
+ if (td != NULL) {
+ js_free_rt(rt, td);
+ }
+}
+
+
+static JSValue
+qjs_text_encoder_ctor(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ JSValue obj;
+
+ obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_TEXT_ENCODER);
+ if (JS_IsException(obj)) {
+ return JS_EXCEPTION;
+ }
+
+ JS_SetOpaque(obj, (void *) 1);
+
+ return obj;
+}
+
+
+static int
+qjs_add_intrinsic_text_encoder(JSContext *cx, JSValueConst global)
+{
+ JSValue ctor, proto;
+
+ proto = JS_NewObject(cx);
+ if (JS_IsException(proto)) {
+ return -1;
+ }
+
+ JS_SetPropertyFunctionList(cx, proto, qjs_text_encoder_proto,
+ njs_nitems(qjs_text_encoder_proto));
+
+ JS_SetClassProto(cx, QJS_CORE_CLASS_ID_TEXT_ENCODER, proto);
+
+ ctor = JS_NewCFunction2(cx, qjs_text_encoder_ctor, "TextEncoder", 0,
+ JS_CFUNC_constructor, 0);
+ if (JS_IsException(ctor)) {
+ return -1;
+ }
+
+ JS_SetConstructor(cx, ctor, proto);
+
+ return JS_SetPropertyStr(cx, global, "TextEncoder", ctor);
+}
+
+
+static JSValue
+qjs_text_encoder_to_string_tag(JSContext *ctx, JSValueConst this_val)
+{
+ return JS_NewString(ctx, "TextEncoder");
+}
+
+
+static JSValue
+qjs_text_encoder_encoding(JSContext *ctx, JSValueConst this_val)
+{
+ return JS_NewString(ctx, "utf-8");
+}
+
+
+static JSValue
+qjs_text_encoder_encode(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ void *te;
+ JSValue len, ta, ret;
+ njs_str_t utf8, dst;
+
+ te = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_TEXT_ENCODER);
+ if (te == NULL) {
+ return JS_ThrowInternalError(cx, "'this' is not a TextEncoder");
+ }
+
+ if (!JS_IsString(argv[0])) {
+ return JS_ThrowTypeError(cx, "The input argument must be a string");
+ }
+
+ utf8.start = (u_char *) JS_ToCStringLen(cx, &utf8.length, argv[0]);
+ if (utf8.start == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ len = JS_NewInt64(cx, utf8.length);
+
+ ta = qjs_new_uint8_array(cx, 1, &len);
+ if (JS_IsException(ta)) {
+ JS_FreeCString(cx, (char *) utf8.start);
+ return ta;
+ }
+
+ ret = qjs_typed_array_data(cx, ta, &dst);
+ if (JS_IsException(ret)) {
+ JS_FreeCString(cx, (char *) utf8.start);
+ return ret;
+ }
+
+ memcpy(dst.start, utf8.start, utf8.length);
+ JS_FreeCString(cx, (char *) utf8.start);
+
+ return ta;
+}
+
+
+static int
+qjs_is_uint8_array(JSContext *cx, JSValueConst value)
+{
+ int ret;
+ JSValue ctor, global;
+
+ global = JS_GetGlobalObject(cx);
+
+ ctor = JS_GetPropertyStr(cx, global, "Uint8Array");
+ if (JS_IsException(ctor)) {
+ JS_FreeValue(cx, global);
+ return -1;
+ }
+
+ ret = JS_IsInstanceOf(cx, value, ctor);
+ JS_FreeValue(cx, ctor);
+ JS_FreeValue(cx, global);
+
+ return ret;
+}
+
+
+static JSValue
+qjs_text_encoder_encode_into(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ int read, written;
+ void *te;
+ size_t size;
+ u_char *to, *to_end;
+ JSValue ret;
+ uint32_t cp;
+ njs_str_t utf8, dst;
+ const u_char *start, *end;
+ njs_unicode_decode_t ctx;
+
+ te = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_TEXT_ENCODER);
+ if (te == NULL) {
+ return JS_ThrowInternalError(cx, "'this' is not a TextEncoder");
+ }
+
+ if (!JS_IsString(argv[0])) {
+ return JS_ThrowTypeError(cx, "The input argument must be a string");
+ }
+
+ ret = qjs_typed_array_data(cx, argv[1], &dst);
+ if (JS_IsException(ret)) {
+ return ret;
+ }
+
+ if (!qjs_is_uint8_array(cx, argv[1])) {
+ return JS_ThrowTypeError(cx, "The output argument must be a"
+ " Uint8Array");
+ }
+
+ utf8.start = (u_char *) JS_ToCStringLen(cx, &utf8.length, argv[0]);
+ if (utf8.start == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ read = 0;
+ written = 0;
+
+ start = utf8.start;
+ end = start + utf8.length;
+
+ to = dst.start;
+ to_end = to + dst.length;
+
+ njs_utf8_decode_init(&ctx);
+
+ while (start < end) {
+ cp = njs_utf8_decode(&ctx, &start, end);
+
+ if (cp > NJS_UNICODE_MAX_CODEPOINT) {
+ cp = NJS_UNICODE_REPLACEMENT;
+ }
+
+ size = njs_utf8_size(cp);
+
+ if (to + size > to_end) {
+ break;
+ }
+
+ read += (cp > 0xFFFF) ? 2 : 1;
+ written += size;
+
+ to = njs_utf8_encode(to, cp);
+ }
+
+ JS_FreeCString(cx, (char *) utf8.start);
+
+ ret = JS_NewObject(cx);
+ if (JS_IsException(ret)) {
+ return ret;
+ }
+
+ if (JS_DefinePropertyValueStr(cx, ret, "read", JS_NewInt32(cx, read),
+ JS_PROP_C_W_E) < 0)
+ {
+ JS_FreeValue(cx, ret);
+ return JS_EXCEPTION;
+ }
+
+ if (JS_DefinePropertyValueStr(cx, ret, "written", JS_NewInt32(cx, written),
+ JS_PROP_C_W_E) < 0)
+ {
+ JS_FreeValue(cx, ret);
+ return JS_EXCEPTION;
+ }
+
+ return ret;
+}
+
int
qjs_to_bytes(JSContext *ctx, qjs_bytes_t *bytes, JSValueConst value)
{
diff --git a/src/qjs.h b/src/qjs.h
index dec6419d..76bf5c3d 100644
--- a/src/qjs.h
+++ b/src/qjs.h
@@ -36,10 +36,12 @@
#define QJS_CORE_CLASS_ID_OFFSET 64
#define QJS_CORE_CLASS_ID_BUFFER (QJS_CORE_CLASS_ID_OFFSET)
#define QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR (QJS_CORE_CLASS_ID_OFFSET + 1)
-#define QJS_CORE_CLASS_ID_FS_STATS (QJS_CORE_CLASS_ID_OFFSET + 2)
-#define QJS_CORE_CLASS_ID_FS_DIRENT (QJS_CORE_CLASS_ID_OFFSET + 3)
-#define QJS_CORE_CLASS_ID_FS_FILEHANDLE (QJS_CORE_CLASS_ID_OFFSET + 4)
-#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_OFFSET + 5)
+#define QJS_CORE_CLASS_ID_TEXT_DECODER (QJS_CORE_CLASS_ID_OFFSET + 2)
+#define QJS_CORE_CLASS_ID_TEXT_ENCODER (QJS_CORE_CLASS_ID_OFFSET + 3)
+#define QJS_CORE_CLASS_ID_FS_STATS (QJS_CORE_CLASS_ID_OFFSET + 4)
+#define QJS_CORE_CLASS_ID_FS_DIRENT (QJS_CORE_CLASS_ID_OFFSET + 5)
+#define QJS_CORE_CLASS_ID_FS_FILEHANDLE (QJS_CORE_CLASS_ID_OFFSET + 6)
+#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_OFFSET + 7)
typedef JSModuleDef *(*qjs_addon_init_pt)(JSContext *ctx, const char *name);
@@ -53,6 +55,7 @@ typedef struct {
JSContext *qjs_new_context(JSRuntime *rt, qjs_module_t **addons);
+JSValue qjs_new_uint8_array(JSContext *ctx, int argc, JSValueConst *argv);
JSValue qjs_buffer_alloc(JSContext *ctx, size_t size);
JSValue qjs_buffer_create(JSContext *ctx, u_char *start, size_t size);
JSValue qjs_buffer_chb_alloc(JSContext *ctx, njs_chb_t *chain);
diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c
index 9f451e26..3652a07a 100644
--- a/src/qjs_buffer.c
+++ b/src/qjs_buffer.c
@@ -90,8 +90,6 @@ static int qjs_hex_encode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst);
static size_t qjs_hex_encode_length(JSContext *ctx, const njs_str_t *src);
static int qjs_hex_decode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst);
static size_t qjs_hex_decode_length(JSContext *ctx, const njs_str_t *src);
-static JSValue qjs_new_uint8_array(JSContext *ctx, int argc,
- JSValueConst *argv);
static JSModuleDef *qjs_buffer_init(JSContext *ctx, const char *name);
@@ -2465,7 +2463,7 @@ qjs_buffer_chb_alloc(JSContext *ctx, njs_chb_t *chain)
}
-static JSValue
+JSValue
qjs_new_uint8_array(JSContext *ctx, int argc, JSValueConst *argv)
{
JSValue ret;
diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
index d6ae4ed8..2e9a5379 100644
--- a/src/test/njs_unit_test.c
+++ b/src/test/njs_unit_test.c
@@ -19339,128 +19339,6 @@ static njs_unit_test_t njs_test[] =
{ njs_str("var t = \"123\"; t = parseInt(t); t"),
njs_str("123") },
- /* TextEncoder. */
-
- { njs_str("var en = new TextEncoder(); typeof en.encode()"),
- njs_str("object") },
-
- { njs_str("var en = new TextEncoder(); en.encode()"),
- njs_str("") },
-
- { njs_str("var en = new TextEncoder(); var res = en.encode('α'); res"),
- njs_str("206,177") },
-
- { njs_str("var en = new TextEncoder(); var res = en.encode('α1α'); res[2]"),
- njs_str("49") },
-
- { njs_str("var en = new TextEncoder(); en.encoding"),
- njs_str("utf-8") },
-
- { njs_str("TextEncoder.prototype.encode.apply({}, [])"),
- njs_str("TypeError: \"this\" is not a TextEncoder") },
-
- { njs_str("var en = new TextEncoder();"
- "var utf8 = new Uint8Array(5);"
- "var res = en.encodeInto('ααααα', utf8); njs.dump(res)"),
- njs_str("{read:2,written:4}") },
-
- { njs_str("var en = new TextEncoder();"
- "var utf8 = new Uint8Array(10);"
- "var res = en.encodeInto('ααααα', utf8); njs.dump(res)"),
- njs_str("{read:5,written:10}") },
-
- { njs_str("var en = new TextEncoder();"
- "var utf8 = new Uint8Array(10);"
- "en.encodeInto('ααααα', utf8.subarray(2)); utf8[0]"),
- njs_str("0") },
-
- { njs_str("TextEncoder.prototype.encodeInto.apply({}, [])"),
- njs_str("TypeError: \"this\" is not a TextEncoder") },
-
- { njs_str("(new TextEncoder()).encodeInto('', 0.12) "),
- njs_str("TypeError: The \"destination\" argument must be an instance of Uint8Array") },
-
- /* TextDecoder. */
-
- { njs_str("var de = new TextDecoder();"
- "var u8arr = new Uint8Array([240, 160, 174, 183]);"
- "var u16arr = new Uint16Array(u8arr.buffer);"
- "var u32arr = new Uint32Array(u8arr.buffer);"
- "[u8arr, u16arr, u32arr].map(v=>de.decode(v)).join(',')"),
- njs_str("𠮷,𠮷,𠮷") },
-
- { njs_str("var de = new TextDecoder();"
- "[new Uint8Array([240, 160]), "
- " new Uint8Array([174]), "
- " new Uint8Array([183])].map(v=>de.decode(v, {stream: 1}))[2]"),
- njs_str("𠮷") },
-
- { njs_str("var de = new TextDecoder();"
- "de.decode(new Uint8Array([240, 160]), {stream: 1});"
- "de.decode(new Uint8Array([174]), {stream: 1});"
- "de.decode(new Uint8Array([183]))"),
- njs_str("𠮷") },
-
- { njs_str("var de = new TextDecoder();"
- "de.decode(new Uint8Array([240, 160]), {stream: 1});"
- "de.decode()"),
- njs_str("�") },
-
- { njs_str("var de = new TextDecoder('utf-8', {fatal: true});"
- "de.decode(new Uint8Array([240, 160]))"),
- njs_str("TypeError: The encoded data was not valid") },
-
- { njs_str("var de = new TextDecoder('utf-8', {fatal: false});"
- "de.decode(new Uint8Array([240, 160]))"),
- njs_str("�") },
-
- { njs_str("var en = new TextEncoder();"
- "var de = new TextDecoder('utf-8', {ignoreBOM: true});"
- "en.encode(de.decode(new Uint8Array([239, 187, 191, 50])))"),
- njs_str("239,187,191,50") },
-
- { njs_str("var en = new TextEncoder();"
- "var de = new TextDecoder('utf-8', {ignoreBOM: false});"
- "en.encode(de.decode(new Uint8Array([239, 187, 191, 50])))"),
- njs_str("50") },
-
- { njs_str("var en = new TextEncoder(); var de = new TextDecoder();"
- "en.encode(de.decode(new Uint8Array([239, 187, 191, 50])))"),
- njs_str("50") },
-
- { njs_str("var de = new TextDecoder(); de.decode('')"),
- njs_str("TypeError: The \"input\" argument must be an instance of TypedArray") },
-
- { njs_str("var de = new TextDecoder({})"),
- njs_str("RangeError: The \"[object Object]\" encoding is not supported") },
-
- { njs_str("var de = new TextDecoder('foo')"),
- njs_str("RangeError: The \"foo\" encoding is not supported") },
-
- { njs_str("var de = new TextDecoder(); de.encoding"),
- njs_str("utf-8") },
-
- { njs_str("var de = new TextDecoder(); de.fatal"),
- njs_str("false") },
-
- { njs_str("var de = new TextDecoder(); de.ignoreBOM"),
- njs_str("false") },
-
- { njs_str("TextDecoder.prototype.decode.apply({}, new Uint8Array([1]))"),
- njs_str("TypeError: \"this\" is not a TextDecoder") },
-
- { njs_str("var de = new TextDecoder();"
- "var buf = new Uint32Array([1,2,3]).buffer;"
- "var en = new TextEncoder();"
- "njs.dump(new Uint32Array(en.encode(de.decode(buf)).buffer))"),
- njs_str("Uint32Array [1,2,3]") },
-
- { njs_str("var de = new TextDecoder();"
- "var buf = new Uint32Array([1,2,3]).subarray(1,2);"
- "var en = new TextEncoder();"
- "njs.dump(new Uint32Array(en.encode(de.decode(buf)).buffer))"),
- njs_str("Uint32Array [2]") },
-
/* let */
{ njs_str("let x"),
diff --git a/test/text_decoder.t.js b/test/text_decoder.t.js
new file mode 100644
index 00000000..2eb879c0
--- /dev/null
+++ b/test/text_decoder.t.js
@@ -0,0 +1,151 @@
+/*---
+includes: [runTsuite.js, compareArray.js]
+flags: [async]
+---*/
+
+function p(args, default_opts) {
+ let params = merge({}, default_opts);
+ params = merge(params, args);
+
+ return params;
+}
+
+let stream_tsuite = {
+ name: "TextDecoder() stream tests",
+ T: async (params) => {
+ let td = new TextDecoder('utf-8');
+
+ if (td.encoding !== 'utf-8') {
+ throw Error(`unexpected encoding "${td.encoding}" != "utf-8"`);
+ }
+
+ if (td.fatal !== false) {
+ throw Error(`unexpected fatal "${td.fatal}" != "false"`);
+ }
+
+ if (td.ignoreBOM !== false) {
+ throw Error(`unexpected ignoreBOM "${td.ignoreBOM}" != "false"`);
+ }
+
+ let chunks = [];
+ for (var i = 0; i < params.chunks.length; i++) {
+ let r = td.decode(params.chunks[i], { stream: (i != params.chunks.length - 1) });
+ chunks.push(r);
+ }
+
+ if (!compareArray(chunks, params.expected)) {
+ throw Error(`unexpected output "${chunks.join('|')}" != "${params.expected.join('|')}"`);
+ }
+
+ return 'SUCCESS';
+ },
+
+ prepare_args: p,
+ opts: {},
+
+ tests: [
+ { chunks: [new Uint8Array([0xF0, 0x9F, 0x8C, 0x9F])],
+ expected: ['🌟'] },
+ // BOM is ignored
+ { chunks: [new Uint8Array([0xEF, 0xBB, 0xBF, 0xF0, 0x9F, 0x8C, 0x9F])],
+ expected: ['🌟'] },
+ { chunks: [(new Uint8Array([0xF0, 0x9F, 0x8C, 0x9F])).buffer],
+ expected: ['🌟'] },
+ { chunks: [new Uint32Array((new Uint8Array([0xF0, 0x9F, 0x8C, 0x9F])).buffer)],
+ expected: ['🌟'] },
+ { chunks: [new Uint8Array((new Uint8Array([0x00, 0xF0, 0x9F, 0x8C, 0x9F, 0x00])).buffer, 1, 4)],
+ expected: ['🌟'] },
+ { chunks: [new Uint8Array([0xF0, 0x9F]), new Uint8Array([0x8C, 0x9F])],
+ expected: ['', '🌟'] },
+ { chunks: [new Uint8Array([0xF0, 0xA0]), new Uint8Array([0xAE]), new Uint8Array([0xB7])],
+ expected: ['', '', '𠮷'] },
+ { chunks: [new Uint8Array([0xF0, 0xA0]), new Uint8Array([])],
+ expected: ['', '�'] },
+ { chunks: [''],
+ exception: 'TypeError: TypeError: not a TypedArray' },
+ ],
+};
+
+let fatal_tsuite = {
+ name: "TextDecoder() fatal tests",
+ T: async (params) => {
+ let td = new TextDecoder('utf8', {fatal: true, ignoreBOM: true});
+
+ if (td.encoding !== 'utf-8') {
+ throw Error(`unexpected encoding "${td.encoding}" != "utf-8"`);
+ }
+
+ if (td.fatal !== true) {
+ throw Error(`unexpected fatal "${td.fatal}" != "true"`);
+ }
+
+ if (td.ignoreBOM !== true) {
+ throw Error(`unexpected ignoreBOM "${td.ignoreBOM}" != "true"`);
+ }
+
+ let chunks = [];
+ for (var i = 0; i < params.chunks.length; i++) {
+ let r = td.decode(params.chunks[i]);
+ chunks.push(r);
+ }
+
+ if (!compareArray(chunks, params.expected)) {
+ throw Error(`unexpected output "${chunks.join('|')}" != "${params.expected.join('|')}"`);
+ }
+
+ return 'SUCCESS';
+ },
+
+ prepare_args: p,
+ opts: {},
+
+ tests: [
+ { chunks: [new Uint8Array([0xF0, 0xA0, 0xAE, 0xB7])],
+ expected: ['𠮷'] },
+ { chunks: [new Uint8Array([0xF0, 0xA0, 0xAE])],
+ exception: 'Error: The encoded data was not valid' },
+ { chunks: [new Uint8Array([0xF0, 0xA0])],
+ exception: 'Error: The encoded data was not valid' },
+ { chunks: [new Uint8Array([0xF0])],
+ exception: 'Error: The encoded data was not valid' },
+ ],
+};
+
+let ignoreBOM_tsuite = {
+ name: "TextDecoder() ignoreBOM tests",
+ T: async (params) => {
+ let td = new TextDecoder('utf8', params.opts);
+ let te = new TextEncoder();
+
+ let res = te.encode(td.decode(params.value));
+
+ if (!compareArray(res, params.expected)) {
+ throw Error(`unexpected output "${res}" != "${params.expected}"`);
+ }
+
+ return 'SUCCESS';
+ },
+
+ prepare_args: p,
+ opts: {},
+
+ tests: [
+ { value: new Uint8Array([239, 187, 191, 50]),
+ opts: {ignoreBOM: true},
+ expected: [239, 187, 191, 50] },
+ { value: new Uint8Array([239, 187, 191, 50]),
+ opts: {ignoreBOM: false},
+ expected: [50] },
+ { value: new Uint8Array([239, 187, 191, 50]),
+ opts: {},
+ expected: [50] },
+ ],
+};
+
+
+run([
+ stream_tsuite,
+ fatal_tsuite,
+ ignoreBOM_tsuite,
+])
+.then($DONE, $DONE);
diff --git a/test/text_encoder.t.js b/test/text_encoder.t.js
new file mode 100644
index 00000000..e790ae37
--- /dev/null
+++ b/test/text_encoder.t.js
@@ -0,0 +1,87 @@
+
+/*---
+includes: [runTsuite.js, compareArray.js]
+flags: [async]
+---*/
+
+function p(args, default_opts) {
+ let params = merge({}, default_opts);
+ params = merge(params, args);
+
+ return params;
+}
+
+let encode_tsuite = {
+ name: "TextEncoder() encode tests",
+ T: async (params) => {
+ let te = new TextEncoder();
+
+ if (te.encoding !== 'utf-8') {
+ throw Error(`unexpected encoding "${td.encoding}" != "utf-8"`);
+ }
+
+ let res = te.encode(params.value);
+
+ if (!(res instanceof Uint8Array)) {
+ throw Error(`unexpected result "${res}" is not Uint8Array`);
+ }
+
+ if (!compareArray(Array.from(res), params.expected)) {
+ throw Error(`unexpected output "${res}" != "${params.expected}"`);
+ }
+
+ return 'SUCCESS';
+ },
+
+ prepare_args: p,
+ opts: {},
+
+ tests: [
+ { value: "", expected: [] },
+ { value: "abc", expected: [97, 98, 99] },
+ { value: "α1α", expected: [206, 177, 49, 206, 177] },
+ { value: 0.12, exception: 'TypeError: TextEncoder.prototype.encode requires a string' },
+ ],
+};
+
+let encodeinto_tsuite = {
+ name: "TextEncoder() encodeInto tests",
+ T: async (params) => {
+ let te = new TextEncoder();
+
+ let res = te.encodeInto(params.value, params.dest);
+
+ if (res.written !== params.expected.length) {
+ throw Error(`unexpected written "${res.written}" != "${params.expected.length}"`);
+ }
+
+ if (res.read !== params.read) {
+ throw Error(`unexpected read "${res.read}" != "${params.read}"`);
+ }
+
+ if (!compareArray(Array.from(params.dest).slice(0, res.written), params.expected)) {
+ throw Error(`unexpected output "${res}" != "${params.expected}"`);
+ }
+
+ return 'SUCCESS';
+ },
+
+ prepare_args: p,
+ opts: {},
+
+ tests: [
+ { value: "", dest: new Uint8Array(4), expected: [], read: 0 },
+ { value: "aα", dest: new Uint8Array(3), expected: [97, 206, 177], read: 2 },
+ { value: "αααα", dest: new Uint8Array(4), expected: [206, 177, 206, 177], read: 2 },
+ { value: "αααα", dest: new Uint8Array(5), expected: [206, 177, 206, 177], read: 2 },
+ { value: "αααα", dest: new Uint8Array(6), expected: [206, 177, 206, 177, 206, 177], read: 3 },
+ { value: "", dest: 0.12, exception: 'TypeError: TextEncoder.prototype.encodeInto requires a string' },
+ { value: 0.12, exception: 'TypeError: TextEncoder.prototype.encodeInto requires a string' },
+ ],
+};
+
+run([
+ encode_tsuite,
+ encodeinto_tsuite,
+])
+.then($DONE, $DONE);
More information about the nginx-devel
mailing list