tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 108d654abff40871cf52b549469502fc6a985e1d
parent 6c8aca084c9078fe3f1f260e3d4fdcbaefa1e9a4
Author: André Bargull <andre.bargull@gmail.com>
Date:   Mon, 13 Oct 2025 12:55:24 +0000

Bug 1990248 - Part 2: Port CanonicalizeLocaleList to C++. r=spidermonkey-reviewers,jandem

The implementation code is in "intl/Locale.cpp" to reuse existing code, but at
some point this should be cleaned, so we no longer have different locale
identifier processing functions defined across multiple files (*).

The `HasAndGetElement` function was copied from "builtin/Array.cpp" and
slightly adjusted.

(*) CommonFunctions.cpp, IntlObject.cpp, LanguageTag.cpp, and Locale.cpp.

Differential Revision: https://phabricator.services.mozilla.com/D265824

Diffstat:
Mjs/src/builtin/Array.cpp | 39++++++++++++++++++++++-----------------
Mjs/src/builtin/Array.h | 7+++++++
Mjs/src/builtin/intl/CommonFunctions.h | 6++++++
Mjs/src/builtin/intl/LanguageTag.cpp | 4++--
Mjs/src/builtin/intl/LanguageTag.h | 3+--
Mjs/src/builtin/intl/Locale.cpp | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
6 files changed, 195 insertions(+), 66 deletions(-)

diff --git a/js/src/builtin/Array.cpp b/js/src/builtin/Array.cpp @@ -311,6 +311,11 @@ static inline bool HasAndGetElement(JSContext* cx, HandleObject obj, T index, return HasAndGetElement(cx, obj, obj, index, hole, vp); } +bool js::HasAndGetElement(JSContext* cx, HandleObject obj, uint64_t index, + bool* hole, MutableHandleValue vp) { + return HasAndGetElement(cx, obj, obj, index, hole, vp); +} + bool ElementAdder::append(JSContext* cx, HandleValue v) { MOZ_ASSERT(index_ < length_); if (resObj_) { @@ -1175,7 +1180,7 @@ JSString* js::ArrayToSource(JSContext* cx, HandleObject obj) { for (uint64_t index = 0; index < length; index++) { bool hole; if (!CheckForInterrupt(cx) || - !HasAndGetElement(cx, obj, index, &hole, &elt)) { + !::HasAndGetElement(cx, obj, index, &hole, &elt)) { return nullptr; } @@ -1687,8 +1692,8 @@ static bool array_reverse(JSContext* cx, unsigned argc, Value* vp) { for (uint64_t i = 0, half = len / 2; i < half; i++) { bool hole, hole2; if (!CheckForInterrupt(cx) || - !HasAndGetElement(cx, obj, i, &hole, &lowval) || - !HasAndGetElement(cx, obj, len - i - 1, &hole2, &hival)) { + !::HasAndGetElement(cx, obj, i, &hole, &lowval) || + !::HasAndGetElement(cx, obj, len - i - 1, &hole2, &hival)) { return false; } @@ -2216,7 +2221,7 @@ static bool ArraySortWithoutComparator(JSContext* cx, Handle<JSObject*> obj, } bool hole; - if (!HasAndGetElement(cx, obj, i, &hole, &v)) { + if (!::HasAndGetElement(cx, obj, i, &hole, &v)) { return false; } if (hole) { @@ -2393,7 +2398,7 @@ static MOZ_ALWAYS_INLINE bool ArraySortPrologue(JSContext* cx, } bool hole; - if (!HasAndGetElement(cx, obj, i, &hole, &v)) { + if (!::HasAndGetElement(cx, obj, i, &hole, &v)) { return false; } if (hole) { @@ -2803,7 +2808,7 @@ static bool array_shift(JSContext* cx, unsigned argc, Value* vp) { return false; } bool hole; - if (!HasAndGetElement(cx, obj, i + 1, &hole, &value)) { + if (!::HasAndGetElement(cx, obj, i + 1, &hole, &value)) { return false; } if (hole) { @@ -2906,7 +2911,7 @@ static bool array_unshift(JSContext* cx, unsigned argc, Value* vp) { return false; } bool hole; - if (!HasAndGetElement(cx, obj, last, &hole, &value)) { + if (!::HasAndGetElement(cx, obj, last, &hole, &value)) { return false; } if (hole) { @@ -3045,7 +3050,7 @@ static bool CopyArrayElements(JSContext* cx, HandleObject obj, uint64_t begin, for (; index < limit; index++) { bool hole; if (!CheckForInterrupt(cx) || - !HasAndGetElement(cx, obj, begin + index, &hole, &value)) { + !::HasAndGetElement(cx, obj, begin + index, &hole, &value)) { return false; } @@ -3073,7 +3078,7 @@ static bool CopyArrayElements(JSContext* cx, HandleObject obj, uint64_t begin, for (uint64_t i = startIndex; i < count; i++) { bool hole; if (!CheckForInterrupt(cx) || - !HasAndGetElement(cx, obj, begin + i, &hole, &value)) { + !::HasAndGetElement(cx, obj, begin + i, &hole, &value)) { return false; } @@ -3228,7 +3233,7 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp, /* Steps 13.b, 13.c.i. */ bool hole; - if (!HasAndGetElement(cx, obj, actualStart + k, &hole, &fromValue)) { + if (!::HasAndGetElement(cx, obj, actualStart + k, &hole, &fromValue)) { return false; } @@ -3290,7 +3295,7 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp, /* Steps 16.b.iii-v */ bool hole; - if (!HasAndGetElement(cx, obj, from, &hole, &fromValue)) { + if (!::HasAndGetElement(cx, obj, from, &hole, &fromValue)) { return false; } @@ -3389,7 +3394,7 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp, /* Steps 17.b.iii, 17.b.iv.1. */ bool hole; - if (!HasAndGetElement(cx, obj, from, &hole, &fromValue)) { + if (!::HasAndGetElement(cx, obj, from, &hole, &fromValue)) { return false; } @@ -3988,7 +3993,7 @@ static bool SliceSparse(JSContext* cx, HandleObject obj, uint64_t begin, MOZ_ASSERT(begin <= index && index < end); bool hole; - if (!HasAndGetElement(cx, obj, index, &hole, &value)) { + if (!::HasAndGetElement(cx, obj, index, &hole, &value)) { return false; } @@ -4152,7 +4157,7 @@ static bool array_slice(JSContext* cx, unsigned argc, Value* vp) { /* Steps 10.a-b, and 10.c.i. */ bool kNotPresent; - if (!HasAndGetElement(cx, obj, k, &kNotPresent, &kValue)) { + if (!::HasAndGetElement(cx, obj, k, &kNotPresent, &kValue)) { return false; } @@ -4554,7 +4559,7 @@ bool js::array_indexOf(JSContext* cx, unsigned argc, Value* vp) { } bool hole; - if (!HasAndGetElement(cx, obj, k, &hole, &v)) { + if (!::HasAndGetElement(cx, obj, k, &hole, &v)) { return false; } if (hole) { @@ -4668,7 +4673,7 @@ bool js::array_lastIndexOf(JSContext* cx, unsigned argc, Value* vp) { } bool hole; - if (!HasAndGetElement(cx, obj, uint64_t(i), &hole, &v)) { + if (!::HasAndGetElement(cx, obj, uint64_t(i), &hole, &v)) { return false; } if (hole) { @@ -5056,7 +5061,7 @@ static bool array_concat(JSContext* cx, unsigned argc, Value* vp) { // Step 5.b.iv.2. bool hole; - if (!HasAndGetElement(cx, obj, k, &hole, &v)) { + if (!::HasAndGetElement(cx, obj, k, &hole, &v)) { return false; } if (!hole) { diff --git a/js/src/builtin/Array.h b/js/src/builtin/Array.h @@ -113,6 +113,13 @@ extern bool SetLengthProperty(JSContext* cx, HandleObject obj, uint32_t length); extern bool GetElements(JSContext* cx, HandleObject aobj, uint32_t length, js::Value* vp); +/* + * If the property at the given index exists, get its value into |vp| and set + * |*hole| to false. Otherwise set |*hole| to true and |vp| to Undefined. + */ +extern bool HasAndGetElement(JSContext* cx, HandleObject obj, uint64_t index, + bool* hole, MutableHandleValue vp); + /* Natives exposed for optimization by the interpreter and JITs. */ extern bool array_includes(JSContext* cx, unsigned argc, js::Value* vp); diff --git a/js/src/builtin/intl/CommonFunctions.h b/js/src/builtin/intl/CommonFunctions.h @@ -10,6 +10,7 @@ #include <stddef.h> #include <stdint.h> +#include "js/GCVector.h" #include "js/RootingAPI.h" #include "js/Utility.h" @@ -95,6 +96,11 @@ extern const OldStyleLanguageTagMapping oldStyleLanguageTagMappings[5]; extern JS::UniqueChars EncodeLocale(JSContext* cx, JSString* locale); +using LocalesList = JS::StackGCVector<JSLinearString*>; + +bool CanonicalizeLocaleList(JSContext* cx, JS::Handle<JS::Value> locales, + JS::MutableHandle<LocalesList> result); + // The inline capacity we use for a Vector<char16_t>. Use this to ensure that // our uses of ICU string functions, below and elsewhere, will try to fill the // buffer's entire inline capacity before growing it and heap-allocating. diff --git a/js/src/builtin/intl/LanguageTag.cpp b/js/src/builtin/intl/LanguageTag.cpp @@ -191,7 +191,7 @@ static bool IsAsciiAlpha(const JSLinearString* str) { return IsAsciiAlpha<char16_t>(str->twoByteRange(nogc)); } -JS::Result<JSString*> js::intl::ParseStandaloneISO639LanguageTag( +JS::Result<JSLinearString*> js::intl::ParseStandaloneISO639LanguageTag( JSContext* cx, Handle<JSLinearString*> str) { // ISO-639 language codes contain either two or three characters. size_t length = str->length(); @@ -229,7 +229,7 @@ JS::Result<JSString*> js::intl::ParseStandaloneISO639LanguageTag( } // Take care to replace deprecated subtags with their preferred values. - JSString* result; + JSLinearString* result; if (mozilla::intl::Locale::LanguageMapping(languageTag) || !isLowerCase) { result = NewStringCopy<CanGC>(cx, languageTag.Span()); } else { diff --git a/js/src/builtin/intl/LanguageTag.h b/js/src/builtin/intl/LanguageTag.h @@ -18,7 +18,6 @@ struct JS_PUBLIC_API JSContext; class JSLinearString; -class JS_PUBLIC_API JSString; class JS_PUBLIC_API JSTracer; namespace js { @@ -67,7 +66,7 @@ namespace intl { * the input could not be parsed or the canonical form of the resulting language * tag contains more than a single language subtag. */ -JS::Result<JSString*> ParseStandaloneISO639LanguageTag( +JS::Result<JSLinearString*> ParseStandaloneISO639LanguageTag( JSContext* cx, JS::Handle<JSLinearString*> str); class UnicodeExtensionKeyword final { diff --git a/js/src/builtin/intl/Locale.cpp b/js/src/builtin/intl/Locale.cpp @@ -20,6 +20,7 @@ #include <string.h> #include <utility> +#include "builtin/Array.h" #include "builtin/Boolean.h" #include "builtin/intl/CommonFunctions.h" #include "builtin/intl/FormatBuffer.h" @@ -1396,56 +1397,23 @@ const ClassSpec LocaleObject::classSpec_ = { ClassSpec::DontDefineConstructor, }; -bool js::intl_ValidateAndCanonicalizeLanguageTag(JSContext* cx, unsigned argc, - Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 2); - - HandleValue tagValue = args[0]; - bool applyToString = args[1].toBoolean(); - - if (tagValue.isObject()) { - JSString* tagStr; - JS_TRY_VAR_OR_RETURN_FALSE( - cx, tagStr, - LanguageTagFromMaybeWrappedLocale(cx, &tagValue.toObject())); - if (tagStr) { - args.rval().setString(tagStr); - return true; - } - } - - if (!applyToString && !tagValue.isString()) { - args.rval().setNull(); - return true; - } - - JSString* tagStr = ToString(cx, tagValue); - if (!tagStr) { - return false; - } - - Rooted<JSLinearString*> tagLinearStr(cx, tagStr->ensureLinear(cx)); - if (!tagLinearStr) { - return false; - } - +static JSLinearString* ValidateAndCanonicalizeLanguageTag( + JSContext* cx, Handle<JSLinearString*> string) { // Handle the common case (a standalone language) first. // Only the following Unicode BCP 47 locale identifier subset is accepted: // unicode_locale_id = unicode_language_id // unicode_language_id = unicode_language_subtag // unicode_language_subtag = alpha{2,3} - JSString* language; - JS_TRY_VAR_OR_RETURN_FALSE( - cx, language, intl::ParseStandaloneISO639LanguageTag(cx, tagLinearStr)); + JSLinearString* language; + JS_TRY_VAR_OR_RETURN_NULL(cx, language, + intl::ParseStandaloneISO639LanguageTag(cx, string)); if (language) { - args.rval().setString(language); - return true; + return language; } mozilla::intl::Locale tag; - if (!intl::ParseLocale(cx, tagLinearStr, tag)) { - return false; + if (!intl::ParseLocale(cx, string, tag)) { + return nullptr; } auto result = tag.Canonicalize(); @@ -1457,20 +1425,70 @@ bool js::intl_ValidateAndCanonicalizeLanguageTag(JSContext* cx, unsigned argc, } else { intl::ReportInternalError(cx); } - return false; + return nullptr; } intl::FormatBuffer<char, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx); if (auto result = tag.ToString(buffer); result.isErr()) { intl::ReportInternalError(cx, result.unwrapErr()); - return false; + return nullptr; } - JSString* resultStr = buffer.toAsciiString(cx); + return buffer.toAsciiString(cx); +} + +static JSLinearString* ValidateAndCanonicalizeLanguageTag( + JSContext* cx, Handle<Value> tagValue) { + if (tagValue.isObject()) { + JSString* tagStr; + JS_TRY_VAR_OR_RETURN_NULL( + cx, tagStr, + LanguageTagFromMaybeWrappedLocale(cx, &tagValue.toObject())); + if (tagStr) { + return tagStr->ensureLinear(cx); + } + } + + JSString* tagStr = ToString(cx, tagValue); + if (!tagStr) { + return nullptr; + } + + Rooted<JSLinearString*> tagLinearStr(cx, tagStr->ensureLinear(cx)); + if (!tagLinearStr) { + return nullptr; + } + return ValidateAndCanonicalizeLanguageTag(cx, tagLinearStr); +} + +bool js::intl_ValidateAndCanonicalizeLanguageTag(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + + HandleValue tagValue = args[0]; + bool applyToString = args[1].toBoolean(); + + if (tagValue.isObject()) { + JSString* tagStr; + JS_TRY_VAR_OR_RETURN_FALSE( + cx, tagStr, + LanguageTagFromMaybeWrappedLocale(cx, &tagValue.toObject())); + if (tagStr) { + args.rval().setString(tagStr); + return true; + } + } + + if (!applyToString && !tagValue.isString()) { + args.rval().setNull(); + return true; + } + + auto* resultStr = ValidateAndCanonicalizeLanguageTag(cx, tagValue); if (!resultStr) { return false; } - args.rval().setString(resultStr); return true; } @@ -1617,3 +1635,97 @@ bool js::intl_ValidateAndCanonicalizeUnicodeExtensionType(JSContext* cx, args.rval().setString(result); return true; } + +/** + * Canonicalizes a locale list. + * + * Spec: ECMAScript Internationalization API Specification, 9.2.1. + */ +bool js::intl::CanonicalizeLocaleList(JSContext* cx, Handle<Value> locales, + MutableHandle<LocalesList> result) { + MOZ_ASSERT(result.empty()); + + // Step 1. + if (locales.isUndefined()) { + return true; + } + + // Step 3 (and the remaining steps). + if (locales.isString()) { + Rooted<JSLinearString*> linear(cx, locales.toString()->ensureLinear(cx)); + if (!linear) { + return false; + } + + auto* languageTag = ValidateAndCanonicalizeLanguageTag(cx, linear); + if (!languageTag) { + return false; + } + return result.append(languageTag); + } + + if (locales.isObject()) { + JSString* languageTag; + JS_TRY_VAR_OR_RETURN_FALSE( + cx, languageTag, + LanguageTagFromMaybeWrappedLocale(cx, &locales.toObject())); + if (languageTag) { + auto* linear = languageTag->ensureLinear(cx); + if (!linear) { + return false; + } + return result.append(linear); + } + } + + // Step 2. (Implicit) + + // Step 4. + Rooted<JSObject*> obj(cx, ToObject(cx, locales)); + if (!obj) { + return false; + } + + // Step 5. + uint64_t length; + if (!GetLengthProperty(cx, obj, &length)) { + return false; + } + + // Steps 6-7. + Rooted<Value> value(cx); + for (uint64_t k = 0; k < length; k++) { + // Step 7.a-c. + bool hole; + if (!CheckForInterrupt(cx) || + !HasAndGetElement(cx, obj, k, &hole, &value)) { + return false; + } + + if (!hole) { + // Step 7.c.ii. + if (!value.isString() && !value.isObject()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_INVALID_LOCALES_ELEMENT); + return false; + } + + // Step 7.c.iii-iv. + JSLinearString* tag = ValidateAndCanonicalizeLanguageTag(cx, value); + if (!tag) { + return false; + } + + // Step 7.c.v. + bool addToResult = + std::none_of(result.begin(), result.end(), + [tag](auto* other) { return EqualStrings(tag, other); }); + if (addToResult && !result.append(tag)) { + return false; + } + } + } + + // Step 8. + return true; +}