From 57c45d0a31091471738401eae5cff2ce717c1d00 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Wed, 11 Feb 2026 11:40:24 -0800 Subject: [PATCH] util: move TextEncoder class to C++ --- lib/internal/encoding.js | 64 +-- src/encoding_binding.cc | 518 +++++++++++++----- src/env_properties.h | 3 + .../test-whatwg-encoding-custom-interop.js | 4 +- 4 files changed, 376 insertions(+), 213 deletions(-) diff --git a/lib/internal/encoding.js b/lib/internal/encoding.js index 7d4747abc23bb9..4dd08e902eebdf 100644 --- a/lib/internal/encoding.js +++ b/lib/internal/encoding.js @@ -46,23 +46,16 @@ const { const { isAnyArrayBuffer, isArrayBufferView, - isUint8Array, } = require('internal/util/types'); const { - validateString, validateObject, kValidateObjectAllowObjectsAndNull, } = require('internal/validators'); const { hasIntl } = internalBinding('config'); const binding = internalBinding('encoding_binding'); -const { - encodeInto, - encodeIntoResults, - encodeUtf8String, - decodeUTF8, -} = binding; +const { decodeUTF8, TextEncoder } = binding; function validateDecoder(obj) { if (obj == null || obj[kDecoder] !== true) @@ -336,61 +329,6 @@ function getEncodingFromLabel(label) { return encodings.get(trimAsciiWhitespace(label.toLowerCase())); } -let lazyInspect; - -class TextEncoder { - #encoding = 'utf-8'; - - #encode(input) { - return encodeUtf8String(`${input}`); - } - - #encodeInto(input, dest) { - encodeInto(input, dest); - // We need to read from the binding here since the buffer gets refreshed - // from the snapshot. - const { 0: read, 1: written } = encodeIntoResults; - return { read, written }; - } - - get encoding() { - return this.#encoding; - } - - encode(input = '') { - return this.#encode(input); - } - - encodeInto(src, dest) { - validateString(src, 'src'); - if (!dest || !isUint8Array(dest)) - throw new ERR_INVALID_ARG_TYPE('dest', 'Uint8Array', dest); - - return this.#encodeInto(src, dest); - } - - [inspect](depth, opts) { - if (typeof depth === 'number' && depth < 0) - return this; - const ctor = getConstructorOf(this); - const obj = { __proto__: { - constructor: ctor === null ? TextEncoder : ctor, - } }; - obj.encoding = this.#encoding; - // Lazy to avoid circular dependency - lazyInspect ??= require('internal/util/inspect').inspect; - return lazyInspect(obj, opts); - } -} - -ObjectDefineProperties( - TextEncoder.prototype, { - 'encode': kEnumerableProperty, - 'encodeInto': kEnumerableProperty, - 'encoding': kEnumerableProperty, - [SymbolToStringTag]: { __proto__: null, configurable: true, value: 'TextEncoder' }, - }); - function parseInput(input) { if (isAnyArrayBuffer(input)) { try { diff --git a/src/encoding_binding.cc b/src/encoding_binding.cc index c569375383e8d9..771e6c63ec82a9 100644 --- a/src/encoding_binding.cc +++ b/src/encoding_binding.cc @@ -5,7 +5,7 @@ #include "node_external_reference.h" #include "simdutf.h" #include "string_bytes.h" -#include "util.h" +#include "util-inl.h" #include "v8.h" #include @@ -20,13 +20,16 @@ using v8::BackingStoreInitializationMode; using v8::BackingStoreOnFailureMode; using v8::Context; using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; using v8::HandleScope; using v8::Isolate; using v8::Local; +using v8::MaybeLocal; using v8::Object; using v8::ObjectTemplate; using v8::SnapshotCreator; using v8::String; +using v8::Symbol; using v8::Uint8Array; using v8::Value; @@ -81,6 +84,9 @@ InternalFieldInfoBase* BindingData::Serialize(int index) { // https://opensource.org/licenses/Apache-2.0 namespace { constexpr int MAX_SIZE_FOR_STACK_ALLOC = 4096; +constexpr int kEncodeIntoResultInternalFieldCount = 2; +constexpr int kEncodeIntoResultReadField = 0; +constexpr int kEncodeIntoResultWrittenField = 1; constexpr bool isSurrogatePair(uint16_t lead, uint16_t trail) { return (lead & 0xfc00) == 0xd800 && (trail & 0xfc00) == 0xdc00; @@ -92,6 +98,20 @@ constexpr size_t simpleUtfEncodingLength(uint16_t c) { return 3; } +void EncodeIntoResultReadGetter(const FunctionCallbackInfo& args) { + Local self = args.This().As(); + Local value = + self->GetInternalField(kEncodeIntoResultReadField).As(); + args.GetReturnValue().Set(value); +} + +void EncodeIntoResultWrittenGetter(const FunctionCallbackInfo& args) { + Local self = args.This().As(); + Local value = + self->GetInternalField(kEncodeIntoResultWrittenField).As(); + args.GetReturnValue().Set(value); +} + // Finds the maximum number of input characters (UTF-16 or Latin1) that can be // encoded into a UTF-8 buffer of the given size. // @@ -178,60 +198,139 @@ size_t findBestFit(const Char* data, size_t length, size_t bufferSize) { } return pos; } -} // namespace -void BindingData::Deserialize(Local context, - Local holder, - int index, - InternalFieldInfoBase* info) { - DCHECK_IS_SNAPSHOT_SLOT(index); - HandleScope scope(Isolate::GetCurrent()); - Realm* realm = Realm::GetCurrent(context); - // Recreate the buffer in the constructor. - InternalFieldInfo* casted_info = static_cast(info); - BindingData* binding = - realm->AddBindingData(holder, casted_info); - CHECK_NOT_NULL(binding); -} -void BindingData::EncodeInto(const FunctionCallbackInfo& args) { - CHECK_GE(args.Length(), 2); - CHECK(args[0]->IsString()); - CHECK(args[1]->IsUint8Array()); +MaybeLocal EncodeUtf8StringImpl(Isolate* isolate, + Local source) { + // For small strings, use the V8 path + static constexpr int kSmallStringThreshold = 16; + if (source->Length() <= kSmallStringThreshold) { + size_t length = source->Utf8LengthV2(isolate); + std::unique_ptr bs = ArrayBuffer::NewBackingStore( + isolate, + length, + BackingStoreInitializationMode::kUninitialized, + BackingStoreOnFailureMode::kReturnNull); - Realm* realm = Realm::GetCurrent(args); - Isolate* isolate = realm->isolate(); - BindingData* binding_data = realm->GetBindingData(); + if (!bs) [[unlikely]] { + THROW_ERR_MEMORY_ALLOCATION_FAILED(isolate); + return MaybeLocal(); + } - Local source = args[0].As(); + source->WriteUtf8V2(isolate, + static_cast(bs->Data()), + bs->MaxByteLength(), + String::WriteFlags::kReplaceInvalidUtf8); + Local ab = ArrayBuffer::New(isolate, std::move(bs)); + return Uint8Array::New(ab, 0, length); + } - Local dest = args[1].As(); + size_t length = source->Length(); + size_t utf8_length = 0; + + if (source->IsOneByte()) { + bool is_ascii = false; + { + v8::String::ValueView view(isolate, source); + auto data = reinterpret_cast(view.data8()); + simdutf::result result = + simdutf::validate_ascii_with_errors(data, length); + if (result.error == simdutf::SUCCESS) { + is_ascii = true; + } else { + utf8_length = simdutf::utf8_length_from_latin1(data, length); + } + } + + size_t output_length = is_ascii ? length : utf8_length; + std::unique_ptr bs = ArrayBuffer::NewBackingStore( + isolate, output_length, BackingStoreInitializationMode::kUninitialized); + CHECK(bs); + + { + v8::String::ValueView view(isolate, source); + auto data = reinterpret_cast(view.data8()); + if (is_ascii) { + memcpy(bs->Data(), data, length); + } else { + [[maybe_unused]] size_t written = simdutf::convert_latin1_to_utf8( + data, length, static_cast(bs->Data())); + DCHECK_EQ(written, output_length); + } + } + + Local ab = ArrayBuffer::New(isolate, std::move(bs)); + return Uint8Array::New(ab, 0, output_length); + } + + bool needs_well_formed = false; + MaybeStackBuffer conversion_buffer; + { + v8::String::ValueView view(isolate, source); + auto data = reinterpret_cast(view.data16()); + simdutf::result validation_result = + simdutf::validate_utf16_with_errors(data, length); + + if (validation_result.error == simdutf::SUCCESS) { + utf8_length = simdutf::utf8_length_from_utf16(data, length); + } else { + conversion_buffer.AllocateSufficientStorage(length); + conversion_buffer.SetLength(length); + simdutf::to_well_formed_utf16(data, length, conversion_buffer.out()); + utf8_length = + simdutf::utf8_length_from_utf16(conversion_buffer.out(), length); + needs_well_formed = true; + } + } + + std::unique_ptr bs = ArrayBuffer::NewBackingStore( + isolate, utf8_length, BackingStoreInitializationMode::kUninitialized); + CHECK(bs); + + if (needs_well_formed) { + [[maybe_unused]] size_t written = simdutf::convert_utf16_to_utf8( + conversion_buffer.out(), length, static_cast(bs->Data())); + DCHECK_EQ(written, utf8_length); + } else { + v8::String::ValueView view(isolate, source); + auto data = reinterpret_cast(view.data16()); + [[maybe_unused]] size_t written = simdutf::convert_utf16_to_utf8( + data, length, static_cast(bs->Data())); + DCHECK_EQ(written, utf8_length); + } + + Local ab = ArrayBuffer::New(isolate, std::move(bs)); + return Uint8Array::New(ab, 0, utf8_length); +} + +struct EncodeIntoResult { + size_t read = 0; + size_t written = 0; +}; + +EncodeIntoResult EncodeIntoImpl(Isolate* isolate, + Local source, + Local dest) { + EncodeIntoResult result; Local buf = dest->Buffer(); // Handle detached buffers - return {read: 0, written: 0} - if (buf->Data() == nullptr) { - binding_data->encode_into_results_buffer_[0] = 0; - binding_data->encode_into_results_buffer_[1] = 0; - return; - } + if (buf->Data() == nullptr) return result; char* write_result = static_cast(buf->Data()) + dest->ByteOffset(); size_t dest_length = dest->ByteLength(); - size_t read = 0; - size_t written = 0; - // For small strings (length <= 32), use the old V8 path for better + // For small strings (length <= 16), use the old V8 path for better // performance - static constexpr int kSmallStringThreshold = 32; + static constexpr int kSmallStringThreshold = 16; if (source->Length() <= kSmallStringThreshold) { - written = source->WriteUtf8V2(isolate, - write_result, - dest_length, - String::WriteFlags::kReplaceInvalidUtf8, - &read); - binding_data->encode_into_results_buffer_[0] = static_cast(read); - binding_data->encode_into_results_buffer_[1] = static_cast(written); - return; + result.written = source->WriteUtf8V2( + isolate, + write_result, + dest_length, + String::WriteFlags::kReplaceInvalidUtf8, + &result.read); + return result; } v8::String::ValueView view(isolate, source); @@ -240,19 +339,20 @@ void BindingData::EncodeInto(const FunctionCallbackInfo& args) { if (view.is_one_byte()) { auto data = reinterpret_cast(view.data8()); - simdutf::result result = + simdutf::result validation_result = simdutf::validate_ascii_with_errors(data, length_that_fits); - written = read = result.count; - memcpy(write_result, data, read); - write_result += read; - data += read; - length_that_fits -= read; - dest_length -= read; + result.written = result.read = validation_result.count; + memcpy(write_result, data, result.read); + write_result += result.read; + data += result.read; + length_that_fits -= result.read; + dest_length -= result.read; if (length_that_fits != 0 && dest_length != 0) { if (size_t rest = findBestFit(data, length_that_fits, dest_length)) { DCHECK_LE(simdutf::utf8_length_from_latin1(data, rest), dest_length); - written += simdutf::convert_latin1_to_utf8(data, rest, write_result); - read += rest; + result.written += + simdutf::convert_latin1_to_utf8(data, rest, write_result); + result.read += rest; } } } else { @@ -273,10 +373,12 @@ void BindingData::EncodeInto(const FunctionCallbackInfo& args) { if (validation_result.error == simdutf::SUCCESS) { // Valid UTF-16 - use the fast path - read = findBestFit(data, length_that_fits, dest_length); - if (read != 0) { - DCHECK_LE(simdutf::utf8_length_from_utf16(data, read), dest_length); - written = simdutf::convert_utf16_to_utf8(data, read, write_result); + result.read = findBestFit(data, length_that_fits, dest_length); + if (result.read != 0) { + DCHECK_LE(simdutf::utf8_length_from_utf16(data, result.read), + dest_length); + result.written = + simdutf::convert_utf16_to_utf8(data, result.read, write_result); } } else { // Invalid UTF-16 with unpaired surrogates - convert to well-formed first @@ -288,21 +390,53 @@ void BindingData::EncodeInto(const FunctionCallbackInfo& args) { data, length_that_fits, conversion_buffer.out()); // Now use findBestFit with the well-formed data - read = + result.read = findBestFit(conversion_buffer.out(), length_that_fits, dest_length); - if (read != 0) { - DCHECK_LE( - simdutf::utf8_length_from_utf16(conversion_buffer.out(), read), - dest_length); - written = simdutf::convert_utf16_to_utf8( - conversion_buffer.out(), read, write_result); + if (result.read != 0) { + DCHECK_LE(simdutf::utf8_length_from_utf16(conversion_buffer.out(), + result.read), + dest_length); + result.written = simdutf::convert_utf16_to_utf8( + conversion_buffer.out(), result.read, write_result); } } } - DCHECK_LE(written, dest->ByteLength()); - binding_data->encode_into_results_buffer_[0] = static_cast(read); - binding_data->encode_into_results_buffer_[1] = static_cast(written); + DCHECK_LE(result.written, dest->ByteLength()); + return result; +} +} // namespace + +void BindingData::Deserialize(Local context, + Local holder, + int index, + InternalFieldInfoBase* info) { + DCHECK_IS_SNAPSHOT_SLOT(index); + HandleScope scope(Isolate::GetCurrent()); + Realm* realm = Realm::GetCurrent(context); + // Recreate the buffer in the constructor. + InternalFieldInfo* casted_info = static_cast(info); + BindingData* binding = + realm->AddBindingData(holder, casted_info); + CHECK_NOT_NULL(binding); +} + +void BindingData::EncodeInto(const FunctionCallbackInfo& args) { + CHECK_GE(args.Length(), 2); + CHECK(args[0]->IsString()); + CHECK(args[1]->IsUint8Array()); + + Realm* realm = Realm::GetCurrent(args); + BindingData* binding_data = realm->GetBindingData(); + + Local source = args[0].As(); + Local dest = args[1].As(); + + EncodeIntoResult result = EncodeIntoImpl(realm->isolate(), source, dest); + binding_data->encode_into_results_buffer_[0] = + static_cast(result.read); + binding_data->encode_into_results_buffer_[1] = + static_cast(result.written); } // Encode a single string to a UTF-8 Uint8Array (not Buffer). @@ -313,104 +447,121 @@ void BindingData::EncodeUtf8String(const FunctionCallbackInfo& args) { CHECK(args[0]->IsString()); Local source = args[0].As(); + MaybeLocal result = EncodeUtf8StringImpl(isolate, source); + if (!result.IsEmpty()) { + args.GetReturnValue().Set(result.ToLocalChecked()); + } +} - // For small strings, use the V8 path - static constexpr int kSmallStringThreshold = 32; - if (source->Length() <= kSmallStringThreshold) { - size_t length = source->Utf8LengthV2(isolate); - std::unique_ptr bs = ArrayBuffer::NewBackingStore( - isolate, - length, - BackingStoreInitializationMode::kUninitialized, - BackingStoreOnFailureMode::kReturnNull); +void TextEncoderConstructor(const FunctionCallbackInfo& args) { + if (!args.IsConstructCall()) { + Isolate* isolate = args.GetIsolate(); + isolate->ThrowException(v8::Exception::TypeError( + OneByteString(isolate, + "Class constructor TextEncoder cannot be invoked without " + "'new'"))); + return; + } +} - if (!bs) [[unlikely]] { - THROW_ERR_MEMORY_ALLOCATION_FAILED(isolate); - return; - } +void TextEncoderEncode(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); - source->WriteUtf8V2(isolate, - static_cast(bs->Data()), - bs->MaxByteLength(), - String::WriteFlags::kReplaceInvalidUtf8); - Local ab = ArrayBuffer::New(isolate, std::move(bs)); - args.GetReturnValue().Set(Uint8Array::New(ab, 0, length)); - return; + Local context = isolate->GetCurrentContext(); + Local input; + if (args.Length() < 1 || args[0]->IsUndefined()) { + input = String::Empty(isolate); + } else { + if (!args[0]->ToString(context).ToLocal(&input)) return; } - size_t length = source->Length(); - size_t utf8_length = 0; - bool is_one_byte = source->IsOneByte(); - - if (is_one_byte) { - // One-byte string (Latin1) - copy to buffer first, then process - MaybeStackBuffer latin1_buffer(length); - source->WriteOneByteV2(isolate, 0, length, latin1_buffer.out()); - - auto data = reinterpret_cast(latin1_buffer.out()); - - // Check if it's pure ASCII - if so, we can just copy - simdutf::result result = simdutf::validate_ascii_with_errors(data, length); - if (result.error == simdutf::SUCCESS) { - // Pure ASCII - direct copy - std::unique_ptr bs = ArrayBuffer::NewBackingStore( - isolate, length, BackingStoreInitializationMode::kUninitialized); - CHECK(bs); - memcpy(bs->Data(), data, length); - Local ab = ArrayBuffer::New(isolate, std::move(bs)); - args.GetReturnValue().Set(Uint8Array::New(ab, 0, length)); - return; - } + MaybeLocal result = EncodeUtf8StringImpl(isolate, input); + if (!result.IsEmpty()) { + args.GetReturnValue().Set(result.ToLocalChecked()); + } +} - // Latin1 with non-ASCII characters - need conversion - utf8_length = simdutf::utf8_length_from_latin1(data, length); - std::unique_ptr bs = ArrayBuffer::NewBackingStore( - isolate, utf8_length, BackingStoreInitializationMode::kUninitialized); - CHECK(bs); - [[maybe_unused]] size_t written = simdutf::convert_latin1_to_utf8( - data, length, static_cast(bs->Data())); - DCHECK_EQ(written, utf8_length); - Local ab = ArrayBuffer::New(isolate, std::move(bs)); - args.GetReturnValue().Set(Uint8Array::New(ab, 0, utf8_length)); +void TextEncoderEncodeInto(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = args.GetIsolate(); + if (args.Length() < 2 || !args[0]->IsString() || + args[0]->IsStringObject()) { + THROW_ERR_INVALID_ARG_TYPE(env, + "The \"src\" argument must be of type string"); return; } - // Two-byte string (UTF-16) - copy to buffer first - MaybeStackBuffer utf16_buffer(length); - source->WriteV2(isolate, 0, length, utf16_buffer.out()); - - auto data = reinterpret_cast(utf16_buffer.out()); + if (!args[1]->IsUint8Array()) { + THROW_ERR_INVALID_ARG_TYPE( + env, + "The \"dest\" argument must be an instance of Uint8Array"); + return; + } - // Check for unpaired surrogates - simdutf::result validation_result = - simdutf::validate_utf16_with_errors(data, length); + Local source = args[0].As(); + Local dest = args[1].As(); - if (validation_result.error == simdutf::SUCCESS) { - // Valid UTF-16 - use the fast path - utf8_length = simdutf::utf8_length_from_utf16(data, length); - std::unique_ptr bs = ArrayBuffer::NewBackingStore( - isolate, utf8_length, BackingStoreInitializationMode::kUninitialized); - CHECK(bs); - [[maybe_unused]] size_t written = simdutf::convert_utf16_to_utf8( - data, length, static_cast(bs->Data())); - DCHECK_EQ(written, utf8_length); - Local ab = ArrayBuffer::New(isolate, std::move(bs)); - args.GetReturnValue().Set(Uint8Array::New(ab, 0, utf8_length)); - return; + EncodeIntoResult result = EncodeIntoImpl(isolate, source, dest); + + // Use a cached ObjectTemplate so every result shares the same hidden class + // (map). This gives V8 fast-properties objects with inline storage, unlike + // DictionaryTemplate which creates slow dictionary-mode objects. + Local context = env->context(); + auto result_template = env->text_encoder_encode_into_result_template(); + if (result_template.IsEmpty()) { + result_template = ObjectTemplate::New(isolate); + result_template->SetInternalFieldCount(kEncodeIntoResultInternalFieldCount); + Local read_getter = + FunctionTemplate::New(isolate, EncodeIntoResultReadGetter); + Local written_getter = + FunctionTemplate::New(isolate, EncodeIntoResultWrittenGetter); + read_getter->SetLength(0); + written_getter->SetLength(0); + result_template->SetAccessorProperty( + env->read_string(), + read_getter, + Local(), + v8::PropertyAttribute::None); + result_template->SetAccessorProperty( + env->written_string(), + written_getter, + Local(), + v8::PropertyAttribute::None); + env->set_text_encoder_encode_into_result_template(result_template); } - // Invalid UTF-16 with unpaired surrogates - convert to well-formed in place - simdutf::to_well_formed_utf16(data, length, data); + Local out; + if (!result_template->NewInstance(context).ToLocal(&out)) return; + out->SetInternalField( + kEncodeIntoResultReadField, + v8::Integer::NewFromUnsigned(isolate, + static_cast(result.read))); + out->SetInternalField( + kEncodeIntoResultWrittenField, + v8::Integer::NewFromUnsigned(isolate, + static_cast(result.written))); + args.GetReturnValue().Set(out); +} - utf8_length = simdutf::utf8_length_from_utf16(data, length); - std::unique_ptr bs = ArrayBuffer::NewBackingStore( - isolate, utf8_length, BackingStoreInitializationMode::kUninitialized); - CHECK(bs); - [[maybe_unused]] size_t written = simdutf::convert_utf16_to_utf8( - data, length, static_cast(bs->Data())); - DCHECK_EQ(written, utf8_length); - Local ab = ArrayBuffer::New(isolate, std::move(bs)); - args.GetReturnValue().Set(Uint8Array::New(ab, 0, utf8_length)); +void TextEncoderEncodingAccessor(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(isolate, "utf-8")); +} + +void TextEncoderInspect(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + if (args.Length() > 0 && args[0]->IsNumber()) { + double depth = args[0].As()->Value(); + if (depth < 0) { + args.GetReturnValue().Set(args.This()); + return; + } + } + + args.GetReturnValue().Set( + OneByteString(isolate, "TextEncoder { encoding: 'utf-8' }")); } // Convert the input into an encoded string @@ -505,6 +656,72 @@ void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, SetMethodNoSideEffect(isolate, target, "decodeUTF8", DecodeUTF8); SetMethodNoSideEffect(isolate, target, "toASCII", ToASCII); SetMethodNoSideEffect(isolate, target, "toUnicode", ToUnicode); + + Local text_encoder = + NewFunctionTemplate(isolate, TextEncoderConstructor); + text_encoder->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "TextEncoder")); + Local signature = v8::Signature::New(isolate, text_encoder); + + Local encode = + NewFunctionTemplate(isolate, + TextEncoderEncode, + signature, + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasSideEffect); + encode->SetLength(0); + Local encode_name = FIXED_ONE_BYTE_STRING(isolate, "encode"); + text_encoder->PrototypeTemplate()->Set(encode_name, encode); + encode->SetClassName(encode_name); + + Local encode_into = + NewFunctionTemplate(isolate, + TextEncoderEncodeInto, + signature, + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasSideEffect); + encode_into->SetLength(2); + Local encode_into_name = + FIXED_ONE_BYTE_STRING(isolate, "encodeInto"); + text_encoder->PrototypeTemplate()->Set(encode_into_name, encode_into); + encode_into->SetClassName(encode_into_name); + + Local encoding_getter = + NewFunctionTemplate(isolate, + TextEncoderEncodingAccessor, + signature, + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasNoSideEffect); + encoding_getter->SetClassName( + FIXED_ONE_BYTE_STRING(isolate, "get encoding")); + encoding_getter->SetLength(0); + text_encoder->PrototypeTemplate()->SetAccessorProperty( + FIXED_ONE_BYTE_STRING(isolate, "encoding"), + encoding_getter, + Local(), + v8::PropertyAttribute::None); + + Local inspect = + NewFunctionTemplate(isolate, + TextEncoderInspect, + signature, + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasNoSideEffect); + inspect->SetLength(2); + Local inspect_symbol = + Symbol::For(isolate, + FIXED_ONE_BYTE_STRING(isolate, + "nodejs.util.inspect.custom")); + text_encoder->PrototypeTemplate()->Set( + inspect_symbol, inspect, v8::PropertyAttribute::DontEnum); + + text_encoder->PrototypeTemplate()->Set( + Symbol::GetToStringTag(isolate), + FIXED_ONE_BYTE_STRING(isolate, "TextEncoder"), + static_cast( + v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontEnum)); + + text_encoder->ReadOnlyPrototype(); + SetConstructorFunction(isolate, target, "TextEncoder", text_encoder); } void BindingData::CreatePerContextProperties(Local target, @@ -520,6 +737,11 @@ void BindingData::RegisterTimerExternalReferences( registry->Register(EncodeInto); registry->Register(EncodeUtf8String); registry->Register(DecodeUTF8); + registry->Register(TextEncoderConstructor); + registry->Register(TextEncoderEncode); + registry->Register(TextEncoderEncodeInto); + registry->Register(TextEncoderEncodingAccessor); + registry->Register(TextEncoderInspect); registry->Register(ToASCII); registry->Register(ToUnicode); } diff --git a/src/env_properties.h b/src/env_properties.h index 77b694e98463e8..28419956f4d0c1 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -305,6 +305,7 @@ V(psk_string, "psk") \ V(public_exponent_string, "publicExponent") \ V(rate_string, "rate") \ + V(read_string, "read") \ V(read_host_object_string, "_readHostObject") \ V(readable_string, "readable") \ V(read_bigints_string, "readBigInts") \ @@ -374,6 +375,7 @@ V(windows_verbatim_arguments_string, "windowsVerbatimArguments") \ V(wrap_string, "wrap") \ V(writable_string, "writable") \ + V(written_string, "written") \ V(write_host_object_string, "_writeHostObject") \ V(write_queue_size_string, "writeQueueSize") @@ -442,6 +444,7 @@ V(srv_record_template, v8::DictionaryTemplate) \ V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \ V(tcp_constructor_template, v8::FunctionTemplate) \ + V(text_encoder_encode_into_result_template, v8::ObjectTemplate) \ V(tlsa_record_template, v8::DictionaryTemplate) \ V(tty_constructor_template, v8::FunctionTemplate) \ V(txt_record_template, v8::DictionaryTemplate) \ diff --git a/test/parallel/test-whatwg-encoding-custom-interop.js b/test/parallel/test-whatwg-encoding-custom-interop.js index 65d47d0c4c2cbd..6ca8720c7d1be6 100644 --- a/test/parallel/test-whatwg-encoding-custom-interop.js +++ b/test/parallel/test-whatwg-encoding-custom-interop.js @@ -47,7 +47,7 @@ assert(TextEncoder); const expectedError = { name: 'TypeError', - message: /from an object whose class did not declare it/, + message: /Illegal invocation/, }; inspectFn.call(instance, Infinity, {}); @@ -62,7 +62,7 @@ assert(TextEncoder); for (const i of invalidThisArgs) { assert.throws(() => encodeFn.call(i), { name: 'TypeError', - message: 'Receiver must be an instance of class TextEncoder', + message: /Illegal invocation/, }); } }