LibJS: Use Utf16StringBuilder for JSON and Temporal strings

Build JSON.stringify, Date ISO strings, and Temporal string results with
Utf16StringBuilder when the result is consumed as a JavaScript string.
Keep UTF-8 conversion only at callers that explicitly need bytes outside
LibJS.
This commit is contained in:
Andreas Kling 2026-06-22 13:06:30 +02:00 committed by Andreas Kling
parent 7c86352f34
commit bcf7f27a4f
12 changed files with 98 additions and 90 deletions

View file

@ -7,8 +7,8 @@
#include <AK/NeverDestroyed.h> #include <AK/NeverDestroyed.h>
#include <AK/NumericLimits.h> #include <AK/NumericLimits.h>
#include <AK/StringBuilder.h>
#include <AK/Time.h> #include <AK/Time.h>
#include <AK/Utf16StringBuilder.h>
#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Date.h> #include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
@ -36,30 +36,30 @@ Date::Date(double date_value, Object& prototype)
Date::~Date() = default; Date::~Date() = default;
ErrorOr<String> Date::iso_date_string() const ErrorOr<Utf16String> Date::iso_date_string() const
{ {
int year = year_from_time(m_date_value); int year = year_from_time(m_date_value);
StringBuilder builder; Utf16StringBuilder builder;
if (year < 0) if (year < 0)
builder.appendff("-{:06}", -year); builder.appendff("-{:06}", -year);
else if (year > 9999) else if (year > 9999)
builder.appendff("+{:06}", year); builder.appendff("+{:06}", year);
else else
builder.appendff("{:04}", year); builder.appendff("{:04}", year);
builder.append('-'); builder.append_ascii('-');
builder.appendff("{:02}", month_from_time(m_date_value) + 1); builder.appendff("{:02}", month_from_time(m_date_value) + 1);
builder.append('-'); builder.append_ascii('-');
builder.appendff("{:02}", date_from_time(m_date_value)); builder.appendff("{:02}", date_from_time(m_date_value));
builder.append('T'); builder.append_ascii('T');
builder.appendff("{:02}", hour_from_time(m_date_value)); builder.appendff("{:02}", hour_from_time(m_date_value));
builder.append(':'); builder.append_ascii(':');
builder.appendff("{:02}", min_from_time(m_date_value)); builder.appendff("{:02}", min_from_time(m_date_value));
builder.append(':'); builder.append_ascii(':');
builder.appendff("{:02}", sec_from_time(m_date_value)); builder.appendff("{:02}", sec_from_time(m_date_value));
builder.append('.'); builder.append_ascii('.');
builder.appendff("{:03}", ms_from_time(m_date_value)); builder.appendff("{:03}", ms_from_time(m_date_value));
builder.append('Z'); builder.append_ascii('Z');
return builder.to_string(); return builder.to_string();
} }

View file

@ -27,7 +27,7 @@ public:
double date_value() const { return m_date_value; } double date_value() const { return m_date_value; }
void set_date_value(double value) { m_date_value = value; } void set_date_value(double value) { m_date_value = value; }
ErrorOr<String> iso_date_string() const; ErrorOr<Utf16String> iso_date_string() const;
private: private:
Date(double date_value, Object& prototype); Date(double date_value, Object& prototype);

View file

@ -6,7 +6,6 @@
#include <AK/Function.h> #include <AK/Function.h>
#include <AK/GenericLexer.h> #include <AK/GenericLexer.h>
#include <AK/StringBuilder.h>
#include <AK/StringConversions.h> #include <AK/StringConversions.h>
#include <AK/TypeCasts.h> #include <AK/TypeCasts.h>
#include <AK/Utf16StringBuilder.h> #include <AK/Utf16StringBuilder.h>
@ -52,7 +51,7 @@ void JSONObject::initialize(Realm& realm)
} }
// 25.5.2 JSON.stringify ( value [ , replacer [ , space ] ] ), https://tc39.es/ecma262/#sec-json.stringify // 25.5.2 JSON.stringify ( value [ , replacer [ , space ] ] ), https://tc39.es/ecma262/#sec-json.stringify
ThrowCompletionOr<Optional<String>> JSONObject::stringify_impl(VM& vm, Value value, Value replacer, Value space) ThrowCompletionOr<Optional<Utf16String>> JSONObject::stringify_impl(VM& vm, Value value, Value replacer, Value space)
{ {
auto& realm = *vm.current_realm(); auto& realm = *vm.current_realm();
@ -99,15 +98,15 @@ ThrowCompletionOr<Optional<String>> JSONObject::stringify_impl(VM& vm, Value val
if (space.is_number()) { if (space.is_number()) {
auto space_mv = MUST(space.to_integer_or_infinity(vm)); auto space_mv = MUST(space.to_integer_or_infinity(vm));
space_mv = min(10, space_mv); space_mv = min(10, space_mv);
state.gap = space_mv < 1 ? String {} : MUST(String::repeated(' ', space_mv)); state.gap = space_mv < 1 ? Utf16String {} : Utf16String::repeated(' ', space_mv);
} else if (space.is_string()) { } else if (space.is_string()) {
auto string = space.as_string().utf8_string(); auto string = space.as_string().utf16_string();
if (string.bytes().size() <= 10) if (string.length_in_code_units() <= 10)
state.gap = string; state.gap = string;
else else
state.gap = MUST(string.substring_from_byte_offset(0, 10)); state.gap = Utf16String::from_utf16(string.utf16_view().substring_view(0, 10));
} else { } else {
state.gap = String {}; state.gap = Utf16String {};
} }
auto wrapper = Object::create(realm, realm.intrinsics().object_prototype()); auto wrapper = Object::create(realm, realm.intrinsics().object_prototype());
@ -115,9 +114,9 @@ ThrowCompletionOr<Optional<String>> JSONObject::stringify_impl(VM& vm, Value val
bool wrote_value = TRY(serialize_json_property(vm, state, Utf16String {}, wrapper)); bool wrote_value = TRY(serialize_json_property(vm, state, Utf16String {}, wrapper));
if (!wrote_value) if (!wrote_value)
return Optional<String> {}; return Optional<Utf16String> {};
return state.builder.to_string_without_validation(); return state.builder.to_string();
} }
// 25.5.2 JSON.stringify ( value [ , replacer [ , space ] ] ), https://tc39.es/ecma262/#sec-json.stringify // 25.5.2 JSON.stringify ( value [ , replacer [ , space ] ] ), https://tc39.es/ecma262/#sec-json.stringify
@ -172,7 +171,7 @@ ThrowCompletionOr<bool> JSONObject::serialize_json_property(VM& vm, StringifySta
// a. If value has an [[IsRawJSON]] internal slot, then // a. If value has an [[IsRawJSON]] internal slot, then
if (is<RawJSONObject>(value_object)) { if (is<RawJSONObject>(value_object)) {
// i. Return ! Get(value, "rawJSON"). // i. Return ! Get(value, "rawJSON").
builder.append(MUST(value_object.get(vm.names.rawJSON)).as_string().utf8_string()); builder.append(MUST(value_object.get(vm.names.rawJSON)).as_string().utf16_string_view());
return true; return true;
} }
// b. If value has a [[NumberData]] internal slot, then // b. If value has a [[NumberData]] internal slot, then
@ -199,14 +198,17 @@ ThrowCompletionOr<bool> JSONObject::serialize_json_property(VM& vm, StringifySta
// 5. If value is null, return "null". // 5. If value is null, return "null".
if (value.is_null()) { if (value.is_null()) {
builder.append("null"sv); builder.append_ascii("null"sv);
return true; return true;
} }
// 6. If value is true, return "true". // 6. If value is true, return "true".
// 7. If value is false, return "false". // 7. If value is false, return "false".
if (value.is_boolean()) { if (value.is_boolean()) {
builder.append(value.as_bool() ? "true"sv : "false"sv); if (value.as_bool())
builder.append_ascii("true"sv);
else
builder.append_ascii("false"sv);
return true; return true;
} }
@ -220,12 +222,12 @@ ThrowCompletionOr<bool> JSONObject::serialize_json_property(VM& vm, StringifySta
if (value.is_number()) { if (value.is_number()) {
// a. If value is finite, return ! ToString(value). // a. If value is finite, return ! ToString(value).
if (value.is_finite_number()) { if (value.is_finite_number()) {
number_to_string(builder, value.as_double()); builder.append(number_to_utf16_string(value.as_double()));
return true; return true;
} }
// b. Return "null". // b. Return "null".
builder.append("null"sv); builder.append_ascii("null"sv);
return true; return true;
} }
@ -253,10 +255,10 @@ ThrowCompletionOr<bool> JSONObject::serialize_json_property(VM& vm, StringifySta
return false; return false;
} }
static void write_indent(StringBuilder& builder, StringView gap, size_t depth) static void write_indent(Utf16StringBuilder& builder, Utf16String const& gap, size_t depth)
{ {
for (size_t i = 0; i < depth; ++i) for (size_t i = 0; i < depth; ++i)
builder.append(gap); builder.append(gap.utf16_view());
} }
// 25.5.2.4 SerializeJSONObject ( state, value ), https://tc39.es/ecma262/#sec-serializejsonobject // 25.5.2.4 SerializeJSONObject ( state, value ), https://tc39.es/ecma262/#sec-serializejsonobject
@ -272,8 +274,8 @@ ThrowCompletionOr<void> JSONObject::serialize_json_object(VM& vm, StringifyState
++state.indent_depth; ++state.indent_depth;
auto& builder = state.builder; auto& builder = state.builder;
builder.append('{'); builder.append_ascii('{');
size_t position_after_open_brace = builder.length(); size_t position_after_open_brace = builder.length_in_code_units();
bool first = true; bool first = true;
auto process_property = [&](PropertyKey const& key) -> ThrowCompletionOr<void> { auto process_property = [&](PropertyKey const& key) -> ThrowCompletionOr<void> {
@ -281,25 +283,25 @@ ThrowCompletionOr<void> JSONObject::serialize_json_object(VM& vm, StringifyState
return {}; return {};
// Mark position before writing anything for this property // Mark position before writing anything for this property
size_t mark = builder.length(); size_t mark = builder.length_in_code_units();
// Write separator (comma and possibly newline/indent) // Write separator (comma and possibly newline/indent)
if (!first) { if (!first) {
builder.append(','); builder.append_ascii(',');
if (!state.gap.is_empty()) { if (!state.gap.is_empty()) {
builder.append('\n'); builder.append_ascii('\n');
write_indent(builder, state.gap, state.indent_depth); write_indent(builder, state.gap, state.indent_depth);
} }
} else if (!state.gap.is_empty()) { } else if (!state.gap.is_empty()) {
builder.append('\n'); builder.append_ascii('\n');
write_indent(builder, state.gap, state.indent_depth); write_indent(builder, state.gap, state.indent_depth);
} }
// Write key and colon // Write key and colon
quote_json_string(builder, key.to_string()); quote_json_string(builder, key.to_string());
builder.append(':'); builder.append_ascii(':');
if (!state.gap.is_empty()) if (!state.gap.is_empty())
builder.append(' '); builder.append_ascii(' ');
// Serialize value // Serialize value
bool wrote_value = TRY(serialize_json_property(vm, state, key, &object)); bool wrote_value = TRY(serialize_json_property(vm, state, key, &object));
@ -308,7 +310,7 @@ ThrowCompletionOr<void> JSONObject::serialize_json_object(VM& vm, StringifyState
first = false; first = false;
} else { } else {
// Rollback - value was undefined, remove everything we wrote for this property // Rollback - value was undefined, remove everything we wrote for this property
builder.trim(builder.length() - mark); builder.trim(builder.length_in_code_units() - mark);
} }
return {}; return {};
}; };
@ -325,11 +327,11 @@ ThrowCompletionOr<void> JSONObject::serialize_json_object(VM& vm, StringifyState
// Close the object // Close the object
--state.indent_depth; --state.indent_depth;
if (builder.length() > position_after_open_brace && !state.gap.is_empty()) { if (builder.length_in_code_units() > position_after_open_brace && !state.gap.is_empty()) {
builder.append('\n'); builder.append_ascii('\n');
write_indent(builder, state.gap, state.indent_depth); write_indent(builder, state.gap, state.indent_depth);
} }
builder.append('}'); builder.append_ascii('}');
state.seen_objects.remove(&object); state.seen_objects.remove(&object);
return {}; return {};
@ -350,44 +352,44 @@ ThrowCompletionOr<void> JSONObject::serialize_json_array(VM& vm, StringifyState&
auto& builder = state.builder; auto& builder = state.builder;
auto length = TRY(length_of_array_like(vm, object)); auto length = TRY(length_of_array_like(vm, object));
builder.append('['); builder.append_ascii('[');
for (size_t i = 0; i < length; ++i) { for (size_t i = 0; i < length; ++i) {
// Write separator // Write separator
if (i > 0) { if (i > 0) {
builder.append(','); builder.append_ascii(',');
if (!state.gap.is_empty()) { if (!state.gap.is_empty()) {
builder.append('\n'); builder.append_ascii('\n');
write_indent(builder, state.gap, state.indent_depth); write_indent(builder, state.gap, state.indent_depth);
} }
} else if (!state.gap.is_empty()) { } else if (!state.gap.is_empty()) {
builder.append('\n'); builder.append_ascii('\n');
write_indent(builder, state.gap, state.indent_depth); write_indent(builder, state.gap, state.indent_depth);
} }
// Serialize value (undefined becomes null for arrays) // Serialize value (undefined becomes null for arrays)
bool wrote_value = TRY(serialize_json_property(vm, state, i, &object)); bool wrote_value = TRY(serialize_json_property(vm, state, i, &object));
if (!wrote_value) if (!wrote_value)
builder.append("null"sv); builder.append_ascii("null"sv);
} }
// Close the array // Close the array
--state.indent_depth; --state.indent_depth;
if (length > 0 && !state.gap.is_empty()) { if (length > 0 && !state.gap.is_empty()) {
builder.append('\n'); builder.append_ascii('\n');
write_indent(builder, state.gap, state.indent_depth); write_indent(builder, state.gap, state.indent_depth);
} }
builder.append(']'); builder.append_ascii(']');
state.seen_objects.remove(&object); state.seen_objects.remove(&object);
return {}; return {};
} }
// 25.5.2.2 QuoteJSONString ( value ), https://tc39.es/ecma262/#sec-quotejsonstring // 25.5.2.2 QuoteJSONString ( value ), https://tc39.es/ecma262/#sec-quotejsonstring
void JSONObject::quote_json_string(StringBuilder& builder, Utf16View const& string) void JSONObject::quote_json_string(Utf16StringBuilder& builder, Utf16View const& string)
{ {
// 1. Let product be the String value consisting solely of the code unit 0x0022 (QUOTATION MARK). // 1. Let product be the String value consisting solely of the code unit 0x0022 (QUOTATION MARK).
builder.append('"'); builder.append_ascii('"');
// 2. For each code point C of StringToCodePoints(value), do // 2. For each code point C of StringToCodePoints(value), do
for (auto code_point : string) { for (auto code_point : string) {
@ -395,25 +397,25 @@ void JSONObject::quote_json_string(StringBuilder& builder, Utf16View const& stri
// i. Set product to the string-concatenation of product and the escape sequence for C as specified in the "Escape Sequence" column of the corresponding row. // i. Set product to the string-concatenation of product and the escape sequence for C as specified in the "Escape Sequence" column of the corresponding row.
switch (code_point) { switch (code_point) {
case '\b': case '\b':
builder.append("\\b"sv); builder.append_ascii("\\b"sv);
break; break;
case '\t': case '\t':
builder.append("\\t"sv); builder.append_ascii("\\t"sv);
break; break;
case '\n': case '\n':
builder.append("\\n"sv); builder.append_ascii("\\n"sv);
break; break;
case '\f': case '\f':
builder.append("\\f"sv); builder.append_ascii("\\f"sv);
break; break;
case '\r': case '\r':
builder.append("\\r"sv); builder.append_ascii("\\r"sv);
break; break;
case '"': case '"':
builder.append("\\\""sv); builder.append_ascii("\\\""sv);
break; break;
case '\\': case '\\':
builder.append("\\\\"sv); builder.append_ascii("\\\\"sv);
break; break;
default: default:
// b. Else if C has a numeric value less than 0x0020 (SPACE), or if C has the same numeric value as a leading surrogate or trailing surrogate, then // b. Else if C has a numeric value less than 0x0020 (SPACE), or if C has the same numeric value as a leading surrogate or trailing surrogate, then
@ -431,7 +433,7 @@ void JSONObject::quote_json_string(StringBuilder& builder, Utf16View const& stri
} }
// 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK). // 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK).
builder.append('"'); builder.append_ascii('"');
} }
// 25.5.1 JSON.parse ( text [ , reviver ] ), https://tc39.es/ecma262/#sec-json.parse // 25.5.1 JSON.parse ( text [ , reviver ] ), https://tc39.es/ecma262/#sec-json.parse

View file

@ -6,6 +6,7 @@
#pragma once #pragma once
#include <AK/Utf16StringBuilder.h>
#include <LibJS/Export.h> #include <LibJS/Export.h>
#include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/Object.h>
@ -38,7 +39,7 @@ public:
// The base implementation of stringify is exposed because it is used by // The base implementation of stringify is exposed because it is used by
// test-js to communicate between the JS tests and the C++ test runner. // test-js to communicate between the JS tests and the C++ test runner.
static ThrowCompletionOr<Optional<String>> stringify_impl(VM&, Value value, Value replacer, Value space); static ThrowCompletionOr<Optional<Utf16String>> stringify_impl(VM&, Value value, Value replacer, Value space);
static ThrowCompletionOr<Value> parse_json(VM&, StringView text, JSONParseRecord* root_record = nullptr); static ThrowCompletionOr<Value> parse_json(VM&, StringView text, JSONParseRecord* root_record = nullptr);
@ -49,16 +50,16 @@ private:
GC::Ptr<FunctionObject> replacer_function; GC::Ptr<FunctionObject> replacer_function;
HashTable<GC::Ptr<Object>> seen_objects; HashTable<GC::Ptr<Object>> seen_objects;
size_t indent_depth { 0 }; size_t indent_depth { 0 };
String gap; Utf16String gap;
Optional<Vector<Utf16String>> property_list; Optional<Vector<Utf16String>> property_list;
StringBuilder builder; Utf16StringBuilder builder;
}; };
// Stringify helpers // Stringify helpers
static ThrowCompletionOr<bool> serialize_json_property(VM&, StringifyState&, PropertyKey const& key, Object* holder); static ThrowCompletionOr<bool> serialize_json_property(VM&, StringifyState&, PropertyKey const& key, Object* holder);
static ThrowCompletionOr<void> serialize_json_object(VM&, StringifyState&, Object&); static ThrowCompletionOr<void> serialize_json_object(VM&, StringifyState&, Object&);
static ThrowCompletionOr<void> serialize_json_array(VM&, StringifyState&, Object&); static ThrowCompletionOr<void> serialize_json_array(VM&, StringifyState&, Object&);
static void quote_json_string(StringBuilder&, Utf16View const&); static void quote_json_string(Utf16StringBuilder&, Utf16View const&);
// Parse helpers // Parse helpers
static ThrowCompletionOr<Value> internalize_json_property(VM&, Object* holder, PropertyKey const& name, FunctionObject& reviver, JSONParseRecord const* parse_record); static ThrowCompletionOr<Value> internalize_json_property(VM&, Object* holder, PropertyKey const& name, FunctionObject& reviver, JSONParseRecord const* parse_record);

View file

@ -8,8 +8,8 @@
*/ */
#include <AK/NeverDestroyed.h> #include <AK/NeverDestroyed.h>
#include <AK/StringBuilder.h>
#include <AK/Utf16String.h> #include <AK/Utf16String.h>
#include <AK/Utf16StringBuilder.h>
#include <LibCrypto/BigFraction/BigFraction.h> #include <LibCrypto/BigFraction/BigFraction.h>
#include <LibJS/Runtime/Date.h> #include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/PropertyKey.h> #include <LibJS/Runtime/PropertyKey.h>
@ -757,44 +757,44 @@ ThrowCompletionOr<bool> is_partial_temporal_object(VM& vm, Value value)
} }
// 13.25 FormatFractionalSeconds ( subSecondNanoseconds, precision ), https://tc39.es/proposal-temporal/#sec-temporal-formatfractionalseconds // 13.25 FormatFractionalSeconds ( subSecondNanoseconds, precision ), https://tc39.es/proposal-temporal/#sec-temporal-formatfractionalseconds
String format_fractional_seconds(u64 sub_second_nanoseconds, Precision precision) Utf16String format_fractional_seconds(u64 sub_second_nanoseconds, Precision precision)
{ {
String fraction_string; Utf16String fraction_string;
// 1. If precision is auto, then // 1. If precision is auto, then
if (precision.has<Auto>()) { if (precision.has<Auto>()) {
// a. If subSecondNanoseconds = 0, return the empty String. // a. If subSecondNanoseconds = 0, return the empty String.
if (sub_second_nanoseconds == 0) if (sub_second_nanoseconds == 0)
return String {}; return Utf16String {};
// b. Let fractionString be ToZeroPaddedDecimalString(subSecondNanoseconds, 9). // b. Let fractionString be ToZeroPaddedDecimalString(subSecondNanoseconds, 9).
fraction_string = MUST(String::formatted("{:09}", sub_second_nanoseconds)); fraction_string = Utf16String::formatted("{:09}", sub_second_nanoseconds);
// c. Set fractionString to the longest prefix of fractionString ending with a code unit other than 0x0030 (DIGIT ZERO). // c. Set fractionString to the longest prefix of fractionString ending with a code unit other than 0x0030 (DIGIT ZERO).
fraction_string = MUST(fraction_string.trim("0"sv, TrimMode::Right)); fraction_string = Utf16String::from_utf16(fraction_string.utf16_view().trim("0"sv, TrimMode::Right));
} }
// 2. Else, // 2. Else,
else { else {
// a. If precision = 0, return the empty String. // a. If precision = 0, return the empty String.
if (precision.get<u8>() == 0) if (precision.get<u8>() == 0)
return String {}; return Utf16String {};
// b. Let fractionString be ToZeroPaddedDecimalString(subSecondNanoseconds, 9). // b. Let fractionString be ToZeroPaddedDecimalString(subSecondNanoseconds, 9).
fraction_string = MUST(String::formatted("{:09}", sub_second_nanoseconds)); fraction_string = Utf16String::formatted("{:09}", sub_second_nanoseconds);
// c. Set fractionString to the substring of fractionString from 0 to precision. // c. Set fractionString to the substring of fractionString from 0 to precision.
fraction_string = MUST(fraction_string.substring_from_byte_offset(0, precision.get<u8>())); fraction_string = Utf16String::from_utf16(fraction_string.utf16_view().substring_view(0, precision.get<u8>()));
} }
// 3. Return the string-concatenation of the code unit 0x002E (FULL STOP) and fractionString. // 3. Return the string-concatenation of the code unit 0x002E (FULL STOP) and fractionString.
StringBuilder builder; Utf16StringBuilder builder;
builder.append('.'); builder.append_ascii('.');
builder.append(fraction_string); builder.append(fraction_string.utf16_view());
return MUST(builder.to_string()); return builder.to_string();
} }
// 13.26 FormatTimeString ( hour, minute, second, subSecondNanoseconds, precision [ , style ] ), https://tc39.es/proposal-temporal/#sec-temporal-formattimestring // 13.26 FormatTimeString ( hour, minute, second, subSecondNanoseconds, precision [ , style ] ), https://tc39.es/proposal-temporal/#sec-temporal-formattimestring
String format_time_string(u8 hour, u8 minute, u8 second, u64 sub_second_nanoseconds, SecondsStringPrecision::Precision precision, Optional<TimeStyle> style) Utf16String format_time_string(u8 hour, u8 minute, u8 second, u64 sub_second_nanoseconds, SecondsStringPrecision::Precision precision, Optional<TimeStyle> style)
{ {
// 1. If style is present and style is UNSEPARATED, let separator be the empty String; else, let separator be ":". // 1. If style is present and style is UNSEPARATED, let separator be the empty String; else, let separator be ":".
auto separator = style == TimeStyle::Unseparated ? ""sv : ":"sv; auto separator = style == TimeStyle::Unseparated ? ""sv : ":"sv;
@ -804,14 +804,14 @@ String format_time_string(u8 hour, u8 minute, u8 second, u64 sub_second_nanoseco
// 4. If precision is minute, return the string-concatenation of hh, separator, and mm. // 4. If precision is minute, return the string-concatenation of hh, separator, and mm.
if (precision.has<SecondsStringPrecision::Minute>()) if (precision.has<SecondsStringPrecision::Minute>())
return MUST(String::formatted("{:02}{}{:02}", hour, separator, minute)); return Utf16String::formatted("{:02}{}{:02}", hour, separator, minute);
// 5. Let ss be ToZeroPaddedDecimalString(second, 2). // 5. Let ss be ToZeroPaddedDecimalString(second, 2).
// 6. Let subSecondsPart be FormatFractionalSeconds(subSecondNanoseconds, precision). // 6. Let subSecondsPart be FormatFractionalSeconds(subSecondNanoseconds, precision).
auto sub_seconds_part = format_fractional_seconds(sub_second_nanoseconds, precision.downcast<Auto, u8>()); auto sub_seconds_part = format_fractional_seconds(sub_second_nanoseconds, precision.downcast<Auto, u8>());
// 7. Return the string-concatenation of hh, separator, mm, separator, ss, and subSecondsPart. // 7. Return the string-concatenation of hh, separator, mm, separator, ss, and subSecondsPart.
return MUST(String::formatted("{:02}{}{:02}{}{:02}{}", hour, separator, minute, separator, second, sub_seconds_part)); return Utf16String::formatted("{:02}{}{:02}{}{:02}{}", hour, separator, minute, separator, second, sub_seconds_part);
} }
// 13.27 GetUnsignedRoundingMode ( roundingMode, sign ), https://tc39.es/proposal-temporal/#sec-getunsignedroundingmode // 13.27 GetUnsignedRoundingMode ( roundingMode, sign ), https://tc39.es/proposal-temporal/#sec-getunsignedroundingmode

View file

@ -183,8 +183,8 @@ UnitCategory temporal_unit_category(Unit);
RoundingIncrement maximum_temporal_duration_rounding_increment(Unit); RoundingIncrement maximum_temporal_duration_rounding_increment(Unit);
Crypto::UnsignedBigInteger const& temporal_unit_length_in_nanoseconds(Unit); Crypto::UnsignedBigInteger const& temporal_unit_length_in_nanoseconds(Unit);
ThrowCompletionOr<bool> is_partial_temporal_object(VM&, Value); ThrowCompletionOr<bool> is_partial_temporal_object(VM&, Value);
String format_fractional_seconds(u64, Precision); Utf16String format_fractional_seconds(u64, Precision);
String format_time_string(u8 hour, u8 minute, u8 second, u64 sub_second_nanoseconds, SecondsStringPrecision::Precision, Optional<TimeStyle> = {}); Utf16String format_time_string(u8 hour, u8 minute, u8 second, u64 sub_second_nanoseconds, SecondsStringPrecision::Precision, Optional<TimeStyle> = {});
UnsignedRoundingMode get_unsigned_rounding_mode(RoundingMode, Sign); UnsignedRoundingMode get_unsigned_rounding_mode(RoundingMode, Sign);
double apply_unsigned_rounding_mode(double, double r1, double r2, UnsignedRoundingMode); double apply_unsigned_rounding_mode(double, double r1, double r2, UnsignedRoundingMode);
Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResult const&, Crypto::SignedBigInteger r1, Crypto::SignedBigInteger r2, UnsignedRoundingMode, Crypto::UnsignedBigInteger const& increment); Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResult const&, Crypto::SignedBigInteger r1, Crypto::SignedBigInteger r2, UnsignedRoundingMode, Crypto::UnsignedBigInteger const& increment);

View file

@ -10,6 +10,7 @@
#include <AK/GenericShorthands.h> #include <AK/GenericShorthands.h>
#include <AK/Math.h> #include <AK/Math.h>
#include <AK/NumericLimits.h> #include <AK/NumericLimits.h>
#include <AK/Utf16StringBuilder.h>
#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Date.h> #include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Intrinsics.h> #include <LibJS/Runtime/Intrinsics.h>
@ -1474,13 +1475,13 @@ ThrowCompletionOr<Crypto::BigFraction> total_relative_duration(VM& vm, InternalD
} }
// 7.5.40 TemporalDurationToString ( duration, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationtostring // 7.5.40 TemporalDurationToString ( duration, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationtostring
String temporal_duration_to_string(Duration const& duration, Precision precision) Utf16String temporal_duration_to_string(Duration const& duration, Precision precision)
{ {
// 1. Let sign be DurationSign(duration). // 1. Let sign be DurationSign(duration).
auto sign = duration_sign(duration); auto sign = duration_sign(duration);
// 2. Let datePart be the empty String. // 2. Let datePart be the empty String.
StringBuilder date_part; Utf16StringBuilder date_part;
// 3. If duration.[[Years]] ≠ 0, then // 3. If duration.[[Years]] ≠ 0, then
if (duration.years() != 0) { if (duration.years() != 0) {
@ -1508,7 +1509,7 @@ String temporal_duration_to_string(Duration const& duration, Precision precision
} }
// 7. Let timePart be the empty String. // 7. Let timePart be the empty String.
StringBuilder time_part; Utf16StringBuilder time_part;
// 8. If duration.[[Hours]] ≠ 0, then // 8. If duration.[[Hours]] ≠ 0, then
if (duration.hours() != 0) { if (duration.hours() != 0) {
@ -1552,17 +1553,20 @@ String temporal_duration_to_string(Duration const& duration, Precision precision
auto sign_part = sign < 0 ? "-"sv : ""sv; auto sign_part = sign < 0 ? "-"sv : ""sv;
// 15. Let result be the string concatenation of signPart, the code unit 0x0050 (LATIN CAPITAL LETTER P) and datePart. // 15. Let result be the string concatenation of signPart, the code unit 0x0050 (LATIN CAPITAL LETTER P) and datePart.
StringBuilder result; Utf16StringBuilder result;
result.appendff("{}P{}", sign_part, date_part.string_view()); result.append_ascii(sign_part);
result.append_ascii('P');
result.append(date_part.view());
// 16. If timePart is not the empty String, then // 16. If timePart is not the empty String, then
if (!time_part.is_empty()) { if (!time_part.is_empty()) {
// a. Set result to the string concatenation of result, the code unit 0x0054 (LATIN CAPITAL LETTER T), and timePart. // a. Set result to the string concatenation of result, the code unit 0x0054 (LATIN CAPITAL LETTER T), and timePart.
result.appendff("T{}", time_part.string_view()); result.append_ascii('T');
result.append(time_part.view());
} }
// 17. Return result. // 17. Return result.
return MUST(result.to_string()); return result.to_string();
} }
// 7.5.41 AddDurations ( operation, duration, other ), https://tc39.es/proposal-temporal/#sec-temporal-adddurations // 7.5.41 AddDurations ( operation, duration, other ), https://tc39.es/proposal-temporal/#sec-temporal-adddurations

View file

@ -153,7 +153,7 @@ ThrowCompletionOr<DurationNudgeResult> nudge_to_day_or_time(VM&, InternalDuratio
ThrowCompletionOr<InternalDuration> bubble_relative_duration(VM&, i8 sign, InternalDuration, Crypto::SignedBigInteger const& nudged_epoch_ns, ISODateTime const&, Optional<String const&> time_zone, String const& calendar, Unit largest_unit, Unit smallest_unit); ThrowCompletionOr<InternalDuration> bubble_relative_duration(VM&, i8 sign, InternalDuration, Crypto::SignedBigInteger const& nudged_epoch_ns, ISODateTime const&, Optional<String const&> time_zone, String const& calendar, Unit largest_unit, Unit smallest_unit);
ThrowCompletionOr<InternalDuration> round_relative_duration(VM&, InternalDuration, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const&, Optional<String const&> time_zone, String const& calendar, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode); ThrowCompletionOr<InternalDuration> round_relative_duration(VM&, InternalDuration, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const&, Optional<String const&> time_zone, String const& calendar, Unit largest_unit, u64 increment, Unit smallest_unit, RoundingMode);
ThrowCompletionOr<Crypto::BigFraction> total_relative_duration(VM&, InternalDuration const&, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const&, Optional<String const&> time_zone, String const& calendar, Unit); ThrowCompletionOr<Crypto::BigFraction> total_relative_duration(VM&, InternalDuration const&, Crypto::SignedBigInteger const& origin_epoch_ns, Crypto::SignedBigInteger const& dest_epoch_ns, ISODateTime const&, Optional<String const&> time_zone, String const& calendar, Unit);
String temporal_duration_to_string(Duration const&, Precision); Utf16String temporal_duration_to_string(Duration const&, Precision);
ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM&, ArithmeticOperation, Duration const&, Value); ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM&, ArithmeticOperation, Duration const&, Value);
} }

View file

@ -445,7 +445,7 @@ ThrowCompletionOr<TemporalTimeLike> to_temporal_time_record(VM& vm, Object const
} }
// 4.5.13 TimeRecordToString ( time, precision ), https://tc39.es/proposal-temporal/#sec-temporal-timerecordtostring // 4.5.13 TimeRecordToString ( time, precision ), https://tc39.es/proposal-temporal/#sec-temporal-timerecordtostring
String time_record_to_string(Time const& time, SecondsStringPrecision::Precision precision) Utf16String time_record_to_string(Time const& time, SecondsStringPrecision::Precision precision)
{ {
// 1. Let subSecondNanoseconds be time.[[Millisecond]] × 10**6 + time.[[Microsecond]] × 10**3 + time.[[Nanosecond]]. // 1. Let subSecondNanoseconds be time.[[Millisecond]] × 10**6 + time.[[Microsecond]] × 10**3 + time.[[Nanosecond]].
auto sub_second_nanoseconds = (static_cast<u64>(time.millisecond) * 1'000'000) + (static_cast<u64>(time.microsecond) * 1000) + static_cast<u64>(time.nanosecond); auto sub_second_nanoseconds = (static_cast<u64>(time.millisecond) * 1'000'000) + (static_cast<u64>(time.microsecond) * 1000) + static_cast<u64>(time.nanosecond);

View file

@ -60,7 +60,7 @@ Time balance_time(double hour, double minute, double second, double millisecond,
Time balance_time(double hour, double minute, double second, double millisecond, double microsecond, Crypto::SignedBigInteger const& nanosecond); Time balance_time(double hour, double minute, double second, double millisecond, double microsecond, Crypto::SignedBigInteger const& nanosecond);
ThrowCompletionOr<GC::Ref<PlainTime>> create_temporal_time(VM&, Time const&, GC::Ptr<FunctionObject> new_target = {}); ThrowCompletionOr<GC::Ref<PlainTime>> create_temporal_time(VM&, Time const&, GC::Ptr<FunctionObject> new_target = {});
ThrowCompletionOr<TemporalTimeLike> to_temporal_time_record(VM&, Object const& temporal_time_like, Completeness = Completeness::Complete); ThrowCompletionOr<TemporalTimeLike> to_temporal_time_record(VM&, Object const& temporal_time_like, Completeness = Completeness::Complete);
String time_record_to_string(Time const&, SecondsStringPrecision::Precision); Utf16String time_record_to_string(Time const&, SecondsStringPrecision::Precision);
i8 compare_time_record(Time const&, Time const&); i8 compare_time_record(Time const&, Time const&);
Time add_time(Time const&, TimeDuration const& time_duration); Time add_time(Time const&, TimeDuration const& time_duration);
Time round_time(Time const&, u64 increment, Unit, RoundingMode); Time round_time(Time const&, u64 increment, Unit, RoundingMode);

View file

@ -263,7 +263,7 @@ inline ErrorOr<JsonValue> get_test_results(JS::Realm& realm)
auto results = MUST(realm.global_object().get("__TestResults__"_utf16_fly_string)); auto results = MUST(realm.global_object().get("__TestResults__"_utf16_fly_string));
auto maybe_json_string = MUST(JS::JSONObject::stringify_impl(*g_vm, results, JS::js_undefined(), JS::js_undefined())); auto maybe_json_string = MUST(JS::JSONObject::stringify_impl(*g_vm, results, JS::js_undefined(), JS::js_undefined()));
if (maybe_json_string.has_value()) if (maybe_json_string.has_value())
return JsonValue::from_string(*maybe_json_string); return JsonValue::from_string(MUST(maybe_json_string->utf16_view().to_utf8()));
return JsonValue(); return JsonValue();
} }

View file

@ -977,7 +977,8 @@ GC::Ref<WebIDL::Promise> SubtleCrypto::wrap_key(Bindings::KeyFormat format, GC::
} }
// 2. Let bytes be the result of UTF-8 encoding json. // 2. Let bytes be the result of UTF-8 encoding json.
bytes = MUST(ByteBuffer::copy(maybe_json.value()->bytes())); auto json = MUST(maybe_json.value()->utf16_view().to_utf8());
bytes = MUST(ByteBuffer::copy(json.bytes()));
} }
// Otherwise: // Otherwise: