Merge remote-tracking branch 'origin/tracking' into custom

This commit is contained in:
roytam1 2023-07-03 15:13:12 +08:00
commit 522a2a6c64
140 changed files with 88916 additions and 72964 deletions

View file

@ -82,6 +82,7 @@ included_inclnames_to_ignore = set([
'unicode/plurrule.h', # ICU
'unicode/timezone.h', # ICU
'unicode/ucal.h', # ICU
'unicode/uchar.h', # ICU
'unicode/uclean.h', # ICU
'unicode/ucol.h', # ICU
'unicode/udat.h', # ICU

View file

@ -913,7 +913,7 @@ struct JSClass {
// application.
#define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
#define JSCLASS_GLOBAL_SLOT_COUNT \
(JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 47)
(JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 50)
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
#define JSCLASS_GLOBAL_FLAGS \

View file

@ -130,6 +130,17 @@ class GCVector
}
};
// AllocPolicy is optional. It has a default value declared in TypeDecls.h
template <typename T, typename AllocPolicy>
class MOZ_STACK_CLASS StackGCVector : public GCVector<T, 8, AllocPolicy> {
public:
using Base = GCVector<T, 8, AllocPolicy>;
private:
// Inherit constructor from GCVector.
using Base::Base;
};
} // namespace JS
namespace js {
@ -191,7 +202,7 @@ class MutableWrappedPtrOperations<JS::GCVector<T, Capacity, AllocPolicy>, Wrappe
void clearAndFree() { vec().clearAndFree(); }
template<typename U> bool append(U&& aU) { return vec().append(mozilla::Forward<U>(aU)); }
template<typename... Args> bool emplaceBack(Args&&... aArgs) {
return vec().emplaceBack(mozilla::Forward<Args...>(aArgs...));
return vec().emplaceBack(mozilla::Forward<Args>(aArgs)...);
}
template<typename U, size_t O, class BP>
bool appendAll(const mozilla::Vector<U, O, BP>& aU) { return vec().appendAll(aU); }
@ -223,6 +234,29 @@ class MutableWrappedPtrOperations<JS::GCVector<T, Capacity, AllocPolicy>, Wrappe
void erase(T* aBegin, T* aEnd) { vec().erase(aBegin, aEnd); }
};
template <typename Wrapper, typename T, typename AllocPolicy>
class WrappedPtrOperations<JS::StackGCVector<T, AllocPolicy>, Wrapper> :
public WrappedPtrOperations<typename JS::StackGCVector<T, AllocPolicy>::Base,
Wrapper> {};
template <typename Wrapper, typename T, typename AllocPolicy>
class MutableWrappedPtrOperations<JS::StackGCVector<T, AllocPolicy>, Wrapper> :
public MutableWrappedPtrOperations<typename JS::StackGCVector<T, AllocPolicy>::Base,
Wrapper> {};
} // namespace js
namespace JS {
// An automatically rooted GCVector for stack use.
template <typename T>
class RootedVector : public Rooted<StackGCVector<T>> {
using Vec = StackGCVector<T>;
using Base = Rooted<Vec>;
public:
explicit RootedVector(JSContext* cx) : Base(cx, Vec(cx)) {}
};
} // namespace JS
#endif // js_GCVector_h

223
js/public/Result.h Normal file
View file

@ -0,0 +1,223 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* `Result` is used as the return type of many SpiderMonkey functions that
* can either succeed or fail. See "/mfbt/Result.h".
*
*
* ## Which return type to use
*
* `Result` is for return values. Obviously, if you're writing a function that
* can't fail, don't use Result. Otherwise:
*
* JS::Result<> - function can fail, doesn't return anything on success
* (defaults to `JS::Result<JS::Ok, JS::Error&>`)
* JS::Result<JS::OOM&> - like JS::Result<>, but fails only on OOM
*
* JS::Result<Data> - function can fail, returns Data on success
* JS::Result<Data, JS::OOM&> - returns Data, fails only on OOM
*
* mozilla::GenericErrorResult<JS::Error&> - always fails
*
* That last type is like a Result with no success type. It's used for
* functions like `js::ReportNotFunction` that always return an error
* result. `GenericErrorResult<E>` implicitly converts to `Result<V, E>`,
* regardless of V.
*
*
* ## Checking Results when your return type is Result
*
* When you call a function that returns a `Result`, use the `MOZ_TRY` macro to
* check for errors:
*
* MOZ_TRY(DefenestrateObject(cx, obj));
*
* If `DefenestrateObject` returns a success result, `MOZ_TRY` is done, and
* control flows to the next statement. If `DefenestrateObject` returns an
* error result, `MOZ_TRY` will immediately return it, propagating the error to
* your caller. It's kind of like exceptions, but more explicit -- you can see
* in the code exactly where errors can happen.
*
* You can do a tail call instead of using `MOZ_TRY`:
*
* return DefenestrateObject(cx, obj);
*
* Indicate success with `return Ok();`.
*
* If the function returns a value on success, use `MOZ_TRY_VAR` to get it:
*
* RootedValue thrug(cx);
* MOZ_TRY_VAR(thrug, GetObjectThrug(cx, obj));
*
* This behaves the same as `MOZ_TRY` on error. On success, the success
* value of `GetObjectThrug(cx, obj)` is assigned to the variable `thrug`.
*
*
* ## Checking Results when your return type is not Result
*
* This header defines alternatives to MOZ_TRY and MOZ_TRY_VAR for when you
* need to call a `Result` function from a function that uses false or nullptr
* to indicate errors:
*
* JS_TRY_OR_RETURN_FALSE(cx, DefenestrateObject(cx, obj));
* JS_TRY_VAR_OR_RETURN_FALSE(cx, v, GetObjectThrug(cx, obj));
*
* JS_TRY_OR_RETURN_NULL(cx, DefenestrateObject(cx, obj));
* JS_TRY_VAR_OR_RETURN_NULL(cx, v, GetObjectThrug(cx, obj));
*
* When TRY is not what you want, because you need to do some cleanup or
* recovery on error, use this idiom:
*
* if (!cx->resultToBool(expr_that_is_a_Result)) {
* ... your recovery code here ...
* }
*
* In place of a tail call, you can use one of these methods:
*
* return cx->resultToBool(expr); // false on error
* return cx->resultToPtr(expr); // null on error
*
* Once we are using `Result` everywhere, including in public APIs, all of
* these will go away.
*
*
* ## GC safety
*
* When a function returns a `JS::Result<JSObject*>`, it is the program's
* responsibility to check for errors and root the object before continuing:
*
* RootedObject wrapper(cx);
* MOZ_TRY_VAR(wrapper, Enwrapify(cx, thing));
*
* This is ideal. On error, there is no object to root; on success, the
* assignment to wrapper roots it. GC safety is ensured.
*
* `Result` has methods .isOk(), .isErr(), .unwrap(), and .unwrapErr(), but if
* you're actually using them, it's possible to create a GC hazard. The static
* analysis will catch it if so, but that's hardly convenient. So try to stick
* to the idioms shown above.
*
*
* ## Future directions
*
* At present, JS::Error and JS::OOM are empty structs. The plan is to make them
* GC things that contain the actual error information (including the exception
* value and a saved stack).
*
* The long-term plan is to remove JS_IsExceptionPending and
* JS_GetPendingException in favor of JS::Error. Exception state will no longer
* exist.
*/
#ifndef js_Result_h
#define js_Result_h
#include "mozilla/Result.h"
struct JSContext;
/**
* Evaluate the boolean expression expr. If it's true, do nothing.
* If it's false, return an error result.
*/
#define JS_TRY_BOOL_TO_RESULT(cx, expr) \
do { \
bool ok_ = (expr); \
if (!ok_) \
return (cx)->boolToResult(ok_); \
} while (0)
/**
* JS_TRY_OR_RETURN_FALSE(cx, expr) runs expr to compute a Result value.
* On success, nothing happens; on error, it returns false immediately.
*
* Implementation note: this involves cx because this may eventually
* do the work of setting a pending exception or reporting OOM.
*/
#define JS_TRY_OR_RETURN_FALSE(cx, expr) \
do { \
auto tmpResult_ = (expr); \
if (tmpResult_.isErr()) \
return (cx)->resultToBool(tmpResult_); \
} while (0)
/**
* Like JS_TRY_OR_RETURN_FALSE, but returning nullptr on error,
* rather than false.
*/
#define JS_TRY_OR_RETURN_NULL(cx, expr) \
do { \
auto tmpResult_ = (expr); \
if (tmpResult_.isErr()) { \
JS_ALWAYS_FALSE((cx)->resultToBool(tmpResult_)); \
return nullptr; \
} \
} while (0)
#define JS_TRY_VAR_OR_RETURN_FALSE(cx, target, expr) \
do { \
auto tmpResult_ = (expr); \
if (tmpResult_.isErr()) \
return (cx)->resultToBool(tmpResult_); \
(target) = tmpResult_.unwrap(); \
} while (0)
#define JS_TRY_VAR_OR_RETURN_NULL(cx, target, expr) \
do { \
auto tmpResult_ = (expr); \
if (tmpResult_.isErr()) { \
JS_ALWAYS_FALSE((cx)->resultToBool(tmpResult_)); \
return nullptr; \
} \
(target) = tmpResult_.unwrap(); \
} while (0)
namespace JS {
using mozilla::Ok;
/**
* Type representing a JS error or exception. At the moment this only "represents"
* an error in a rather abstract way.
*/
struct Error
{
// Ensure sizeof(Error) > 1 so that Result<V, Error&> can use pointer
// tagging.
int dummy;
};
struct OOM : public Error
{
};
/**
* `Result` is intended to be the return type of JSAPI calls and internal
* functions that can run JS code or allocate memory from the JS GC heap. Such
* functions can:
*
* - succeed, possibly returning a value;
*
* - fail with a JS exception (out-of-memory falls in this category); or
*
* - fail because JS execution was terminated, which occurs when e.g. a
* user kills a script from the "slow script" UI. This is also how we
* unwind the stack when the debugger forces the current function to
* return. JS `catch` blocks can't catch this kind of failure,
* and JS `finally` blocks don't execute.
*/
template <typename V = Ok, typename E = Error&>
using Result = mozilla::Result<V, E>;
static_assert(sizeof(Result<>) == sizeof(uintptr_t),
"Result<> should be pointer-sized");
static_assert(sizeof(Result<int*, Error&>) == sizeof(uintptr_t),
"Result<V*, Error&> should be pointer-sized");
} // namespace JS
#endif // js_Result_h

View file

@ -30,6 +30,10 @@ class JSAddonId;
struct jsid;
namespace js {
class TempAllocPolicy;
}; // namespace js
namespace JS {
typedef unsigned char Latin1Char;
@ -40,6 +44,8 @@ template <typename T> class Handle;
template <typename T> class MutableHandle;
template <typename T> class Rooted;
template <typename T> class PersistentRooted;
template <typename T> class RootedVector;
template <typename T, typename AllocPolicy = js::TempAllocPolicy> class StackGCVector;
typedef Handle<JSFunction*> HandleFunction;
typedef Handle<jsid> HandleId;
@ -48,6 +54,7 @@ typedef Handle<JSScript*> HandleScript;
typedef Handle<JSString*> HandleString;
typedef Handle<JS::Symbol*> HandleSymbol;
typedef Handle<Value> HandleValue;
typedef Handle<StackGCVector<Value>> HandleValueVector;
typedef MutableHandle<JSFunction*> MutableHandleFunction;
typedef MutableHandle<jsid> MutableHandleId;
@ -56,6 +63,7 @@ typedef MutableHandle<JSScript*> MutableHandleScript;
typedef MutableHandle<JSString*> MutableHandleString;
typedef MutableHandle<JS::Symbol*> MutableHandleSymbol;
typedef MutableHandle<Value> MutableHandleValue;
typedef MutableHandle<StackGCVector<Value>> MutableHandleValueVector;
typedef Rooted<JSObject*> RootedObject;
typedef Rooted<JSFunction*> RootedFunction;
@ -65,6 +73,8 @@ typedef Rooted<JS::Symbol*> RootedSymbol;
typedef Rooted<jsid> RootedId;
typedef Rooted<JS::Value> RootedValue;
typedef RootedVector<JS::Value> RootedValueVector;
typedef PersistentRooted<JSFunction*> PersistentRootedFunction;
typedef PersistentRooted<jsid> PersistentRootedId;
typedef PersistentRooted<JSObject*> PersistentRootedObject;
@ -73,6 +83,11 @@ typedef PersistentRooted<JSString*> PersistentRootedString;
typedef PersistentRooted<JS::Symbol*> PersistentRootedSymbol;
typedef PersistentRooted<Value> PersistentRootedValue;
template <typename T>
using HandleVector = Handle<StackGCVector<T>>;
template <typename T>
using MutableHandleVector = MutableHandle<StackGCVector<T>>;
} // namespace JS
#endif /* js_TypeDecls_h */

View file

@ -82,10 +82,18 @@ using JS::UTF8CharsZ;
using JS::UniqueChars;
using JS::UniqueTwoByteChars;
using JS::Result;
using JS::Ok;
using JS::OOM;
using JS::AutoValueVector;
using JS::AutoIdVector;
using JS::AutoObjectVector;
using JS::RootedValueVector;
using JS::HandleValueVector;
using JS::MutableHandleValueVector;
using JS::ValueVector;
using JS::IdVector;
using JS::ScriptVector;

View file

@ -195,17 +195,42 @@ function ArrayStaticSome(list, callbackfn/*, thisArg*/) {
return callFunction(ArraySome, list, callbackfn, T);
}
/* ES6 draft 2016-1-15 22.1.3.25 Array.prototype.sort (comparefn) */
// ES2018 draft rev 3bbc87cd1b9d3bf64c3e68ca2fe9c5a3f2c304c0
// 22.1.3.25 Array.prototype.sort ( comparefn )
function ArraySort(comparefn) {
/* Step 1. */
var O = ToObject(this);
if (comparefn !== undefined) {
if (!IsCallable(comparefn)) {
ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, comparefn));
}
}
/* Step 2. */
var O = ToObject(this);
/* Step 3. */
var len = ToLength(O.length);
if (len <= 1)
return this;
if (comparefn === undefined) {
// {Goanna} This implementation slightly breaks the standard. The default
// comparator function depends on the type of items in the Array
// (Strings, Numbers, etc.) and can be literal, lexicograpic, numeric,
// lexicograpic-number...
// Mozilla implements this correctly only in the native implementation.
// Note that this must be stable regardless of casting, so we can only
// use one of > or <, as the other may involve weird equality.
comparefn = function(x, y) {
/* Step 4.a. */
if (x == y)
return 0;
if (x > y)
return 1;
return -1;
}
}
/* 22.1.3.25.1 Runtime Semantics: SortCompare( x, y ) */
var wrappedCompareFn = comparefn;
comparefn = function(x, y) {

View file

@ -104,7 +104,7 @@ function Date_toLocaleString() {
}
// Step 7.
return intl_FormatDateTime(dateTimeFormat, x, false);
return intl_FormatDateTime(dateTimeFormat, x, /* formatToParts = */ false);
}
@ -137,7 +137,7 @@ function Date_toLocaleDateString() {
}
// Step 7.
return intl_FormatDateTime(dateTimeFormat, x, false);
return intl_FormatDateTime(dateTimeFormat, x, /* formatToParts = */ false);
}
@ -170,5 +170,5 @@ function Date_toLocaleTimeString() {
}
// Step 7.
return intl_FormatDateTime(dateTimeFormat, x, false);
return intl_FormatDateTime(dateTimeFormat, x, /* formatToParts = */ false);
}

View file

@ -974,8 +974,7 @@ IsTrailSurrogateWithLeadSurrogate(JSContext* cx, HandleLinearString input, int32
*/
static RegExpRunStatus
ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string,
int32_t lastIndex,
MatchPairs* matches, size_t* endIndex, RegExpStaticsUpdate staticsUpdate)
int32_t lastIndex, MatchPairs* matches, size_t* endIndex)
{
/*
* WARNING: Despite the presence of spec step comment numbers, this
@ -990,14 +989,9 @@ ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string,
if (!RegExpObject::getShared(cx, reobj, &re))
return RegExpRunStatus_Error;
RegExpStatics* res;
if (staticsUpdate == UpdateRegExpStatics) {
res = GlobalObject::getRegExpStatics(cx, cx->global());
if (!res)
return RegExpRunStatus_Error;
} else {
res = nullptr;
}
RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global());
if (!res)
return RegExpRunStatus_Error;
RootedLinearString input(cx, string->ensureLinear(cx));
if (!input)
@ -1051,15 +1045,14 @@ ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string,
* steps 3, 9-25, except 12.a.i, 12.c.i.1, 15.
*/
static bool
RegExpMatcherImpl(JSContext* cx, HandleObject regexp, HandleString string,
int32_t lastIndex, RegExpStaticsUpdate staticsUpdate, MutableHandleValue rval)
RegExpMatcherImpl(JSContext* cx, HandleObject regexp, HandleString string, int32_t lastIndex,
MutableHandleValue rval)
{
/* Execute regular expression and gather matches. */
ScopedMatchPairs matches(&cx->tempLifoAlloc());
/* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
&matches, nullptr, staticsUpdate);
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex, &matches, nullptr);
if (status == RegExpRunStatus_Error)
return false;
@ -1099,8 +1092,7 @@ js::RegExpMatcher(JSContext* cx, unsigned argc, Value* vp)
return false;
/* Steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */
return RegExpMatcherImpl(cx, regexp, string, lastIndex,
UpdateRegExpStatics, args.rval());
return RegExpMatcherImpl(cx, regexp, string, lastIndex, args.rval());
}
/*
@ -1123,8 +1115,7 @@ js::RegExpMatcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
return false;
return CreateRegExpMatchResult(cx, *shared, input, *maybeMatches, output);
}
return RegExpMatcherImpl(cx, regexp, input, lastIndex,
UpdateRegExpStatics, output);
return RegExpMatcherImpl(cx, regexp, input, lastIndex, output);
}
/*
@ -1135,14 +1126,13 @@ js::RegExpMatcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
*/
static bool
RegExpSearcherImpl(JSContext* cx, HandleObject regexp, HandleString string,
int32_t lastIndex, RegExpStaticsUpdate staticsUpdate, int32_t* result)
int32_t lastIndex, int32_t* result)
{
/* Execute regular expression and gather matches. */
ScopedMatchPairs matches(&cx->tempLifoAlloc());
/* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
&matches, nullptr, staticsUpdate);
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex, &matches, nullptr);
if (status == RegExpRunStatus_Error)
return false;
@ -1180,7 +1170,7 @@ js::RegExpSearcher(JSContext* cx, unsigned argc, Value* vp)
/* Steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */
int32_t result = 0;
if (!RegExpSearcherImpl(cx, regexp, string, lastIndex, UpdateRegExpStatics, &result))
if (!RegExpSearcherImpl(cx, regexp, string, lastIndex, &result))
return false;
args.rval().setInt32(result);
@ -1203,23 +1193,7 @@ js::RegExpSearcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
*result = CreateRegExpSearchResult(cx, *maybeMatches);
return true;
}
return RegExpSearcherImpl(cx, regexp, input, lastIndex,
UpdateRegExpStatics, result);
}
bool
js::regexp_exec_no_statics(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(IsRegExpObject(args[0]));
MOZ_ASSERT(args[1].isString());
RootedObject regexp(cx, &args[0].toObject());
RootedString string(cx, args[1].toString());
return RegExpMatcherImpl(cx, regexp, string, 0,
DontUpdateRegExpStatics, args.rval());
return RegExpSearcherImpl(cx, regexp, input, lastIndex, result);
}
/*
@ -1245,8 +1219,7 @@ js::RegExpTester(JSContext* cx, unsigned argc, Value* vp)
/* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
size_t endIndex = 0;
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
nullptr, &endIndex, UpdateRegExpStatics);
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex, nullptr, &endIndex);
if (status == RegExpRunStatus_Error)
return false;
@ -1271,8 +1244,7 @@ js::RegExpTesterRaw(JSContext* cx, HandleObject regexp, HandleString input,
MOZ_ASSERT(lastIndex >= 0);
size_t endIndexTmp = 0;
RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, lastIndex,
nullptr, &endIndexTmp, UpdateRegExpStatics);
RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, lastIndex, nullptr, &endIndexTmp);
if (status == RegExpRunStatus_Success) {
MOZ_ASSERT(endIndexTmp <= INT32_MAX);
@ -1287,24 +1259,6 @@ js::RegExpTesterRaw(JSContext* cx, HandleObject regexp, HandleString input,
return false;
}
bool
js::regexp_test_no_statics(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(IsRegExpObject(args[0]));
MOZ_ASSERT(args[1].isString());
RootedObject regexp(cx, &args[0].toObject());
RootedString string(cx, args[1].toString());
size_t ignored = 0;
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, 0,
nullptr, &ignored, DontUpdateRegExpStatics);
args.rval().setBoolean(status == RegExpRunStatus_Success);
return status != RegExpRunStatus_Error;
}
static void
GetParen(JSLinearString* matched, const JS::Value& capture, JSSubString* out)
{

View file

@ -18,10 +18,6 @@ namespace js {
JSObject*
InitRegExpClass(JSContext* cx, HandleObject obj);
// Whether RegExp statics should be updated with the input and results of a
// regular expression execution.
enum RegExpStaticsUpdate { UpdateRegExpStatics, DontUpdateRegExpStatics };
/*
* Legacy behavior of ExecuteRegExp(), which is baked into the JSAPI.
*
@ -71,22 +67,6 @@ intrinsic_GetStringDataProperty(JSContext* cx, unsigned argc, Value* vp);
* The following functions are for use by self-hosted code.
*/
/*
* Behaves like regexp.exec(string), but doesn't set RegExp statics.
*
* Usage: match = regexp_exec_no_statics(regexp, string)
*/
extern MOZ_MUST_USE bool
regexp_exec_no_statics(JSContext* cx, unsigned argc, Value* vp);
/*
* Behaves like regexp.test(string), but doesn't set RegExp statics.
*
* Usage: does_match = regexp_test_no_statics(regexp, string)
*/
extern MOZ_MUST_USE bool
regexp_test_no_statics(JSContext* cx, unsigned argc, Value* vp);
/*
* Behaves like RegExp(pattern, flags).
* |pattern| should be a RegExp object, |flags| should be a raw integer value.

View file

@ -731,6 +731,88 @@ function String_localeCompare(that) {
return intl_CompareStrings(collator, S, That);
}
/**
* 13.1.2 String.prototype.toLocaleLowerCase ( [ locales ] )
*
* ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
*/
function String_toLocaleLowerCase() {
// Step 1.
RequireObjectCoercible(this);
// Step 2.
var string = ToString(this);
// Handle the common cases (no locales argument or a single string
// argument) first.
var locales = arguments.length > 0 ? arguments[0] : undefined;
var requestedLocale;
if (locales === undefined) {
// Steps 3, 6.
requestedLocale = undefined;
} else if (typeof locales === "string") {
// Steps 3, 5.
requestedLocale = intl_ValidateAndCanonicalizeLanguageTag(locales, false);
} else {
// Step 3.
var requestedLocales = CanonicalizeLocaleList(locales);
// Steps 4-6.
requestedLocale = requestedLocales.length > 0 ? requestedLocales[0] : undefined;
}
// Trivial case: When the input is empty, directly return the empty string.
if (string.length === 0)
return "";
if (requestedLocale === undefined)
requestedLocale = DefaultLocale();
// Steps 7-16.
return intl_toLocaleLowerCase(string, requestedLocale);
}
/**
* 13.1.3 String.prototype.toLocaleUpperCase ( [ locales ] )
*
* ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
*/
function String_toLocaleUpperCase() {
// Step 1.
RequireObjectCoercible(this);
// Step 2.
var string = ToString(this);
// Handle the common cases (no locales argument or a single string
// argument) first.
var locales = arguments.length > 0 ? arguments[0] : undefined;
var requestedLocale;
if (locales === undefined) {
// Steps 3, 6.
requestedLocale = undefined;
} else if (typeof locales === "string") {
// Steps 3, 5.
requestedLocale = intl_ValidateAndCanonicalizeLanguageTag(locales, false);
} else {
// Step 3.
var requestedLocales = CanonicalizeLocaleList(locales);
// Steps 4-6.
requestedLocale = requestedLocales.length > 0 ? requestedLocales[0] : undefined;
}
// Trivial case: When the input is empty, directly return the empty string.
if (string.length === 0)
return "";
if (requestedLocale === undefined)
requestedLocale = DefaultLocale();
// Steps 7-16.
return intl_toLocaleUpperCase(string, requestedLocale);
}
/* ES6 Draft May 22, 2014 21.1.2.4 */
function String_static_raw(callSite, ...substitutions) {
// Step 1 (implicit).
@ -1014,13 +1096,15 @@ _SetCanonicalName(String_static_trimEnd, "trimEnd");
function String_static_toLocaleLowerCase(string) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toLocaleLowerCase');
return callFunction(std_String_toLocaleLowerCase, string);
var locales = arguments.length > 1 ? arguments[1] : undefined;
return callFunction(String_toLocaleLowerCase, string, locales);
}
function String_static_toLocaleUpperCase(string) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toLocaleUpperCase');
return callFunction(std_String_toLocaleUpperCase, string);
var locales = arguments.length > 1 ? arguments[1] : undefined;
return callFunction(String_toLocaleUpperCase, string, locales);
}
function String_static_normalize(string) {

View file

@ -50,7 +50,7 @@ const JSFunctionSpec SymbolObject::staticMethods[] = {
};
JSObject*
SymbolObject::initClass(JSContext* cx, HandleObject obj)
SymbolObject::initClass(JSContext* cx, HandleObject obj, bool defineMembers)
{
Handle<GlobalObject*> global = obj.as<GlobalObject>();
@ -66,25 +66,33 @@ SymbolObject::initClass(JSContext* cx, HandleObject obj)
if (!ctor)
return nullptr;
// Define the well-known symbol properties, such as Symbol.iterator.
ImmutablePropertyNamePtr* names = cx->names().wellKnownSymbolNames();
RootedValue value(cx);
unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT;
WellKnownSymbols* wks = cx->runtime()->wellKnownSymbols;
for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
value.setSymbol(wks->get(i));
if (!NativeDefineProperty(cx, ctor, names[i], value, nullptr, nullptr, attrs))
return nullptr;
if (defineMembers) {
// Define the well-known symbol properties, such as Symbol.iterator.
ImmutablePropertyNamePtr* names = cx->names().wellKnownSymbolNames();
RootedValue value(cx);
unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT;
WellKnownSymbols* wks = cx->runtime()->wellKnownSymbols;
for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
value.setSymbol(wks->get(i));
if (!NativeDefineProperty(cx, ctor, names[i], value, nullptr, nullptr, attrs))
return nullptr;
}
}
if (!LinkConstructorAndPrototype(cx, ctor, proto) ||
!DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
!DefineToStringTag(cx, proto, cx->names().Symbol) ||
!DefinePropertiesAndFunctions(cx, ctor, nullptr, staticMethods) ||
!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Symbol, ctor, proto))
{
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
if (defineMembers) {
if (!DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
!DefineToStringTag(cx, proto, cx->names().Symbol) ||
!DefinePropertiesAndFunctions(cx, ctor, nullptr, staticMethods))
{
return nullptr;
}
}
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Symbol, ctor, proto))
return nullptr;
return proto;
}
@ -258,5 +266,11 @@ SymbolObject::descriptionGetter(JSContext* cx, unsigned argc, Value* vp)
JSObject*
js::InitSymbolClass(JSContext* cx, HandleObject obj)
{
return SymbolObject::initClass(cx, obj);
return SymbolObject::initClass(cx, obj, true);
}
JSObject*
js::InitBareSymbolCtor(JSContext* cx, HandleObject obj)
{
return SymbolObject::initClass(cx, obj, false);
}

View file

@ -21,7 +21,7 @@ class SymbolObject : public NativeObject
static const Class class_;
static JSObject* initClass(JSContext* cx, js::HandleObject obj);
static JSObject* initClass(JSContext* cx, js::HandleObject obj, bool defineMembers);
/*
* Creates a new Symbol object boxing the given primitive Symbol. The
@ -63,6 +63,9 @@ class SymbolObject : public NativeObject
extern JSObject*
InitSymbolClass(JSContext* cx, HandleObject obj);
extern JSObject*
InitBareSymbolCtor(JSContext* cx, HandleObject obj);
} /* namespace js */
#endif /* builtin_SymbolObject_h */

View file

@ -50,6 +50,8 @@
// Do not create an alias to a self-hosted builtin, otherwise it will be cloned
// twice.
//
// Symbol is a bare constructor without properties or methods.
var std_Symbol = Symbol;
// WeakMap is a bare constructor without properties or methods.
var std_WeakMap = WeakMap;
// StopIteration is a bare constructor without properties or methods.
@ -78,12 +80,6 @@ MakeConstructible(Record, {});
/********** Abstract operations defined in ECMAScript Language Specification **********/
/* Spec: ECMAScript Language Specification, 5.1 edition, 8.12.6 and 11.8.7 */
function HasProperty(o, p) {
return p in o;
}
/* Spec: ECMAScript Language Specification, 5.1 edition, 9.2 and 11.4.9 */
function ToBoolean(v) {
return !!v;

View file

@ -8,12 +8,14 @@
#include "builtin/intl/Collator.h"
#include "mozilla/Assertions.h"
#include "mozilla/Span.h"
#include "jsapi.h"
#include "jscntxt.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/LanguageTag.h"
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/intl/SharedIntlData.h"
#include "js/TypeDecls.h"
@ -24,7 +26,6 @@
#include "jsobjinlines.h"
using namespace js;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::ReportInternalError;
using js::intl::SharedIntlData;
@ -79,64 +80,36 @@ static const JSFunctionSpec collator_methods[] = {
* ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
*/
static bool
Collator(JSContext* cx, const CallArgs& args, bool construct)
Collator(JSContext* cx, const CallArgs& args)
{
RootedObject obj(cx);
// Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
// We're following ECMA-402 1st Edition when Collator is called because of
// backward compatibility issues.
// See https://github.com/tc39/ecma402/issues/57
if (!construct) {
// ES Intl 1st ed., 10.1.2.1 step 3
JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
if (!intl)
return false;
RootedValue self(cx, args.thisv());
if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
// ES Intl 1st ed., 10.1.2.1 step 4
obj = ToObject(cx, self);
if (!obj)
return false;
// ES Intl 1st ed., 10.1.2.1 step 5
bool extensible;
if (!IsExtensible(cx, obj, &extensible))
return false;
if (!extensible)
return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
} else {
// ES Intl 1st ed., 10.1.2.1 step 3.a
construct = true;
}
}
if (construct) {
// Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
if (!proto) {
proto = GlobalObject::getOrCreateCollatorPrototype(cx, cx->global());
if (!proto)
return false;
}
obj = NewObjectWithGivenProto<CollatorObject>(cx, proto);
if (!obj)
return false;
obj->as<NativeObject>().setReservedSlot(CollatorObject::INTERNALS_SLOT, NullValue());
obj->as<NativeObject>().setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr));
}
RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
// Step 6.
if (!intl::InitializeObject(cx, obj, cx->names().InitializeCollator, locales, options))
// Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
args.rval().setObject(*obj);
if (!proto) {
proto = GlobalObject::getOrCreateCollatorPrototype(cx, cx->global());
if (!proto)
return false;
}
Rooted<CollatorObject*> collator(cx, NewObjectWithGivenProto<CollatorObject>(cx, proto));
if (!collator)
return false;
collator->setReservedSlot(CollatorObject::INTERNALS_SLOT, NullValue());
collator->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr));
RootedValue locales(cx, args.get(0));
RootedValue options(cx, args.get(1));
// Step 6.
if (!intl::InitializeObject(cx, collator, cx->names().InitializeCollator, locales, options))
return false;
args.rval().setObject(*collator);
return true;
}
@ -144,7 +117,7 @@ static bool
Collator(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return Collator(cx, args, args.isConstructing());
return Collator(cx, args);
}
bool
@ -153,9 +126,8 @@ js::intl_Collator(JSContext* cx, unsigned argc, Value* vp)
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(!args.isConstructing());
// intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot
// be used with "new", but it still has to be treated as a constructor.
return Collator(cx, args, true);
return Collator(cx, args);
}
void
@ -163,15 +135,9 @@ js::CollatorObject::finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(fop->onMainThread());
// This is-undefined check shouldn't be necessary, but for internal
// brokenness in object allocation code. For the moment, hack around it by
// explicitly guarding against the possibility of the reserved slot not
// containing a private. See bug 949220.
const Value& slot = obj->as<NativeObject>().getReservedSlot(CollatorObject::UCOLLATOR_SLOT);
if (!slot.isUndefined()) {
if (UCollator* coll = static_cast<UCollator*>(slot.toPrivate()))
ucol_close(coll);
}
const Value& slot = obj->as<CollatorObject>().getReservedSlot(CollatorObject::UCOLLATOR_SLOT);
if (UCollator* coll = static_cast<UCollator*>(slot.toPrivate()))
ucol_close(coll);
}
JSObject*
@ -182,10 +148,9 @@ js::CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObjec
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &CollatorObject::class_));
RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
if (!proto)
return nullptr;
proto->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
@ -213,14 +178,6 @@ js::CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObjec
return nullptr;
}
RootedValue options(cx);
if (!intl::CreateDefaultOptions(cx, &options))
return nullptr;
// 10.2.1 and 10.3
if (!intl::InitializeObject(cx, proto, cx->names().InitializeCollator, UndefinedHandleValue, options))
return nullptr;
// 8.1
RootedValue ctorValue(cx, ObjectValue(*ctor));
if (!DefineProperty(cx, Intl, cx->names().Collator, ctorValue, nullptr, nullptr, 0))
@ -229,19 +186,6 @@ js::CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObjec
return proto;
}
bool
js::intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedValue result(cx);
if (!GetAvailableLocales(cx, ucol_countAvailable, ucol_getAvailable, &result))
return false;
args.rval().set(result);
return true;
}
bool
js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp)
{
@ -271,6 +215,14 @@ js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp)
return false;
uint32_t index = 0;
// The first element of the collations array must be |null| per
// ES2017 Intl, 10.2.3 Internal Slots.
if (!DefineElement(cx, collations, index++, NullHandleValue))
return false;
RootedValue element(cx);
for (uint32_t i = 0; i < count; i++) {
const char* collation = uenum_next(values, nullptr, &status);
if (U_FAILURE(status)) {
@ -285,21 +237,11 @@ js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp)
if (StringsAreEqual(collation, "standard") || StringsAreEqual(collation, "search"))
continue;
// ICU returns old-style keyword values; map them to BCP 47 equivalents
// (see http://bugs.icu-project.org/trac/ticket/9620).
if (StringsAreEqual(collation, "dictionary"))
collation = "dict";
else if (StringsAreEqual(collation, "gb2312han"))
collation = "gb2312";
else if (StringsAreEqual(collation, "phonebook"))
collation = "phonebk";
else if (StringsAreEqual(collation, "traditional"))
collation = "trad";
RootedString jscollation(cx, JS_NewStringCopyZ(cx, collation));
// ICU returns old-style keyword values; map them to BCP 47 equivalents.
JSString* jscollation = JS_NewStringCopyZ(cx, uloc_toUnicodeLocaleType("co", collation));
if (!jscollation)
return false;
RootedValue element(cx, StringValue(jscollation));
element = StringValue(jscollation);
if (!DefineElement(cx, collations, index++, element))
return false;
}
@ -313,7 +255,7 @@ js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp)
* of the given Collator.
*/
static UCollator*
NewUCollator(JSContext* cx, HandleObject collator)
NewUCollator(JSContext* cx, Handle<CollatorObject*> collator)
{
RootedValue value(cx);
@ -343,32 +285,33 @@ NewUCollator(JSContext* cx, HandleObject collator)
return nullptr;
if (StringsAreEqual(usage, "search")) {
// ICU expects search as a Unicode locale extension on locale.
// Unicode locale extensions must occur before private use extensions.
const char* oldLocale = locale.ptr();
const char* p;
size_t index;
size_t localeLen = strlen(oldLocale);
if ((p = strstr(oldLocale, "-x-")))
index = p - oldLocale;
else
index = localeLen;
const char* insert;
if ((p = strstr(oldLocale, "-u-")) && static_cast<size_t>(p - oldLocale) < index) {
index = p - oldLocale + 2;
insert = "-co-search";
} else {
insert = "-u-co-search";
}
size_t insertLen = strlen(insert);
char* newLocale = cx->pod_malloc<char>(localeLen + insertLen + 1);
if (!newLocale)
intl::LanguageTag tag(cx);
if (!intl::LanguageTagParser::parse(
cx, mozilla::MakeCStringSpan(locale.ptr()), tag)) {
return nullptr;
memcpy(newLocale, oldLocale, index);
memcpy(newLocale + index, insert, insertLen);
memcpy(newLocale + index + insertLen, oldLocale + index, localeLen - index + 1); // '\0'
}
JS::RootedVector<intl::UnicodeExtensionKeyword> keywords(cx);
if (!keywords.emplaceBack("co", cx->names().search)) {
return nullptr;
}
// |ApplyUnicodeExtensionToTag| applies the new keywords to the front of
// the Unicode extension subtag. We're then relying on ICU to follow RFC
// 6067, which states that any trailing keywords using the same key
// should be ignored.
if (!intl::ApplyUnicodeExtensionToTag(cx, tag, keywords)) {
return nullptr;
}
locale.clear();
locale.initBytes(newLocale);
locale.encodeLatin1(cx, tag.toString(cx));
if (!locale) {
return nullptr;
}
} else {
MOZ_ASSERT(StringsAreEqual(usage, "sort"));
}
// We don't need to look at the collation property - it can only be set
@ -417,8 +360,10 @@ NewUCollator(JSContext* cx, HandleObject collator)
uCaseFirst = UCOL_UPPER_FIRST;
else if (StringsAreEqual(caseFirst, "lower"))
uCaseFirst = UCOL_LOWER_FIRST;
else
else {
MOZ_ASSERT(StringsAreEqual(caseFirst, "false"));
uCaseFirst = UCOL_OFF;
}
}
UErrorCode status = U_ZERO_ERROR;
@ -490,40 +435,38 @@ js::intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp)
MOZ_ASSERT(args[2].isString());
Rooted<CollatorObject*> collator(cx, &args[0].toObject().as<CollatorObject>());
// Obtain a UCollator object, cached if possible.
// Obtain a cached UCollator object.
// XXX Does this handle Collator instances from other globals correctly?
bool isCollatorInstance = collator->getClass() == &CollatorObject::class_;
UCollator* coll;
if (isCollatorInstance) {
void* priv = collator->getReservedSlot(CollatorObject::UCOLLATOR_SLOT).toPrivate();
coll = static_cast<UCollator*>(priv);
if (!coll) {
coll = NewUCollator(cx, collator);
if (!coll)
return false;
collator->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(coll));
}
} else {
// There's no good place to cache the ICU collator for an object
// that has been initialized as a Collator but is not a Collator
// instance. One possibility might be to add a Collator instance as an
// internal property to each such object.
void* priv = collator->getReservedSlot(CollatorObject::UCOLLATOR_SLOT).toPrivate();
UCollator* coll = static_cast<UCollator*>(priv);
if (!coll) {
coll = NewUCollator(cx, collator);
if (!coll)
return false;
collator->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(coll));
}
// Use the UCollator to actually compare the strings.
RootedString str1(cx, args[1].toString());
RootedString str2(cx, args[2].toString());
RootedValue result(cx);
bool success = intl_CompareStrings(cx, coll, str1, str2, &result);
return intl_CompareStrings(cx, coll, str1, str2, args.rval());
}
if (!isCollatorInstance)
ucol_close(coll);
if (!success)
bool
js::intl_isUpperCaseFirst(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isString());
SharedIntlData& sharedIntlData = cx->sharedIntlData;
RootedString locale(cx, args[0].toString());
bool isUpperFirst;
if (!sharedIntlData.isUpperCaseFirst(cx, locale, &isUpperFirst))
return false;
args.rval().set(result);
args.rval().setBoolean(isUpperFirst);
return true;
}

View file

@ -52,17 +52,6 @@ CreateCollatorPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
extern MOZ_MUST_USE bool
intl_Collator(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an object indicating the supported locales for collation
* by having a true-valued property for each such locale with the
* canonicalized language tag as the property name. The object has no
* prototype.
*
* Usage: availableLocales = intl_Collator_availableLocales()
*/
extern MOZ_MUST_USE bool
intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an array with the collation type identifiers per Unicode
* Technical Standard 35, Unicode Locale Data Markup Language, for the
@ -87,6 +76,15 @@ intl_availableCollations(JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool
intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns true if the given locale sorts upper-case before lower-case
* characters.
*
* Usage: result = intl_isUpperCaseFirst(locale)
*/
extern MOZ_MUST_USE bool
intl_isUpperCaseFirst(JSContext* cx, unsigned argc, Value* vp);
} // namespace js

View file

@ -5,18 +5,6 @@
/********** Intl.Collator **********/
/**
* Mapping from Unicode extension keys for collation to options properties,
* their types and permissible values.
*
* Spec: ECMAScript Internationalization API Specification, 10.1.1.
*/
var collatorKeyMappings = {
kn: {property: "numeric", type: "boolean"},
kf: {property: "caseFirst", type: "string", values: ["upper", "lower", "false"]}
};
/**
* Compute an internal properties object from |lazyCollatorData|.
*/
@ -26,80 +14,64 @@ function resolveCollatorInternals(lazyCollatorData)
var internalProps = std_Object_create(null);
// Step 7.
internalProps.usage = lazyCollatorData.usage;
// Step 8.
var Collator = collatorInternalProperties;
// Step 9.
// Step 5.
internalProps.usage = lazyCollatorData.usage;
// Steps 6-7.
var collatorIsSorting = lazyCollatorData.usage === "sort";
var localeData = collatorIsSorting
? Collator.sortLocaleData
: Collator.searchLocaleData;
// Compute effective locale.
// Step 14.
// Step 16.
var relevantExtensionKeys = Collator.relevantExtensionKeys;
// Step 15.
var r = ResolveLocale(callFunction(Collator.availableLocales, Collator),
// Step 17.
var r = ResolveLocale("Collator",
lazyCollatorData.requestedLocales,
lazyCollatorData.opt,
relevantExtensionKeys,
localeData);
// Step 16.
// Step 18.
internalProps.locale = r.locale;
// Steps 17-19.
var key, property, value, mapping;
var i = 0, len = relevantExtensionKeys.length;
while (i < len) {
// Step 19.a.
key = relevantExtensionKeys[i];
if (key === "co") {
// Step 19.b.
property = "collation";
value = r.co === null ? "default" : r.co;
} else {
// Step 19.c.
mapping = collatorKeyMappings[key];
property = mapping.property;
value = r[key];
if (mapping.type === "boolean")
value = value === "true";
}
// Step 19.
var collation = r.co;
// Step 19.d.
internalProps[property] = value;
// Step 20.
if (collation === null)
collation = "default";
// Step 19.e.
i++;
}
// Step 21.
internalProps.collation = collation;
// Step 22.
internalProps.numeric = r.kn === "true";
// Step 23.
internalProps.caseFirst = r.kf;
// Compute remaining collation options.
// Steps 21-22.
// Step 25.
var s = lazyCollatorData.rawSensitivity;
if (s === undefined) {
if (collatorIsSorting) {
// Step 21.a.
s = "variant";
} else {
// Step 21.b.
var dataLocale = r.dataLocale;
var dataLocaleData = localeData(dataLocale);
s = dataLocaleData.sensitivity;
}
// In theory the default sensitivity for the "search" collator is
// locale dependent; in reality the CLDR/ICU default strength is
// always tertiary. Therefore use "variant" as the default value for
// both collation modes.
s = "variant";
}
// Step 26.
internalProps.sensitivity = s;
// Step 24.
// Step 28.
internalProps.ignorePunctuation = lazyCollatorData.ignorePunctuation;
// Step 25.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
// object using |setInternalProperties|.
return internalProps;
@ -107,11 +79,13 @@ function resolveCollatorInternals(lazyCollatorData)
/**
* Returns an object containing the Collator internal properties of |obj|, or
* throws a TypeError if |obj| isn't Collator-initialized.
* Returns an object containing the Collator internal properties of |obj|.
*/
function getCollatorInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "Collator", methodName);
function getCollatorInternals(obj) {
assert(IsObject(obj), "getCollatorInternals called with non-object");
assert(IsCollator(obj), "getCollatorInternals called with non-Collator");
var internals = getIntlObjectInternals(obj);
assert(internals.type === "Collator", "bad type escaped getIntlObjectInternals");
// If internal properties have already been computed, use them.
@ -138,14 +112,8 @@ function getCollatorInternals(obj, methodName) {
* Spec: ECMAScript Internationalization API Specification, 10.1.1.
*/
function InitializeCollator(collator, locales, options) {
assert(IsObject(collator), "InitializeCollator");
// Step 1.
if (isInitializedIntlObject(collator))
ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
// Step 2.
var internals = initializeIntlObject(collator);
assert(IsObject(collator), "InitializeCollator called with non-object");
assert(IsCollator(collator), "InitializeCollator called with non-Collator");
// Lazy Collator data has the following structure:
//
@ -167,11 +135,11 @@ function InitializeCollator(collator, locales, options) {
// subset of them.
var lazyCollatorData = std_Object_create(null);
// Step 3.
// Step 1.
var requestedLocales = CanonicalizeLocaleList(locales);
lazyCollatorData.requestedLocales = requestedLocales;
// Steps 4-5.
// Steps 2-3.
//
// If we ever need more speed here at startup, we should try to detect the
// case where |options === undefined| and Object.prototype hasn't been
@ -184,42 +152,43 @@ function InitializeCollator(collator, locales, options) {
options = ToObject(options);
// Compute options that impact interpretation of locale.
// Step 6.
// Step 4.
var u = GetOption(options, "usage", "string", ["sort", "search"], "sort");
lazyCollatorData.usage = u;
// Step 10.
// Step 8.
var opt = new Record();
lazyCollatorData.opt = opt;
// Steps 11-12.
// Steps 9-10.
var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
// Step 13, unrolled.
// Steps 11-13.
var numericValue = GetOption(options, "numeric", "boolean", undefined, undefined);
if (numericValue !== undefined)
numericValue = numericValue ? 'true' : 'false';
opt.kn = numericValue;
// Steps 14-15.
var caseFirstValue = GetOption(options, "caseFirst", "string", ["upper", "lower", "false"], undefined);
opt.kf = caseFirstValue;
// Compute remaining collation options.
// Step 20.
// Step 24.
var s = GetOption(options, "sensitivity", "string",
["base", "accent", "case", "variant"], undefined);
lazyCollatorData.rawSensitivity = s;
// Step 23.
// Step 27.
var ip = GetOption(options, "ignorePunctuation", "boolean", undefined, false);
lazyCollatorData.ignorePunctuation = ip;
// Step 26.
// Step 29.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
setLazyData(internals, "Collator", lazyCollatorData);
initializeIntlObject(collator, "Collator", lazyCollatorData);
}
@ -233,9 +202,13 @@ function InitializeCollator(collator, locales, options) {
function Intl_Collator_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
var availableLocales = callFunction(collatorInternalProperties.availableLocales,
collatorInternalProperties);
// Step 1.
var availableLocales = "Collator";
// Step 2.
var requestedLocales = CanonicalizeLocaleList(locales);
// Step 3.
return SupportedLocales(availableLocales, requestedLocales, options);
}
@ -248,46 +221,106 @@ function Intl_Collator_supportedLocalesOf(locales /*, options*/) {
var collatorInternalProperties = {
sortLocaleData: collatorSortLocaleData,
searchLocaleData: collatorSearchLocaleData,
_availableLocales: null,
availableLocales: function()
{
var locales = this._availableLocales;
if (locales)
return locales;
locales = intl_Collator_availableLocales();
addSpecialMissingLanguageTags(locales);
return (this._availableLocales = locales);
},
relevantExtensionKeys: ["co", "kn"]
relevantExtensionKeys: ["co", "kn", "kf"]
};
function collatorSortLocaleData(locale) {
var collations = intl_availableCollations(locale);
callFunction(std_Array_unshift, collations, null);
return {
co: collations,
kn: ["false", "true"]
};
}
/**
* Returns the actual locale used when a collator for |locale| is constructed.
*/
function collatorActualLocale(locale) {
assert(typeof locale === "string", "locale should be string");
function collatorSearchLocaleData(locale) {
return {
co: [null],
kn: ["false", "true"],
// In theory the default sensitivity is locale dependent;
// in reality the CLDR/ICU default strength is always tertiary.
sensitivity: "variant"
};
// If |locale| is the default locale (e.g. da-DK), but only supported
// through a fallback (da), we need to get the actual locale before we
// can call intl_isUpperCaseFirst. Also see intl_BestAvailableLocale.
return BestAvailableLocaleIgnoringDefault("Collator", locale);
}
/**
* Function to be bound and returned by Intl.Collator.prototype.format.
* Returns the default caseFirst values for the given locale. The first
* element in the returned array denotes the default value per ES2017 Intl,
* 9.1 Internal slots of Service Constructors.
*/
function collatorSortCaseFirst(locale) {
var actualLocale = collatorActualLocale(locale);
if (intl_isUpperCaseFirst(actualLocale))
return ["upper", "false", "lower"];
// Default caseFirst values for all other languages.
return ["false", "lower", "upper"];
}
/**
* Returns the default caseFirst value for the given locale.
*/
function collatorSortCaseFirstDefault(locale) {
var actualLocale = collatorActualLocale(locale);
if (intl_isUpperCaseFirst(actualLocale))
return "upper";
// Default caseFirst value for all other languages.
return "false";
}
function collatorSortLocaleData() {
/* eslint-disable object-shorthand */
return {
co: intl_availableCollations,
kn: function() {
return ["false", "true"];
},
kf: collatorSortCaseFirst,
default: {
co: function() {
// The first element of the collations array must be |null|
// per ES2017 Intl, 10.2.3 Internal Slots.
return null;
},
kn: function() {
return "false";
},
kf: collatorSortCaseFirstDefault,
}
};
/* eslint-enable object-shorthand */
}
function collatorSearchLocaleData() {
/* eslint-disable object-shorthand */
return {
co: function() {
return [null];
},
kn: function() {
return ["false", "true"];
},
kf: function() {
return ["false", "lower", "upper"];
},
default: {
co: function() {
return null;
},
kn: function() {
return "false";
},
kf: function() {
return "false";
},
}
};
/* eslint-enable object-shorthand */
}
/**
* Function to be bound and returned by Intl.Collator.prototype.compare.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
* Spec: ECMAScript Internationalization API Specification, 10.3.3.1.
*/
function collatorCompareToBind(x, y) {
// Steps 1.a.i-ii implemented by ECMAScript declaration binding instantiation,
@ -307,49 +340,60 @@ function collatorCompareToBind(x, y) {
* than 0 if x > y according to the sort order for the locale and collation
* options of this Collator object.
*
* Spec: ECMAScript Internationalization API Specification, 10.3.2.
* Spec: ECMAScript Internationalization API Specification, 10.3.3.
*/
function Intl_Collator_compare_get() {
// Check "this Collator object" per introduction of section 10.3.
var internals = getCollatorInternals(this, "compare");
// Step 1.
if (internals.boundCompare === undefined) {
// Step 1.a.
var F = collatorCompareToBind;
var collator = this;
// Step 1.b-d.
var bc = callFunction(FunctionBind, F, this);
internals.boundCompare = bc;
// Steps 2-3.
if (!IsObject(collator) || !IsCollator(collator))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "Collator", "compare", "Collator");
var internals = getCollatorInternals(collator);
// Step 4.
if (internals.boundCompare === undefined) {
// Steps 4.a-b.
var F = callFunction(FunctionBind, collatorCompareToBind, collator);
// Step 4.c.
internals.boundCompare = F;
}
// Step 2.
// Step 5.
return internals.boundCompare;
}
_SetCanonicalName(Intl_Collator_compare_get, "get compare");
/**
* Returns the resolved options for a Collator object.
*
* Spec: ECMAScript Internationalization API Specification, 10.3.3 and 10.4.
* Spec: ECMAScript Internationalization API Specification, 10.3.4.
*/
function Intl_Collator_resolvedOptions() {
// Check "this Collator object" per introduction of section 10.3.
var internals = getCollatorInternals(this, "resolvedOptions");
// Step 1.
var collator = this;
// Steps 2-3.
if (!IsObject(collator) || !IsCollator(collator))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "Collator", "resolvedOptions", "Collator");
var internals = getCollatorInternals(collator);
// Steps 4-5.
var result = {
locale: internals.locale,
usage: internals.usage,
sensitivity: internals.sensitivity,
ignorePunctuation: internals.ignorePunctuation
ignorePunctuation: internals.ignorePunctuation,
collation: internals.collation,
numeric: internals.numeric,
caseFirst: internals.caseFirst,
};
var relevantExtensionKeys = collatorInternalProperties.relevantExtensionKeys;
for (var i = 0; i < relevantExtensionKeys.length; i++) {
var key = relevantExtensionKeys[i];
var property = (key === "co") ? "collation" : collatorKeyMappings[key].property;
_DefineDataProperty(result, property, internals[property]);
}
// Step 6.
return result;
}

View file

@ -19,26 +19,10 @@
#include "jsobjinlines.h"
bool
js::intl::CreateDefaultOptions(JSContext* cx, MutableHandleValue defaultOptions)
{
RootedObject options(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
if (!options)
return false;
defaultOptions.setObject(*options);
return true;
}
bool
js::intl::InitializeObject(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
HandleValue locales, HandleValue options)
{
RootedValue initializerValue(cx);
if (!GlobalObject::getIntrinsicValue(cx, cx->global(), initializer, &initializerValue))
return false;
MOZ_ASSERT(initializerValue.isObject());
MOZ_ASSERT(initializerValue.toObject().is<JSFunction>());
FixedInvokeArgs<3> args(cx);
args[0].setObject(*obj);
@ -47,7 +31,33 @@ js::intl::InitializeObject(JSContext* cx, HandleObject obj, Handle<PropertyName*
RootedValue thisv(cx, NullValue());
RootedValue ignored(cx);
return js::Call(cx, initializerValue, thisv, args, &ignored);
if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, &ignored))
return false;
MOZ_ASSERT(ignored.isUndefined(),
"Unexpected return value from non-legacy Intl object initializer");
return true;
}
bool
js::intl::LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
HandleValue thisValue, HandleValue locales, HandleValue options,
DateTimeFormatOptions dtfOptions, MutableHandleValue result)
{
FixedInvokeArgs<5> args(cx);
args[0].setObject(*obj);
args[1].set(thisValue);
args[2].set(locales);
args[3].set(options);
args[4].setBoolean(dtfOptions == DateTimeFormatOptions::EnableMozExtensions);
RootedValue thisv(cx, NullValue());
if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, result))
return false;
MOZ_ASSERT(result.isObject(), "Legacy Intl object initializer must return an object");
return true;
}
/**
@ -56,21 +66,12 @@ js::intl::InitializeObject(JSContext* cx, HandleObject obj, Handle<PropertyName*
JSObject*
js::intl::GetInternalsObject(JSContext* cx, HandleObject obj)
{
RootedValue getInternalsValue(cx);
if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().getInternals,
&getInternalsValue))
{
return nullptr;
}
MOZ_ASSERT(getInternalsValue.isObject());
MOZ_ASSERT(getInternalsValue.toObject().is<JSFunction>());
FixedInvokeArgs<1> args(cx);
args[0].setObject(*obj);
RootedValue v(cx, NullValue());
if (!js::Call(cx, getInternalsValue, v, args, &v))
if (!js::CallSelfHostedFunction(cx, cx->names().getInternals, v, args, &v))
return nullptr;
return &v.toObject();
@ -82,34 +83,10 @@ js::intl::ReportInternalError(JSContext* cx)
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
}
bool
js::intl::GetAvailableLocales(JSContext* cx, CountAvailable countAvailable,
GetAvailable getAvailable, MutableHandleValue result)
{
RootedObject locales(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
if (!locales)
return false;
const js::intl::OldStyleLanguageTagMapping
js::intl::oldStyleLanguageTagMappings[] = {
{"pa-PK", "pa-Arab-PK"}, {"zh-CN", "zh-Hans-CN"},
{"zh-HK", "zh-Hant-HK"}, {"zh-SG", "zh-Hans-SG"},
{"zh-TW", "zh-Hant-TW"},
};
uint32_t count = countAvailable();
RootedValue t(cx, BooleanValue(true));
for (uint32_t i = 0; i < count; i++) {
const char* locale = getAvailable(i);
auto lang = DuplicateString(cx, locale);
if (!lang)
return false;
char* p;
while ((p = strchr(lang.get(), '_')))
*p = '-';
RootedAtom a(cx, Atomize(cx, lang.get(), strlen(lang.get())));
if (!a)
return false;
if (!DefineProperty(cx, locales, a->asPropertyName(), t, nullptr, nullptr,
JSPROP_ENUMERATE))
{
return false;
}
}
result.setObject(*locales);
return true;
}

View file

@ -26,12 +26,6 @@ namespace js {
namespace intl {
/**
* Setup the |options| argument of |IntlInitialize|
*/
extern bool
CreateDefaultOptions(JSContext* cx, MutableHandleValue defaultOptions);
/**
* Initialize a new Intl.* object using the named self-hosted function.
*/
@ -39,6 +33,22 @@ extern bool
InitializeObject(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
HandleValue locales, HandleValue options);
/**
* Initialize an existing object as an Intl.* object using the named
* self-hosted function. This is only for a few old Intl.* constructors, for
* legacy reasons -- new ones should use the function above instead.
*/
enum class DateTimeFormatOptions
{
Standard,
EnableMozExtensions,
};
extern bool
LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
HandleValue thisValue, HandleValue locales, HandleValue options,
DateTimeFormatOptions dtfOptions, MutableHandleValue result);
/**
* Returns the object holding the internal properties for obj.
*/
@ -61,6 +71,32 @@ StringsAreEqual(JSAutoByteString& s1, const char* s2)
return !strcmp(s1.ptr(), s2);
}
/**
* The last-ditch locale is used if none of the available locales satisfies a
* request. "en-GB" is used based on the assumptions that English is the most
* common second language, that both en-GB and en-US are normally available in
* an implementation, and that en-GB is more representative of the English used
* in other locales.
*/
static inline const char* LastDitchLocale() { return "en-GB"; }
/**
* Certain old, commonly-used language tags that lack a script, are expected to
* nonetheless imply one. This object maps these old-style tags to modern
* equivalents.
*/
struct OldStyleLanguageTagMapping {
const char* const oldStyle;
const char* const modernStyle;
// Provide a constructor to catch missing initializers in the mappings array.
constexpr OldStyleLanguageTagMapping(const char* oldStyle,
const char* modernStyle)
: oldStyle(oldStyle), modernStyle(modernStyle) {}
};
extern const OldStyleLanguageTagMapping oldStyleLanguageTagMappings[5];
static inline const char*
IcuLocale(const char* locale)
{
@ -79,9 +115,9 @@ static_assert(mozilla::IsSame<UChar, char16_t>::value,
// buffer's entire inline capacity before growing it and heap-allocating.
static const size_t INITIAL_CHAR_BUFFER_SIZE = 32;
template <typename ICUStringFunction, size_t InlineCapacity>
template <typename ICUStringFunction, typename CharT, size_t InlineCapacity>
static int32_t
CallICU(JSContext* cx, Vector<char16_t, InlineCapacity>& chars, const ICUStringFunction& strFn)
CallICU(JSContext* cx, Vector<CharT, InlineCapacity>& chars, const ICUStringFunction& strFn)
{
MOZ_ASSERT(chars.length() == 0);
MOZ_ALWAYS_TRUE(chars.resize(InlineCapacity));
@ -119,25 +155,6 @@ CallICU(JSContext* cx, const ICUStringFunction& strFn)
return NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size));
}
// CountAvailable and GetAvailable describe the signatures used for ICU API
// to determine available locales for various functionality.
using CountAvailable = int32_t (*)();
using GetAvailable = const char* (*)(int32_t localeIndex);
/**
* Return an object whose own property names are the locales indicated as
* available by |countAvailable| that provides an overall count, and by
* |getAvailable| that when called passing a number less than that count,
* returns the corresponding locale as a borrowed string. For example:
*
* RootedValue v(cx);
* if (!GetAvailableLocales(cx, unum_countAvailable, unum_getAvailable, &v))
* return false;
*/
extern bool
GetAvailableLocales(JSContext* cx, CountAvailable countAvailable, GetAvailable getAvailable,
JS::MutableHandle<JS::Value> result);
} // namespace intl
} // namespace js

File diff suppressed because it is too large Load diff

View file

@ -9,12 +9,14 @@
#include "mozilla/Assertions.h"
#include "mozilla/Range.h"
#include "mozilla/Span.h"
#include "jscntxt.h"
#include "jsfriendapi.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/LanguageTag.h"
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/intl/SharedIntlData.h"
#include "builtin/intl/TimeZoneDataGenerated.h"
@ -33,7 +35,7 @@ using JS::ClippedTime;
using JS::TimeClip;
using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::DateTimeFormatOptions;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::SharedIntlData;
@ -89,72 +91,58 @@ static const JSFunctionSpec dateTimeFormat_methods[] = {
* ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
*/
static bool
DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct)
DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct, DateTimeFormatOptions dtfOptions)
{
RootedObject obj(cx);
// Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
// We're following ECMA-402 1st Edition when DateTimeFormat is called
// because of backward compatibility issues.
// See https://github.com/tc39/ecma402/issues/57
if (!construct) {
// ES Intl 1st ed., 12.1.2.1 step 3
JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
if (!intl)
return false;
RootedValue self(cx, args.thisv());
if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
// ES Intl 1st ed., 12.1.2.1 step 4
obj = ToObject(cx, self);
if (!obj)
return false;
// ES Intl 1st ed., 12.1.2.1 step 5
bool extensible;
if (!IsExtensible(cx, obj, &extensible))
return false;
if (!extensible)
return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
} else {
// ES Intl 1st ed., 12.1.2.1 step 3.a
construct = true;
}
}
if (construct) {
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
if (!proto) {
proto = GlobalObject::getOrCreateDateTimeFormatPrototype(cx, cx->global());
if (!proto)
return false;
}
obj = NewObjectWithGivenProto<DateTimeFormatObject>(cx, proto);
if (!obj)
return false;
obj->as<NativeObject>().setReservedSlot(DateTimeFormatObject::INTERNALS_SLOT, NullValue());
obj->as<NativeObject>().setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(nullptr));
}
RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
// Step 3.
if (!intl::InitializeObject(cx, obj, cx->names().InitializeDateTimeFormat, locales, options))
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
args.rval().setObject(*obj);
return true;
if (!proto) {
proto = GlobalObject::getOrCreateDateTimeFormatPrototype(cx, cx->global());
if (!proto)
return false;
}
Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
dateTimeFormat = NewObjectWithGivenProto<DateTimeFormatObject>(cx, proto);
if (!dateTimeFormat)
return false;
dateTimeFormat->setReservedSlot(DateTimeFormatObject::INTERNALS_SLOT, NullValue());
dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT,
PrivateValue(nullptr));
RootedValue thisValue(cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv());
RootedValue locales(cx, args.get(0));
RootedValue options(cx, args.get(1));
// Step 3.
return intl::LegacyIntlInitialize(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat,
thisValue, locales, options, dtfOptions, args.rval());
}
static bool
DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return DateTimeFormat(cx, args, args.isConstructing());
return DateTimeFormat(cx, args, args.isConstructing(), DateTimeFormatOptions::Standard);
}
static bool
MozDateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Don't allow to call mozIntl.DateTimeFormat as a function. That way we
// don't need to worry how to handle the legacy initialization semantics
// when applied on mozIntl.DateTimeFormat.
if (!ThrowIfNotConstructing(cx, args, "mozIntl.DateTimeFormat"))
return false;
return DateTimeFormat(cx, args, true, DateTimeFormatOptions::EnableMozExtensions);
}
bool
@ -166,7 +154,7 @@ js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
// intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it
// cannot be used with "new", but it still has to be treated as a
// constructor.
return DateTimeFormat(cx, args, true);
return DateTimeFormat(cx, args, true, DateTimeFormatOptions::Standard);
}
void
@ -174,30 +162,25 @@ js::DateTimeFormatObject::finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(fop->onMainThread());
// This is-undefined check shouldn't be necessary, but for internal
// brokenness in object allocation code. For the moment, hack around it by
// explicitly guarding against the possibility of the reserved slot not
// containing a private. See bug 949220.
const Value& slot = obj->as<DateTimeFormatObject>().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT);
if (!slot.isUndefined()) {
if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate()))
udat_close(df);
}
if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate()))
udat_close(df);
}
JSObject*
js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
MutableHandleObject constructor, DateTimeFormatOptions dtfOptions)
{
RootedFunction ctor(cx);
ctor = GlobalObject::createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0);
ctor = dtfOptions == DateTimeFormatOptions::EnableMozExtensions
? GlobalObject::createConstructor(cx, MozDateTimeFormat, cx->names().DateTimeFormat, 0)
: GlobalObject::createConstructor(cx, DateTimeFormat, cx->names().DateTimeFormat, 0);
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global,
&DateTimeFormatObject::class_));
RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
if (!proto)
return nullptr;
proto->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
@ -226,51 +209,60 @@ js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<Globa
return nullptr;
}
RootedValue options(cx);
if (!intl::CreateDefaultOptions(cx, &options))
return nullptr;
// 12.2.1 and 12.3
if (!intl::InitializeObject(cx, proto, cx->names().InitializeDateTimeFormat, UndefinedHandleValue,
options))
{
return nullptr;
}
// 8.1
RootedValue ctorValue(cx, ObjectValue(*ctor));
if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0))
return nullptr;
constructor.set(ctor);
return proto;
}
bool
js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
js::AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
Handle<GlobalObject*> global = cx->global();
RootedValue result(cx);
if (!GetAvailableLocales(cx, udat_countAvailable, udat_getAvailable, &result))
RootedObject mozDateTimeFormat(cx);
JSObject* mozDateTimeFormatProto =
CreateDateTimeFormatPrototype(cx, intl, global, &mozDateTimeFormat, DateTimeFormatOptions::EnableMozExtensions);
return mozDateTimeFormatProto != nullptr;
}
static bool
DefaultCalendar(JSContext* cx, const JSAutoByteString& locale, MutableHandleValue rval)
{
UErrorCode status = U_ZERO_ERROR;
UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status);
// This correctly handles nullptr |cal| when opening failed.
ScopedICUObject<UCalendar, ucal_close> closeCalendar(cal);
const char* calendar = ucal_getType(cal, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
args.rval().set(result);
}
// ICU returns old-style keyword values; map them to BCP 47 equivalents
JSString* str = JS_NewStringCopyZ(cx, uloc_toUnicodeLocaleType("ca", calendar));
if (!str)
return false;
rval.setString(str);
return true;
}
// ICU returns old-style keyword values; map them to BCP 47 equivalents
// (see http://bugs.icu-project.org/trac/ticket/9620).
static const char*
bcp47CalendarName(const char* icuName)
struct CalendarAlias
{
if (StringsAreEqual(icuName, "ethiopic-amete-alem"))
return "ethioaa";
if (StringsAreEqual(icuName, "gregorian"))
return "gregory";
if (StringsAreEqual(icuName, "islamic-civil"))
return "islamicc";
return icuName;
}
const char* const calendar;
const char* const alias;
};
const CalendarAlias calendarAliases[] = {
{ "islamic-civil", "islamicc" },
{ "ethioaa", "ethiopic-amete-alem" }
};
bool
js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp)
@ -289,30 +281,15 @@ js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp)
uint32_t index = 0;
// We need the default calendar for the locale as the first result.
UErrorCode status = U_ZERO_ERROR;
RootedString jscalendar(cx);
{
UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status);
RootedValue element(cx);
if (!DefaultCalendar(cx, locale, &element))
return false;
// This correctly handles nullptr |cal| when opening failed.
ScopedICUObject<UCalendar, ucal_close> closeCalendar(cal);
const char* calendar = ucal_getType(cal, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar));
if (!jscalendar)
return false;
}
RootedValue element(cx, StringValue(jscalendar));
if (!DefineElement(cx, calendars, index++, element))
return false;
// Now get the calendars that "would make a difference", i.e., not the default.
UErrorCode status = U_ZERO_ERROR;
UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
@ -333,18 +310,47 @@ js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp)
return false;
}
jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar));
// ICU returns old-style keyword values; map them to BCP 47 equivalents
calendar = uloc_toUnicodeLocaleType("ca", calendar);
JSString* jscalendar = JS_NewStringCopyZ(cx, calendar);
if (!jscalendar)
return false;
element = StringValue(jscalendar);
if (!DefineElement(cx, calendars, index++, element))
return false;
// ICU doesn't return calendar aliases, append them here.
for (const auto& calendarAlias : calendarAliases) {
if (StringsAreEqual(calendar, calendarAlias.calendar)) {
JSString* jscalendar = JS_NewStringCopyZ(cx, calendarAlias.alias);
if (!jscalendar)
return false;
element = StringValue(jscalendar);
if (!DefineElement(cx, calendars, index++, element))
return false;
}
}
}
args.rval().setObject(*calendars);
return true;
}
bool
js::intl_defaultCalendar(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isString());
JSAutoByteString locale(cx, args[0].toString());
if (!locale)
return false;
return DefaultCalendar(cx, locale, args.rval());
}
bool
js::intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp)
{
@ -448,13 +454,139 @@ js::intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp) {
return true;
}
enum class HourCycle {
// 12 hour cycle, from 0 to 11.
H11,
// 12 hour cycle, from 1 to 12.
H12,
// 24 hour cycle, from 0 to 23.
H23,
// 24 hour cycle, from 1 to 24.
H24
};
static bool
IsHour12(HourCycle hc)
{
return hc == HourCycle::H11 || hc == HourCycle::H12;
}
static char16_t
HourSymbol(HourCycle hc)
{
switch (hc) {
case HourCycle::H11:
return 'K';
case HourCycle::H12:
return 'h';
case HourCycle::H23:
return 'H';
case HourCycle::H24:
return 'k';
}
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected hour cycle");
}
/**
* Parse a pattern according to the format specified in
* <https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns>.
*/
template <typename CharT>
class PatternIterator {
CharT* iter_;
const CharT* const end_;
public:
explicit PatternIterator(mozilla::Span<CharT> pattern)
: iter_(pattern.data()), end_(pattern.data() + pattern.size()) {}
CharT* next() {
MOZ_ASSERT(iter_ != nullptr);
bool inQuote = false;
while (iter_ < end_) {
CharT* cur = iter_++;
if (*cur == '\'') {
inQuote = !inQuote;
} else if (!inQuote) {
return cur;
}
}
iter_ = nullptr;
return nullptr;
}
};
/**
* Return the hour cycle for the given option string.
*/
static HourCycle
HourCycleFromOption(JSLinearString* str)
{
if (StringEqualsAscii(str, "h11")) {
return HourCycle::H11;
}
if (StringEqualsAscii(str, "h12")) {
return HourCycle::H12;
}
if (StringEqualsAscii(str, "h23")) {
return HourCycle::H23;
}
MOZ_ASSERT(StringEqualsAscii(str, "h24"));
return HourCycle::H24;
}
/**
* Return the hour cycle used in the input pattern or Nothing if none was found.
*/
static mozilla::Maybe<HourCycle>
HourCycleFromPattern(mozilla::Span<const char16_t> pattern)
{
PatternIterator<const char16_t> iter(pattern);
while (const auto* ptr = iter.next()) {
switch (*ptr) {
case 'K':
return mozilla::Some(HourCycle::H11);
case 'h':
return mozilla::Some(HourCycle::H12);
case 'H':
return mozilla::Some(HourCycle::H23);
case 'k':
return mozilla::Some(HourCycle::H24);
}
}
return mozilla::Nothing();
}
/**
* Replaces all hour pattern characters in |pattern| to use the matching hour
* representation for |hourCycle|.
*/
static void
ReplaceHourSymbol(mozilla::Span<char16_t> pattern, HourCycle hc)
{
char16_t replacement = HourSymbol(hc);
PatternIterator<char16_t> iter(pattern);
while (auto* ptr = iter.next()) {
char16_t ch = *ptr;
if (ch == 'K' || ch == 'h' || ch == 'H' || ch == 'k') {
*ptr = replacement;
}
}
}
bool
js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(args[0].isString());
MOZ_ASSERT(args[1].isString());
MOZ_ASSERT(args[2].isString() || args[2].isUndefined());
JSAutoByteString locale(cx, args[0].toString());
if (!locale)
@ -468,6 +600,16 @@ js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp)
if (!stableChars.initTwoByte(cx, skeletonFlat))
return false;
mozilla::Maybe<HourCycle> hourCycle;
if (args[2].isString()) {
JSLinearString* hourCycleStr = args[2].toString()->ensureLinear(cx);
if (!hourCycleStr) {
return false;
}
hourCycle.emplace(HourCycleFromOption(hourCycleStr));
}
mozilla::Range<const char16_t> skeletonChars = stableChars.twoByteRange();
uint32_t skeletonLen = u_strlen(Char16ToUChar(skeletonChars.begin().get()));
@ -479,11 +621,220 @@ js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp)
}
ScopedICUObject<UDateTimePatternGenerator, udatpg_close> toClose(gen);
JSString* str =
CallICU(cx, [gen, &skeletonChars, skeletonLen](UChar* chars, uint32_t size, UErrorCode* status) {
return udatpg_getBestPattern(gen, skeletonChars.begin().get(), skeletonLen,
chars, size, status);
Vector<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> pattern(cx);
int32_t patternSize = CallICU(
cx,
pattern,
[gen, &skeletonChars](UChar* chars, uint32_t size, UErrorCode* status) {
return udatpg_getBestPattern(gen, skeletonChars.begin().get(),
skeletonChars.length(), chars, size, status);
});
if (patternSize < 0) {
return false;
}
// If the hourCycle option was set, adjust the resolved pattern to use the
// requested hour cycle representation.
if (hourCycle) {
ReplaceHourSymbol(pattern, hourCycle.value());
}
JSString* str = NewStringCopyN<CanGC>(cx, pattern.begin(), pattern.length());
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
/**
* Find a matching pattern using the requested hour-12 options.
*
* This function is needed to work around the following two issues.
* - https://unicode-org.atlassian.net/browse/ICU-21023
* - https://unicode-org.atlassian.net/browse/CLDR-13425
*
* We're currently using a relatively simple workaround, which doesn't give the
* most accurate results. For example:
*
* ```
* var dtf = new Intl.DateTimeFormat("en", {
* timeZone: "UTC",
* dateStyle: "long",
* timeStyle: "long",
* hourCycle: "h12",
* });
* print(dtf.format(new Date("2020-01-01T00:00Z")));
* ```
*
* Returns the pattern "MMMM d, y 'at' h:mm:ss a z", but when going through
* |udatpg_getSkeleton| and then |udatpg_getBestPattern| to find an equivalent
* pattern for "h23", we'll end up with the pattern "MMMM d, y, HH:mm:ss z", so
* the combinator element " 'at' " was lost in the process.
*/
template <size_t N>
static bool
FindPatternWithHourCycle(JSContext* cx, const char* locale,
Vector<char16_t, N>& pattern, bool hour12)
{
UErrorCode status = U_ZERO_ERROR;
UDateTimePatternGenerator* gen = udatpg_open(IcuLocale(locale), &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UDateTimePatternGenerator, udatpg_close> toClose(gen);
if (!gen) {
return false;
}
Vector<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> skeleton(cx);
int32_t skeletonSize = CallICU(
cx,
skeleton,
[&pattern](UChar* chars, uint32_t size, UErrorCode* status) {
return udatpg_getSkeleton(nullptr, pattern.begin(), pattern.length(),
chars, size, status);
});
if (skeletonSize < 0) {
return false;
}
// Input skeletons don't differentiate between "K" and "h" resp. "k" and "H".
ReplaceHourSymbol(skeleton, hour12 ? HourCycle::H12 : HourCycle::H23);
MOZ_ALWAYS_TRUE(pattern.resize(0));
int32_t patternSize = CallICU(
cx,
pattern,
[gen, &skeleton](UChar* chars, uint32_t size, UErrorCode* status) {
return udatpg_getBestPattern(gen, skeleton.begin(), skeleton.length(),
chars, size, status);
});
if (patternSize < 0) {
return false;
}
return true;
}
bool
js::intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 6);
MOZ_ASSERT(args[0].isString());
MOZ_ASSERT(args[1].isString() || args[1].isUndefined());
MOZ_ASSERT(args[2].isString() || args[2].isUndefined());
MOZ_ASSERT(args[3].isString());
MOZ_ASSERT(args[4].isBoolean() || args[4].isUndefined());
MOZ_ASSERT(args[5].isString() || args[5].isUndefined());
JSAutoByteString locale(cx, args[0].toString());
if (!locale)
return false;
auto toDateFormatStyle = [](JSLinearString* str) {
if (StringEqualsAscii(str, "full")) {
return UDAT_FULL;
}
if (StringEqualsAscii(str, "long")) {
return UDAT_LONG;
}
if (StringEqualsAscii(str, "medium")) {
return UDAT_MEDIUM;
}
MOZ_ASSERT(StringEqualsAscii(str, "short"));
return UDAT_SHORT;
};
UDateFormatStyle dateStyle = UDAT_NONE;
if (args[1].isString()) {
JSLinearString* dateStyleStr = args[1].toString()->ensureLinear(cx);
if (!dateStyleStr)
return false;
dateStyle = toDateFormatStyle(dateStyleStr);
}
UDateFormatStyle timeStyle = UDAT_NONE;
if (args[2].isString()) {
JSLinearString* timeStyleStr = args[2].toString()->ensureLinear(cx);
if (!timeStyleStr)
return false;
timeStyle = toDateFormatStyle(timeStyleStr);
}
AutoStableStringChars timeZone(cx);
if (!timeZone.initTwoByte(cx, args[3].toString()))
return false;
mozilla::Maybe<bool> hour12;
if (args[4].isBoolean()) {
hour12.emplace(args[4].toBoolean());
}
mozilla::Maybe<HourCycle> hourCycle;
if (args[5].isString()) {
JSLinearString* hourCycleStr = args[5].toString()->ensureLinear(cx);
if (!hourCycleStr) {
return false;
}
hourCycle.emplace(HourCycleFromOption(hourCycleStr));
}
mozilla::Range<const char16_t> timeZoneChars = timeZone.twoByteRange();
UErrorCode status = U_ZERO_ERROR;
UDateFormat* df = udat_open(timeStyle, dateStyle, IcuLocale(locale.ptr()),
Char16ToUChar(timeZoneChars.begin().get()),
timeZoneChars.length(), nullptr, -1, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
ScopedICUObject<UDateFormat, udat_close> toClose(df);
Vector<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> pattern(cx);
int32_t patternSize = CallICU(
cx,
pattern,
[df](UChar* chars, uint32_t size, UErrorCode* status) {
return udat_toPattern(df, false, chars, size, status);
});
if (patternSize < 0) {
return false;
}
// If a specific hour cycle was requested and this hour cycle doesn't match
// the hour cycle used in the resolved pattern, find an equivalent pattern
// with the correct hour cycle.
if (timeStyle != UDAT_NONE && (hour12 || hourCycle)) {
if (auto hcPattern = HourCycleFromPattern(pattern)) {
bool wantHour12 = hour12 ? hour12.value() : IsHour12(hourCycle.value());
if (wantHour12 != IsHour12(hcPattern.value())) {
if (!FindPatternWithHourCycle(cx, locale.ptr(), pattern, wantHour12)) {
return false;
}
}
}
}
// If the hourCycle option was set, adjust the resolved pattern to use the
// requested hour cycle representation.
if (hourCycle) {
ReplaceHourSymbol(pattern, hourCycle.value());
}
JSString* str = NewStringCopyN<CanGC>(cx, pattern.begin(), pattern.length());
if (!str)
return false;
args.rval().setString(str);
@ -495,7 +846,7 @@ js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp)
* of the given DateTimeFormat.
*/
static UDateFormat*
NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat)
NewUDateFormat(JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat)
{
RootedValue value(cx);
@ -505,13 +856,56 @@ NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat)
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
return nullptr;
JSAutoByteString locale(cx, value.toString());
if (!locale)
// ICU expects calendar and numberingSystem as Unicode locale extensions on
// locale.
intl::LanguageTag tag(cx);
{
JSLinearString* locale = value.toString()->ensureLinear(cx);
if (!locale)
return nullptr;
if (!intl::LanguageTagParser::parse(cx, locale, tag))
return nullptr;
}
JS::RootedVector<intl::UnicodeExtensionKeyword> keywords(cx);
if (!GetProperty(cx, internals, internals, cx->names().calendar, &value))
return nullptr;
// We don't need to look at calendar and numberingSystem - they can only be
// set via the Unicode locale extension and are therefore already set on
// locale.
{
JSLinearString* calendar = value.toString()->ensureLinear(cx);
if (!calendar)
return nullptr;
if (!keywords.emplaceBack("ca", calendar))
return nullptr;
}
if (!GetProperty(cx, internals, internals, cx->names().numberingSystem, &value))
return nullptr;
{
JSLinearString* numberingSystem = value.toString()->ensureLinear(cx);
if (!numberingSystem)
return nullptr;
if (!keywords.emplaceBack("nu", numberingSystem))
return nullptr;
}
// |ApplyUnicodeExtensionToTag| applies the new keywords to the front of
// the Unicode extension subtag. We're then relying on ICU to follow RFC
// 6067, which states that any trailing keywords using the same key
// should be ignored.
if (!intl::ApplyUnicodeExtensionToTag(cx, tag, keywords))
return nullptr;
UniqueChars locale = tag.toStringZ(cx);
if (!locale)
return nullptr;
if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value))
return nullptr;
@ -537,7 +931,7 @@ NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat)
UErrorCode status = U_ZERO_ERROR;
UDateFormat* df =
udat_open(UDAT_PATTERN, UDAT_PATTERN, IcuLocale(locale.ptr()), uTimeZone, uTimeZoneLength,
udat_open(UDAT_PATTERN, UDAT_PATTERN, IcuLocale(locale.get()), uTimeZone, uTimeZoneLength,
uPattern, uPatternLength, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
@ -558,7 +952,8 @@ static bool
intl_FormatDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue result)
{
if (!IsFinite(x)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE,
"DateTimeFormat", "format");
return false;
}
@ -664,7 +1059,8 @@ static bool
intl_FormatToPartsDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue result)
{
if (!IsFinite(x)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE,
"DateTimeFormat", "formatToParts");
return false;
}
@ -777,42 +1173,23 @@ js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp)
MOZ_ASSERT(args[1].isNumber());
MOZ_ASSERT(args[2].isBoolean());
RootedObject dateTimeFormat(cx, &args[0].toObject());
Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>();
// Obtain a UDateFormat object, cached if possible.
bool isDateTimeFormatInstance = dateTimeFormat->getClass() == &DateTimeFormatObject::class_;
UDateFormat* df;
if (isDateTimeFormatInstance) {
void* priv =
dateTimeFormat->as<NativeObject>().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT).toPrivate();
df = static_cast<UDateFormat*>(priv);
if (!df) {
df = NewUDateFormat(cx, dateTimeFormat);
if (!df)
return false;
dateTimeFormat->as<NativeObject>().setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(df));
}
} else {
// There's no good place to cache the ICU date-time format for an object
// that has been initialized as a DateTimeFormat but is not a
// DateTimeFormat instance. One possibility might be to add a
// DateTimeFormat instance as an internal property to each such object.
// Obtain a cached UDateFormat object.
void* priv =
dateTimeFormat->getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT).toPrivate();
UDateFormat* df = static_cast<UDateFormat*>(priv);
if (!df) {
df = NewUDateFormat(cx, dateTimeFormat);
if (!df)
return false;
dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(df));
}
// Use the UDateFormat to actually format the time stamp.
RootedValue result(cx);
bool success = args[2].toBoolean()
? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), &result)
: intl_FormatDateTime(cx, df, args[1].toNumber(), &result);
if (!isDateTimeFormatInstance)
udat_close(df);
if (!success)
return false;
args.rval().set(result);
return true;
return args[2].toBoolean()
? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), args.rval())
: intl_FormatDateTime(cx, df, args[1].toNumber(), args.rval());
}

View file

@ -41,7 +41,8 @@ class DateTimeFormatObject : public NativeObject
extern JSObject*
CreateDateTimeFormatPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
JS::Handle<GlobalObject*> global);
JS::Handle<GlobalObject*> global, MutableHandleObject constructor,
intl::DateTimeFormatOptions dtfOptions);
/**
* Returns a new instance of the standard built-in DateTimeFormat constructor.
@ -53,17 +54,6 @@ CreateDateTimeFormatPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
extern MOZ_MUST_USE bool
intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an object indicating the supported locales for date and time
* formatting by having a true-valued property for each such locale with the
* canonicalized language tag as the property name. The object has no
* prototype.
*
* Usage: availableLocales = intl_DateTimeFormat_availableLocales()
*/
extern MOZ_MUST_USE bool
intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an array with the calendar type identifiers per Unicode
* Technical Standard 35, Unicode Locale Data Markup Language, for the
@ -75,6 +65,16 @@ intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool
intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns the calendar type identifier per Unicode Technical Standard 35,
* Unicode Locale Data Markup Language, for the default calendar for the given
* locale.
*
* Usage: calendar = intl_defaultCalendar(locale)
*/
extern MOZ_MUST_USE bool
intl_defaultCalendar(JSContext* cx, unsigned argc, Value* vp);
/**
* 6.4.1 IsValidTimeZoneName ( timeZone )
*
@ -119,11 +119,45 @@ intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp);
* best-fit date-time format pattern corresponding to skeleton for the
* given locale.
*
* Usage: pattern = intl_patternForSkeleton(locale, skeleton)
* Usage: pattern = intl_patternForSkeleton(locale, skeleton, hourCycle)
*/
extern MOZ_MUST_USE bool
intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp);
/**
* Return a pattern in the date-time format pattern language of Unicode
* Technical Standard 35, Unicode Locale Data Markup Language, for the
* best-fit date-time style for the given locale.
* The function takes six arguments:
*
* locale
* BCP47 compliant locale string
* dateStyle
* A string with values: full or long or medium or short, or `undefined`
* timeStyle
* A string with values: full or long or medium or short, or `undefined`
* timeZone
* IANA time zone name
* hour12
* A boolean to request hour12 representation, or `undefined`
* hourCycle
* A string with values: h11, h12, h23, or h24, or `undefined`
*
* Date and time style categories map to CLDR time/date standard
* format patterns.
*
* For the definition of a pattern string, see LDML 4.8:
* http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
*
* If `undefined` is passed to `dateStyle` or `timeStyle`, the respective
* portions of the pattern will not be included in the result.
*
* Usage: pattern = intl_patternForStyle(locale, dateStyle, timeStyle, timeZone,
* hour12, hourCycle)
*/
extern MOZ_MUST_USE bool
intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns a String value representing x (which must be a Number value)
* according to the effective locale and the formatting options of the
@ -131,7 +165,7 @@ intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp);
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
*
* Usage: formatted = intl_FormatDateTime(dateTimeFormat, x)
* Usage: formatted = intl_FormatDateTime(dateTimeFormat, x, formatToParts)
*/
extern MOZ_MUST_USE bool
intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp);

View file

@ -20,7 +20,11 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
// {
// localeMatcher: "lookup" / "best fit",
//
// hour12: true / false, // optional
// ca: string matching a Unicode extension type, // optional
//
// nu: string matching a Unicode extension type, // optional
//
// hc: "h11" / "h12" / "h23" / "h24", // optional
// }
//
// timeZone: IANA time zone name,
@ -29,9 +33,18 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
// {
// // all the properties/values listed in Table 3
// // (weekday, era, year, month, day, &c.)
//
// hour12: true / false, // optional
// }
//
// formatMatcher: "basic" / "best fit",
//
// dateStyle: "full" / "long" / "medium" / "short" / undefined,
//
// timeStyle: "full" / "long" / "medium" / "short" / undefined,
//
// patternOption:
// String representing LDML Date Format pattern or undefined
// }
//
// Note that lazy data is only installed as a final step of initialization,
@ -39,22 +52,22 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
// never a subset of them.
var internalProps = std_Object_create(null);
// Compute effective locale.
// Step 8.
var DateTimeFormat = dateTimeFormatInternalProperties;
// Step 9.
var localeData = DateTimeFormat.localeData;
// Compute effective locale.
// Step 10.
var r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
var localeData = DateTimeFormat.localeData;
// Step 11.
var r = ResolveLocale("DateTimeFormat",
lazyDateTimeFormatData.requestedLocales,
lazyDateTimeFormatData.localeOpt,
DateTimeFormat.relevantExtensionKeys,
localeData);
// Steps 11-13.
// Steps 12-13, 15.
internalProps.locale = r.locale;
internalProps.calendar = r.ca;
internalProps.numberingSystem = r.nu;
@ -63,26 +76,43 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
// Step 14.
var dataLocale = r.dataLocale;
// Steps 15-17.
var tz = lazyDateTimeFormatData.timeZone;
if (tz === undefined) {
// Step 16.
tz = DefaultTimeZone();
}
internalProps.timeZone = tz;
// Steps 20.
internalProps.timeZone = lazyDateTimeFormatData.timeZone;
// Step 18.
// Step 21.
var formatOpt = lazyDateTimeFormatData.formatOpt;
// Steps 27-28, more or less - see comment after this function.
var pattern = toBestICUPattern(dataLocale, formatOpt);
// Step 16.
// Copy the hourCycle setting, if present, to the format options. But
// only do this if no hour12 option is present, because the latter takes
// precedence over hourCycle.
if (r.hc !== null && formatOpt.hour12 === undefined)
formatOpt.hourCycle = r.hc;
// Step 29.
// Steps 26-30, more or less - see comment after this function.
var pattern;
if (lazyDateTimeFormatData.patternOption !== undefined) {
pattern = lazyDateTimeFormatData.patternOption;
internalProps.patternOption = lazyDateTimeFormatData.patternOption;
} else if (lazyDateTimeFormatData.dateStyle !== undefined ||
lazyDateTimeFormatData.timeStyle !== undefined) {
pattern = intl_patternForStyle(dataLocale,
lazyDateTimeFormatData.dateStyle,
lazyDateTimeFormatData.timeStyle,
lazyDateTimeFormatData.timeZone,
formatOpt.hour12,
formatOpt.hourCycle);
internalProps.dateStyle = lazyDateTimeFormatData.dateStyle;
internalProps.timeStyle = lazyDateTimeFormatData.timeStyle;
} else {
pattern = toBestICUPattern(dataLocale, formatOpt);
}
// Step 31.
internalProps.pattern = pattern;
// Step 30.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
// object using |setInternalProperties|.
return internalProps;
@ -90,11 +120,13 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
/**
* Returns an object containing the DateTimeFormat internal properties of |obj|,
* or throws a TypeError if |obj| isn't DateTimeFormat-initialized.
* Returns an object containing the DateTimeFormat internal properties of |obj|.
*/
function getDateTimeFormatInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "DateTimeFormat", methodName);
function getDateTimeFormatInternals(obj) {
assert(IsObject(obj), "getDateTimeFormatInternals called with non-object");
assert(IsDateTimeFormat(obj), "getDateTimeFormatInternals called with non-DateTimeFormat");
var internals = getIntlObjectInternals(obj);
assert(internals.type === "DateTimeFormat", "bad type escaped getIntlObjectInternals");
// If internal properties have already been computed, use them.
@ -214,6 +246,31 @@ function DefaultTimeZone() {
return defaultTimeZone;
}
/**
* 12.1.10 UnwrapDateTimeFormat( dtf )
*/
function UnwrapDateTimeFormat(dtf, methodName) {
// Step 1 (not applicable in our implementation).
// Step 2.
if ((!IsObject(dtf) || !IsDateTimeFormat(dtf)) &&
dtf instanceof GetDateTimeFormatConstructor())
{
dtf = dtf[intlFallbackSymbol()];
}
// Step 3.
if (!IsObject(dtf) || !IsDateTimeFormat(dtf)) {
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "DateTimeFormat", methodName,
"DateTimeFormat");
}
// Step 4.
return dtf;
}
/**
* Initializes an object as a DateTimeFormat.
*
@ -225,15 +282,10 @@ function DefaultTimeZone() {
*
* Spec: ECMAScript Internationalization API Specification, 12.1.1.
*/
function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat");
// Step 1.
if (isInitializedIntlObject(dateTimeFormat))
ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
// Step 2.
var internals = initializeIntlObject(dateTimeFormat);
function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options, mozExtensions) {
assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat called with non-Object");
assert(IsDateTimeFormat(dateTimeFormat),
"InitializeDateTimeFormat called with non-DateTimeFormat");
// Lazy DateTimeFormat data has the following structure:
//
@ -243,6 +295,12 @@ function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
// localeOpt: // *first* opt computed in InitializeDateTimeFormat
// {
// localeMatcher: "lookup" / "best fit",
//
// ca: string matching a Unicode extension type, // optional
//
// nu: string matching a Unicode extension type, // optional
//
// hc: "h11" / "h12" / "h23" / "h24", // optional
// }
//
// timeZone: IANA time zone name,
@ -252,7 +310,7 @@ function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
// // all the properties/values listed in Table 3
// // (weekday, era, year, month, day, &c.)
//
// hour12: true / false // optional
// hour12: true / false, // optional
// }
//
// formatMatcher: "basic" / "best fit",
@ -263,45 +321,89 @@ function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
// never a subset of them.
var lazyDateTimeFormatData = std_Object_create(null);
// Step 3.
// Step 1.
var requestedLocales = CanonicalizeLocaleList(locales);
lazyDateTimeFormatData.requestedLocales = requestedLocales;
// Step 4.
// Step24.
options = ToDateTimeOptions(options, "any", "date");
// Compute options that impact interpretation of locale.
// Step 5.
// Step 3.
var localeOpt = new Record();
lazyDateTimeFormatData.localeOpt = localeOpt;
// Steps 6-7.
// Steps 4-5.
var localeMatcher =
GetOption(options, "localeMatcher", "string", ["lookup", "best fit"],
"best fit");
localeOpt.localeMatcher = localeMatcher;
// Steps 15-17.
var calendar = GetOption(options, "calendar", "string", undefined, undefined);
if (calendar !== undefined) {
calendar = intl_ValidateAndCanonicalizeUnicodeExtensionType(calendar, "calendar", "ca");
}
localeOpt.ca = calendar;
var numberingSystem = GetOption(options, "numberingSystem", "string", undefined, undefined);
if (numberingSystem !== undefined) {
numberingSystem = intl_ValidateAndCanonicalizeUnicodeExtensionType(numberingSystem,
"numberingSystem",
"nu");
}
localeOpt.nu = numberingSystem;
// Step 6.
var hr12 = GetOption(options, "hour12", "boolean", undefined, undefined);
// Step 7.
var hc = GetOption(options, "hourCycle", "string", ["h11", "h12", "h23", "h24"], undefined);
// Step 8.
if (hr12 !== undefined) {
// The "hourCycle" option is ignored if "hr12" is also present.
hc = null;
}
// Step 9.
localeOpt.hc = hc;
// Steps 10-16 (see resolveDateTimeFormatInternals).
// Steps 17-20.
var tz = options.timeZone;
if (tz !== undefined) {
// Step 15.a.
// Step 18.a.
tz = ToString(tz);
// Step 15.b.
// Step 18.b.
var timeZone = intl_IsValidTimeZoneName(tz);
if (timeZone === null)
ThrowRangeError(JSMSG_INVALID_TIME_ZONE, tz);
// Step 15.c.
// Step 18.c.
tz = CanonicalizeTimeZoneName(timeZone);
} else {
// Step 19.
tz = DefaultTimeZone();
}
lazyDateTimeFormatData.timeZone = tz;
// Step 18.
// Step 21.
var formatOpt = new Record();
lazyDateTimeFormatData.formatOpt = formatOpt;
// Step 19.
if (mozExtensions) {
let pattern = GetOption(options, "pattern", "string", undefined, undefined);
lazyDateTimeFormatData.patternOption = pattern;
}
// Step 22.
// 12.1, Table 5: Components of date and time formats.
var i, prop;
for (i = 0; i < dateTimeComponents.length; i++) {
prop = dateTimeComponents[i];
@ -309,9 +411,9 @@ function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
formatOpt[prop] = value;
}
// Steps 20-21 provided by ICU - see comment after this function.
// Steps 23-24 provided by ICU - see comment after this function.
// Step 22.
// Step 25.
//
// For some reason (ICU not exposing enough interface?) we drop the
// requested format matcher on the floor after this. In any case, even if
@ -321,20 +423,58 @@ function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
GetOption(options, "formatMatcher", "string", ["basic", "best fit"],
"best fit");
// Steps 23-25 provided by ICU, more or less - see comment after this function.
// "DateTimeFormat dateStyle & timeStyle" propsal
// https://github.com/tc39/proposal-intl-datetime-style
var dateStyle = GetOption(options, "dateStyle", "string", ["full", "long", "medium", "short"],
undefined);
lazyDateTimeFormatData.dateStyle = dateStyle;
// Step 26.
var hr12 = GetOption(options, "hour12", "boolean", undefined, undefined);
var timeStyle = GetOption(options, "timeStyle", "string", ["full", "long", "medium", "short"],
undefined);
lazyDateTimeFormatData.timeStyle = timeStyle;
if (dateStyle !== undefined || timeStyle !== undefined) {
var optionsList = [
"weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZoneName",
];
for (var i = 0; i < optionsList.length; i++) {
var option = optionsList[i];
if (formatOpt[option] !== undefined) {
ThrowTypeError(JSMSG_INVALID_DATETIME_OPTION, option,
dateStyle !== undefined ? "dateStyle" : "timeStyle");
}
}
}
// Steps 26-28 provided by ICU, more or less - see comment after this function.
// Steps 29-30.
// Pass hr12 on to ICU.
if (hr12 !== undefined)
formatOpt.hour12 = hr12;
// Step 31.
// Step 32.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
setLazyData(internals, "DateTimeFormat", lazyDateTimeFormatData);
initializeIntlObject(dateTimeFormat, "DateTimeFormat", lazyDateTimeFormatData);
// 12.2.1, steps 4-5.
// TODO: spec issue - The current spec doesn't have the IsObject check,
// which means |Intl.DateTimeFormat.call(null)| is supposed to throw here.
if (dateTimeFormat !== thisValue && thisValue instanceof GetDateTimeFormatConstructor()) {
if (!IsObject(thisValue))
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof thisValue);
_DefineDataProperty(thisValue, intlFallbackSymbol(), dateTimeFormat,
ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE);
return thisValue;
}
// 12.2.1, step 6.
return dateTimeFormat;
}
@ -396,6 +536,7 @@ function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
// - [[weekday]], [[era]], [[year]], [[month]], [[day]], [[hour]], [[minute]],
// [[second]], [[timeZoneName]]
// - [[hour12]]
// - [[hourCycle]]
// - [[hourNo0]]
// When needed for the resolvedOptions method, the resolveICUPattern function
// maps the instance's ICU pattern back to the specified properties of the
@ -469,12 +610,24 @@ function toBestICUPattern(locale, options) {
skeleton += "d";
break;
}
// If hour12 and hourCycle are both present, hour12 takes precedence.
var hourSkeletonChar = "j";
if (options.hour12 !== undefined) {
if (options.hour12)
hourSkeletonChar = "h";
else
hourSkeletonChar = "H";
} else {
switch (options.hourCycle) {
case "h11":
case "h12":
hourSkeletonChar = "h";
break;
case "h23":
case "h24":
hourSkeletonChar = "H";
break;
}
}
switch (options.hour) {
case "2-digit":
@ -510,7 +663,7 @@ function toBestICUPattern(locale, options) {
}
// Let ICU convert the ICU skeleton to an ICU pattern for the given locale.
return intl_patternForSkeleton(locale, skeleton);
return intl_patternForSkeleton(locale, skeleton, options.hourCycle);
}
@ -526,17 +679,19 @@ function ToDateTimeOptions(options, required, defaults) {
assert(typeof required === "string", "ToDateTimeOptions");
assert(typeof defaults === "string", "ToDateTimeOptions");
// Steps 1-3.
// Steps 1-2.
if (options === undefined)
options = null;
else
options = ToObject(options);
options = std_Object_create(options);
// Step 4.
// Step 3.
var needDefaults = true;
// Step 5.
// Step 4.
// TODO: spec issue - The spec requires to retrieve all options, so using
// the ||-operator with its lazy evaluation semantics is incorrect.
if ((required === "date" || required === "any") &&
(options.weekday !== undefined || options.year !== undefined ||
options.month !== undefined || options.day !== undefined))
@ -544,7 +699,9 @@ function ToDateTimeOptions(options, required, defaults) {
needDefaults = false;
}
// Step 6.
// Step 5.
// TODO: spec issue - The spec requires to retrieve all options, so using
// the ||-operator with its lazy evaluation semantics is incorrect.
if ((required === "time" || required === "any") &&
(options.hour !== undefined || options.minute !== undefined ||
options.second !== undefined))
@ -552,7 +709,21 @@ function ToDateTimeOptions(options, required, defaults) {
needDefaults = false;
}
// Step 7.
// "DateTimeFormat dateStyle & timeStyle" propsal
// https://github.com/tc39/proposal-intl-datetime-style
var dateStyle = options.dateStyle;
var timeStyle = options.timeStyle;
if (dateStyle !== undefined || timeStyle !== undefined)
needDefaults = false;
if (required === "date" && timeStyle !== undefined)
ThrowTypeError(JSMSG_INVALID_DATETIME_STYLE, "timeStyle", "toLocaleDateString");
if (required === "time" && dateStyle !== undefined)
ThrowTypeError(JSMSG_INVALID_DATETIME_STYLE, "dateStyle", "toLocaleTimeString");
// Step 6.
if (needDefaults && (defaults === "date" || defaults === "all")) {
// The specification says to call [[DefineOwnProperty]] with false for
// the Throw parameter, while Object.defineProperty uses true. For the
@ -563,7 +734,7 @@ function ToDateTimeOptions(options, required, defaults) {
_DefineDataProperty(options, "day", "numeric");
}
// Step 8.
// Step 7.
if (needDefaults && (defaults === "time" || defaults === "all")) {
// See comment for step 7.
_DefineDataProperty(options, "hour", "numeric");
@ -571,7 +742,7 @@ function ToDateTimeOptions(options, required, defaults) {
_DefineDataProperty(options, "second", "numeric");
}
// Step 9.
// Step 8.
return options;
}
@ -623,7 +794,7 @@ function BasicFormatMatcher(options, formats) {
formatProp = undefined;
// Steps 11.c.ii-iii.
if (callFunction(std_Object_hasOwnProperty, format, property))
if (hasOwn(property, format))
formatProp = format[property];
if (optionsProp === undefined && formatProp !== undefined) {
@ -681,14 +852,18 @@ function BestFitFormatMatcher(options, formats) {
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript Internationalization API Specification, 12.2.2.
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
*/
function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
var availableLocales = callFunction(dateTimeFormatInternalProperties.availableLocales,
dateTimeFormatInternalProperties);
// Step 1.
var availableLocales = "DateTimeFormat";
// Step 2.
var requestedLocales = CanonicalizeLocaleList(locales);
// Step 3.
return SupportedLocales(availableLocales, requestedLocales, options);
}
@ -696,29 +871,28 @@ function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) {
/**
* DateTimeFormat internal properties.
*
* Spec: ECMAScript Internationalization API Specification, 9.1 and 12.2.3.
* Spec: ECMAScript Internationalization API Specification, 9.1 and 12.3.3.
*/
var dateTimeFormatInternalProperties = {
localeData: dateTimeFormatLocaleData,
_availableLocales: null,
availableLocales: function()
{
var locales = this._availableLocales;
if (locales)
return locales;
locales = intl_DateTimeFormat_availableLocales();
addSpecialMissingLanguageTags(locales);
return (this._availableLocales = locales);
},
relevantExtensionKeys: ["ca", "nu"]
relevantExtensionKeys: ["ca", "nu", "hc"]
};
function dateTimeFormatLocaleData(locale) {
function dateTimeFormatLocaleData() {
return {
ca: intl_availableCalendars(locale),
nu: getNumberingSystems(locale)
ca: intl_availableCalendars,
nu: getNumberingSystems,
hc: () => {
return [null, "h11", "h12", "h23", "h24"];
},
default: {
ca: intl_defaultCalendar,
nu: intl_numberingSystem,
hc: () => {
return null;
}
}
};
}
@ -726,7 +900,7 @@ function dateTimeFormatLocaleData(locale) {
/**
* Function to be bound and returned by Intl.DateTimeFormat.prototype.format.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
* Spec: ECMAScript Internationalization API Specification, 12.1.5.
*/
function dateTimeFormatFormatToBind() {
// Steps 1.a.i-ii
@ -734,7 +908,7 @@ function dateTimeFormatFormatToBind() {
var x = (date === undefined) ? std_Date_now() : ToNumber(date);
// Step 1.a.iii.
return intl_FormatDateTime(this, x, false);
return intl_FormatDateTime(this, x, /* formatToParts = */ false);
}
/**
@ -742,82 +916,97 @@ function dateTimeFormatFormatToBind() {
* representing the result of calling ToNumber(date) according to the
* effective locale and the formatting options of this DateTimeFormat.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
* Spec: ECMAScript Internationalization API Specification, 12.4.3.
*/
function Intl_DateTimeFormat_format_get() {
// Check "this DateTimeFormat object" per introduction of section 12.3.
var internals = getDateTimeFormatInternals(this, "format");
// Steps 1-3.
var dtf = UnwrapDateTimeFormat(this, "format");
// Step 1.
var internals = getDateTimeFormatInternals(dtf);
// Step 4.
if (internals.boundFormat === undefined) {
// Step 1.a.
var F = dateTimeFormatFormatToBind;
// Steps 4.a-b.
var F = callFunction(FunctionBind, dateTimeFormatFormatToBind, dtf);
// Step 1.b-d.
var bf = callFunction(FunctionBind, F, this);
internals.boundFormat = bf;
// Step 4.c.
internals.boundFormat = F;
}
// Step 2.
// Step 5.
return internals.boundFormat;
}
_SetCanonicalName(Intl_DateTimeFormat_format_get, "get format");
/**
* Intl.DateTimeFormat.prototype.formatToParts ( date )
*
* Spec: ECMAScript Internationalization API Specification, 12.4.4.
*/
function Intl_DateTimeFormat_formatToParts() {
// Check "this DateTimeFormat object" per introduction of section 12.3.
getDateTimeFormatInternals(this, "formatToParts");
// Steps 1-3.
var dtf = UnwrapDateTimeFormat(this, "formatToParts");
// Steps 1.a.i-ii
// Ensure the DateTimeFormat internals are resolved.
getDateTimeFormatInternals(dtf);
// Steps 4-5.
var date = arguments.length > 0 ? arguments[0] : undefined;
var x = (date === undefined) ? std_Date_now() : ToNumber(date);
// Step 1.a.iii.
return intl_FormatDateTime(this, x, true);
// Step 6.
return intl_FormatDateTime(dtf, x, /* formatToParts = */ true);
}
/**
* Returns the resolved options for a DateTimeFormat object.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4.
* Spec: ECMAScript Internationalization API Specification, 12.4.5.
*/
function Intl_DateTimeFormat_resolvedOptions() {
// Check "this DateTimeFormat object" per introduction of section 12.3.
var internals = getDateTimeFormatInternals(this, "resolvedOptions");
// Steps 1-3.
var dtf = UnwrapDateTimeFormat(this, "resolvedOptions");
var internals = getDateTimeFormatInternals(dtf);
// Steps 4-5.
var result = {
locale: internals.locale,
calendar: internals.calendar,
numberingSystem: internals.numberingSystem,
timeZone: internals.timeZone
timeZone: internals.timeZone,
};
resolveICUPattern(internals.pattern, result);
if (internals.patternOption !== undefined) {
_DefineDataProperty(result, "pattern", internals.pattern);
}
var hasDateStyle = internals.dateStyle !== undefined;
var hasTimeStyle = internals.timeStyle !== undefined;
if (hasDateStyle || hasTimeStyle) {
if (hasTimeStyle) {
// timeStyle (unlike dateStyle) requires resolving the pattern to
// ensure "hourCycle" and "hour12" properties are added to |result|.
resolveICUPattern(internals.pattern, result, /* includeDateTimeFields = */ false);
}
if (hasDateStyle) {
_DefineDataProperty(result, "dateStyle", internals.dateStyle);
}
if (hasTimeStyle) {
_DefineDataProperty(result, "timeStyle", internals.timeStyle);
}
} else {
resolveICUPattern(internals.pattern, result, /* includeDateTimeFields = */ true);
}
// Step 6.
return result;
}
// Table mapping ICU pattern characters back to the corresponding date-time
// components of DateTimeFormat. See
// http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
var icuPatternCharToComponent = {
E: "weekday",
G: "era",
y: "year",
M: "month",
L: "month",
d: "day",
h: "hour",
H: "hour",
k: "hour",
K: "hour",
m: "minute",
s: "second",
z: "timeZoneName",
v: "timeZoneName",
V: "timeZoneName"
};
/**
* Maps an ICU pattern string to a corresponding set of date-time components
* and their values, and adds properties for these components to the result
@ -825,8 +1014,12 @@ var icuPatternCharToComponent = {
* interpretation of ICU pattern characters, see
* http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
*/
function resolveICUPattern(pattern, result) {
function resolveICUPattern(pattern, result, includeDateTimeFields) {
assert(IsObject(result), "resolveICUPattern");
var hourCycle, weekday, era, year, month, day, hour, minute, second,
timeZoneName;
var i = 0;
while (i < pattern.length) {
var c = pattern[i++];
@ -886,13 +1079,91 @@ function resolveICUPattern(pattern, result) {
default:
// skip other pattern characters and literal text
}
if (callFunction(std_Object_hasOwnProperty, icuPatternCharToComponent, c))
_DefineDataProperty(result, icuPatternCharToComponent[c], value);
if (c === "h" || c === "K")
_DefineDataProperty(result, "hour12", true);
else if (c === "H" || c === "k")
_DefineDataProperty(result, "hour12", false);
// Map ICU pattern characters back to the corresponding date-time
// components of DateTimeFormat. See
// http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
switch (c) {
case "E":
case "c":
weekday = value;
break;
case "G":
era = value;
break;
case "y":
year = value;
break;
case "M":
case "L":
month = value;
break;
case "d":
day = value;
break;
case "h":
hourCycle = "h12";
hour = value;
break;
case "H":
hourCycle = "h23";
hour = value;
break;
case "k":
hourCycle = "h24";
hour = value;
break;
case "K":
hourCycle = "h11";
hour = value;
break;
case "m":
minute = value;
break;
case "s":
second = value;
break;
case "z":
case "v":
case "V":
timeZoneName = value;
break;
}
}
}
if (hourCycle) {
_DefineDataProperty(result, "hourCycle", hourCycle);
_DefineDataProperty(result, "hour12", hourCycle === "h11" || hourCycle === "h12");
}
if (!includeDateTimeFields) {
return;
}
if (weekday) {
_DefineDataProperty(result, "weekday", weekday);
}
if (era) {
_DefineDataProperty(result, "era", era);
}
if (year) {
_DefineDataProperty(result, "year", year);
}
if (month) {
_DefineDataProperty(result, "month", month);
}
if (day) {
_DefineDataProperty(result, "day", day);
}
if (hour) {
_DefineDataProperty(result, "hour", hour);
}
if (minute) {
_DefineDataProperty(result, "minute", minute);
}
if (second) {
_DefineDataProperty(result, "second", second);
}
if (timeZoneName) {
_DefineDataProperty(result, "timeZoneName", timeZoneName);
}
}

View file

@ -13,6 +13,7 @@
#include "unicode/udatpg.h"
#include "unicode/udisplaycontext.h"
#include "unicode/uenum.h"
#include "unicode/uloc.h"
#include "unicode/unum.h"
#include "unicode/unumsys.h"
#include "unicode/upluralrules.h"

View file

@ -11,6 +11,9 @@
#include "mozilla/Likely.h"
#include "mozilla/Range.h"
#include <algorithm>
#include <iterator>
#include "jsapi.h"
#include "jscntxt.h"
#include "jsobj.h"
@ -18,11 +21,15 @@
#include "builtin/intl/Collator.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/DateTimeFormat.h"
#include "builtin/intl/LanguageTag.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/Locale.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/intl/PluralRules.h"
#include "builtin/intl/RelativeTimeFormat.h"
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/intl/SharedIntlData.h"
#include "js/Result.h"
#include "vm/GlobalObject.h"
#include "jsobjinlines.h"
@ -30,7 +37,7 @@
using namespace js;
using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::DateTimeFormatOptions;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::StringsAreEqual;
@ -405,6 +412,272 @@ js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp)
return true;
}
using SupportedLocaleKind = js::intl::SharedIntlData::SupportedLocaleKind;
// 9.2.2 BestAvailableLocale ( availableLocales, locale )
static JS::Result<JSString*>
BestAvailableLocale(JSContext* cx, SupportedLocaleKind kind, HandleLinearString locale,
HandleLinearString defaultLocale)
{
// In the spec, [[availableLocales]] is formally a list of all available
// locales. But in our implementation, it's an *incomplete* list, not
// necessarily including the default locale (and all locales implied by it,
// e.g. "de" implied by "de-CH"), if that locale isn't in every
// [[availableLocales]] list (because that locale is supported through
// fallback, e.g. "de-CH" supported through "de").
//
// If we're considering the default locale, augment the spec loop with
// additional checks to also test whether the current prefix is a prefix of
// the default locale.
intl::SharedIntlData& sharedIntlData = cx->sharedIntlData;
auto findLast = [](const auto* chars, size_t length) {
auto rbegin = std::make_reverse_iterator(chars + length);
auto rend = std::make_reverse_iterator(chars);
auto p = std::find(rbegin, rend, '-');
// |dist(chars, p.base())| is equal to |dist(p, rend)|, pick whichever you
// find easier to reason about when using reserve iterators.
ptrdiff_t r = std::distance(chars, p.base());
MOZ_ASSERT(r == std::distance(p, rend));
// But always subtract one to convert from the reverse iterator result to
// the corresponding forward iterator value, because reserve iterators point
// to one element past the forward iterator value.
return r - 1;
};
// Step 1.
RootedLinearString candidate(cx, locale);
// Step 2.
while (true) {
// Step 2.a.
bool supported = false;
if (!sharedIntlData.isSupportedLocale(cx, kind, candidate, &supported)) {
return cx->alreadyReportedError();
}
if (supported) {
return candidate.get();
}
if (defaultLocale && candidate->length() <= defaultLocale->length()) {
if (EqualStrings(candidate, defaultLocale)) {
return candidate.get();
}
if (candidate->length() < defaultLocale->length() &&
HasSubstringAt(defaultLocale, candidate, 0) &&
defaultLocale->latin1OrTwoByteChar(candidate->length()) == '-') {
return candidate.get();
}
}
// Step 2.b.
ptrdiff_t pos;
if (candidate->hasLatin1Chars()) {
JS::AutoCheckCannotGC nogc;
pos = findLast(candidate->latin1Chars(nogc), candidate->length());
} else {
JS::AutoCheckCannotGC nogc;
pos = findLast(candidate->twoByteChars(nogc), candidate->length());
}
if (pos < 0) {
return nullptr;
}
// Step 2.c.
size_t length = size_t(pos);
if (length >= 2 && candidate->latin1OrTwoByteChar(length - 2) == '-') {
length -= 2;
}
// Step 2.d.
candidate = NewDependentString(cx, candidate, 0, length);
if (!candidate) {
return cx->alreadyReportedError();
}
}
}
// 9.2.2 BestAvailableLocale ( availableLocales, locale )
//
// Carries an additional third argument in our implementation to provide the
// default locale. See the doc-comment in the header file.
bool
js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
SupportedLocaleKind kind;
{
JSLinearString* typeStr = args[0].toString()->ensureLinear(cx);
if (!typeStr) {
return false;
}
if (StringEqualsAscii(typeStr, "Collator")) {
kind = SupportedLocaleKind::Collator;
} else if (StringEqualsAscii(typeStr, "DateTimeFormat")) {
kind = SupportedLocaleKind::DateTimeFormat;
} else if (StringEqualsAscii(typeStr, "NumberFormat")) {
kind = SupportedLocaleKind::NumberFormat;
} else if (StringEqualsAscii(typeStr, "PluralRules")) {
kind = SupportedLocaleKind::PluralRules;
} else {
MOZ_ASSERT(StringEqualsAscii(typeStr, "RelativeTimeFormat"));
kind = SupportedLocaleKind::RelativeTimeFormat;
}
}
RootedLinearString locale(cx, args[1].toString()->ensureLinear(cx));
if (!locale) {
return false;
}
#ifdef DEBUG
{
intl::LanguageTag tag(cx);
bool ok;
JS_TRY_VAR_OR_RETURN_FALSE(cx, ok, intl::LanguageTagParser::tryParse(cx, locale, tag));
MOZ_ASSERT(ok, "locale is a structurally valid language tag");
MOZ_ASSERT(!tag.unicodeExtension(),
"locale must contain no Unicode extensions");
if (!tag.canonicalize(cx)) {
return false;
}
JSString* tagStr = tag.toString(cx);
if (!tagStr) {
return false;
}
bool canonical;
if (!EqualStrings(cx, locale, tagStr, &canonical)) {
return false;
}
MOZ_ASSERT(canonical, "locale is a canonicalized language tag");
}
#endif
MOZ_ASSERT(args[2].isNull() || args[2].isString());
RootedLinearString defaultLocale(cx);
if (args[2].isString()) {
defaultLocale = args[2].toString()->ensureLinear(cx);
if (!defaultLocale) {
return false;
}
}
JSString* result;
JS_TRY_VAR_OR_RETURN_FALSE(cx, result, BestAvailableLocale(cx, kind, locale, defaultLocale));
if (result) {
args.rval().setString(result);
} else {
args.rval().setUndefined();
}
return true;
}
bool
js::intl_supportedLocaleOrFallback(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedLinearString locale(cx, args[0].toString()->ensureLinear(cx));
if (!locale) {
return false;
}
intl::LanguageTag tag(cx);
bool ok;
JS_TRY_VAR_OR_RETURN_FALSE(cx, ok, intl::LanguageTagParser::tryParse(cx, locale, tag));
RootedLinearString candidate(cx);
if (!ok) {
candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
if (!candidate) {
return false;
}
} else {
if (!tag.canonicalize(cx)) {
return false;
}
// The default locale must be in [[AvailableLocales]], and that list must
// not contain any locales with Unicode extension sequences, so remove any
// present in the candidate.
tag.clearUnicodeExtension();
JSString* canonical = tag.toString(cx);
if (!canonical) {
return false;
}
candidate = canonical->ensureLinear(cx);
if (!candidate) {
return false;
}
for (const auto& mapping : js::intl::oldStyleLanguageTagMappings) {
const char* oldStyle = mapping.oldStyle;
const char* modernStyle = mapping.modernStyle;
if (StringEqualsAscii(candidate, oldStyle)) {
candidate = NewStringCopyZ<CanGC>(cx, modernStyle);
if (!candidate) {
return false;
}
break;
}
}
}
// 9.1 Internal slots of Service Constructors
//
// - [[AvailableLocales]] is a List [...]. The list must include the value
// returned by the DefaultLocale abstract operation (6.2.4), [...].
//
// That implies we must ignore any candidate which isn't supported by all Intl
// service constructors.
//
// Note: We don't test the supported locales of either Intl.PluralRules or
// Intl.RelativeTimeFormat, because ICU doesn't provide the necessary API to
// return actual set of supported locales for these constructors. Instead it
// returns the complete set of available locales for ULocale, which is a
// superset of the locales supported by Collator, NumberFormat, and
// DateTimeFormat.
bool isSupported = true;
for (auto kind : {SupportedLocaleKind::Collator, SupportedLocaleKind::DateTimeFormat,
SupportedLocaleKind::NumberFormat}) {
JSString* supported;
JS_TRY_VAR_OR_RETURN_FALSE(cx, supported, BestAvailableLocale(cx, kind, candidate, nullptr));
if (!supported) {
isSupported = false;
break;
}
}
if (!isSupported) {
candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
if (!candidate) {
return false;
}
}
args.rval().setString(candidate);
return true;
}
const Class js::IntlClass = {
js_Object_str,
JSCLASS_HAS_CACHED_PROTO(JSProto_Intl)
@ -454,10 +727,16 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global));
if (!collatorProto)
return false;
RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global));
RootedObject dateTimeFormatProto(cx), dateTimeFormat(cx);
dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat, DateTimeFormatOptions::Standard);
if (!dateTimeFormatProto)
return false;
RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
RootedObject localeProto(cx);
localeProto = CreateLocalePrototype(cx, intl, global);
if (!localeProto)
return false;
RootedObject numberFormatProto(cx), numberFormat(cx);
numberFormatProto = CreateNumberFormatPrototype(cx, intl, global, &numberFormat);
if (!numberFormatProto)
return false;
RootedObject pluralRulesProto(cx, CreatePluralRulesPrototype(cx, intl, global));
@ -487,7 +766,10 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
// |getPrototype(JSProto_*)|, but that has global-object-property-related
// baggage we don't need or want, so we use one-off reserved slots.
global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
global->setReservedSlot(DATE_TIME_FORMAT, ObjectValue(*dateTimeFormat));
global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
global->setReservedSlot(LOCALE_PROTO, ObjectValue(*localeProto));
global->setReservedSlot(NUMBER_FORMAT, ObjectValue(*numberFormat));
global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
global->setReservedSlot(PLURAL_RULES_PROTO, ObjectValue(*pluralRulesProto));
global->setReservedSlot(RELATIVE_TIME_FORMAT_PROTO, ObjectValue(*relativeTimeFmtProto));

View file

@ -95,6 +95,35 @@ intl_GetCalendarInfo(JSContext* cx, unsigned argc, JS::Value* vp);
extern MOZ_MUST_USE bool
intl_ComputeDisplayNames(JSContext* cx, unsigned argc, JS::Value* vp);
/**
* Compares a BCP 47 language tag against the locales in availableLocales and
* returns the best available match -- or |undefined| if no match was found.
* Uses the fallback mechanism of RFC 4647, section 3.4.
*
* The set of available locales consulted doesn't necessarily include the
* default locale or any generalized forms of it (e.g. "de" is a more-general
* form of "de-CH"). If you want to be sure to consider the default local and
* its generalized forms (you usually will), pass the default locale as the
* value of |defaultOrNull|; otherwise pass null.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.2.
* Spec: RFC 4647, section 3.4.
*
* Usage: result = intl_BestAvailableLocale("Collator", locale, defaultOrNull)
*/
extern MOZ_MUST_USE bool
intl_BestAvailableLocale(JSContext* cx, unsigned argc, JS::Value* vp);
/**
* Returns the input locale in its canonicalized form if ICU supports that
* locale (perhaps via fallback, e.g. supporting "de-ZA" through "de" support
* implied by a "de-DE" locale). Otherwise uses the last-ditch locale.
*
* Usage: result = intl_supportedLocaleOrFallback(locale)
*/
extern MOZ_MUST_USE bool
intl_supportedLocaleOrFallback(JSContext* cx, unsigned argc, JS::Value* vp);
} // namespace js
#endif /* builtin_intl_IntlObject_h */

View file

@ -3,44 +3,79 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function Intl_getCanonicalLocales(locales) {
let codes = CanonicalizeLocaleList(locales);
let result = [];
// Step 1.
var localeList = CanonicalizeLocaleList(locales);
let len = codes.length;
let k = 0;
// Step 2 (Inlined CreateArrayFromList).
var array = [];
while (k < len) {
_DefineDataProperty(result, k, codes[k]);
k++;
}
return result;
}
for (var n = 0, len = localeList.length; n < len; n++)
_DefineDataProperty(array, n, localeList[n]);
function Intl_getCalendarInfo(locales) {
const requestedLocales = CanonicalizeLocaleList(locales);
const DateTimeFormat = dateTimeFormatInternalProperties;
const localeData = DateTimeFormat.localeData;
const localeOpt = new Record();
localeOpt.localeMatcher = "best fit";
const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
requestedLocales,
localeOpt,
DateTimeFormat.relevantExtensionKeys,
localeData);
const result = intl_GetCalendarInfo(r.locale);
result.calendar = r.ca;
result.locale = r.locale;
return result;
return array;
}
/**
* This function is a custom method designed after Intl API, but currently
* not part of the spec or spec proposal.
* This function is a custom function in the style of the standard Intl.*
* functions, that isn't part of any spec or proposal yet.
*
* Returns an object with the following properties:
* locale:
* The actual resolved locale.
*
* calendar:
* The default calendar of the resolved locale.
*
* firstDayOfWeek:
* The first day of the week for the resolved locale.
*
* minDays:
* The minimum number of days in a week for the resolved locale.
*
* weekendStart:
* The day considered the beginning of a weekend for the resolved locale.
*
* weekendEnd:
* The day considered the end of a weekend for the resolved locale.
*
* Days are encoded as integers in the range 1=Sunday to 7=Saturday.
*/
function Intl_getCalendarInfo(locales) {
// 1. Let requestLocales be ? CanonicalizeLocaleList(locales).
const requestedLocales = CanonicalizeLocaleList(locales);
const DateTimeFormat = dateTimeFormatInternalProperties;
// 2. Let localeData be %DateTimeFormat%.[[localeData]].
const localeData = DateTimeFormat.localeData;
// 3. Let localeOpt be a new Record.
const localeOpt = new Record();
// 4. Set localeOpt.[[localeMatcher]] to "best fit".
localeOpt.localeMatcher = "best fit";
// 5. Let r be ResolveLocale(%DateTimeFormat%.[[availableLocales]],
// requestedLocales, localeOpt,
// %DateTimeFormat%.[[relevantExtensionKeys]], localeData).
const r = ResolveLocale("DateTimeFormat",
requestedLocales,
localeOpt,
DateTimeFormat.relevantExtensionKeys,
localeData);
// 6. Let result be GetCalendarInfo(r.[[locale]]).
const result = intl_GetCalendarInfo(r.locale);
_DefineDataProperty(result, "calendar", r.ca);
_DefineDataProperty(result, "locale", r.locale);
// 7. Return result.
return result;
}
/**
* This function is a custom function in the style of the standard Intl.*
* functions, that isn't part of any spec or proposal yet.
* We want to use it internally to retrieve translated values from CLDR in
* order to ensure they're aligned with what Intl API returns.
*
@ -86,21 +121,23 @@ function Intl_getDisplayNames(locales, options) {
// 4. Let localeData be %DateTimeFormat%.[[localeData]].
const localeData = DateTimeFormat.localeData;
// 5. Let opt be a new Record.
// 5. Let localeOpt be a new Record.
const localeOpt = new Record();
// 6. Set localeOpt.[[localeMatcher]] to "best fit".
localeOpt.localeMatcher = "best fit";
// 7. Let r be ResolveLocale(%DateTimeFormat%.[[availableLocales]], requestedLocales, localeOpt,
// %DateTimeFormat%.[[relevantExtensionKeys]], localeData).
const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
requestedLocales,
localeOpt,
DateTimeFormat.relevantExtensionKeys,
localeData);
const r = ResolveLocale("DateTimeFormat",
requestedLocales,
localeOpt,
DateTimeFormat.relevantExtensionKeys,
localeData);
// 8. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long").
const style = GetOption(options, "style", "string", ["long", "short", "narrow"], "long");
// 9. Let keys be ? Get(options, "keys").
let keys = options.keys;
@ -119,8 +156,10 @@ function Intl_getDisplayNames(locales, options) {
// |intl_ComputeDisplayNames| may infallibly access the list's length via
// |ArrayObject::length|.)
let processedKeys = [];
// 13. Let len be ? ToLength(? Get(keys, "length")).
let len = ToLength(keys.length);
// 14. Let i be 0.
// 15. Repeat, while i < len
for (let i = 0; i < len; i++) {

View file

@ -1,382 +0,0 @@
// Generated by make_intl_data.py. DO NOT EDIT.
// Mappings from complete tags to preferred values.
// Derived from IANA Language Subtag Registry, file date 2016-10-12.
// http://www.iana.org/assignments/language-subtag-registry
var langTagMappings = {
"art-lojban": "jbo",
"cel-gaulish": "cel-gaulish",
"en-gb-oed": "en-GB-oxendict",
"i-ami": "ami",
"i-bnn": "bnn",
"i-default": "i-default",
"i-enochian": "i-enochian",
"i-hak": "hak",
"i-klingon": "tlh",
"i-lux": "lb",
"i-mingo": "i-mingo",
"i-navajo": "nv",
"i-pwn": "pwn",
"i-tao": "tao",
"i-tay": "tay",
"i-tsu": "tsu",
"ja-latn-hepburn-heploc": "ja-Latn-alalc97",
"no-bok": "nb",
"no-nyn": "nn",
"sgn-be-fr": "sfb",
"sgn-be-nl": "vgt",
"sgn-br": "bzs",
"sgn-ch-de": "sgg",
"sgn-co": "csn",
"sgn-de": "gsg",
"sgn-dk": "dsl",
"sgn-es": "ssp",
"sgn-fr": "fsl",
"sgn-gb": "bfi",
"sgn-gr": "gss",
"sgn-ie": "isg",
"sgn-it": "ise",
"sgn-jp": "jsl",
"sgn-mx": "mfs",
"sgn-ni": "ncs",
"sgn-nl": "dse",
"sgn-no": "nsl",
"sgn-pt": "psr",
"sgn-se": "swl",
"sgn-us": "ase",
"sgn-za": "sfs",
"zh-cmn": "cmn",
"zh-cmn-hans": "cmn-Hans",
"zh-cmn-hant": "cmn-Hant",
"zh-gan": "gan",
"zh-guoyu": "cmn",
"zh-hakka": "hak",
"zh-min": "zh-min",
"zh-min-nan": "nan",
"zh-wuu": "wuu",
"zh-xiang": "hsn",
"zh-yue": "yue",
};
// Mappings from non-extlang subtags to preferred values.
// Derived from IANA Language Subtag Registry, file date 2016-10-12.
// http://www.iana.org/assignments/language-subtag-registry
var langSubtagMappings = {
"BU": "MM",
"DD": "DE",
"FX": "FR",
"TP": "TL",
"YD": "YE",
"ZR": "CD",
"aam": "aas",
"adp": "dz",
"aue": "ktz",
"ayx": "nun",
"bgm": "bcg",
"bjd": "drl",
"ccq": "rki",
"cjr": "mom",
"cka": "cmr",
"cmk": "xch",
"coy": "pij",
"cqu": "quh",
"drh": "khk",
"drw": "prs",
"gav": "dev",
"gfx": "vaj",
"ggn": "gvr",
"gti": "nyc",
"guv": "duz",
"hrr": "jal",
"ibi": "opa",
"ilw": "gal",
"in": "id",
"iw": "he",
"ji": "yi",
"jw": "jv",
"kgc": "tdf",
"kgh": "kml",
"koj": "kwv",
"ktr": "dtp",
"kvs": "gdj",
"kwq": "yam",
"kxe": "tvd",
"kzj": "dtp",
"kzt": "dtp",
"lii": "raq",
"lmm": "rmx",
"meg": "cir",
"mo": "ro",
"mst": "mry",
"mwj": "vaj",
"myt": "mry",
"nad": "xny",
"nnx": "ngv",
"nts": "pij",
"oun": "vaj",
"pcr": "adx",
"pmc": "huw",
"pmu": "phr",
"ppa": "bfy",
"ppr": "lcq",
"pry": "prt",
"puz": "pub",
"sca": "hle",
"tdu": "dtp",
"thc": "tpo",
"thx": "oyb",
"tie": "ras",
"tkk": "twm",
"tlw": "weo",
"tmp": "tyj",
"tne": "kak",
"tnf": "prs",
"tsf": "taj",
"uok": "ema",
"xba": "cax",
"xia": "acn",
"xkh": "waw",
"xsj": "suj",
"ybd": "rki",
"yma": "lrr",
"ymt": "mtm",
"yos": "zom",
"yuu": "yug",
};
// Mappings from extlang subtags to preferred values.
// Derived from IANA Language Subtag Registry, file date 2016-10-12.
// http://www.iana.org/assignments/language-subtag-registry
var extlangMappings = {
"aao": {preferred: "aao", prefix: "ar"},
"abh": {preferred: "abh", prefix: "ar"},
"abv": {preferred: "abv", prefix: "ar"},
"acm": {preferred: "acm", prefix: "ar"},
"acq": {preferred: "acq", prefix: "ar"},
"acw": {preferred: "acw", prefix: "ar"},
"acx": {preferred: "acx", prefix: "ar"},
"acy": {preferred: "acy", prefix: "ar"},
"adf": {preferred: "adf", prefix: "ar"},
"ads": {preferred: "ads", prefix: "sgn"},
"aeb": {preferred: "aeb", prefix: "ar"},
"aec": {preferred: "aec", prefix: "ar"},
"aed": {preferred: "aed", prefix: "sgn"},
"aen": {preferred: "aen", prefix: "sgn"},
"afb": {preferred: "afb", prefix: "ar"},
"afg": {preferred: "afg", prefix: "sgn"},
"ajp": {preferred: "ajp", prefix: "ar"},
"apc": {preferred: "apc", prefix: "ar"},
"apd": {preferred: "apd", prefix: "ar"},
"arb": {preferred: "arb", prefix: "ar"},
"arq": {preferred: "arq", prefix: "ar"},
"ars": {preferred: "ars", prefix: "ar"},
"ary": {preferred: "ary", prefix: "ar"},
"arz": {preferred: "arz", prefix: "ar"},
"ase": {preferred: "ase", prefix: "sgn"},
"asf": {preferred: "asf", prefix: "sgn"},
"asp": {preferred: "asp", prefix: "sgn"},
"asq": {preferred: "asq", prefix: "sgn"},
"asw": {preferred: "asw", prefix: "sgn"},
"auz": {preferred: "auz", prefix: "ar"},
"avl": {preferred: "avl", prefix: "ar"},
"ayh": {preferred: "ayh", prefix: "ar"},
"ayl": {preferred: "ayl", prefix: "ar"},
"ayn": {preferred: "ayn", prefix: "ar"},
"ayp": {preferred: "ayp", prefix: "ar"},
"bbz": {preferred: "bbz", prefix: "ar"},
"bfi": {preferred: "bfi", prefix: "sgn"},
"bfk": {preferred: "bfk", prefix: "sgn"},
"bjn": {preferred: "bjn", prefix: "ms"},
"bog": {preferred: "bog", prefix: "sgn"},
"bqn": {preferred: "bqn", prefix: "sgn"},
"bqy": {preferred: "bqy", prefix: "sgn"},
"btj": {preferred: "btj", prefix: "ms"},
"bve": {preferred: "bve", prefix: "ms"},
"bvl": {preferred: "bvl", prefix: "sgn"},
"bvu": {preferred: "bvu", prefix: "ms"},
"bzs": {preferred: "bzs", prefix: "sgn"},
"cdo": {preferred: "cdo", prefix: "zh"},
"cds": {preferred: "cds", prefix: "sgn"},
"cjy": {preferred: "cjy", prefix: "zh"},
"cmn": {preferred: "cmn", prefix: "zh"},
"coa": {preferred: "coa", prefix: "ms"},
"cpx": {preferred: "cpx", prefix: "zh"},
"csc": {preferred: "csc", prefix: "sgn"},
"csd": {preferred: "csd", prefix: "sgn"},
"cse": {preferred: "cse", prefix: "sgn"},
"csf": {preferred: "csf", prefix: "sgn"},
"csg": {preferred: "csg", prefix: "sgn"},
"csl": {preferred: "csl", prefix: "sgn"},
"csn": {preferred: "csn", prefix: "sgn"},
"csq": {preferred: "csq", prefix: "sgn"},
"csr": {preferred: "csr", prefix: "sgn"},
"czh": {preferred: "czh", prefix: "zh"},
"czo": {preferred: "czo", prefix: "zh"},
"doq": {preferred: "doq", prefix: "sgn"},
"dse": {preferred: "dse", prefix: "sgn"},
"dsl": {preferred: "dsl", prefix: "sgn"},
"dup": {preferred: "dup", prefix: "ms"},
"ecs": {preferred: "ecs", prefix: "sgn"},
"esl": {preferred: "esl", prefix: "sgn"},
"esn": {preferred: "esn", prefix: "sgn"},
"eso": {preferred: "eso", prefix: "sgn"},
"eth": {preferred: "eth", prefix: "sgn"},
"fcs": {preferred: "fcs", prefix: "sgn"},
"fse": {preferred: "fse", prefix: "sgn"},
"fsl": {preferred: "fsl", prefix: "sgn"},
"fss": {preferred: "fss", prefix: "sgn"},
"gan": {preferred: "gan", prefix: "zh"},
"gds": {preferred: "gds", prefix: "sgn"},
"gom": {preferred: "gom", prefix: "kok"},
"gse": {preferred: "gse", prefix: "sgn"},
"gsg": {preferred: "gsg", prefix: "sgn"},
"gsm": {preferred: "gsm", prefix: "sgn"},
"gss": {preferred: "gss", prefix: "sgn"},
"gus": {preferred: "gus", prefix: "sgn"},
"hab": {preferred: "hab", prefix: "sgn"},
"haf": {preferred: "haf", prefix: "sgn"},
"hak": {preferred: "hak", prefix: "zh"},
"hds": {preferred: "hds", prefix: "sgn"},
"hji": {preferred: "hji", prefix: "ms"},
"hks": {preferred: "hks", prefix: "sgn"},
"hos": {preferred: "hos", prefix: "sgn"},
"hps": {preferred: "hps", prefix: "sgn"},
"hsh": {preferred: "hsh", prefix: "sgn"},
"hsl": {preferred: "hsl", prefix: "sgn"},
"hsn": {preferred: "hsn", prefix: "zh"},
"icl": {preferred: "icl", prefix: "sgn"},
"iks": {preferred: "iks", prefix: "sgn"},
"ils": {preferred: "ils", prefix: "sgn"},
"inl": {preferred: "inl", prefix: "sgn"},
"ins": {preferred: "ins", prefix: "sgn"},
"ise": {preferred: "ise", prefix: "sgn"},
"isg": {preferred: "isg", prefix: "sgn"},
"isr": {preferred: "isr", prefix: "sgn"},
"jak": {preferred: "jak", prefix: "ms"},
"jax": {preferred: "jax", prefix: "ms"},
"jcs": {preferred: "jcs", prefix: "sgn"},
"jhs": {preferred: "jhs", prefix: "sgn"},
"jls": {preferred: "jls", prefix: "sgn"},
"jos": {preferred: "jos", prefix: "sgn"},
"jsl": {preferred: "jsl", prefix: "sgn"},
"jus": {preferred: "jus", prefix: "sgn"},
"kgi": {preferred: "kgi", prefix: "sgn"},
"knn": {preferred: "knn", prefix: "kok"},
"kvb": {preferred: "kvb", prefix: "ms"},
"kvk": {preferred: "kvk", prefix: "sgn"},
"kvr": {preferred: "kvr", prefix: "ms"},
"kxd": {preferred: "kxd", prefix: "ms"},
"lbs": {preferred: "lbs", prefix: "sgn"},
"lce": {preferred: "lce", prefix: "ms"},
"lcf": {preferred: "lcf", prefix: "ms"},
"liw": {preferred: "liw", prefix: "ms"},
"lls": {preferred: "lls", prefix: "sgn"},
"lsg": {preferred: "lsg", prefix: "sgn"},
"lsl": {preferred: "lsl", prefix: "sgn"},
"lso": {preferred: "lso", prefix: "sgn"},
"lsp": {preferred: "lsp", prefix: "sgn"},
"lst": {preferred: "lst", prefix: "sgn"},
"lsy": {preferred: "lsy", prefix: "sgn"},
"ltg": {preferred: "ltg", prefix: "lv"},
"lvs": {preferred: "lvs", prefix: "lv"},
"lzh": {preferred: "lzh", prefix: "zh"},
"max": {preferred: "max", prefix: "ms"},
"mdl": {preferred: "mdl", prefix: "sgn"},
"meo": {preferred: "meo", prefix: "ms"},
"mfa": {preferred: "mfa", prefix: "ms"},
"mfb": {preferred: "mfb", prefix: "ms"},
"mfs": {preferred: "mfs", prefix: "sgn"},
"min": {preferred: "min", prefix: "ms"},
"mnp": {preferred: "mnp", prefix: "zh"},
"mqg": {preferred: "mqg", prefix: "ms"},
"mre": {preferred: "mre", prefix: "sgn"},
"msd": {preferred: "msd", prefix: "sgn"},
"msi": {preferred: "msi", prefix: "ms"},
"msr": {preferred: "msr", prefix: "sgn"},
"mui": {preferred: "mui", prefix: "ms"},
"mzc": {preferred: "mzc", prefix: "sgn"},
"mzg": {preferred: "mzg", prefix: "sgn"},
"mzy": {preferred: "mzy", prefix: "sgn"},
"nan": {preferred: "nan", prefix: "zh"},
"nbs": {preferred: "nbs", prefix: "sgn"},
"ncs": {preferred: "ncs", prefix: "sgn"},
"nsi": {preferred: "nsi", prefix: "sgn"},
"nsl": {preferred: "nsl", prefix: "sgn"},
"nsp": {preferred: "nsp", prefix: "sgn"},
"nsr": {preferred: "nsr", prefix: "sgn"},
"nzs": {preferred: "nzs", prefix: "sgn"},
"okl": {preferred: "okl", prefix: "sgn"},
"orn": {preferred: "orn", prefix: "ms"},
"ors": {preferred: "ors", prefix: "ms"},
"pel": {preferred: "pel", prefix: "ms"},
"pga": {preferred: "pga", prefix: "ar"},
"pgz": {preferred: "pgz", prefix: "sgn"},
"pks": {preferred: "pks", prefix: "sgn"},
"prl": {preferred: "prl", prefix: "sgn"},
"prz": {preferred: "prz", prefix: "sgn"},
"psc": {preferred: "psc", prefix: "sgn"},
"psd": {preferred: "psd", prefix: "sgn"},
"pse": {preferred: "pse", prefix: "ms"},
"psg": {preferred: "psg", prefix: "sgn"},
"psl": {preferred: "psl", prefix: "sgn"},
"pso": {preferred: "pso", prefix: "sgn"},
"psp": {preferred: "psp", prefix: "sgn"},
"psr": {preferred: "psr", prefix: "sgn"},
"pys": {preferred: "pys", prefix: "sgn"},
"rms": {preferred: "rms", prefix: "sgn"},
"rsi": {preferred: "rsi", prefix: "sgn"},
"rsl": {preferred: "rsl", prefix: "sgn"},
"rsm": {preferred: "rsm", prefix: "sgn"},
"sdl": {preferred: "sdl", prefix: "sgn"},
"sfb": {preferred: "sfb", prefix: "sgn"},
"sfs": {preferred: "sfs", prefix: "sgn"},
"sgg": {preferred: "sgg", prefix: "sgn"},
"sgx": {preferred: "sgx", prefix: "sgn"},
"shu": {preferred: "shu", prefix: "ar"},
"slf": {preferred: "slf", prefix: "sgn"},
"sls": {preferred: "sls", prefix: "sgn"},
"sqk": {preferred: "sqk", prefix: "sgn"},
"sqs": {preferred: "sqs", prefix: "sgn"},
"ssh": {preferred: "ssh", prefix: "ar"},
"ssp": {preferred: "ssp", prefix: "sgn"},
"ssr": {preferred: "ssr", prefix: "sgn"},
"svk": {preferred: "svk", prefix: "sgn"},
"swc": {preferred: "swc", prefix: "sw"},
"swh": {preferred: "swh", prefix: "sw"},
"swl": {preferred: "swl", prefix: "sgn"},
"syy": {preferred: "syy", prefix: "sgn"},
"tmw": {preferred: "tmw", prefix: "ms"},
"tse": {preferred: "tse", prefix: "sgn"},
"tsm": {preferred: "tsm", prefix: "sgn"},
"tsq": {preferred: "tsq", prefix: "sgn"},
"tss": {preferred: "tss", prefix: "sgn"},
"tsy": {preferred: "tsy", prefix: "sgn"},
"tza": {preferred: "tza", prefix: "sgn"},
"ugn": {preferred: "ugn", prefix: "sgn"},
"ugy": {preferred: "ugy", prefix: "sgn"},
"ukl": {preferred: "ukl", prefix: "sgn"},
"uks": {preferred: "uks", prefix: "sgn"},
"urk": {preferred: "urk", prefix: "ms"},
"uzn": {preferred: "uzn", prefix: "uz"},
"uzs": {preferred: "uzs", prefix: "uz"},
"vgt": {preferred: "vgt", prefix: "sgn"},
"vkk": {preferred: "vkk", prefix: "ms"},
"vkt": {preferred: "vkt", prefix: "ms"},
"vsi": {preferred: "vsi", prefix: "sgn"},
"vsl": {preferred: "vsl", prefix: "sgn"},
"vsv": {preferred: "vsv", prefix: "sgn"},
"wuu": {preferred: "wuu", prefix: "zh"},
"xki": {preferred: "xki", prefix: "sgn"},
"xml": {preferred: "xml", prefix: "sgn"},
"xmm": {preferred: "xmm", prefix: "ms"},
"xms": {preferred: "xms", prefix: "sgn"},
"ygs": {preferred: "ygs", prefix: "sgn"},
"yhs": {preferred: "yhs", prefix: "sgn"},
"ysl": {preferred: "ysl", prefix: "sgn"},
"yue": {preferred: "yue", prefix: "zh"},
"zib": {preferred: "zib", prefix: "sgn"},
"zlm": {preferred: "zlm", prefix: "ms"},
"zmi": {preferred: "zmi", prefix: "ms"},
"zsl": {preferred: "zsl", prefix: "sgn"},
"zsm": {preferred: "zsm", prefix: "ms"},
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,770 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Structured representation of Unicode locale IDs used with Intl functions. */
#ifndef builtin_intl_LanguageTag_h
#define builtin_intl_LanguageTag_h
#include "mozilla/Assertions.h"
#include "mozilla/Span.h"
#include "mozilla/TextUtils.h"
#include "mozilla/TypedEnumBits.h"
#include "mozilla/Variant.h"
#include <algorithm>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <utility>
#include "jsalloc.h"
#include "js/Result.h"
#include "js/GCAPI.h"
#include "js/Utility.h"
#include "js/Vector.h"
struct JSContext;
class JSLinearString;
class JSString;
class JSTracer;
namespace js {
namespace intl {
/**
* Return true if |language| is a valid language subtag.
*/
template <typename CharT>
bool IsStructurallyValidLanguageTag(mozilla::Span<const CharT> language);
/**
* Return true if |script| is a valid script subtag.
*/
template <typename CharT>
bool IsStructurallyValidScriptTag(mozilla::Span<const CharT> script);
/**
* Return true if |region| is a valid region subtag.
*/
template <typename CharT>
bool IsStructurallyValidRegionTag(mozilla::Span<const CharT> region);
#ifdef DEBUG
/**
* Return true if |variant| is a valid variant subtag.
*/
bool IsStructurallyValidVariantTag(mozilla::Span<const char> variant);
/**
* Return true if |extension| is a valid Unicode extension subtag.
*/
bool IsStructurallyValidUnicodeExtensionTag(
mozilla::Span<const char> extension);
/**
* Return true if |privateUse| is a valid private-use subtag.
*/
bool IsStructurallyValidPrivateUseTag(mozilla::Span<const char> privateUse);
#endif
template <typename CharT>
char AsciiToLowerCase(CharT c) {
MOZ_ASSERT(mozilla::IsAscii(c));
return mozilla::IsAsciiUppercaseAlpha(c) ? (c + 0x20) : c;
}
template <typename CharT>
char AsciiToUpperCase(CharT c) {
MOZ_ASSERT(mozilla::IsAscii(c));
return mozilla::IsAsciiLowercaseAlpha(c) ? (c - 0x20) : c;
}
template <typename CharT>
void AsciiToLowerCase(CharT* chars, size_t length, char* dest) {
// Tell the analysis the |std::transform| function can't GC.
JS::AutoSuppressGCAnalysis nogc;
char (&fn)(CharT) = AsciiToLowerCase;
std::transform(chars, chars + length, dest, fn);
}
template <typename CharT>
void AsciiToUpperCase(CharT* chars, size_t length, char* dest) {
// Tell the analysis the |std::transform| function can't GC.
JS::AutoSuppressGCAnalysis nogc;
char (&fn)(CharT) = AsciiToUpperCase;
std::transform(chars, chars + length, dest, fn);
}
template <typename CharT>
void AsciiToTitleCase(CharT* chars, size_t length, char* dest) {
if (length > 0) {
AsciiToUpperCase(chars, 1, dest);
AsciiToLowerCase(chars + 1, length - 1, dest + 1);
}
}
// Constants for language subtag lengths.
namespace LanguageTagLimits {
// unicode_language_subtag = alpha{2,3} | alpha{5,8} ;
static constexpr size_t LanguageLength = 8;
// unicode_script_subtag = alpha{4} ;
static constexpr size_t ScriptLength = 4;
// unicode_region_subtag = (alpha{2} | digit{3}) ;
static constexpr size_t RegionLength = 3;
static constexpr size_t AlphaRegionLength = 2;
static constexpr size_t DigitRegionLength = 3;
// key = alphanum alpha ;
static constexpr size_t UnicodeKeyLength = 2;
// tkey = alpha digit ;
static constexpr size_t TransformKeyLength = 2;
} // namespace LanguageTagLimits
// Fixed size language subtag which is stored inline in LanguageTag.
template <size_t Length>
class LanguageTagSubtag final {
uint8_t length_ = 0;
char chars_[Length] = {}; // zero initialize
public:
LanguageTagSubtag() = default;
LanguageTagSubtag(const LanguageTagSubtag&) = delete;
LanguageTagSubtag& operator=(const LanguageTagSubtag&) = delete;
size_t length() const { return length_; }
bool missing() const { return length_ == 0; }
bool present() const { return length_ > 0; }
mozilla::Span<const char> span() const { return {chars_, length_}; }
template <typename CharT>
void set(mozilla::Span<const CharT> str) {
MOZ_ASSERT(str.size() <= Length);
std::copy_n(str.data(), str.size(), chars_);
length_ = str.size();
}
// The toXYZCase() methods are using |Length| instead of |length()|, because
// current compilers (tested GCC and Clang) can't infer the maximum string
// length - even when using hints like |std::min| - and instead are emitting
// SIMD optimized code. Using a fixed sized length avoids emitting the SIMD
// code. (Emitting SIMD code doesn't make sense here, because the SIMD code
// only kicks in for long strings.) A fixed length will additionally ensure
// the compiler unrolls the loop in the case conversion code.
void toLowerCase() { AsciiToLowerCase(chars_, Length, chars_); }
void toUpperCase() { AsciiToUpperCase(chars_, Length, chars_); }
void toTitleCase() { AsciiToTitleCase(chars_, Length, chars_); }
template <size_t N>
bool equalTo(const char (&str)[N]) const {
static_assert(N - 1 <= Length,
"subtag literals must not exceed the maximum subtag length");
return length_ == N - 1 && memcmp(chars_, str, N - 1) == 0;
}
};
using LanguageSubtag = LanguageTagSubtag<LanguageTagLimits::LanguageLength>;
using ScriptSubtag = LanguageTagSubtag<LanguageTagLimits::ScriptLength>;
using RegionSubtag = LanguageTagSubtag<LanguageTagLimits::RegionLength>;
/**
* Object representing a language tag.
*
* All subtags are already in canonicalized case.
*/
class MOZ_STACK_CLASS LanguageTag final {
LanguageSubtag language_ = {};
ScriptSubtag script_ = {};
RegionSubtag region_ = {};
using VariantsVector = Vector<JS::UniqueChars, 2>;
using ExtensionsVector = Vector<JS::UniqueChars, 2>;
VariantsVector variants_;
ExtensionsVector extensions_;
JS::UniqueChars privateuse_ = nullptr;
friend class LanguageTagParser;
bool canonicalizeUnicodeExtension(JSContext* cx,
JS::UniqueChars& unicodeExtension);
bool canonicalizeTransformExtension(JSContext* cx,
JS::UniqueChars& transformExtension);
public:
static bool languageMapping(LanguageSubtag& language);
static bool complexLanguageMapping(const LanguageSubtag& language);
private:
static bool regionMapping(RegionSubtag& region);
static bool complexRegionMapping(const RegionSubtag& region);
void performComplexLanguageMappings();
void performComplexRegionMappings();
MOZ_MUST_USE bool performVariantMappings(JSContext* cx);
MOZ_MUST_USE bool updateGrandfatheredMappings(JSContext* cx);
static const char* replaceTransformExtensionType(
mozilla::Span<const char> key, mozilla::Span<const char> type);
public:
/**
* Given a Unicode key and type, return the null-terminated preferred
* replacement for that type if there is one, or null if there is none, e.g.
* in effect
* |replaceUnicodeExtensionType("ca", "islamicc") == "islamic-civil"|
* and
* |replaceUnicodeExtensionType("ca", "islamic-civil") == nullptr|.
*/
static const char* replaceUnicodeExtensionType(
mozilla::Span<const char> key, mozilla::Span<const char> type);
public:
explicit LanguageTag(JSContext* cx) : variants_(cx), extensions_(cx) {}
LanguageTag(const LanguageTag&) = delete;
LanguageTag& operator=(const LanguageTag&) = delete;
const LanguageSubtag& language() const { return language_; }
const ScriptSubtag& script() const { return script_; }
const RegionSubtag& region() const { return region_; }
const auto& variants() const { return variants_; }
const auto& extensions() const { return extensions_; }
const char* privateuse() const { return privateuse_.get(); }
/**
* Return the Unicode extension subtag or nullptr if not present.
*/
const char* unicodeExtension() const;
private:
ptrdiff_t unicodeExtensionIndex() const;
public:
/**
* Set the language subtag. The input must be a valid language subtag.
*/
template <size_t N>
void setLanguage(const char (&language)[N]) {
mozilla::Span<const char> span(language, N - 1);
MOZ_ASSERT(IsStructurallyValidLanguageTag(span));
language_.set(span);
}
/**
* Set the language subtag. The input must be a valid language subtag.
*/
void setLanguage(const LanguageSubtag& language) {
MOZ_ASSERT(IsStructurallyValidLanguageTag(language.span()));
language_.set(language.span());
}
/**
* Set the script subtag. The input must be a valid script subtag.
*/
template <size_t N>
void setScript(const char (&script)[N]) {
mozilla::Span<const char> span(script, N - 1);
MOZ_ASSERT(IsStructurallyValidScriptTag(span));
script_.set(span);
}
/**
* Set the script subtag. The input must be a valid script subtag or the empty
* string.
*/
void setScript(const ScriptSubtag& script) {
MOZ_ASSERT(script.missing() || IsStructurallyValidScriptTag(script.span()));
script_.set(script.span());
}
/**
* Set the region subtag. The input must be a valid region subtag.
*/
template <size_t N>
void setRegion(const char (&region)[N]) {
mozilla::Span<const char> span(region, N - 1);
MOZ_ASSERT(IsStructurallyValidRegionTag(span));
region_.set(span);
}
/**
* Set the region subtag. The input must be a valid region subtag or the empty
* empty string.
*/
void setRegion(const RegionSubtag& region) {
MOZ_ASSERT(region.missing() || IsStructurallyValidRegionTag(region.span()));
region_.set(region.span());
}
/**
* Removes all variant subtags.
*/
void clearVariants() { variants_.clearAndFree(); }
/**
* Set the Unicode extension subtag. The input must be a valid Unicode
* extension subtag.
*/
bool setUnicodeExtension(JS::UniqueChars extension);
/**
* Remove any Unicode extension subtag if present.
*/
void clearUnicodeExtension();
/**
* Set the private-use subtag. The input must be a valid private-use subtag
* or nullptr.
*/
void setPrivateuse(JS::UniqueChars privateuse) {
MOZ_ASSERT(!privateuse ||
IsStructurallyValidPrivateUseTag(
{privateuse.get(), strlen(privateuse.get())}));
privateuse_ = std::move(privateuse);
}
private:
enum class DuplicateVariants { Reject, Accept };
bool canonicalizeBaseName(JSContext* cx, DuplicateVariants duplicateVariants);
public:
/**
* Canonicalize the base-name subtags, that means the language, script,
* region, and variant subtags.
*/
bool canonicalizeBaseName(JSContext* cx) {
return canonicalizeBaseName(cx, DuplicateVariants::Reject);
}
/**
* Canonicalize all extension subtags.
*/
bool canonicalizeExtensions(JSContext* cx);
/**
* Canonicalizes the given structurally valid Unicode BCP 47 locale
* identifier, including regularized case of subtags. For example, the
* language tag Zh-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE,
* where
*
* Zh ; 2*3ALPHA
* -haNS ; ["-" script]
* -bu ; ["-" region]
* -variant2 ; *("-" variant)
* -Variant1
* -u-ca-chinese ; *("-" extension)
* -t-Zh-laTN
* -x-PRIVATE ; ["-" privateuse]
*
* becomes zh-Hans-MM-variant1-variant2-t-zh-latn-u-ca-chinese-x-private
*
* Spec: ECMAScript Internationalization API Specification, 6.2.3.
*/
bool canonicalize(JSContext* cx) {
return canonicalizeBaseName(cx) && canonicalizeExtensions(cx);
}
/**
* Return the string representation of this language tag.
*/
JSString* toString(JSContext* cx) const;
/**
* Return the string representation of this language tag as a null-terminated
* C-string.
*/
JS::UniqueChars toStringZ(JSContext* cx) const;
/**
* Add likely-subtags to the language tag.
*
* Spec: <https://www.unicode.org/reports/tr35/#Likely_Subtags>
*/
bool addLikelySubtags(JSContext* cx);
/**
* Remove likely-subtags from the language tag.
*
* Spec: <https://www.unicode.org/reports/tr35/#Likely_Subtags>
*/
bool removeLikelySubtags(JSContext* cx);
};
/**
* Parser for Unicode BCP 47 locale identifiers.
*
* <https://unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers>
*/
class MOZ_STACK_CLASS LanguageTagParser final {
public:
// Exposed as |public| for |MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS|.
enum class TokenKind : uint8_t {
None = 0b000,
Alpha = 0b001,
Digit = 0b010,
AlphaDigit = 0b011,
Error = 0b100
};
private:
class Token final {
size_t index_;
size_t length_;
TokenKind kind_;
public:
Token(TokenKind kind, size_t index, size_t length)
: index_(index), length_(length), kind_(kind) {}
TokenKind kind() const { return kind_; }
size_t index() const { return index_; }
size_t length() const { return length_; }
bool isError() const { return kind_ == TokenKind::Error; }
bool isNone() const { return kind_ == TokenKind::None; }
bool isAlpha() const { return kind_ == TokenKind::Alpha; }
bool isDigit() const { return kind_ == TokenKind::Digit; }
bool isAlphaDigit() const { return kind_ == TokenKind::AlphaDigit; }
};
using LocaleChars = mozilla::Variant<const JS::Latin1Char*, const char16_t*>;
const LocaleChars& locale_;
size_t length_;
size_t index_ = 0;
LanguageTagParser(const LocaleChars& locale, size_t length)
: locale_(locale), length_(length) {}
char16_t charAtUnchecked(size_t index) const {
if (locale_.is<const JS::Latin1Char*>()) {
return locale_.as<const JS::Latin1Char*>()[index];
}
return locale_.as<const char16_t*>()[index];
}
char charAt(size_t index) const {
char16_t c = charAtUnchecked(index);
MOZ_ASSERT(mozilla::IsAscii(c));
return c;
}
// Copy the token characters into |subtag|.
template <size_t N>
void copyChars(const Token& tok, LanguageTagSubtag<N>& subtag) const {
size_t index = tok.index();
size_t length = tok.length();
if (locale_.is<const JS::Latin1Char*>()) {
using T = const JS::Latin1Char;
subtag.set(mozilla::MakeSpan(locale_.as<T*>() + index, length));
} else {
using T = const char16_t;
subtag.set(mozilla::MakeSpan(locale_.as<T*>() + index, length));
}
}
// Create a string copy of |length| characters starting at |index|.
JS::UniqueChars chars(JSContext* cx, size_t index, size_t length) const;
// Create a string copy of the token characters.
JS::UniqueChars chars(JSContext* cx, const Token& tok) const {
return chars(cx, tok.index(), tok.length());
}
JS::UniqueChars extension(JSContext* cx, const Token& start,
const Token& end) const {
MOZ_ASSERT(start.index() < end.index());
size_t length = end.index() - 1 - start.index();
return chars(cx, start.index(), length);
}
Token nextToken();
// unicode_language_subtag = alpha{2,3} | alpha{5,8} ;
//
// Four character language subtags are not allowed in Unicode BCP 47 locale
// identifiers. Also see the comparison to Unicode CLDR locale identifiers in
// <https://unicode.org/reports/tr35/#BCP_47_Conformance>.
bool isLanguage(const Token& tok) const {
return tok.isAlpha() && ((2 <= tok.length() && tok.length() <= 3) ||
(5 <= tok.length() && tok.length() <= 8));
}
// unicode_script_subtag = alpha{4} ;
bool isScript(const Token& tok) const {
return tok.isAlpha() && tok.length() == 4;
}
// unicode_region_subtag = (alpha{2} | digit{3}) ;
bool isRegion(const Token& tok) const {
return (tok.isAlpha() && tok.length() == 2) ||
(tok.isDigit() && tok.length() == 3);
}
// unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3}) ;
bool isVariant(const Token& tok) const {
return (5 <= tok.length() && tok.length() <= 8) ||
(tok.length() == 4 && mozilla::IsAsciiDigit(charAt(tok.index())));
}
// Returns the code unit of the first character at the given singleton token.
// Always returns the lower case form of an alphabetical character.
char singletonKey(const Token& tok) const {
MOZ_ASSERT(tok.length() == 1);
return AsciiToLowerCase(charAt(tok.index()));
}
// extensions = unicode_locale_extensions |
// transformed_extensions |
// other_extensions ;
//
// unicode_locale_extensions = sep [uU] ((sep keyword)+ |
// (sep attribute)+ (sep keyword)*) ;
//
// transformed_extensions = sep [tT] ((sep tlang (sep tfield)*) |
// (sep tfield)+) ;
//
// other_extensions = sep [alphanum-[tTuUxX]] (sep alphanum{2,8})+ ;
bool isExtensionStart(const Token& tok) const {
return tok.length() == 1 && singletonKey(tok) != 'x';
}
// other_extensions = sep [alphanum-[tTuUxX]] (sep alphanum{2,8})+ ;
bool isOtherExtensionPart(const Token& tok) const {
return 2 <= tok.length() && tok.length() <= 8;
}
// unicode_locale_extensions = sep [uU] ((sep keyword)+ |
// (sep attribute)+ (sep keyword)*) ;
// keyword = key (sep type)? ;
bool isUnicodeExtensionPart(const Token& tok) const {
return isUnicodeExtensionKey(tok) || isUnicodeExtensionType(tok) ||
isUnicodeExtensionAttribute(tok);
}
// attribute = alphanum{3,8} ;
bool isUnicodeExtensionAttribute(const Token& tok) const {
return 3 <= tok.length() && tok.length() <= 8;
}
// key = alphanum alpha ;
bool isUnicodeExtensionKey(const Token& tok) const {
return tok.length() == 2 && mozilla::IsAsciiAlpha(charAt(tok.index() + 1));
}
// type = alphanum{3,8} (sep alphanum{3,8})* ;
bool isUnicodeExtensionType(const Token& tok) const {
return 3 <= tok.length() && tok.length() <= 8;
}
// tkey = alpha digit ;
bool isTransformExtensionKey(const Token& tok) const {
return tok.length() == 2 && mozilla::IsAsciiAlpha(charAt(tok.index())) &&
mozilla::IsAsciiDigit(charAt(tok.index() + 1));
}
// tvalue = (sep alphanum{3,8})+ ;
bool isTransformExtensionPart(const Token& tok) const {
return 3 <= tok.length() && tok.length() <= 8;
}
// pu_extensions = sep [xX] (sep alphanum{1,8})+ ;
bool isPrivateUseStart(const Token& tok) const {
return tok.length() == 1 && singletonKey(tok) == 'x';
}
// pu_extensions = sep [xX] (sep alphanum{1,8})+ ;
bool isPrivateUsePart(const Token& tok) const {
return 1 <= tok.length() && tok.length() <= 8;
}
// Helper function for use in |parseBaseName| and
// |parseTlangInTransformExtension|. Do not use this directly!
static JS::Result<bool> internalParseBaseName(JSContext* cx,
LanguageTagParser& ts,
LanguageTag& tag, Token& tok);
// Parse the `unicode_language_id` production, i.e. the
// language/script/region/variants portion of a language tag, into |tag|.
// |tok| must be the current token.
static JS::Result<bool> parseBaseName(JSContext* cx, LanguageTagParser& ts,
LanguageTag& tag, Token& tok) {
return internalParseBaseName(cx, ts, tag, tok);
}
// Parse the `tlang` production within a parsed 't' transform extension.
// The precise requirements for "previously parsed" are:
//
// * the input begins from current token |tok| with a valid `tlang`
// * the `tlang` is wholly lowercase (*not* canonical case)
// * variant subtags in the `tlang` may contain duplicates and be
// unordered
//
// Return an error on internal failure. Otherwise, return a success value. If
// there was no `tlang`, then |tag.language().missing()|. But if there was a
// `tlang`, then |tag| is filled with subtags exactly as they appeared in the
// parse input.
static JS::Result<JS::Ok> parseTlangInTransformExtension(
JSContext* cx, LanguageTagParser& ts, LanguageTag& tag, Token& tok) {
MOZ_ASSERT(ts.isLanguage(tok));
return internalParseBaseName(cx, ts, tag, tok).map([](bool parsed) {
MOZ_ASSERT(parsed);
return JS::Ok();
});
}
friend class LanguageTag;
class Range final {
size_t begin_;
size_t length_;
public:
Range(size_t begin, size_t length) : begin_(begin), length_(length) {}
template <typename T>
T* begin(T* ptr) const {
return ptr + begin_;
}
size_t length() const { return length_; }
};
using TFieldVector = js::Vector<Range, 8>;
using AttributesVector = js::Vector<Range, 8>;
using KeywordsVector = js::Vector<Range, 8>;
// Parse |extension|, which must be a validated, fully lowercase
// `transformed_extensions` subtag, and fill |tag| and |fields| from the
// `tlang` and `tfield` components. Data in |tag| is lowercase, consistent
// with |extension|.
static JS::Result<bool> parseTransformExtension(
JSContext* cx, mozilla::Span<const char> extension, LanguageTag& tag,
TFieldVector& fields);
// Parse |extension|, which must be a validated, fully lowercase
// `unicode_locale_extensions` subtag, and fill |attributes| and |keywords|
// from the `attribute` and `keyword` components.
static JS::Result<bool> parseUnicodeExtension(
JSContext* cx, mozilla::Span<const char> extension,
AttributesVector& attributes, KeywordsVector& keywords);
static JS::Result<bool> tryParse(JSContext* cx, LocaleChars& localeChars,
size_t localeLength, LanguageTag& tag);
public:
// Parse the input string as a language tag. Reports an error to the context
// if the input can't be parsed completely.
static bool parse(JSContext* cx, JSLinearString* locale, LanguageTag& tag);
// Parse the input string as a language tag. Reports an error to the context
// if the input can't be parsed completely.
static bool parse(JSContext* cx, mozilla::Span<const char> locale,
LanguageTag& tag);
// Parse the input string as a language tag. Returns Ok(true) if the input
// could be completely parsed, Ok(false) if the input couldn't be parsed,
// or Err() in case of internal error.
static JS::Result<bool> tryParse(JSContext* cx, JSLinearString* locale,
LanguageTag& tag);
// Parse the input string as a language tag. Returns Ok(true) if the input
// could be completely parsed, Ok(false) if the input couldn't be parsed,
// or Err() in case of internal error.
static JS::Result<bool> tryParse(JSContext* cx,
mozilla::Span<const char> locale,
LanguageTag& tag);
// Parse the input string as the base-name parts (language, script, region,
// variants) of a language tag. Ignores any trailing characters.
static bool parseBaseName(JSContext* cx, mozilla::Span<const char> locale,
LanguageTag& tag);
// Return true iff |extension| can be parsed as a Unicode extension subtag.
static bool canParseUnicodeExtension(mozilla::Span<const char> extension);
// Return true iff |unicodeType| can be parsed as a Unicode extension type.
static bool canParseUnicodeExtensionType(JSLinearString* unicodeType);
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(LanguageTagParser::TokenKind)
/**
* Parse a string as a standalone |language| tag. If |str| is a standalone
* language tag, store it in |result| and return true. Otherwise return false.
*/
MOZ_MUST_USE bool ParseStandaloneLanguageTag(JS::Handle<JSLinearString*> str,
LanguageSubtag& result);
/**
* Parse a string as a standalone |script| tag. If |str| is a standalone script
* tag, store it in |result| and return true. Otherwise return false.
*/
MOZ_MUST_USE bool ParseStandaloneScriptTag(JS::Handle<JSLinearString*> str,
ScriptSubtag& result);
/**
* Parse a string as a standalone |region| tag. If |str| is a standalone region
* tag, store it in |result| and return true. Otherwise return false.
*/
MOZ_MUST_USE bool ParseStandaloneRegionTag(JS::Handle<JSLinearString*> str,
RegionSubtag& result);
/**
* Parse a string as an ISO-639 language code. Return |nullptr| in the result if
* 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(
JSContext* cx, JS::Handle<JSLinearString*> str);
class UnicodeExtensionKeyword final {
char key_[LanguageTagLimits::UnicodeKeyLength];
JSLinearString* type_;
public:
using UnicodeKey = const char (&)[LanguageTagLimits::UnicodeKeyLength + 1];
using UnicodeKeySpan =
mozilla::Span<const char, LanguageTagLimits::UnicodeKeyLength>;
UnicodeExtensionKeyword(UnicodeKey key, JSLinearString* type)
: key_{key[0], key[1]}, type_(type) {}
UnicodeKeySpan key() const { return {key_, sizeof(key_)}; }
JSLinearString* type() const { return type_; }
void trace(JSTracer* trc);
};
extern MOZ_MUST_USE bool ApplyUnicodeExtensionToTag(
JSContext* cx, LanguageTag& tag,
JS::HandleVector<UnicodeExtensionKeyword> keywords);
} // namespace intl
} // namespace js
#endif /* builtin_intl_LanguageTag_h */

View file

@ -0,0 +1,950 @@
// Generated by make_intl_data.py. DO NOT EDIT.
// Version: CLDR-35.1
// URL: https://unicode.org/Public/cldr/35.1/core.zip
#include "mozilla/Assertions.h"
#include "mozilla/Span.h"
#include "mozilla/TextUtils.h"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <iterator>
#include <string>
#include <type_traits>
#include "jscntxt.h"
#include "jsstr.h"
#include "builtin/intl/LanguageTag.h"
using namespace js::intl::LanguageTagLimits;
template <size_t Length, size_t TagLength, size_t SubtagLength>
static inline bool HasReplacement(
const char (&subtags)[Length][TagLength],
const js::intl::LanguageTagSubtag<SubtagLength>& subtag) {
MOZ_ASSERT(subtag.length() == TagLength - 1,
"subtag must have the same length as the list of subtags");
const char* ptr = subtag.span().data();
return std::binary_search(std::begin(subtags), std::end(subtags), ptr,
[](const char* a, const char* b) {
return memcmp(a, b, TagLength - 1) < 0;
});
}
template <size_t Length, size_t TagLength, size_t SubtagLength>
static inline const char* SearchReplacement(
const char (&subtags)[Length][TagLength],
const char* (&aliases)[Length],
const js::intl::LanguageTagSubtag<SubtagLength>& subtag) {
MOZ_ASSERT(subtag.length() == TagLength - 1,
"subtag must have the same length as the list of subtags");
const char* ptr = subtag.span().data();
auto p = std::lower_bound(std::begin(subtags), std::end(subtags), ptr,
[](const char* a, const char* b) {
return memcmp(a, b, TagLength - 1) < 0;
});
if (p != std::end(subtags) && memcmp(*p, ptr, TagLength - 1) == 0) {
return aliases[std::distance(std::begin(subtags), p)];
}
return nullptr;
}
#ifdef DEBUG
static bool IsAsciiLowercaseAlphanumeric(char c) {
return mozilla::IsAsciiLowercaseAlpha(c) || mozilla::IsAsciiDigit(c);
}
static bool IsAsciiLowercaseAlphanumericOrDash(char c) {
return IsAsciiLowercaseAlphanumeric(c) || c == '-';
}
static bool IsCanonicallyCasedLanguageTag(mozilla::Span<const char> span) {
// Tell the analysis the |std::all_of| function can't GC.
JS::AutoSuppressGCAnalysis nogc;
return std::all_of(span.begin(), span.end(), mozilla::IsAsciiLowercaseAlpha<char>);
}
static bool IsCanonicallyCasedRegionTag(mozilla::Span<const char> span) {
// Tell the analysis the |std::all_of| function can't GC.
JS::AutoSuppressGCAnalysis nogc;
return std::all_of(span.begin(), span.end(), mozilla::IsAsciiUppercaseAlpha<char>) ||
std::all_of(span.begin(), span.end(), mozilla::IsAsciiDigit<char>);
}
static bool IsCanonicallyCasedVariantTag(mozilla::Span<const char> span) {
// Tell the analysis the |std::all_of| function can't GC.
JS::AutoSuppressGCAnalysis nogc;
return std::all_of(span.begin(), span.end(), IsAsciiLowercaseAlphanumeric);
}
static bool IsCanonicallyCasedUnicodeKey(mozilla::Span<const char> key) {
return std::all_of(key.begin(), key.end(), IsAsciiLowercaseAlphanumeric);
}
static bool IsCanonicallyCasedUnicodeType(mozilla::Span<const char> type) {
return std::all_of(type.begin(), type.end(), IsAsciiLowercaseAlphanumericOrDash);
}
static bool IsCanonicallyCasedTransformKey(mozilla::Span<const char> key) {
return std::all_of(key.begin(), key.end(), IsAsciiLowercaseAlphanumeric);
}
static bool IsCanonicallyCasedTransformType(mozilla::Span<const char> type) {
return std::all_of(type.begin(), type.end(), IsAsciiLowercaseAlphanumericOrDash);
}
#endif
// Mappings from language subtags to preferred values.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
bool js::intl::LanguageTag::languageMapping(LanguageSubtag& language) {
MOZ_ASSERT(IsStructurallyValidLanguageTag(language.span()));
MOZ_ASSERT(IsCanonicallyCasedLanguageTag(language.span()));
if (language.length() == 2) {
static const char languages[9][3] = {
"bh", "in", "iw", "ji", "jw", "mo", "no", "tl", "tw",
};
static const char* aliases[9] = {
"bho", "id", "he", "yi", "jv", "ro", "nb", "fil", "ak",
};
if (const char* replacement = SearchReplacement(languages, aliases, language)) {
language.set(mozilla::MakeCStringSpan(replacement));
return true;
}
return false;
}
if (language.length() == 3) {
static const char languages[340][4] = {
"aam", "aar", "abk", "adp", "afr", "aju", "aka", "alb", "als", "amh",
"ara", "arb", "arg", "arm", "asm", "aue", "ava", "ave", "aym", "ayr",
"ayx", "aze", "azj", "bak", "bam", "baq", "bcc", "bcl", "bel", "ben",
"bgm", "bih", "bis", "bjd", "bod", "bos", "bre", "bul", "bur", "bxk",
"bxr", "cat", "ccq", "ces", "cha", "che", "chi", "chu", "chv", "cjr",
"cka", "cld", "cmk", "cmn", "cor", "cos", "coy", "cqu", "cre", "cwd",
"cym", "cze", "dan", "deu", "dgo", "dhd", "dik", "diq", "div", "drh",
"dut", "dzo", "ekk", "ell", "emk", "eng", "epo", "esk", "est", "eus",
"ewe", "fao", "fas", "fat", "fij", "fin", "fra", "fre", "fry", "fuc",
"ful", "gav", "gaz", "gbo", "geo", "ger", "gfx", "ggn", "gla", "gle",
"glg", "glv", "gno", "gre", "grn", "gti", "gug", "guj", "guv", "gya",
"hat", "hau", "hdn", "hea", "heb", "her", "him", "hin", "hmo", "hrr",
"hrv", "hun", "hye", "ibi", "ibo", "ice", "ido", "iii", "ike", "iku",
"ile", "ilw", "ina", "ind", "ipk", "isl", "ita", "jav", "jeg", "jpn",
"kal", "kan", "kas", "kat", "kau", "kaz", "kgc", "kgh", "khk", "khm",
"kik", "kin", "kir", "kmr", "knc", "kng", "knn", "koj", "kom", "kon",
"kor", "kpv", "krm", "ktr", "kua", "kur", "kvs", "kwq", "kxe", "kzj",
"kzt", "lao", "lat", "lav", "lbk", "lii", "lim", "lin", "lit", "lmm",
"ltz", "lub", "lug", "lvs", "mac", "mah", "mal", "mao", "mar", "may",
"meg", "mhr", "mkd", "mlg", "mlt", "mnk", "mol", "mon", "mri", "msa",
"mst", "mup", "mwj", "mya", "myt", "nad", "nau", "nav", "nbl", "ncp",
"nde", "ndo", "nep", "nld", "nno", "nnx", "nob", "nor", "npi", "nts",
"nya", "oci", "ojg", "oji", "ori", "orm", "ory", "oss", "oun", "pan",
"pbu", "pcr", "per", "pes", "pli", "plt", "pmc", "pmu", "pnb", "pol",
"por", "ppa", "ppr", "pry", "pus", "puz", "que", "quz", "rmy", "roh",
"ron", "rum", "run", "rus", "sag", "san", "sca", "scc", "scr", "sin",
"skk", "slk", "slo", "slv", "sme", "smo", "sna", "snd", "som", "sot",
"spa", "spy", "sqi", "src", "srd", "srp", "ssw", "sun", "swa", "swe",
"swh", "tah", "tam", "tat", "tdu", "tel", "tgk", "tgl", "tha", "thc",
"thx", "tib", "tie", "tir", "tkk", "tlw", "tmp", "tne", "ton", "tsf",
"tsn", "tso", "ttq", "tuk", "tur", "twi", "uig", "ukr", "umu", "uok",
"urd", "uzb", "uzn", "ven", "vie", "vol", "wel", "wln", "wol", "xba",
"xho", "xia", "xkh", "xpe", "xsj", "xsl", "ybd", "ydd", "yid", "yma",
"ymt", "yor", "yos", "yuu", "zai", "zha", "zho", "zsm", "zul", "zyb",
};
static const char* aliases[340] = {
"aas", "aa", "ab", "dz", "af", "jrb", "ak", "sq", "sq", "am",
"ar", "ar", "an", "hy", "as", "ktz", "av", "ae", "ay", "ay",
"nun", "az", "az", "ba", "bm", "eu", "bal", "bik", "be", "bn",
"bcg", "bho", "bi", "drl", "bo", "bs", "br", "bg", "my", "luy",
"bua", "ca", "rki", "cs", "ch", "ce", "zh", "cu", "cv", "mom",
"cmr", "syr", "xch", "zh", "kw", "co", "pij", "quh", "cr", "cr",
"cy", "cs", "da", "de", "doi", "mwr", "din", "zza", "dv", "mn",
"nl", "dz", "et", "el", "man", "en", "eo", "ik", "et", "eu",
"ee", "fo", "fa", "ak", "fj", "fi", "fr", "fr", "fy", "ff",
"ff", "dev", "om", "grb", "ka", "de", "vaj", "gvr", "gd", "ga",
"gl", "gv", "gon", "el", "gn", "nyc", "gn", "gu", "duz", "gba",
"ht", "ha", "hai", "hmn", "he", "hz", "srx", "hi", "ho", "jal",
"hr", "hu", "hy", "opa", "ig", "is", "io", "ii", "iu", "iu",
"ie", "gal", "ia", "id", "ik", "is", "it", "jv", "oyb", "ja",
"kl", "kn", "ks", "ka", "kr", "kk", "tdf", "kml", "mn", "km",
"ki", "rw", "ky", "ku", "kr", "kg", "kok", "kwv", "kv", "kg",
"ko", "kv", "bmf", "dtp", "kj", "ku", "gdj", "yam", "tvd", "dtp",
"dtp", "lo", "la", "lv", "bnc", "raq", "li", "ln", "lt", "rmx",
"lb", "lu", "lg", "lv", "mk", "mh", "ml", "mi", "mr", "ms",
"cir", "chm", "mk", "mg", "mt", "man", "ro", "mn", "mi", "ms",
"mry", "raj", "vaj", "my", "mry", "xny", "na", "nv", "nr", "kdz",
"nd", "ng", "ne", "nl", "nn", "ngv", "nb", "nb", "ne", "pij",
"ny", "oc", "oj", "oj", "or", "om", "or", "os", "vaj", "pa",
"ps", "adx", "fa", "fa", "pi", "mg", "huw", "phr", "lah", "pl",
"pt", "bfy", "lcq", "prt", "ps", "pub", "qu", "qu", "rom", "rm",
"ro", "ro", "rn", "ru", "sg", "sa", "hle", "sr", "hr", "si",
"oyb", "sk", "sk", "sl", "se", "sm", "sn", "sd", "so", "st",
"es", "kln", "sq", "sc", "sc", "sr", "ss", "su", "sw", "sv",
"sw", "ty", "ta", "tt", "dtp", "te", "tg", "fil", "th", "tpo",
"oyb", "bo", "ras", "ti", "twm", "weo", "tyj", "kak", "to", "taj",
"tn", "ts", "tmh", "tk", "tr", "ak", "ug", "uk", "del", "ema",
"ur", "uz", "uz", "ve", "vi", "vo", "cy", "wa", "wo", "cax",
"xh", "acn", "waw", "kpe", "suj", "den", "rki", "yi", "yi", "lrr",
"mtm", "yo", "zom", "yug", "zap", "za", "zh", "ms", "zu", "za",
};
if (const char* replacement = SearchReplacement(languages, aliases, language)) {
language.set(mozilla::MakeCStringSpan(replacement));
return true;
}
return false;
}
return false;
}
// Language subtags with complex mappings.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
bool js::intl::LanguageTag::complexLanguageMapping(const LanguageSubtag& language) {
MOZ_ASSERT(IsStructurallyValidLanguageTag(language.span()));
MOZ_ASSERT(IsCanonicallyCasedLanguageTag(language.span()));
if (language.length() == 2) {
return language.equalTo("sh");
}
if (language.length() == 3) {
static const char languages[6][4] = {
"cnr", "drw", "hbs", "prs", "swc", "tnf",
};
return HasReplacement(languages, language);
}
return false;
}
// Mappings from region subtags to preferred values.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
bool js::intl::LanguageTag::regionMapping(RegionSubtag& region) {
MOZ_ASSERT(IsStructurallyValidRegionTag(region.span()));
MOZ_ASSERT(IsCanonicallyCasedRegionTag(region.span()));
if (region.length() == 2) {
static const char regions[23][3] = {
"BU", "CS", "CT", "DD", "DY", "FQ", "FX", "HV", "JT", "MI",
"NH", "NQ", "PU", "PZ", "QU", "RH", "TP", "UK", "VD", "WK",
"YD", "YU", "ZR",
};
static const char* aliases[23] = {
"MM", "RS", "KI", "DE", "BJ", "AQ", "FR", "BF", "UM", "UM",
"VU", "AQ", "UM", "PA", "EU", "ZW", "TL", "GB", "VN", "UM",
"YE", "RS", "CD",
};
if (const char* replacement = SearchReplacement(regions, aliases, region)) {
region.set(mozilla::MakeCStringSpan(replacement));
return true;
}
return false;
}
{
static const char regions[300][4] = {
"004", "008", "010", "012", "016", "020", "024", "028", "031", "032",
"036", "040", "044", "048", "050", "051", "052", "056", "060", "062",
"064", "068", "070", "072", "074", "076", "084", "086", "090", "092",
"096", "100", "104", "108", "112", "116", "120", "124", "132", "136",
"140", "144", "148", "152", "156", "158", "162", "166", "170", "174",
"175", "178", "180", "184", "188", "191", "192", "196", "203", "204",
"208", "212", "214", "218", "222", "226", "230", "231", "232", "233",
"234", "238", "239", "242", "246", "248", "249", "250", "254", "258",
"260", "262", "266", "268", "270", "275", "276", "278", "280", "288",
"292", "296", "300", "304", "308", "312", "316", "320", "324", "328",
"332", "334", "336", "340", "344", "348", "352", "356", "360", "364",
"368", "372", "376", "380", "384", "388", "392", "398", "400", "404",
"408", "410", "414", "417", "418", "422", "426", "428", "430", "434",
"438", "440", "442", "446", "450", "454", "458", "462", "466", "470",
"474", "478", "480", "484", "492", "496", "498", "499", "500", "504",
"508", "512", "516", "520", "524", "528", "531", "533", "534", "535",
"540", "548", "554", "558", "562", "566", "570", "574", "578", "580",
"581", "583", "584", "585", "586", "591", "598", "600", "604", "608",
"612", "616", "620", "624", "626", "630", "634", "638", "642", "643",
"646", "652", "654", "659", "660", "662", "663", "666", "670", "674",
"678", "682", "686", "688", "690", "694", "702", "703", "704", "705",
"706", "710", "716", "720", "724", "728", "729", "732", "736", "740",
"744", "748", "752", "756", "760", "762", "764", "768", "772", "776",
"780", "784", "788", "792", "795", "796", "798", "800", "804", "807",
"818", "826", "830", "831", "832", "833", "834", "840", "850", "854",
"858", "860", "862", "876", "882", "886", "887", "891", "894", "958",
"959", "960", "962", "963", "964", "965", "966", "967", "968", "969",
"970", "971", "972", "973", "974", "975", "976", "977", "978", "979",
"980", "981", "982", "983", "984", "985", "986", "987", "988", "989",
"990", "991", "992", "993", "994", "995", "996", "997", "998", "999",
};
static const char* aliases[300] = {
"AF", "AL", "AQ", "DZ", "AS", "AD", "AO", "AG", "AZ", "AR",
"AU", "AT", "BS", "BH", "BD", "AM", "BB", "BE", "BM", "034",
"BT", "BO", "BA", "BW", "BV", "BR", "BZ", "IO", "SB", "VG",
"BN", "BG", "MM", "BI", "BY", "KH", "CM", "CA", "CV", "KY",
"CF", "LK", "TD", "CL", "CN", "TW", "CX", "CC", "CO", "KM",
"YT", "CG", "CD", "CK", "CR", "HR", "CU", "CY", "CZ", "BJ",
"DK", "DM", "DO", "EC", "SV", "GQ", "ET", "ET", "ER", "EE",
"FO", "FK", "GS", "FJ", "FI", "AX", "FR", "FR", "GF", "PF",
"TF", "DJ", "GA", "GE", "GM", "PS", "DE", "DE", "DE", "GH",
"GI", "KI", "GR", "GL", "GD", "GP", "GU", "GT", "GN", "GY",
"HT", "HM", "VA", "HN", "HK", "HU", "IS", "IN", "ID", "IR",
"IQ", "IE", "IL", "IT", "CI", "JM", "JP", "KZ", "JO", "KE",
"KP", "KR", "KW", "KG", "LA", "LB", "LS", "LV", "LR", "LY",
"LI", "LT", "LU", "MO", "MG", "MW", "MY", "MV", "ML", "MT",
"MQ", "MR", "MU", "MX", "MC", "MN", "MD", "ME", "MS", "MA",
"MZ", "OM", "NA", "NR", "NP", "NL", "CW", "AW", "SX", "BQ",
"NC", "VU", "NZ", "NI", "NE", "NG", "NU", "NF", "NO", "MP",
"UM", "FM", "MH", "PW", "PK", "PA", "PG", "PY", "PE", "PH",
"PN", "PL", "PT", "GW", "TL", "PR", "QA", "RE", "RO", "RU",
"RW", "BL", "SH", "KN", "AI", "LC", "MF", "PM", "VC", "SM",
"ST", "SA", "SN", "RS", "SC", "SL", "SG", "SK", "VN", "SI",
"SO", "ZA", "ZW", "YE", "ES", "SS", "SD", "EH", "SD", "SR",
"SJ", "SZ", "SE", "CH", "SY", "TJ", "TH", "TG", "TK", "TO",
"TT", "AE", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "MK",
"EG", "GB", "JE", "GG", "JE", "IM", "TZ", "US", "VI", "BF",
"UY", "UZ", "VE", "WF", "WS", "YE", "YE", "RS", "ZM", "AA",
"QM", "QN", "QP", "QQ", "QR", "QS", "QT", "EU", "QV", "QW",
"QX", "QY", "QZ", "XA", "XB", "XC", "XD", "XE", "XF", "XG",
"XH", "XI", "XJ", "XK", "XL", "XM", "XN", "XO", "XP", "XQ",
"XR", "XS", "XT", "XU", "XV", "XW", "XX", "XY", "XZ", "ZZ",
};
if (const char* replacement = SearchReplacement(regions, aliases, region)) {
region.set(mozilla::MakeCStringSpan(replacement));
return true;
}
return false;
}
}
// Region subtags with complex mappings.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
bool js::intl::LanguageTag::complexRegionMapping(const RegionSubtag& region) {
MOZ_ASSERT(IsStructurallyValidRegionTag(region.span()));
MOZ_ASSERT(IsCanonicallyCasedRegionTag(region.span()));
if (region.length() == 2) {
return region.equalTo("AN") ||
region.equalTo("NT") ||
region.equalTo("PC") ||
region.equalTo("SU");
}
{
static const char regions[8][4] = {
"172", "200", "530", "532", "536", "582", "810", "890",
};
return HasReplacement(regions, region);
}
}
// Language subtags with complex mappings.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
void js::intl::LanguageTag::performComplexLanguageMappings() {
MOZ_ASSERT(IsStructurallyValidLanguageTag(language().span()));
MOZ_ASSERT(IsCanonicallyCasedLanguageTag(language().span()));
if (language().equalTo("cnr")) {
setLanguage("sr");
if (region().missing()) {
setRegion("ME");
}
}
else if (language().equalTo("drw") ||
language().equalTo("prs") ||
language().equalTo("tnf")) {
setLanguage("fa");
if (region().missing()) {
setRegion("AF");
}
}
else if (language().equalTo("hbs") ||
language().equalTo("sh")) {
setLanguage("sr");
if (script().missing()) {
setScript("Latn");
}
}
else if (language().equalTo("swc")) {
setLanguage("sw");
if (region().missing()) {
setRegion("CD");
}
}
}
// Region subtags with complex mappings.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
void js::intl::LanguageTag::performComplexRegionMappings() {
MOZ_ASSERT(IsStructurallyValidLanguageTag(language().span()));
MOZ_ASSERT(IsCanonicallyCasedLanguageTag(language().span()));
MOZ_ASSERT(IsStructurallyValidRegionTag(region().span()));
MOZ_ASSERT(IsCanonicallyCasedRegionTag(region().span()));
if (region().equalTo("172")) {
if (language().equalTo("hy") ||
(language().equalTo("und") && script().equalTo("Armn"))) {
setRegion("AM");
}
else if (language().equalTo("az") ||
language().equalTo("tkr") ||
language().equalTo("tly") ||
language().equalTo("ttt")) {
setRegion("AZ");
}
else if (language().equalTo("be")) {
setRegion("BY");
}
else if (language().equalTo("ab") ||
language().equalTo("ka") ||
language().equalTo("os") ||
(language().equalTo("und") && script().equalTo("Geor")) ||
language().equalTo("xmf")) {
setRegion("GE");
}
else if (language().equalTo("ky")) {
setRegion("KG");
}
else if (language().equalTo("kk") ||
(language().equalTo("ug") && script().equalTo("Cyrl"))) {
setRegion("KZ");
}
else if (language().equalTo("gag")) {
setRegion("MD");
}
else if (language().equalTo("tg")) {
setRegion("TJ");
}
else if (language().equalTo("tk")) {
setRegion("TM");
}
else if (language().equalTo("crh") ||
language().equalTo("got") ||
language().equalTo("ji") ||
language().equalTo("rue") ||
language().equalTo("uk") ||
(language().equalTo("und") && script().equalTo("Goth"))) {
setRegion("UA");
}
else if (language().equalTo("kaa") ||
language().equalTo("sog") ||
(language().equalTo("und") && script().equalTo("Sogd")) ||
(language().equalTo("und") && script().equalTo("Sogo")) ||
language().equalTo("uz")) {
setRegion("UZ");
}
else {
setRegion("RU");
}
}
else if (region().equalTo("200")) {
if (language().equalTo("sk")) {
setRegion("SK");
}
else {
setRegion("CZ");
}
}
else if (region().equalTo("530") ||
region().equalTo("532") ||
region().equalTo("AN")) {
if (language().equalTo("vic")) {
setRegion("SX");
}
else {
setRegion("CW");
}
}
else if (region().equalTo("536") ||
region().equalTo("NT")) {
if (language().equalTo("akk") ||
language().equalTo("ckb") ||
(language().equalTo("ku") && script().equalTo("Arab")) ||
language().equalTo("mis") ||
language().equalTo("syr") ||
(language().equalTo("und") && script().equalTo("Syrc")) ||
(language().equalTo("und") && script().equalTo("Xsux")) ||
(language().equalTo("und") && script().equalTo("Hatr"))) {
setRegion("IQ");
}
else {
setRegion("SA");
}
}
else if (region().equalTo("582") ||
region().equalTo("PC")) {
if (language().equalTo("mh")) {
setRegion("MH");
}
else if (language().equalTo("pau")) {
setRegion("PW");
}
else {
setRegion("FM");
}
}
else if (region().equalTo("810") ||
region().equalTo("SU")) {
if (language().equalTo("hy") ||
(language().equalTo("und") && script().equalTo("Armn"))) {
setRegion("AM");
}
else if (language().equalTo("az") ||
language().equalTo("tkr") ||
language().equalTo("tly") ||
language().equalTo("ttt")) {
setRegion("AZ");
}
else if (language().equalTo("be")) {
setRegion("BY");
}
else if (language().equalTo("et") ||
language().equalTo("vro")) {
setRegion("EE");
}
else if (language().equalTo("ab") ||
language().equalTo("ka") ||
language().equalTo("os") ||
(language().equalTo("und") && script().equalTo("Geor")) ||
language().equalTo("xmf")) {
setRegion("GE");
}
else if (language().equalTo("ky")) {
setRegion("KG");
}
else if (language().equalTo("kk") ||
(language().equalTo("ug") && script().equalTo("Cyrl"))) {
setRegion("KZ");
}
else if (language().equalTo("lt") ||
language().equalTo("sgs")) {
setRegion("LT");
}
else if (language().equalTo("ltg") ||
language().equalTo("lv")) {
setRegion("LV");
}
else if (language().equalTo("gag")) {
setRegion("MD");
}
else if (language().equalTo("tg")) {
setRegion("TJ");
}
else if (language().equalTo("tk")) {
setRegion("TM");
}
else if (language().equalTo("crh") ||
language().equalTo("got") ||
language().equalTo("ji") ||
language().equalTo("rue") ||
language().equalTo("uk") ||
(language().equalTo("und") && script().equalTo("Goth"))) {
setRegion("UA");
}
else if (language().equalTo("kaa") ||
language().equalTo("sog") ||
(language().equalTo("und") && script().equalTo("Sogd")) ||
(language().equalTo("und") && script().equalTo("Sogo")) ||
language().equalTo("uz")) {
setRegion("UZ");
}
else {
setRegion("RU");
}
}
else if (region().equalTo("890")) {
if (language().equalTo("bs")) {
setRegion("BA");
}
else if (language().equalTo("hr")) {
setRegion("HR");
}
else if (language().equalTo("mk")) {
setRegion("MK");
}
else if (language().equalTo("sl")) {
setRegion("SI");
}
else {
setRegion("RS");
}
}
}
static const char* ToCharPointer(const char* str) {
return str;
}
static const char* ToCharPointer(const js::UniqueChars& str) {
return str.get();
}
template <typename T, typename U = T>
static bool IsLessThan(const T& a, const U& b) {
return strcmp(ToCharPointer(a), ToCharPointer(b)) < 0;
}
// Mappings from variant subtags to preferred values.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
bool js::intl::LanguageTag::performVariantMappings(JSContext* cx) {
// The variant subtags need to be sorted for binary search.
MOZ_ASSERT(std::is_sorted(variants_.begin(), variants_.end(),
IsLessThan<decltype(variants_)::ElementType>));
auto insertVariantSortedIfNotPresent = [&](const char* variant) {
auto* p = std::lower_bound(variants_.begin(), variants_.end(), variant,
IsLessThan<decltype(variants_)::ElementType,
decltype(variant)>);
// Don't insert the replacement when already present.
if (p != variants_.end() && strcmp(p->get(), variant) == 0) {
return true;
}
// Insert the preferred variant in sort order.
auto preferred = DuplicateString(cx, variant);
if (!preferred) {
return false;
}
return !!variants_.insert(p, std::move(preferred));
};
for (size_t i = 0; i < variants_.length(); ) {
auto& variant = variants_[i];
MOZ_ASSERT(IsCanonicallyCasedVariantTag(mozilla::MakeCStringSpan(variant.get())));
if (strcmp(variant.get(), "aaland") == 0) {
variants_.erase(variants_.begin() + i);
setRegion("AX");
}
else if (strcmp(variant.get(), "arevela") == 0) {
variants_.erase(variants_.begin() + i);
setLanguage("hy");
}
else if (strcmp(variant.get(), "arevmda") == 0) {
variants_.erase(variants_.begin() + i);
setLanguage("hyw");
}
else if (strcmp(variant.get(), "heploc") == 0) {
variants_.erase(variants_.begin() + i);
if (!insertVariantSortedIfNotPresent("alalc97")) {
return false;
}
}
else if (strcmp(variant.get(), "polytoni") == 0) {
variants_.erase(variants_.begin() + i);
if (!insertVariantSortedIfNotPresent("polyton")) {
return false;
}
}
else {
i++;
}
}
return true;
}
// Canonicalize grandfathered locale identifiers.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
bool js::intl::LanguageTag::updateGrandfatheredMappings(JSContext* cx) {
// We're mapping regular grandfathered tags to non-grandfathered form here.
// Other tags remain unchanged.
//
// regular = "art-lojban"
// / "cel-gaulish"
// / "no-bok"
// / "no-nyn"
// / "zh-guoyu"
// / "zh-hakka"
// / "zh-min"
// / "zh-min-nan"
// / "zh-xiang"
//
// Therefore we can quickly exclude most tags by checking every
// |unicode_locale_id| subcomponent for characteristics not shared by any of
// the regular grandfathered (RG) tags:
//
// * Real-world |unicode_language_subtag|s are all two or three letters,
// so don't waste time running a useless |language.length > 3| fast-path.
// * No RG tag has a "script"-looking component.
// * No RG tag has a "region"-looking component.
// * The RG tags that match |unicode_locale_id| (art-lojban, cel-gaulish,
// zh-guoyu, zh-hakka, zh-xiang) have exactly one "variant". (no-bok,
// no-nyn, zh-min, and zh-min-nan require BCP47's extlang subtag
// that |unicode_locale_id| doesn't support.)
// * No RG tag contains |extensions| or |pu_extensions|.
if (script().present() ||
region().present() ||
variants().length() != 1 ||
extensions().length() != 0 ||
privateuse()) {
return true;
}
MOZ_ASSERT(IsCanonicallyCasedLanguageTag(language().span()));
MOZ_ASSERT(IsCanonicallyCasedVariantTag(mozilla::MakeCStringSpan(variants()[0].get())));
auto variantEqualTo = [this](const char* variant) {
return strcmp(variants()[0].get(), variant) == 0;
};
// art-lojban -> jbo
if (language().equalTo("art") && variantEqualTo("lojban")) {
setLanguage("jbo");
clearVariants();
return true;
}
// cel-gaulish -> xtg-x-cel-gaulish
else if (language().equalTo("cel") && variantEqualTo("gaulish")) {
setLanguage("xtg");
clearVariants();
auto privateuse = DuplicateString(cx, "x-cel-gaulish");
if (!privateuse) {
return false;
}
setPrivateuse(std::move(privateuse));
return true;
}
// zh-guoyu -> zh
else if (language().equalTo("zh") && variantEqualTo("guoyu")) {
setLanguage("zh");
clearVariants();
return true;
}
// zh-hakka -> hak
else if (language().equalTo("zh") && variantEqualTo("hakka")) {
setLanguage("hak");
clearVariants();
return true;
}
// zh-xiang -> hsn
else if (language().equalTo("zh") && variantEqualTo("xiang")) {
setLanguage("hsn");
clearVariants();
return true;
}
return true;
}
template <size_t Length>
static inline bool IsUnicodeKey(
mozilla::Span<const char> key, const char (&str)[Length]) {
static_assert(Length == UnicodeKeyLength + 1,
"Unicode extension key is two characters long");
return memcmp(key.data(), str, Length - 1) == 0;
}
template <size_t Length>
static inline bool IsUnicodeType(
mozilla::Span<const char> type, const char (&str)[Length]) {
static_assert(Length > UnicodeKeyLength + 1,
"Unicode extension type contains more than two characters");
return type.size() == (Length - 1) &&
memcmp(type.data(), str, Length - 1) == 0;
}
static int32_t CompareUnicodeType(const char* a, mozilla::Span<const char> b) {
MOZ_ASSERT(!std::char_traits<char>::find(b.data(), b.size(), '\0'),
"unexpected null-character in string");
using UnsignedChar = unsigned char;
for (size_t i = 0; i < b.size(); i++) {
// |a| is zero-terminated and |b| doesn't contain a null-terminator. So if
// we've reached the end of |a|, the below if-statement will always be true.
// That ensures we don't read past the end of |a|.
if (int32_t r = UnsignedChar(a[i]) - UnsignedChar(b[i])) {
return r;
}
}
// Return zero if both strings are equal or a negative number if |b| is a
// prefix of |a|.
return -int32_t(UnsignedChar(a[b.size()]));
}
template <size_t Length>
static inline const char* SearchUnicodeReplacement(
const char* (&types)[Length], const char* (&aliases)[Length],
mozilla::Span<const char> type) {
auto p = std::lower_bound(std::begin(types), std::end(types), type,
[](const auto& a, const auto& b) {
return CompareUnicodeType(a, b) < 0;
});
if (p != std::end(types) && CompareUnicodeType(*p, type) == 0) {
return aliases[std::distance(std::begin(types), p)];
}
return nullptr;
}
/**
* Mapping from deprecated BCP 47 Unicode extension types to their preferred
* values.
*
* Spec: https://www.unicode.org/reports/tr35/#Unicode_Locale_Extension_Data_Files
* Spec: https://www.unicode.org/reports/tr35/#t_Extension
*/
const char* js::intl::LanguageTag::replaceUnicodeExtensionType(
mozilla::Span<const char> key, mozilla::Span<const char> type) {
MOZ_ASSERT(key.size() == UnicodeKeyLength);
MOZ_ASSERT(IsCanonicallyCasedUnicodeKey(key));
MOZ_ASSERT(type.size() > UnicodeKeyLength);
MOZ_ASSERT(IsCanonicallyCasedUnicodeType(type));
if (IsUnicodeKey(key, "ca")) {
if (IsUnicodeType(type, "ethiopic-amete-alem")) {
return "ethioaa";
}
if (IsUnicodeType(type, "islamicc")) {
return "islamic-civil";
}
}
else if (IsUnicodeKey(key, "kb") ||
IsUnicodeKey(key, "kc") ||
IsUnicodeKey(key, "kh") ||
IsUnicodeKey(key, "kk") ||
IsUnicodeKey(key, "kn")) {
if (IsUnicodeType(type, "yes")) {
return "true";
}
}
else if (IsUnicodeKey(key, "ks")) {
if (IsUnicodeType(type, "primary")) {
return "level1";
}
if (IsUnicodeType(type, "tertiary")) {
return "level3";
}
}
else if (IsUnicodeKey(key, "ms")) {
if (IsUnicodeType(type, "imperial")) {
return "uksystem";
}
}
else if (IsUnicodeKey(key, "rg") ||
IsUnicodeKey(key, "sd")) {
static const char* types[116] = {
"cn11", "cn12", "cn13", "cn14", "cn15", "cn21", "cn22", "cn23",
"cn31", "cn32", "cn33", "cn34", "cn35", "cn36", "cn37", "cn41",
"cn42", "cn43", "cn44", "cn45", "cn46", "cn50", "cn51", "cn52",
"cn53", "cn54", "cn61", "cn62", "cn63", "cn64", "cn65", "cz10a",
"cz10b", "cz10c", "cz10d", "cz10e", "cz10f", "cz611", "cz612", "cz613",
"cz614", "cz615", "cz621", "cz622", "cz623", "cz624", "cz626", "cz627",
"czjc", "czjm", "czka", "czkr", "czli", "czmo", "czol", "czpa",
"czpl", "czpr", "czst", "czus", "czvy", "czzl", "fra", "frb",
"frc", "frd", "fre", "frf", "frg", "frh", "fri", "frj",
"frk", "frl", "frm", "frn", "fro", "frp", "frq", "frr",
"frs", "frt", "fru", "frv", "laxn", "lud", "lug", "lul",
"mrnkc", "nzn", "nzs", "omba", "omsh", "plds", "plkp", "pllb",
"plld", "pllu", "plma", "plmz", "plop", "plpd", "plpk", "plpm",
"plsk", "plsl", "plwn", "plwp", "plzp", "tteto", "ttrcm", "ttwto",
"twkhq", "twtnq", "twtpq", "twtxq",
};
static const char* aliases[116] = {
"cnbj", "cntj", "cnhe", "cnsx", "cnmn", "cnln", "cnjl", "cnhl",
"cnsh", "cnjs", "cnzj", "cnah", "cnfj", "cnjx", "cnsd", "cnha",
"cnhb", "cnhn", "cngd", "cngx", "cnhi", "cncq", "cnsc", "cngz",
"cnyn", "cnxz", "cnsn", "cngs", "cnqh", "cnnx", "cnxj", "cz110",
"cz111", "cz112", "cz113", "cz114", "cz115", "cz663", "cz632", "cz633",
"cz634", "cz635", "cz641", "cz642", "cz643", "cz644", "cz646", "cz647",
"cz31", "cz64", "cz41", "cz52", "cz51", "cz80", "cz71", "cz53",
"cz32", "cz10", "cz20", "cz42", "cz63", "cz72", "frges", "frnaq",
"frara", "frbfc", "frbre", "frcvl", "frges", "frcor", "frbfc", "fridf",
"frocc", "frnaq", "frges", "frocc", "frhdf", "frnor", "frnor", "frpdl",
"frhdf", "frnaq", "frpac", "frara", "laxs", "lucl", "luec", "luca",
"mr13", "nzauk", "nzcan", "ombj", "omsj", "pl02", "pl04", "pl08",
"pl10", "pl06", "pl12", "pl14", "pl16", "pl20", "pl18", "pl22",
"pl26", "pl24", "pl28", "pl30", "pl32", "tttob", "ttmrc", "tttob",
"twkhh", "twtnn", "twnwt", "twtxg",
};
return SearchUnicodeReplacement(types, aliases, type);
}
else if (IsUnicodeKey(key, "tz")) {
static const char* types[28] = {
"aqams", "cnckg", "cnhrb", "cnkhg", "cuba", "egypt",
"eire", "est", "gmt0", "hongkong", "hst", "iceland",
"iran", "israel", "jamaica", "japan", "libya", "mst",
"navajo", "poland", "portugal", "prc", "roc", "rok",
"turkey", "uct", "usnavajo", "zulu",
};
static const char* aliases[28] = {
"nzakl", "cnsha", "cnsha", "cnurc", "cuhav", "egcai",
"iedub", "utcw05", "gmt", "hkhkg", "utcw10", "isrey",
"irthr", "jeruslm", "jmkin", "jptyo", "lytip", "utcw07",
"usden", "plwaw", "ptlis", "cnsha", "twtpe", "krsel",
"trist", "utc", "usden", "utc",
};
return SearchUnicodeReplacement(types, aliases, type);
}
return nullptr;
}
template <size_t Length>
static inline bool IsTransformKey(
mozilla::Span<const char> key, const char (&str)[Length]) {
static_assert(Length == TransformKeyLength + 1,
"Transform extension key is two characters long");
return memcmp(key.data(), str, Length - 1) == 0;
}
template <size_t Length>
static inline bool IsTransformType(
mozilla::Span<const char> type, const char (&str)[Length]) {
static_assert(Length > TransformKeyLength + 1,
"Transform extension type contains more than two characters");
return type.size() == (Length - 1) &&
memcmp(type.data(), str, Length - 1) == 0;
}
/**
* Mapping from deprecated BCP 47 Transform extension types to their preferred
* values.
*
* Spec: https://www.unicode.org/reports/tr35/#Unicode_Locale_Extension_Data_Files
* Spec: https://www.unicode.org/reports/tr35/#t_Extension
*/
const char* js::intl::LanguageTag::replaceTransformExtensionType(
mozilla::Span<const char> key, mozilla::Span<const char> type) {
MOZ_ASSERT(key.size() == TransformKeyLength);
MOZ_ASSERT(IsCanonicallyCasedTransformKey(key));
MOZ_ASSERT(type.size() > TransformKeyLength);
MOZ_ASSERT(IsCanonicallyCasedTransformType(type));
if (IsTransformKey(key, "d0")) {
if (IsTransformType(type, "name")) {
return "charname";
}
}
else if (IsTransformKey(key, "m0")) {
if (IsTransformType(type, "names")) {
return "prprname";
}
}
return nullptr;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,63 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef builtin_intl_Locale_h
#define builtin_intl_Locale_h
#include <stdint.h>
#include "builtin/SelfHostingDefines.h"
#include "js/Class.h"
#include "vm/NativeObject.h"
namespace js {
class GlobalObject;
class LocaleObject : public NativeObject {
public:
static const Class class_;
static constexpr uint32_t LANGUAGE_TAG_SLOT = 0;
static constexpr uint32_t BASENAME_SLOT = 1;
static constexpr uint32_t UNICODE_EXTENSION_SLOT = 2;
static constexpr uint32_t SLOT_COUNT = 3;
/**
* Returns the complete language tag, including any extensions and privateuse
* subtags.
*/
JSString* languageTag() const {
return getFixedSlot(LANGUAGE_TAG_SLOT).toString();
}
/**
* Returns the basename subtags, i.e. excluding any extensions and privateuse
* subtags.
*/
JSString* baseName() const { return getFixedSlot(BASENAME_SLOT).toString(); }
const Value& unicodeExtension() const {
return getFixedSlot(UNICODE_EXTENSION_SLOT);
}
};
extern JSObject* CreateLocalePrototype(JSContext* cx,
JS::Handle<JSObject*> Intl,
JS::Handle<GlobalObject*> global);
extern MOZ_MUST_USE bool intl_ValidateAndCanonicalizeLanguageTag(JSContext* cx,
unsigned argc,
Value* vp);
extern MOZ_MUST_USE bool intl_TryValidateAndCanonicalizeLanguageTag(
JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool intl_ValidateAndCanonicalizeUnicodeExtensionType(
JSContext* cx, unsigned argc, Value* vp);
} // namespace js
#endif /* builtin_intl_Locale_h */

View file

@ -18,6 +18,7 @@
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/LanguageTag.h"
#include "builtin/intl/ScopedICUObject.h"
#include "ds/Sort.h"
#include "js/RootingAPI.h"
@ -35,7 +36,7 @@ using mozilla::IsFinite;
using mozilla::IsNaN;
using mozilla::IsNegativeZero;
using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::DateTimeFormatOptions;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::StringsAreEqual;
@ -92,63 +93,34 @@ static const JSFunctionSpec numberFormat_methods[] = {
static bool
NumberFormat(JSContext* cx, const CallArgs& args, bool construct)
{
RootedObject obj(cx);
// Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
// We're following ECMA-402 1st Edition when NumberFormat is called
// because of backward compatibility issues.
// See https://github.com/tc39/ecma402/issues/57
if (!construct) {
// ES Intl 1st ed., 11.1.2.1 step 3
JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
if (!intl)
return false;
RootedValue self(cx, args.thisv());
if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
// ES Intl 1st ed., 11.1.2.1 step 4
obj = ToObject(cx, self);
if (!obj)
return false;
// ES Intl 1st ed., 11.1.2.1 step 5
bool extensible;
if (!IsExtensible(cx, obj, &extensible))
return false;
if (!extensible)
return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
} else {
// ES Intl 1st ed., 11.1.2.1 step 3.a
construct = true;
}
}
if (construct) {
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
if (!proto) {
proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
if (!proto)
return false;
}
obj = NewObjectWithGivenProto<NumberFormatObject>(cx, proto);
if (!obj)
return false;
obj->as<NativeObject>().setReservedSlot(NumberFormatObject::INTERNALS_SLOT, NullValue());
obj->as<NativeObject>().setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
}
RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
// Step 3.
if (!intl::InitializeObject(cx, obj, cx->names().InitializeNumberFormat, locales, options))
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
args.rval().setObject(*obj);
return true;
if (!proto) {
proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
if (!proto)
return false;
}
Rooted<NumberFormatObject*> numberFormat(cx);
numberFormat = NewObjectWithGivenProto<NumberFormatObject>(cx, proto);
if (!numberFormat)
return false;
numberFormat->setReservedSlot(NumberFormatObject::INTERNALS_SLOT, NullValue());
numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
RootedValue thisValue(cx, construct ? ObjectValue(*numberFormat) : args.thisv());
RootedValue locales(cx, args.get(0));
RootedValue options(cx, args.get(1));
// Step 3.
return intl::LegacyIntlInitialize(cx, numberFormat, cx->names().InitializeNumberFormat, thisValue,
locales, options, DateTimeFormatOptions::Standard, args.rval());
}
static bool
@ -175,30 +147,23 @@ js::NumberFormatObject::finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(fop->onMainThread());
// This is-undefined check shouldn't be necessary, but for internal
// brokenness in object allocation code. For the moment, hack around it by
// explicitly guarding against the possibility of the reserved slot not
// containing a private. See bug 949220.
const Value& slot = obj->as<NativeObject>().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT);
if (!slot.isUndefined()) {
if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate()))
unum_close(nf);
}
const Value& slot = obj->as<NumberFormatObject>().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT);
if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate()))
unum_close(nf);
}
JSObject*
js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
MutableHandleObject constructor)
{
RootedFunction ctor(cx);
ctor = GlobalObject::createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0);
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global,
&NumberFormatObject::class_));
RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
if (!proto)
return nullptr;
proto->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
@ -229,38 +194,15 @@ js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalO
return nullptr;
}
RootedValue options(cx);
if (!intl::CreateDefaultOptions(cx, &options))
return nullptr;
// 11.2.1 and 11.3
if (!intl::InitializeObject(cx, proto, cx->names().InitializeNumberFormat, UndefinedHandleValue,
options))
{
return nullptr;
}
// 8.1
RootedValue ctorValue(cx, ObjectValue(*ctor));
if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0))
return nullptr;
constructor.set(ctor);
return proto;
}
bool
js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedValue result(cx);
if (!GetAvailableLocales(cx, unum_countAvailable, unum_getAvailable, &result))
return false;
args.rval().set(result);
return true;
}
bool
js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp)
{
@ -295,7 +237,7 @@ js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp)
* of the given NumberFormat.
*/
static UNumberFormat*
NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
NewUNumberFormat(JSContext* cx, Handle<NumberFormatObject*> numberFormat)
{
RootedValue value(cx);
@ -305,7 +247,41 @@ NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
return nullptr;
JSAutoByteString locale(cx, value.toString());
// ICU expects numberingSystem as a Unicode locale extensions on locale.
intl::LanguageTag tag(cx);
{
JSLinearString* locale = value.toString()->ensureLinear(cx);
if (!locale)
return nullptr;
if (!intl::LanguageTagParser::parse(cx, locale, tag))
return nullptr;
}
JS::RootedVector<intl::UnicodeExtensionKeyword> keywords(cx);
if (!GetProperty(cx, internals, internals, cx->names().numberingSystem, &value))
return nullptr;
{
JSLinearString* numberingSystem = value.toString()->ensureLinear(cx);
if (!numberingSystem)
return nullptr;
if (!keywords.emplaceBack("nu", numberingSystem))
return nullptr;
}
// |ApplyUnicodeExtensionToTag| applies the new keywords to the front of
// the Unicode extension subtag. We're then relying on ICU to follow RFC
// 6067, which states that any trailing keywords using the same key
// should be ignored.
if (!intl::ApplyUnicodeExtensionToTag(cx, tag, keywords))
return nullptr;
UniqueChars locale = tag.toStringZ(cx);
if (!locale)
return nullptr;
@ -323,9 +299,6 @@ NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
RootedString currency(cx);
AutoStableStringChars stableChars(cx);
// We don't need to look at numberingSystem - it can only be set via
// the Unicode locale extension and is therefore already set on locale.
if (!GetProperty(cx, internals, internals, cx->names().style, &value))
return nullptr;
JSAutoByteString style(cx, value.toString());
@ -398,7 +371,7 @@ NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
uUseGrouping = value.toBoolean();
UErrorCode status = U_ZERO_ERROR;
UNumberFormat* nf = unum_open(uStyle, nullptr, 0, IcuLocale(locale.ptr()), nullptr, &status);
UNumberFormat* nf = unum_open(uStyle, nullptr, 0, IcuLocale(locale.get()), nullptr, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return nullptr;
@ -854,50 +827,23 @@ js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp)
MOZ_ASSERT(args[1].isNumber());
MOZ_ASSERT(args[2].isBoolean());
RootedObject numberFormat(cx, &args[0].toObject());
Rooted<NumberFormatObject*> numberFormat(cx, &args[0].toObject().as<NumberFormatObject>());
// Obtain a UNumberFormat object, cached if possible.
bool isNumberFormatInstance = numberFormat->getClass() == &NumberFormatObject::class_;
UNumberFormat* nf;
if (isNumberFormatInstance) {
void* priv =
numberFormat->as<NativeObject>().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT).toPrivate();
nf = static_cast<UNumberFormat*>(priv);
if (!nf) {
nf = NewUNumberFormat(cx, numberFormat);
if (!nf)
return false;
numberFormat->as<NativeObject>().setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nf));
}
} else {
// There's no good place to cache the ICU number format for an object
// that has been initialized as a NumberFormat but is not a
// NumberFormat instance. One possibility might be to add a
// NumberFormat instance as an internal property to each such object.
// Obtain a cached UNumberFormat object.
void* priv =
numberFormat->getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT).toPrivate();
UNumberFormat* nf = static_cast<UNumberFormat*>(priv);
if (!nf) {
nf = NewUNumberFormat(cx, numberFormat);
if (!nf)
return false;
numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nf));
}
// Use the UNumberFormat to actually format the number.
double d = args[1].toNumber();
RootedValue result(cx);
bool success;
if (args[2].toBoolean()) {
success = intl_FormatNumberToParts(cx, nf, d, &result);
} else {
MOZ_ASSERT(!args[2].toBoolean(),
"shouldn't be doing formatToParts without an ICU that "
"supports it");
success = js::intl_FormatNumber(cx, nf, d, &result);
return intl_FormatNumberToParts(cx, nf, args[1].toNumber(), args.rval());
}
if (!isNumberFormatInstance)
unum_close(nf);
if (!success)
return false;
args.rval().set(result);
return true;
return intl_FormatNumber(cx, nf, args[1].toNumber(), args.rval());
}

View file

@ -37,7 +37,8 @@ class NumberFormatObject : public NativeObject
};
extern JSObject*
CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global);
CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
MutableHandleObject constructor);
/**
* Returns a new instance of the standard built-in NumberFormat constructor.
@ -49,17 +50,6 @@ CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObjec
extern MOZ_MUST_USE bool
intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an object indicating the supported locales for number formatting
* by having a true-valued property for each such locale with the
* canonicalized language tag as the property name. The object has no
* prototype.
*
* Usage: availableLocales = intl_NumberFormat_availableLocales()
*/
extern MOZ_MUST_USE bool
intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns the numbering system type identifier per Unicode
* Technical Standard 35, Unicode Locale Data Markup Language, for the
@ -76,7 +66,7 @@ intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp);
*
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
*
* Usage: formatted = intl_FormatNumber(numberFormat, x)
* Usage: formatted = intl_FormatNumber(numberFormat, x, formatToParts)
*/
extern MOZ_MUST_USE bool
intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp);

View file

@ -8,21 +8,10 @@
/**
* NumberFormat internal properties.
*
* Spec: ECMAScript Internationalization API Specification, 9.1 and 11.2.3.
* Spec: ECMAScript Internationalization API Specification, 9.1 and 11.3.3.
*/
var numberFormatInternalProperties = {
localeData: numberFormatLocaleData,
_availableLocales: null,
availableLocales: function()
{
var locales = this._availableLocales;
if (locales)
return locales;
locales = intl_NumberFormat_availableLocales();
addSpecialMissingLanguageTags(locales);
return (this._availableLocales = locales);
},
relevantExtensionKeys: ["nu"]
};
@ -35,44 +24,38 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
var internalProps = std_Object_create(null);
// Step 3.
var requestedLocales = lazyNumberFormatData.requestedLocales;
// Compute options that impact interpretation of locale.
// Step 6.
var opt = lazyNumberFormatData.opt;
var NumberFormat = numberFormatInternalProperties;
// Step 9.
// Compute effective locale.
// Step 7.
var localeData = NumberFormat.localeData;
// Step 10.
var r = ResolveLocale(callFunction(NumberFormat.availableLocales, NumberFormat),
// Step 8.
var r = ResolveLocale("NumberFormat",
lazyNumberFormatData.requestedLocales,
lazyNumberFormatData.opt,
NumberFormat.relevantExtensionKeys,
localeData);
// Steps 11-12. (Step 13 is not relevant to our implementation.)
// Steps 9-10. (Step 11 is not relevant to our implementation.)
internalProps.locale = r.locale;
internalProps.numberingSystem = r.nu;
// Compute formatting options.
// Step 15.
// Step 13.
var s = lazyNumberFormatData.style;
internalProps.style = s;
// Steps 19, 21.
// Steps 17, 19.
if (s === "currency") {
internalProps.currency = lazyNumberFormatData.currency;
internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay;
}
// Step 22.
internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits;
internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits;
internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits;
if ("minimumSignificantDigits" in lazyNumberFormatData) {
@ -83,12 +66,9 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
internalProps.maximumSignificantDigits = lazyNumberFormatData.maximumSignificantDigits;
}
// Step 27.
// Step 24.
internalProps.useGrouping = lazyNumberFormatData.useGrouping;
// Step 34.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
// object using |setInternalProperties|.
return internalProps;
@ -96,11 +76,13 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
/**
* Returns an object containing the NumberFormat internal properties of |obj|,
* or throws a TypeError if |obj| isn't NumberFormat-initialized.
* Returns an object containing the NumberFormat internal properties of |obj|.
*/
function getNumberFormatInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "NumberFormat", methodName);
function getNumberFormatInternals(obj) {
assert(IsObject(obj), "getNumberFormatInternals called with non-object");
assert(IsNumberFormat(obj), "getNumberFormatInternals called with non-NumberFormat");
var internals = getIntlObjectInternals(obj);
assert(internals.type === "NumberFormat", "bad type escaped getIntlObjectInternals");
// If internal properties have already been computed, use them.
@ -114,24 +96,45 @@ function getNumberFormatInternals(obj, methodName) {
return internalProps;
}
/**
* 11.1.11 UnwrapNumberFormat( nf )
*/
function UnwrapNumberFormat(nf, methodName) {
// Step 1 (not applicable in our implementation).
// Step 2.
if ((!IsObject(nf) || !IsNumberFormat(nf)) && nf instanceof GetNumberFormatConstructor()) {
nf = nf[intlFallbackSymbol()];
}
// Step 3.
if (!IsObject(nf) || !IsNumberFormat(nf))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "NumberFormat", methodName, "NumberFormat");
// Step 4.
return nf;
}
/**
* Applies digit options used for number formatting onto the intl object.
*
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
*/
function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault) {
// We skip Step 1 because we set the properties on a lazyData object.
// We skip step 1 because we set the properties on a lazyData object.
// Step 2-3.
// Steps 2-4.
assert(IsObject(options), "SetNumberFormatDigitOptions");
assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions");
// Steps 4-6.
// Steps 5-8.
const mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
const mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
const mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20);
// Steps 7-8.
// Steps 9-10.
let mnsd = options.minimumSignificantDigits;
let mxsd = options.maximumSignificantDigits;
@ -175,17 +178,9 @@ function toASCIIUpperCase(s) {
*
* Spec: ECMAScript Internationalization API Specification, 6.3.1.
*/
function getIsWellFormedCurrencyCodeRE() {
return internalIntlRegExps.isWellFormedCurrencyCodeRE ||
(internalIntlRegExps.isWellFormedCurrencyCodeRE = RegExpCreate("[^A-Z]"));
}
function IsWellFormedCurrencyCode(currency) {
var c = ToString(currency);
var normalized = toASCIIUpperCase(c);
if (normalized.length !== 3)
return false;
return !regexp_test_no_statics(getIsWellFormedCurrencyCodeRE(), normalized);
assert(typeof currency === "string", "currency is a string value");
return currency.length === 3 && IsASCIIAlphaString(currency);
}
/**
@ -197,17 +192,11 @@ function IsWellFormedCurrencyCode(currency) {
* This later work occurs in |resolveNumberFormatInternals|; steps not noted
* here occur there.
*
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
* Spec: ECMAScript Internationalization API Specification, 11.1.2.
*/
function InitializeNumberFormat(numberFormat, locales, options) {
assert(IsObject(numberFormat), "InitializeNumberFormat");
// Step 1.
if (isInitializedIntlObject(numberFormat))
ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
// Step 2.
var internals = initializeIntlObject(numberFormat);
function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
assert(IsObject(numberFormat), "InitializeNumberFormat called with non-object");
assert(IsNumberFormat(numberFormat), "InitializeNumberFormat called with non-NumberFormat");
// Lazy NumberFormat data has the following structure:
//
@ -222,6 +211,8 @@ function InitializeNumberFormat(numberFormat, locales, options) {
// opt: // opt object computed in InitializeNumberFormat
// {
// localeMatcher: "lookup" / "best fit",
//
// nu: string matching a Unicode extension type, // optional
// }
//
// minimumIntegerDigits: integer ∈ [1, 21],
@ -240,11 +231,11 @@ function InitializeNumberFormat(numberFormat, locales, options) {
// subset of them.
var lazyNumberFormatData = std_Object_create(null);
// Step 3.
// Step 1.
var requestedLocales = CanonicalizeLocaleList(locales);
lazyNumberFormatData.requestedLocales = requestedLocales;
// Steps 4-5.
// Steps 2-3.
//
// If we ever need more speed here at startup, we should try to detect the
// case where |options === undefined| and Object.prototype hasn't been
@ -257,20 +248,30 @@ function InitializeNumberFormat(numberFormat, locales, options) {
options = ToObject(options);
// Compute options that impact interpretation of locale.
// Step 6.
// Step 4.
var opt = new Record();
lazyNumberFormatData.opt = opt;
// Steps 7-8.
// Steps 5-6.
var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
var numberingSystem = GetOption(options, "numberingSystem", "string", undefined, undefined);
if (numberingSystem !== undefined) {
numberingSystem = intl_ValidateAndCanonicalizeUnicodeExtensionType(numberingSystem,
"numberingSystem",
"nu");
}
opt.nu = numberingSystem;
// Compute formatting options.
// Step 14.
// Step 12.
var s = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal");
lazyNumberFormatData.style = s;
// Steps 16-19.
// Steps 14-17.
var c = GetOption(options, "currency", "string", undefined, undefined);
if (c !== undefined && !IsWellFormedCurrencyCode(c))
ThrowRangeError(JSMSG_INVALID_CURRENCY_CODE, c);
@ -285,12 +286,12 @@ function InitializeNumberFormat(numberFormat, locales, options) {
cDigits = CurrencyDigits(c);
}
// Step 20.
// Step 18.
var cd = GetOption(options, "currencyDisplay", "string", ["code", "symbol", "name"], "symbol");
if (s === "currency")
lazyNumberFormatData.currencyDisplay = cd;
// Steps 22-24.
// Steps 20-22.
SetNumberFormatDigitOptions(lazyNumberFormatData, options, s === "currency" ? cDigits: 0);
// Step 25.
@ -304,15 +305,31 @@ function InitializeNumberFormat(numberFormat, locales, options) {
std_Math_max(lazyNumberFormatData.minimumFractionDigits, mxfdDefault);
}
// Step 26.
// Steps 23.
var g = GetOption(options, "useGrouping", "boolean", undefined, true);
lazyNumberFormatData.useGrouping = g;
// Steps 35-36.
// Step 31.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
setLazyData(internals, "NumberFormat", lazyNumberFormatData);
initializeIntlObject(numberFormat, "NumberFormat", lazyNumberFormatData);
// 11.2.1, steps 4-5.
// TODO: spec issue - The current spec doesn't have the IsObject check,
// which means |Intl.NumberFormat.call(null)| is supposed to throw here.
if (numberFormat !== thisValue && thisValue instanceof GetNumberFormatConstructor()) {
if (!IsObject(thisValue))
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof thisValue);
_DefineDataProperty(thisValue, intlFallbackSymbol(), numberFormat,
ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE);
return thisValue;
}
// 11.2.1, step 6.
return numberFormat;
}
@ -356,17 +373,14 @@ var currencyDigits = {
/**
* Returns the number of decimal digits to be used for the given currency.
*
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
* Spec: ECMAScript Internationalization API Specification, 11.1.3.
*/
function getCurrencyDigitsRE() {
return internalIntlRegExps.currencyDigitsRE ||
(internalIntlRegExps.currencyDigitsRE = RegExpCreate("^[A-Z]{3}$"));
}
function CurrencyDigits(currency) {
assert(typeof currency === "string", "CurrencyDigits");
assert(regexp_test_no_statics(getCurrencyDigitsRE(), currency), "CurrencyDigits");
assert(typeof currency === "string", "currency is a string value");
assert(IsWellFormedCurrencyCode(currency), "currency is well-formed");
assert(currency == toASCIIUpperCase(currency), "currency is all upper-case");
if (callFunction(std_Object_hasOwnProperty, currencyDigits, currency))
if (hasOwn(currency, currencyDigits))
return currencyDigits[currency];
return 2;
}
@ -377,14 +391,18 @@ function CurrencyDigits(currency) {
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript Internationalization API Specification, 11.2.2.
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
*/
function Intl_NumberFormat_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
var availableLocales = callFunction(numberFormatInternalProperties.availableLocales,
numberFormatInternalProperties);
// Step 1.
var availableLocales = "NumberFormat";
// Step 2.
var requestedLocales = CanonicalizeLocaleList(locales);
// Step 3.
return SupportedLocales(availableLocales, requestedLocales, options);
}
@ -397,8 +415,8 @@ function getNumberingSystems(locale) {
// Algorithmic numbering systems are typically tied to one locale, so for
// lack of information we don't offer them. To increase chances that
// other software will process output correctly, we further restrict to
// those decimal numbering systems explicitly listed in table 2 of
// the ECMAScript Internationalization API Specification, 11.3.2, which
// those decimal numbering systems explicitly listed in table 3 of
// the ECMAScript Internationalization API Specification, 11.1.6, which
// in turn are those with full specifications in version 21 of Unicode
// Technical Standard #35 using digits that were defined in Unicode 5.0,
// the Unicode version supported in Windows Vista.
@ -416,9 +434,12 @@ function getNumberingSystems(locale) {
}
function numberFormatLocaleData(locale) {
function numberFormatLocaleData() {
return {
nu: getNumberingSystems(locale)
nu: getNumberingSystems,
default: {
nu: intl_numberingSystem,
}
};
}
@ -426,7 +447,7 @@ function numberFormatLocaleData(locale) {
/**
* Function to be bound and returned by Intl.NumberFormat.prototype.format.
*
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
* Spec: ECMAScript Internationalization API Specification, 11.1.4.
*/
function numberFormatFormatToBind(value) {
// Steps 1.a.i implemented by ECMAScript declaration binding instantiation,
@ -443,31 +464,37 @@ function numberFormatFormatToBind(value) {
* representing the result of calling ToNumber(value) according to the
* effective locale and the formatting options of this NumberFormat.
*
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
* Spec: ECMAScript Internationalization API Specification, 11.4.3.
*/
function Intl_NumberFormat_format_get() {
// Check "this NumberFormat object" per introduction of section 11.3.
var internals = getNumberFormatInternals(this, "format");
// Steps 1-3.
var nf = UnwrapNumberFormat(this, "format");
// Step 1.
var internals = getNumberFormatInternals(nf);
// Step 4.
if (internals.boundFormat === undefined) {
// Step 1.a.
var F = numberFormatFormatToBind;
// Steps 4.a-b.
var F = callFunction(FunctionBind, numberFormatFormatToBind, nf);
// Step 1.b-d.
var bf = callFunction(FunctionBind, F, this);
internals.boundFormat = bf;
// Step 4.c.
internals.boundFormat = F;
}
// Step 2.
// Step 5.
return internals.boundFormat;
}
_SetCanonicalName(Intl_NumberFormat_format_get, "get format");
/**
* 11.4.4 Intl.NumberFormat.prototype.formatToParts ( value )
*/
function Intl_NumberFormat_formatToParts(value) {
// Step 1.
var nf = this;
// Steps 1-3.
var nf = UnwrapNumberFormat(this, "formatToParts");
// Steps 2-3.
getNumberFormatInternals(nf, "formatToParts");
// Ensure the NumberFormat internals are resolved.
getNumberFormatInternals(nf);
// Step 4.
var x = ToNumber(value);
@ -479,12 +506,15 @@ function Intl_NumberFormat_formatToParts(value) {
/**
* Returns the resolved options for a NumberFormat object.
*
* Spec: ECMAScript Internationalization API Specification, 11.3.3 and 11.4.
* Spec: ECMAScript Internationalization API Specification, 11.4.5.
*/
function Intl_NumberFormat_resolvedOptions() {
// Check "this NumberFormat object" per introduction of section 11.3.
var internals = getNumberFormatInternals(this, "resolvedOptions");
// Steps 1-3.
var nf = UnwrapNumberFormat(this, "resolvedOptions");
var internals = getNumberFormatInternals(nf);
// Steps 4-5.
var result = {
locale: internals.locale,
numberingSystem: internals.numberingSystem,
@ -494,17 +524,31 @@ function Intl_NumberFormat_resolvedOptions() {
maximumFractionDigits: internals.maximumFractionDigits,
useGrouping: internals.useGrouping
};
var optionalProperties = [
"currency",
"currencyDisplay",
"minimumSignificantDigits",
"maximumSignificantDigits"
];
for (var i = 0; i < optionalProperties.length; i++) {
var p = optionalProperties[i];
if (callFunction(std_Object_hasOwnProperty, internals, p))
_DefineDataProperty(result, p, internals[p]);
// currency and currencyDisplay are only present for currency formatters.
assert(hasOwn("currency", internals) === (internals.style === "currency"),
"currency is present iff style is 'currency'");
assert(hasOwn("currencyDisplay", internals) === (internals.style === "currency"),
"currencyDisplay is present iff style is 'currency'");
if (hasOwn("currency", internals)) {
_DefineDataProperty(result, "currency", internals.currency);
_DefineDataProperty(result, "currencyDisplay", internals.currencyDisplay);
}
// Min/Max significant digits are either both present or not at all.
assert(hasOwn("minimumSignificantDigits", internals) ===
hasOwn("maximumSignificantDigits", internals),
"minimumSignificantDigits is present iff maximumSignificantDigits is present");
if (hasOwn("minimumSignificantDigits", internals)) {
_DefineDataProperty(result, "minimumSignificantDigits",
internals.minimumSignificantDigits);
_DefineDataProperty(result, "maximumSignificantDigits",
internals.maximumSignificantDigits);
}
// Step 6.
return result;
}

View file

@ -28,7 +28,6 @@ using namespace js;
using mozilla::AssertedCast;
using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::StringsAreEqual;
@ -79,7 +78,7 @@ static const JSFunctionSpec pluralRules_methods[] = {
/**
* PluralRules constructor.
* Spec: ECMAScript 402 API, PluralRules, 1.1
* Spec: ECMAScript 402 API, PluralRules, 13.2.1
*/
static bool
PluralRules(JSContext* cx, const CallArgs& args, bool construct)
@ -113,8 +112,8 @@ PluralRules(JSContext* cx, const CallArgs& args, bool construct)
if (!obj)
return false;
obj->as<NativeObject>().setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue());
obj->as<NativeObject>().setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
obj->as<PluralRulesObject>().setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue());
obj->as<PluralRulesObject>().setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
}
RootedValue locales(cx, args.get(0));
@ -147,15 +146,9 @@ js::PluralRulesObject::finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(fop->onMainThread());
// This is-undefined check shouldn't be necessary, but for internal
// brokenness in object allocation code. For the moment, hack around it by
// explicitly guarding against the possibility of the reserved slot not
// containing a private. See bug 949220.
const Value& slot = obj->as<PluralRulesObject>().getReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT);
if (!slot.isUndefined()) {
if (UPluralRules* pr = static_cast<UPluralRules*>(slot.toPrivate()))
uplrules_close(pr);
}
if (UPluralRules* pr = static_cast<UPluralRules*>(slot.toPrivate()))
uplrules_close(pr);
}
JSObject*
@ -166,10 +159,9 @@ js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalOb
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &PluralRulesObject::class_));
RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
if (!proto)
return nullptr;
proto->setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
@ -180,16 +172,6 @@ js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalOb
if (!JS_DefineFunctions(cx, proto, pluralRules_methods))
return nullptr;
RootedValue options(cx);
if (!intl::CreateDefaultOptions(cx, &options))
return nullptr;
if (!intl::InitializeObject(cx, proto, cx->names().InitializePluralRules, UndefinedHandleValue,
options))
{
return nullptr;
}
RootedValue ctorValue(cx, ObjectValue(*ctor));
if (!DefineProperty(cx, Intl, cx->names().PluralRules, ctorValue, nullptr, nullptr, 0))
return nullptr;
@ -197,21 +179,6 @@ js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalOb
return proto;
}
bool
js::intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedValue result(cx);
// We're going to use ULocale availableLocales as per ICU recommendation:
// https://ssl.icu-project.org/trac/ticket/12756
if (!GetAvailableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
return false;
args.rval().set(result);
return true;
}
/**
*
* This creates new UNumberFormat with calculated digit formatting
@ -222,7 +189,7 @@ js::intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp)
*
*/
static UNumberFormat*
NewUNumberFormatForPluralRules(JSContext* cx, HandleObject pluralRules)
NewUNumberFormatForPluralRules(JSContext* cx, Handle<PluralRulesObject*> pluralRules)
{
RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
if (!internals)
@ -299,7 +266,7 @@ js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject pluralRules(cx, &args[0].toObject());
Rooted<PluralRulesObject*> pluralRules(cx, &args[0].toObject().as<PluralRulesObject>());
UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
if (!nf)

View file

@ -49,17 +49,6 @@ CreatePluralRulesPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
extern MOZ_MUST_USE bool
intl_PluralRules(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an object indicating the supported locales for plural rules
* by having a true-valued property for each such locale with the
* canonicalized language tag as the property name. The object has no
* prototype.
*
* Usage: availableLocales = intl_PluralRules_availableLocales()
*/
extern MOZ_MUST_USE bool
intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns a plural rule for the number x according to the effective
* locale and the formatting options of the given PluralRules.

View file

@ -7,22 +7,20 @@
/**
* PluralRules internal properties.
*
* Spec: ECMAScript 402 API, PluralRules, 1.3.3.
* Spec: ECMAScript 402 API, PluralRules, 13.3.3.
*/
var pluralRulesInternalProperties = {
_availableLocales: null,
availableLocales: function()
{
var locales = this._availableLocales;
if (locales)
return locales;
locales = intl_PluralRules_availableLocales();
addSpecialMissingLanguageTags(locales);
return (this._availableLocales = locales);
}
localeData: pluralRulesLocaleData,
relevantExtensionKeys: [],
};
function pluralRulesLocaleData() {
// PluralRules don't support any extension keys.
return {};
}
/**
* Compute an internal properties object from |lazyPluralRulesData|.
*/
@ -35,20 +33,25 @@ function resolvePluralRulesInternals(lazyPluralRulesData) {
var PluralRules = pluralRulesInternalProperties;
// Step 13.
const r = ResolveLocale(callFunction(PluralRules.availableLocales, PluralRules),
lazyPluralRulesData.requestedLocales,
lazyPluralRulesData.opt,
noRelevantExtensionKeys, undefined);
// Compute effective locale.
// Step 14.
// Step 10.
var localeData = PluralRules.localeData;
// Step 11.
const r = ResolveLocale("PluralRules",
lazyPluralRulesData.requestedLocales,
lazyPluralRulesData.opt,
PluralRules.relevantExtensionKeys,
localeData);
// Step 12.
internalProps.locale = r.locale;
// Step 8.
internalProps.type = lazyPluralRulesData.type;
internalProps.pluralCategories = intl_GetPluralCategories(
internalProps.locale,
internalProps.type);
// Step 9.
internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits;
internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits;
internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits;
@ -59,15 +62,20 @@ function resolvePluralRulesInternals(lazyPluralRulesData) {
internalProps.maximumSignificantDigits = lazyPluralRulesData.maximumSignificantDigits;
}
// Step 13 (lazily computed on first access).
internalProps.pluralCategories = null;
return internalProps;
}
/**
* Returns an object containing the PluralRules internal properties of |obj|,
* or throws a TypeError if |obj| isn't PluralRules-initialized.
* Returns an object containing the PluralRules internal properties of |obj|.
*/
function getPluralRulesInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "PluralRules", methodName);
function getPluralRulesInternals(obj) {
assert(IsObject(obj), "getPluralRulesInternals called with non-object");
assert(IsPluralRules(obj), "getPluralRulesInternals called with non-PluralRules");
var internals = getIntlObjectInternals(obj);
assert(internals.type === "PluralRules", "bad type escaped getIntlObjectInternals");
var internalProps = maybeInternalProperties(internals);
@ -88,16 +96,11 @@ function getPluralRulesInternals(obj, methodName) {
* This later work occurs in |resolvePluralRulesInternals|; steps not noted
* here occur there.
*
* Spec: ECMAScript 402 API, PluralRules, 1.1.1.
* Spec: ECMAScript 402 API, PluralRules, 13.1.1.
*/
function InitializePluralRules(pluralRules, locales, options) {
assert(IsObject(pluralRules), "InitializePluralRules");
// Step 1.
if (isInitializedIntlObject(pluralRules))
ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
let internals = initializeIntlObject(pluralRules);
assert(IsObject(pluralRules), "InitializePluralRules called with non-object");
assert(IsPluralRules(pluralRules), "InitializePluralRules called with non-PluralRules");
// Lazy PluralRules data has the following structure:
//
@ -124,30 +127,29 @@ function InitializePluralRules(pluralRules, locales, options) {
// subset of them.
const lazyPluralRulesData = std_Object_create(null);
// Step 3.
// Step 1.
let requestedLocales = CanonicalizeLocaleList(locales);
lazyPluralRulesData.requestedLocales = requestedLocales;
// Steps 4-5.
// Steps 2-3.
if (options === undefined)
options = {};
else
options = ToObject(options);
// Step 6.
const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal");
lazyPluralRulesData.type = type;
// Step 8.
// Step 4.
let opt = new Record();
lazyPluralRulesData.opt = opt;
// Steps 9-10.
// Steps 5-6.
let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
// Step 7.
const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal");
lazyPluralRulesData.type = type;
// Step 11.
// Step 9.
SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0);
// Step 12.
@ -156,7 +158,11 @@ function InitializePluralRules(pluralRules, locales, options) {
std_Math_max(lazyPluralRulesData.minimumFractionDigits, 3);
}
setLazyData(internals, "PluralRules", lazyPluralRulesData)
// Step 15.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
initializeIntlObject(pluralRules, "PluralRules", lazyPluralRulesData)
}
/**
@ -164,14 +170,14 @@ function InitializePluralRules(pluralRules, locales, options) {
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript 402 API, PluralRules, 1.3.2.
* Spec: ECMAScript 402 API, PluralRules, 13.3.2.
*/
function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(pluralRulesInternalProperties.availableLocales,
pluralRulesInternalProperties);
var availableLocales = "PluralRules";
// Step 2.
let requestedLocales = CanonicalizeLocaleList(locales);
@ -184,15 +190,20 @@ function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
* the number passed as value according to the
* effective locale and the formatting options of this PluralRules.
*
* Spec: ECMAScript 402 API, PluralRules, 1.4.3.
* Spec: ECMAScript 402 API, PluralRules, 13.4.3.
*/
function Intl_PluralRules_select(value) {
// Step 1.
let pluralRules = this;
// Step 2.
let internals = getPluralRulesInternals(pluralRules, "select");
// Steps 3-4.
// Steps 2-3.
if (!IsObject(pluralRules) || !IsPluralRules(pluralRules))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "PluralRules", "select", "PluralRules");
// Ensure the PluralRules internals are resolved.
getPluralRulesInternals(pluralRules);
// Step 4.
let n = ToNumber(value);
// Step 5.
@ -202,11 +213,34 @@ function Intl_PluralRules_select(value) {
/**
* Returns the resolved options for a PluralRules object.
*
* Spec: ECMAScript 402 API, PluralRules, 1.4.4.
* Spec: ECMAScript 402 API, PluralRules, 13.4.4.
*/
function Intl_PluralRules_resolvedOptions() {
var internals = getPluralRulesInternals(this, "resolvedOptions");
// Step 1.
var pluralRules = this;
// Steps 2-3.
if (!IsObject(pluralRules) || !IsPluralRules(pluralRules)) {
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "PluralRules", "resolvedOptions",
"PluralRules");
}
var internals = getPluralRulesInternals(pluralRules);
var internalsPluralCategories = internals.pluralCategories;
if (internalsPluralCategories === null) {
internalsPluralCategories = intl_GetPluralCategories(internals.locale, internals.type);
internals.pluralCategories = internalsPluralCategories;
}
// TODO: The current spec actually requires to return the internal array
// object and not a copy of it.
// <https://github.com/tc39/proposal-intl-plural-rules/issues/28#issuecomment-341557030>
var pluralCategories = [];
for (var i = 0; i < internalsPluralCategories.length; i++)
_DefineDataProperty(pluralCategories, i, internalsPluralCategories[i]);
// Steps 4-5.
var result = {
locale: internals.locale,
type: internals.type,
@ -216,16 +250,19 @@ function Intl_PluralRules_resolvedOptions() {
maximumFractionDigits: internals.maximumFractionDigits,
};
var optionalProperties = [
"minimumSignificantDigits",
"maximumSignificantDigits"
];
// Min/Max significant digits are either both present or not at all.
assert(hasOwn("minimumSignificantDigits", internals) ===
hasOwn("maximumSignificantDigits", internals),
"minimumSignificantDigits is present iff maximumSignificantDigits is present");
for (var i = 0; i < optionalProperties.length; i++) {
var p = optionalProperties[i];
if (callFunction(std_Object_hasOwnProperty, internals, p))
_DefineDataProperty(result, p, internals[p]);
if (hasOwn("minimumSignificantDigits", internals)) {
_DefineDataProperty(result, "minimumSignificantDigits",
internals.minimumSignificantDigits);
_DefineDataProperty(result, "maximumSignificantDigits",
internals.maximumSignificantDigits);
}
// Step 6.
return result;
}

View file

@ -26,7 +26,6 @@ using mozilla::Range;
using mozilla::RangedPtr;
using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::StringsAreEqual;
@ -108,8 +107,8 @@ RelativeTimeFormat(JSContext* cx, unsigned argc, Value* vp)
if (!relativeTimeFormat)
return false;
relativeTimeFormat->as<NativeObject>().setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue());
relativeTimeFormat->as<NativeObject>().setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr));
relativeTimeFormat->as<RelativeTimeFormatObject>().setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue());
relativeTimeFormat->as<RelativeTimeFormatObject>().setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr));
RootedValue locales(cx, args.get(0));
RootedValue options(cx, args.get(1));
@ -127,15 +126,9 @@ js::RelativeTimeFormatObject::finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(fop->onMainThread());
// This is-undefined check shouldn't be necessary, but for internal
// brokenness in object allocation code. For the moment, hack around it by
// explicitly guarding against the possibility of the reserved slot not
// containing a private. See bug 949220.
const Value& slot = obj->as<RelativeTimeFormatObject>().getReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT);
if (!slot.isUndefined()) {
if (URelativeDateTimeFormatter* rtf = static_cast<URelativeDateTimeFormatter*>(slot.toPrivate()))
ureldatefmt_close(rtf);
}
if (URelativeDateTimeFormatter* rtf = static_cast<URelativeDateTimeFormatter*>(slot.toPrivate()))
ureldatefmt_close(rtf);
}
JSObject*
@ -146,10 +139,9 @@ js::CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<G
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &RelativeTimeFormatObject::class_));
RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
if (!proto)
return nullptr;
proto->setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
@ -163,16 +155,6 @@ js::CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<G
if (!JS_DefineProperties(cx, proto, relativeTimeFormat_properties))
return nullptr;
RootedValue options(cx);
if (!intl::CreateDefaultOptions(cx, &options))
return nullptr;
if (!intl::InitializeObject(cx, proto, cx->names().InitializeRelativeTimeFormat, UndefinedHandleValue,
options))
{
return nullptr;
}
RootedValue ctorValue(cx, ObjectValue(*ctor));
if (!DefineProperty(cx, Intl, cx->names().RelativeTimeFormat, ctorValue, nullptr, nullptr, 0)) {
return nullptr;
@ -181,22 +163,6 @@ js::CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<G
return proto;
}
bool
js::intl_RelativeTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedValue result(cx);
// We're going to use ULocale availableLocales as per ICU recommendation:
// https://ssl.icu-project.org/trac/ticket/12756
if (!GetAvailableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
return false;
args.rval().set(result);
return true;
}
enum class RelativeTimeNumeric
{
/**

View file

@ -39,17 +39,6 @@ extern JSObject*
CreateRelativeTimeFormatPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
JS::Handle<GlobalObject*> global);
/**
* Returns an object indicating the supported locales for relative time format
* by having a true-valued property for each such locale with the
* canonicalized language tag as the property name. The object has no
* prototype.
*
* Usage: availableLocales = intl_RelativeTimeFormat_availableLocales()
*/
extern MOZ_MUST_USE bool
intl_RelativeTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns a relative time as a string formatted according to the effective
* locale and the formatting options of the given RelativeTimeFormat.

View file

@ -11,17 +11,6 @@
*/
var relativeTimeFormatInternalProperties = {
localeData: relativeTimeFormatLocaleData,
_availableLocales: null,
availableLocales: function() // eslint-disable-line object-shorthand
{
var locales = this._availableLocales;
if (locales)
return locales;
locales = intl_RelativeTimeFormat_availableLocales();
addSpecialMissingLanguageTags(locales);
return (this._availableLocales = locales);
},
relevantExtensionKeys: [],
};
@ -41,7 +30,7 @@ function resolveRelativeTimeFormatInternals(lazyRelativeTimeFormatData) {
var RelativeTimeFormat = relativeTimeFormatInternalProperties;
// Steps 7-8.
const r = ResolveLocale(callFunction(RelativeTimeFormat.availableLocales, RelativeTimeFormat),
const r = ResolveLocale("RelativeTimeFormat",
lazyRelativeTimeFormatData.requestedLocales,
lazyRelativeTimeFormatData.opt,
RelativeTimeFormat.relevantExtensionKeys,
@ -69,8 +58,11 @@ function resolveRelativeTimeFormatInternals(lazyRelativeTimeFormatData) {
* Returns an object containing the RelativeTimeFormat internal properties of |obj|,
* or throws a TypeError if |obj| isn't RelativeTimeFormat-initialized.
*/
function getRelativeTimeFormatInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "RelativeTimeFormat", methodName);
function getRelativeTimeFormatInternals(obj) {
assert(IsObject(obj), "getRelativeTimeFormatInternals called with non-object");
assert(IsRelativeTimeFormat(obj), "getRelativeTimeFormatInternals called with non-RelativeTimeFormat");
var internals = getIntlObjectInternals(obj);
assert(internals.type === "RelativeTimeFormat", "bad type escaped getIntlObjectInternals");
var internalProps = maybeInternalProperties(internals);
@ -94,12 +86,8 @@ function getRelativeTimeFormatInternals(obj, methodName) {
* Spec: ECMAScript 402 API, RelativeTimeFormat, 1.1.1.
*/
function InitializeRelativeTimeFormat(relativeTimeFormat, locales, options) {
assert(IsObject(relativeTimeFormat), "InitializeRelativeTimeFormat");
if (isInitializedIntlObject(relativeTimeFormat))
ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
let internals = initializeIntlObject(relativeTimeFormat);
assert(IsObject(relativeTimeFormat), "InitializeRelativeTimeFormat called with non-object");
assert(IsRelativeTimeFormat(relativeTimeFormat), "InitializeRelativeTimeFormat called with non-RelativeTimeFormat");
// Lazy RelativeTimeFormat data has the following structure:
//
@ -146,7 +134,7 @@ function InitializeRelativeTimeFormat(relativeTimeFormat, locales, options) {
const numeric = GetOption(options, "numeric", "string", ["always", "auto"], "always");
lazyRelativeTimeFormatData.numeric = numeric;
setLazyData(internals, "RelativeTimeFormat", lazyRelativeTimeFormatData)
initializeIntlObject(relativeTimeFormat, "RelativeTimeFormat", lazyRelativeTimeFormatData)
}
/**
@ -160,8 +148,8 @@ function Intl_RelativeTimeFormat_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(relativeTimeFormatInternalProperties.availableLocales,
relativeTimeFormatInternalProperties);
var availableLocales = "RelativeTimeFormat";
// Step 2.
let requestedLocales = CanonicalizeLocaleList(locales);
@ -181,7 +169,11 @@ function Intl_RelativeTimeFormat_format(value, unit) {
let relativeTimeFormat = this;
// Step 2.
let internals = getRelativeTimeFormatInternals(relativeTimeFormat, "format");
if (!IsObject(relativeTimeFormat) || !IsRelativeTimeFormat(relativeTimeFormat))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "RelativeTimeFormat", "format", "RelativeTimeFormat");
// Ensure the RelativeTimeFormat internals are resolved.
let internals = getRelativeTimeFormatInternals(relativeTimeFormat);
// Step 3.
let t = ToNumber(value);
@ -191,7 +183,7 @@ function Intl_RelativeTimeFormat_format(value, unit) {
// PartitionRelativeTimePattern, step 4.
if (!Number_isFinite(t)) {
ThrowRangeError(JSMSG_DATE_NOT_FINITE, "RelativeTimeFormat");
ThrowRangeError(JSMSG_DATE_NOT_FINITE, "RelativeTimeFormat", "format");
}
// PartitionRelativeTimePattern, step 5.
@ -227,7 +219,13 @@ function Intl_RelativeTimeFormat_format(value, unit) {
* Spec: ECMAScript 402 API, RelativeTimeFormat, 1.4.4.
*/
function Intl_RelativeTimeFormat_resolvedOptions() {
var internals = getRelativeTimeFormatInternals(this, "resolvedOptions");
// Check "this RelativeTimeFormat object" per introduction of section 1.4.
if (!IsObject(this) || !IsRelativeTimeFormat(this)) {
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "RelativeTimeFormat", "resolvedOptions",
"RelativeTimeFormat");
}
var internals = getRelativeTimeFormatInternals(this);
// Steps 4-5.
var result = {

View file

@ -10,6 +10,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/HashFunctions.h"
#include <algorithm>
#include <stdint.h>
#include "jsatom.h"
@ -21,6 +22,7 @@
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/intl/TimeZoneDataGenerated.h"
#include "js/Utility.h"
#include "js/Vector.h"
using js::HashNumber;
using js::intl::StringsAreEqual;
@ -29,9 +31,7 @@ template<typename Char>
static constexpr Char
ToUpperASCII(Char c)
{
return ('a' <= c && c <= 'z')
? (c & ~0x20)
: c;
return mozilla::IsAsciiLowercaseAlpha(c) ? (c - 0x20) : c;
}
static_assert(ToUpperASCII('a') == 'A', "verifying 'a' uppercases correctly");
@ -63,15 +63,12 @@ EqualCharsIgnoreCaseASCII(const Char1* s1, const Char2* s2, size_t len)
}
js::intl::SharedIntlData::TimeZoneHasher::Lookup::Lookup(JSFlatString* timeZone)
: isLatin1(timeZone->hasLatin1Chars()), length(timeZone->length())
: js::intl::SharedIntlData::LinearStringLookup(timeZone)
{
if (isLatin1) {
latin1Chars = timeZone->latin1Chars(nogc);
if (isLatin1)
hash = HashStringIgnoreCaseASCII(latin1Chars, length);
} else {
twoByteChars = timeZone->twoByteChars(nogc);
else
hash = HashStringIgnoreCaseASCII(twoByteChars, length);
}
}
bool
@ -110,7 +107,7 @@ js::intl::SharedIntlData::ensureTimeZones(JSContext* cx)
if (timeZoneDataInitialized)
return true;
// If initTimeZones() was called previously, but didn't complete due to
// If ensureTimeZones() was called previously, but didn't complete due to
// OOM, clear all sets/maps and start from scratch.
if (availableTimeZones.initialized())
availableTimeZones.finish();
@ -272,12 +269,307 @@ js::intl::SharedIntlData::tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* c
return true;
}
js::intl::SharedIntlData::LocaleHasher::Lookup::Lookup(JSLinearString* locale)
: js::intl::SharedIntlData::LinearStringLookup(locale)
{
if (isLatin1)
hash = mozilla::HashString(latin1Chars, length);
else
hash = mozilla::HashString(twoByteChars, length);
}
js::intl::SharedIntlData::LocaleHasher::Lookup::Lookup(const char* chars,
size_t length)
: js::intl::SharedIntlData::LinearStringLookup(chars, length)
{
hash = mozilla::HashString(latin1Chars, length);
}
bool
js::intl::SharedIntlData::LocaleHasher::match(Locale key, const Lookup& lookup)
{
if (key->length() != lookup.length)
return false;
if (key->hasLatin1Chars()) {
const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
if (lookup.isLatin1)
return EqualChars(keyChars, lookup.latin1Chars, lookup.length);
return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
}
const char16_t* keyChars = key->twoByteChars(lookup.nogc);
if (lookup.isLatin1)
return EqualChars(lookup.latin1Chars, keyChars, lookup.length);
return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
}
bool
js::intl::SharedIntlData::getAvailableLocales(JSContext* cx, LocaleSet& locales,
CountAvailable countAvailable,
GetAvailable getAvailable)
{
auto addLocale = [cx, &locales](const char* locale, size_t length) {
JSAtom* atom = Atomize(cx, locale, length);
if (!atom)
return false;
LocaleHasher::Lookup lookup(atom);
LocaleSet::AddPtr p = locales.lookupForAdd(lookup);
// ICU shouldn't report any duplicate locales, but if it does, just
// ignore the duplicated locale.
if (!p && !locales.add(p, atom)) {
ReportOutOfMemory(cx);
return false;
}
return true;
};
js::Vector<char, 16> lang(cx);
int32_t count = countAvailable();
for (int32_t i = 0; i < count; i++) {
const char* locale = getAvailable(i);
size_t length = strlen(locale);
lang.clear();
if (!lang.append(locale, length))
return false;
std::replace(lang.begin(), lang.end(), '_', '-');
if (!addLocale(lang.begin(), length))
return false;
}
// Add old-style language tags without script code for locales that in current
// usage would include a script subtag. Also add an entry for the last-ditch
// locale, in case ICU doesn't directly support it (but does support it
// through fallback, e.g. supporting "en-GB" indirectly using "en" support).
// Certain old-style language tags lack a script code, but in current usage
// they *would* include a script code. Map these over to modern forms.
for (const auto& mapping : js::intl::oldStyleLanguageTagMappings) {
const char* oldStyle = mapping.oldStyle;
const char* modernStyle = mapping.modernStyle;
LocaleHasher::Lookup lookup(modernStyle, strlen(modernStyle));
if (locales.has(lookup)) {
if (!addLocale(oldStyle, strlen(oldStyle)))
return false;
}
}
// Also forcibly provide the last-ditch locale.
{
const char* lastDitch = intl::LastDitchLocale();
MOZ_ASSERT(strcmp(lastDitch, "en-GB") == 0);
#ifdef DEBUG
static constexpr char lastDitchParent[] = "en";
LocaleHasher::Lookup lookup(lastDitchParent, strlen(lastDitchParent));
MOZ_ASSERT(locales.has(lookup),
"shouldn't be a need to add every locale implied by the "
"last-ditch locale, merely just the last-ditch locale");
#endif
if (!addLocale(lastDitch, strlen(lastDitch)))
return false;
}
return true;
}
#ifdef DEBUG
template <typename CountAvailable, typename GetAvailable>
static bool
IsSameAvailableLocales(CountAvailable countAvailable1,
GetAvailable getAvailable1,
CountAvailable countAvailable2,
GetAvailable getAvailable2)
{
int32_t count = countAvailable1();
if (count != countAvailable2()) {
return false;
}
for (int32_t i = 0; i < count; i++) {
if (getAvailable1(i) != getAvailable2(i)) {
return false;
}
}
return true;
}
#endif
bool
js::intl::SharedIntlData::ensureSupportedLocales(JSContext* cx)
{
if (supportedLocalesInitialized)
return true;
// If ensureSupportedLocales() was called previously, but didn't complete due
// to OOM, clear all data and start from scratch.
if (supportedLocales.initialized())
supportedLocales.finish();
if (collatorSupportedLocales.initialized())
collatorSupportedLocales.finish();
if (!supportedLocales.init() ||
!collatorSupportedLocales.init()) {
ReportOutOfMemory(cx);
return false;
}
if (!getAvailableLocales(cx, supportedLocales, uloc_countAvailable, uloc_getAvailable))
return false;
if (!getAvailableLocales(cx, collatorSupportedLocales, ucol_countAvailable, ucol_getAvailable))
return false;
MOZ_ASSERT(IsSameAvailableLocales(uloc_countAvailable, uloc_getAvailable,
udat_countAvailable, udat_getAvailable));
MOZ_ASSERT(IsSameAvailableLocales(uloc_countAvailable, uloc_getAvailable,
unum_countAvailable, unum_getAvailable));
MOZ_ASSERT(!supportedLocalesInitialized, "ensureSupportedLocales is neither reentrant nor thread-safe");
supportedLocalesInitialized = true;
return true;
}
bool
js::intl::SharedIntlData::isSupportedLocale(JSContext* cx,
SupportedLocaleKind kind,
HandleString locale,
bool* supported)
{
if (!ensureSupportedLocales(cx))
return false;
RootedLinearString localeLinear(cx, locale->ensureLinear(cx));
if (!localeLinear)
return false;
LocaleHasher::Lookup lookup(localeLinear);
switch (kind) {
case SupportedLocaleKind::Collator:
*supported = collatorSupportedLocales.has(lookup);
return true;
case SupportedLocaleKind::DateTimeFormat:
case SupportedLocaleKind::NumberFormat:
case SupportedLocaleKind::PluralRules:
case SupportedLocaleKind::RelativeTimeFormat:
*supported = supportedLocales.has(lookup);
return true;
}
MOZ_CRASH("Invalid Intl constructor");
return true;
}
bool
js::intl::SharedIntlData::ensureUpperCaseFirstLocales(JSContext* cx)
{
if (upperCaseFirstInitialized)
return true;
// If ensureUpperCaseFirstLocales() was called previously, but didn't
// complete due to OOM, clear all data and start from scratch.
if (upperCaseFirstLocales.initialized())
upperCaseFirstLocales.finish();
if (!upperCaseFirstLocales.init()) {
ReportOutOfMemory(cx);
return false;
}
UErrorCode status = U_ZERO_ERROR;
UEnumeration* available = ucol_openAvailableLocales(&status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
ScopedICUObject<UEnumeration, uenum_close> toClose(available);
RootedAtom locale(cx);
while (true) {
int32_t size;
const char* rawLocale = uenum_next(available, &size, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
if (rawLocale == nullptr)
break;
UCollator* collator = ucol_open(rawLocale, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
ScopedICUObject<UCollator, ucol_close> toCloseCollator(collator);
UColAttributeValue caseFirst = ucol_getAttribute(collator, UCOL_CASE_FIRST, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
if (caseFirst != UCOL_UPPER_FIRST)
continue;
MOZ_ASSERT(size >= 0);
locale = Atomize(cx, rawLocale, size_t(size));
if (!locale)
return false;
LocaleHasher::Lookup lookup(locale);
LocaleSet::AddPtr p = upperCaseFirstLocales.lookupForAdd(lookup);
// ICU shouldn't report any duplicate locales, but if it does, just
// ignore the duplicated locale.
if (!p && !upperCaseFirstLocales.add(p, locale)) {
ReportOutOfMemory(cx);
return false;
}
}
MOZ_ASSERT(!upperCaseFirstInitialized,
"ensureUpperCaseFirstLocales is neither reentrant nor thread-safe");
upperCaseFirstInitialized = true;
return true;
}
bool
js::intl::SharedIntlData::isUpperCaseFirst(JSContext* cx, HandleString locale, bool* isUpperFirst)
{
if (!ensureUpperCaseFirstLocales(cx))
return false;
RootedLinearString localeLinear(cx, locale->ensureLinear(cx));
if (!localeLinear)
return false;
LocaleHasher::Lookup lookup(localeLinear);
*isUpperFirst = upperCaseFirstLocales.has(lookup);
return true;
}
void
js::intl::SharedIntlData::destroyInstance()
{
availableTimeZones.finish();
ianaZonesTreatedAsLinksByICU.finish();
ianaLinksCanonicalizedDifferentlyByICU.finish();
supportedLocales.finish();
collatorSupportedLocales.finish();
upperCaseFirstLocales.finish();
}
void
@ -288,6 +580,9 @@ js::intl::SharedIntlData::trace(JSTracer* trc)
availableTimeZones.trace(trc);
ianaZonesTreatedAsLinksByICU.trace(trc);
ianaLinksCanonicalizedDifferentlyByICU.trace(trc);
supportedLocales.trace(trc);
collatorSupportedLocales.trace(trc);
upperCaseFirstLocales.trace(trc);
}
}
@ -296,5 +591,8 @@ js::intl::SharedIntlData::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf
{
return availableTimeZones.sizeOfExcludingThis(mallocSizeOf) +
ianaZonesTreatedAsLinksByICU.sizeOfExcludingThis(mallocSizeOf) +
ianaLinksCanonicalizedDifferentlyByICU.sizeOfExcludingThis(mallocSizeOf);
ianaLinksCanonicalizedDifferentlyByICU.sizeOfExcludingThis(mallocSizeOf) +
supportedLocales.sizeOfExcludingThis(mallocSizeOf) +
collatorSupportedLocales.sizeOfExcludingThis(mallocSizeOf) +
upperCaseFirstLocales.sizeOfExcludingThis(mallocSizeOf);
}

View file

@ -30,6 +30,34 @@ namespace intl {
*/
class SharedIntlData
{
struct LinearStringLookup
{
union {
const JS::Latin1Char* latin1Chars;
const char16_t* twoByteChars;
};
bool isLatin1;
size_t length;
JS::AutoCheckCannotGC nogc;
HashNumber hash = 0;
explicit LinearStringLookup(JSLinearString* string)
: isLatin1(string->hasLatin1Chars()), length(string->length())
{
if (isLatin1)
latin1Chars = string->latin1Chars(nogc);
else
twoByteChars = string->twoByteChars(nogc);
}
LinearStringLookup(const char* chars, size_t length)
: isLatin1(true), length(length)
{
latin1Chars = reinterpret_cast<const JS::Latin1Char*>(chars);
}
};
private:
/**
* Information tracking the set of the supported time zone names, derived
* from the IANA time zone database <https://www.iana.org/time-zones>.
@ -59,17 +87,8 @@ class SharedIntlData
struct TimeZoneHasher
{
struct Lookup
struct Lookup : LinearStringLookup
{
union {
const JS::Latin1Char* latin1Chars;
const char16_t* twoByteChars;
};
bool isLatin1;
size_t length;
JS::AutoCheckCannotGC nogc;
HashNumber hash;
explicit Lookup(JSFlatString* timeZone);
};
@ -148,7 +167,110 @@ class SharedIntlData
*/
bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
JS::MutableHandleString result);
private:
using Locale = JSAtom*;
struct LocaleHasher
{
struct Lookup : LinearStringLookup
{
explicit Lookup(JSLinearString* locale);
Lookup(const char* chars, size_t length);
};
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
static bool match(Locale key, const Lookup& lookup);
};
using LocaleSet = GCHashSet<Locale, LocaleHasher, SystemAllocPolicy>;
// Set of supported locales for all Intl service constructors except Collator,
// which uses its own set.
//
// UDateFormat:
// udat_[count,get]Available() return the same results as their
// uloc_[count,get]Available() counterparts.
//
// UNumberFormatter:
// unum_[count,get]Available() return the same results as their
// uloc_[count,get]Available() counterparts.
//
// UPluralRules and URelativeDateTimeFormatter:
// We're going to use ULocale availableLocales as per ICU recommendation:
// https://unicode-org.atlassian.net/browse/ICU-12756
LocaleSet supportedLocales;
// ucol_[count,get]Available() return different results compared to
// uloc_[count,get]Available(), we can't use |supportedLocales| here.
LocaleSet collatorSupportedLocales;
bool supportedLocalesInitialized = false;
// CountAvailable and GetAvailable describe the signatures used for ICU API
// to determine available locales for various functionality.
using CountAvailable = int32_t (*)();
using GetAvailable = const char* (*)(int32_t localeIndex);
static bool getAvailableLocales(JSContext* cx, LocaleSet& locales,
CountAvailable countAvailable,
GetAvailable getAvailable);
/**
* Precomputes the available locales sets.
*/
bool ensureSupportedLocales(JSContext* cx);
public:
enum class SupportedLocaleKind {
Collator,
DateTimeFormat,
NumberFormat,
PluralRules,
RelativeTimeFormat
};
/**
* Sets |supported| to true if |locale| is supported by the requested Intl
* service constructor. Otherwise sets |supported| to false.
*/
MOZ_MUST_USE bool isSupportedLocale(JSContext* cx, SupportedLocaleKind kind,
JS::Handle<JSString*> locale,
bool* supported);
private:
/**
* The case first parameter (BCP47 key "kf") allows to switch the order of
* upper- and lower-case characters. ICU doesn't directly provide an API
* to query the default case first value of a given locale, but instead
* requires to instantiate a collator object and then query the case first
* attribute (UCOL_CASE_FIRST).
* To avoid instantiating an additional collator object whenever we need
* to retrieve the default case first value of a specific locale, we
* compute the default case first value for every supported locale only
* once and then keep a list of all locales which don't use the default
* case first setting.
* There is almost no difference between lower-case first and when case
* first is disabled (UCOL_LOWER_FIRST resp. UCOL_OFF), so we only need to
* track locales which use upper-case first as their default setting.
*/
LocaleSet upperCaseFirstLocales;
bool upperCaseFirstInitialized = false;
/**
* Precomputes the available locales which use upper-case first sorting.
*/
bool ensureUpperCaseFirstLocales(JSContext* cx);
public:
/**
* Sets |isUpperFirst| to true if |locale| sorts upper-case characters
* before lower-case characters.
*/
bool isUpperCaseFirst(JSContext* cx, JS::HandleString locale, bool* isUpperFirst);
public:
void destroyInstance();
void trace(JSTracer* trc);

File diff suppressed because it is too large Load diff

View file

@ -254,7 +254,7 @@ GCRuntime::checkIncrementalZoneState(ExclusiveContext* cx, T* t)
return;
Zone* zone = cx->asJSContext()->zone();
MOZ_ASSERT_IF(t && zone->wasGCStarted() && (zone->isGCMarking() || zone->isGCSweeping()),
MOZ_ASSERT_IF(t && zone->wasGCStarted() && (zone->shouldMarkInZone() || zone->isGCSweeping()),
t->asTenured().arena()->allocatedDuringIncremental);
#endif
}

View file

@ -35,6 +35,12 @@ class MarkingValidator;
class AutoTraceSession;
struct MovingTracer;
enum IncrementalProgress
{
NotFinished = 0,
Finished
};
class ChunkPool
{
Chunk* head_;
@ -737,6 +743,8 @@ class GCRuntime
bool isShrinkingGC() const { return invocationKind == GC_SHRINK; }
static bool initializeSweepActions();
void setGrayRootsTracer(JSTraceDataOp traceOp, void* data);
MOZ_MUST_USE bool addBlackRootsTracer(JSTraceDataOp traceOp, void* data);
void removeBlackRootsTracer(JSTraceDataOp traceOp, void* data);
@ -863,12 +871,6 @@ class GCRuntime
static TenuredCell* refillFreeListInGC(Zone* zone, AllocKind thingKind);
private:
enum IncrementalProgress
{
NotFinished = 0,
Finished
};
// For ArenaLists::allocateFromArena()
friend class ArenaLists;
Chunk* pickChunk(const AutoLockGC& lock,
@ -951,7 +953,15 @@ class GCRuntime
void beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock);
bool shouldReleaseObservedTypes();
void endSweepingZoneGroup();
IncrementalProgress sweepPhase(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
IncrementalProgress performSweepActions(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
static IncrementalProgress sweepTypeInformation(GCRuntime* gc, FreeOp* fop, Zone* zone,
SliceBudget& budget, AllocKind kind);
static IncrementalProgress mergeSweptObjectArenas(GCRuntime* gc, FreeOp* fop, Zone* zone,
SliceBudget& budget, AllocKind kind);
static IncrementalProgress finalizeAllocKind(GCRuntime* gc, FreeOp* fop, Zone* zone,
SliceBudget& budget, AllocKind kind);
static IncrementalProgress sweepShapeTree(GCRuntime* gc, FreeOp* fop, Zone* zone,
SliceBudget& budget, AllocKind kind);
void endSweepPhase(bool lastGC, AutoLockForExclusiveAccess& lock);
void sweepZones(FreeOp* fop, bool lastGC);
void decommitAllWithoutUnlocking(const AutoLockGC& lock);
@ -1165,10 +1175,9 @@ class GCRuntime
*/
JS::Zone* zoneGroups;
JS::Zone* currentZoneGroup;
bool sweepingTypes;
unsigned finalizePhase;
size_t sweepPhaseIndex;
JS::Zone* sweepZone;
AllocKind sweepKind;
size_t sweepActionIndex;
bool abortSweepAfterCurrentGroup;
/*

View file

@ -308,7 +308,7 @@ ShouldMarkCrossCompartment(JSTracer* trc, JSObject* src, Cell* cell)
MOZ_ASSERT(!zone->isCollecting());
trc->runtime()->gc.setFoundBlackGrayEdges(tenured);
}
return zone->isGCMarking();
return zone->shouldMarkInZone();
} else {
if (zone->isGCMarkingBlack()) {
/*
@ -331,26 +331,26 @@ ShouldMarkCrossCompartment(JSTracer* trc, JSObject* src, const Value& val)
}
static void
AssertZoneIsMarking(Cell* thing)
AssertShouldMarkInZone(Cell* thing)
{
MOZ_ASSERT(TenuredCell::fromPointer(thing)->zone()->isGCMarking());
MOZ_ASSERT(thing->asTenured().zone()->shouldMarkInZone());
}
static void
AssertZoneIsMarking(JSString* str)
AssertShouldMarkInZone(JSString* str)
{
#ifdef DEBUG
Zone* zone = TenuredCell::fromPointer(str)->zone();
MOZ_ASSERT(zone->isGCMarking() || zone->isAtomsZone());
Zone* zone = str->asTenured().zone();
MOZ_ASSERT(zone->shouldMarkInZone() || zone->isAtomsZone());
#endif
}
static void
AssertZoneIsMarking(JS::Symbol* sym)
AssertShouldMarkInZone(JS::Symbol* sym)
{
#ifdef DEBUG
Zone* zone = TenuredCell::fromPointer(sym)->zone();
MOZ_ASSERT(zone->isGCMarking() || zone->isAtomsZone());
Zone* zone = sym->asTenured().zone();
MOZ_ASSERT(zone->shouldMarkInZone() || zone->isAtomsZone());
#endif
}
@ -730,7 +730,7 @@ GCMarker::markImplicitEdgesHelper(T markedThing)
return;
Zone* zone = gc::TenuredCell::fromPointer(markedThing)->zone();
MOZ_ASSERT(zone->isGCMarking());
MOZ_ASSERT(zone->shouldMarkInZone());
MOZ_ASSERT(!zone->isGCSweeping());
auto p = zone->gcWeakKeys.get(JS::GCCellPtr(markedThing));
@ -759,35 +759,35 @@ GCMarker::markImplicitEdges(T* thing)
template <typename T>
static inline bool
MustSkipMarking(GCMarker* gcmarker, T thing)
ShouldMark(GCMarker* gcmarker, T thing)
{
// Don't trace things that are owned by another runtime.
if (IsOwnedByOtherRuntime(gcmarker->runtime(), thing))
return true;
return false;
// Don't mark things outside a zone if we are in a per-zone GC.
return !thing->zone()->isGCMarking();
return thing->zone()->shouldMarkInZone();
}
template <>
bool
MustSkipMarking<JSObject*>(GCMarker* gcmarker, JSObject* obj)
ShouldMark<JSObject*>(GCMarker* gcmarker, JSObject* obj)
{
// Don't trace things that are owned by another runtime.
if (IsOwnedByOtherRuntime(gcmarker->runtime(), obj))
return true;
return false;
// We may mark a Nursery thing outside the context of the
// MinorCollectionTracer because of a pre-barrier. The pre-barrier is not
// needed in this case because we perform a minor collection before each
// incremental slice.
if (IsInsideNursery(obj))
return true;
return false;
// Don't mark things outside a zone if we are in a per-zone GC. It is
// faster to check our own arena, which we can do since we know that
// the object is tenured.
return !TenuredCell::fromPointer(obj)->zone()->isGCMarking();
return obj->asTenured().zone()->shouldMarkInZone();
}
template <typename T>
@ -795,7 +795,7 @@ void
DoMarking(GCMarker* gcmarker, T* thing)
{
// Do per-type marking precondition checks.
if (MustSkipMarking(gcmarker, thing))
if (!ShouldMark(gcmarker, thing))
return;
CheckTracedThing(gcmarker, thing);
@ -822,7 +822,7 @@ void
NoteWeakEdge(GCMarker* gcmarker, T** thingp)
{
// Do per-type marking precondition checks.
if (MustSkipMarking(gcmarker, *thingp))
if (!ShouldMark(gcmarker, *thingp))
return;
CheckTracedThing(gcmarker, *thingp);
@ -971,7 +971,7 @@ template <typename T>
bool
js::GCMarker::mark(T* thing)
{
AssertZoneIsMarking(thing);
AssertShouldMarkInZone(thing);
MOZ_ASSERT(!IsInsideNursery(gc::TenuredCell::fromPointer(thing)));
return gc::ParticipatesInCC<T>::value
? gc::TenuredCell::fromPointer(thing)->markIfUnmarked(markColor())
@ -1106,7 +1106,7 @@ JSString::traceBase(JSTracer* trc)
inline void
js::GCMarker::eagerlyMarkChildren(JSLinearString* linearStr)
{
AssertZoneIsMarking(linearStr);
AssertShouldMarkInZone(linearStr);
MOZ_ASSERT(linearStr->isMarked());
MOZ_ASSERT(linearStr->JSString::isLinear());
@ -1116,7 +1116,7 @@ js::GCMarker::eagerlyMarkChildren(JSLinearString* linearStr)
MOZ_ASSERT(linearStr->JSString::isLinear());
if (linearStr->isPermanentAtom())
break;
AssertZoneIsMarking(linearStr);
AssertShouldMarkInZone(linearStr);
if (!mark(static_cast<JSString*>(linearStr)))
break;
}
@ -1169,7 +1169,7 @@ js::GCMarker::eagerlyMarkChildren(JSRope* rope)
JS_DIAGNOSTICS_ASSERT(rope->getTraceKind() == JS::TraceKind::String);
JS_DIAGNOSTICS_ASSERT(rope->JSString::isRope());
AssertZoneIsMarking(rope);
AssertShouldMarkInZone(rope);
MOZ_ASSERT(rope->isMarked());
JSRope* next = nullptr;
@ -1649,7 +1649,7 @@ GCMarker::processMarkStackTop(SliceBudget& budget)
case ObjectTag: {
obj = reinterpret_cast<JSObject*>(addr);
AssertZoneIsMarking(obj);
AssertShouldMarkInZone(obj);
goto scan_obj;
}
@ -1712,7 +1712,7 @@ GCMarker::processMarkStackTop(SliceBudget& budget)
scan_obj:
{
AssertZoneIsMarking(obj);
AssertShouldMarkInZone(obj);
budget.step();
if (budget.isOverBudget()) {

View file

@ -232,11 +232,9 @@ struct Zone : public JS::shadow::Zone,
return rt->isHeapMajorCollecting() && !rt->gc.isHeapCompacting() && gcState_ != NoGC;
}
bool isGCMarking() {
if (runtimeFromMainThread()->isHeapCollecting())
return gcState_ == Mark || gcState_ == MarkGray;
else
return needsIncrementalBarrier();
bool shouldMarkInZone() const {
return needsIncrementalBarrier() ||
(gcState_ == Mark || gcState_ == MarkGray);
}
GCState gcState() const { return gcState_; }

View file

@ -13,5 +13,5 @@ evalcx(`
');
oomTest(() => eval('Array(..."")'));
if ('Intl' in this)
Intl.NumberFormat.prototype.format(0);
new Intl.NumberFormat().format(0);
`, newGlobal());

View file

@ -4,15 +4,8 @@ load(libdir + "asserts.js");
// any value except "best fit" or "lookup" is okay.
Object.prototype.localeMatcher = "invalid matcher option";
// The Intl API may not be available in the testing environment. Note that |hasOwnProperty("Intl")|
// initializes the Intl API if present, so this if-statement needs to appear after "localeMatcher"
// was added to Object.prototype.
// The Intl API may not be available in the testing environment.
if (this.hasOwnProperty("Intl")) {
// Intl prototypes are properly initialized despite changed Object.prototype.
Intl.Collator.prototype.compare("a", "b");
Intl.NumberFormat.prototype.format(10);
Intl.DateTimeFormat.prototype.format(new Date);
// Intl constructors no longer work properly, because "localeMatcher" defaults to the invalid
// value from Object.prototype. Except for Intl.DateTimeFormat, cf. ECMA-402 ToDateTimeOptions.
assertThrowsInstanceOf(() => new Intl.Collator(), RangeError);

View file

@ -15,6 +15,7 @@ for (var i = 0; i < 3; i++) {
x.toString();
assertEq(0, 1);
} catch (e) {
assertEq(e.message, "y is undefined");
assertEq(e.message === "y is undefined" ||
e.message === "undefined has no properties", true);
}
}

View file

@ -26,6 +26,12 @@
_(AtomicsXor) \
_(AtomicsIsLockFree) \
\
_(IntlIsCollator) \
_(IntlIsDateTimeFormat) \
_(IntlIsNumberFormat) \
_(IntlIsPluralRules) \
_(IntlIsRelativeTimeFormat) \
\
_(MathAbs) \
_(MathFloor) \
_(MathCeil) \

View file

@ -13,6 +13,11 @@
#include "builtin/SIMD.h"
#include "builtin/TestingFunctions.h"
#include "builtin/TypedObject.h"
#include "builtin/intl/Collator.h"
#include "builtin/intl/DateTimeFormat.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/intl/PluralRules.h"
#include "builtin/intl/RelativeTimeFormat.h"
#include "jit/BaselineInspector.h"
#include "jit/InlinableNatives.h"
#include "jit/IonBuilder.h"
@ -104,6 +109,18 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target)
case InlinableNative::AtomicsIsLockFree:
return inlineAtomicsIsLockFree(callInfo);
// Intl natives.
case InlinableNative::IntlIsCollator:
return inlineHasClass(callInfo, &CollatorObject::class_);
case InlinableNative::IntlIsDateTimeFormat:
return inlineHasClass(callInfo, &DateTimeFormatObject::class_);
case InlinableNative::IntlIsNumberFormat:
return inlineHasClass(callInfo, &NumberFormatObject::class_);
case InlinableNative::IntlIsPluralRules:
return inlineHasClass(callInfo, &PluralRulesObject::class_);
case InlinableNative::IntlIsRelativeTimeFormat:
return inlineHasClass(callInfo, &RelativeTimeFormatObject::class_);
// Math natives.
case InlinableNative::MathAbs:
return inlineMathAbs(callInfo);

View file

@ -484,10 +484,10 @@ MSG_DEF(JSMSG_DEBUG_PROMISE_NOT_REJECTED, 0, JSEXN_TYPEERR, "Promise hasn't been
MSG_DEF(JSMSG_TRACELOGGER_ENABLE_FAIL, 1, JSEXN_ERR, "enabling tracelogger failed: {0}")
// Intl
MSG_DEF(JSMSG_DATE_NOT_FINITE, 0, JSEXN_RANGEERR, "date value is not finite in DateTimeFormat.format()")
MSG_DEF(JSMSG_DATE_NOT_FINITE, 2, JSEXN_RANGEERR, "date value is not finite in {0}.{1}()")
MSG_DEF(JSMSG_DUPLICATE_VARIANT_SUBTAG, 1, JSEXN_RANGEERR, "duplicate variant subtag: {0}")
MSG_DEF(JSMSG_INTERNAL_INTL_ERROR, 0, JSEXN_ERR, "internal error while computing Intl data")
MSG_DEF(JSMSG_INTL_OBJECT_NOT_INITED, 3, JSEXN_TYPEERR, "Intl.{0}.prototype.{1} called on value that's not an object initialized as a {2}")
MSG_DEF(JSMSG_INTL_OBJECT_REINITED, 0, JSEXN_TYPEERR, "can't initialize object twice as an object of an Intl constructor")
MSG_DEF(JSMSG_INVALID_CURRENCY_CODE, 1, JSEXN_RANGEERR, "invalid currency code in NumberFormat(): {0}")
MSG_DEF(JSMSG_INVALID_DIGITS_VALUE, 1, JSEXN_RANGEERR, "invalid digits value: {0}")
MSG_DEF(JSMSG_INVALID_KEYS_TYPE, 0, JSEXN_TYPEERR, "calendar info keys must be an object or undefined")
@ -497,6 +497,8 @@ MSG_DEF(JSMSG_INVALID_LOCALES_ELEMENT, 0, JSEXN_TYPEERR, "invalid element in loc
MSG_DEF(JSMSG_INVALID_LOCALE_MATCHER, 1, JSEXN_RANGEERR, "invalid locale matcher in supportedLocalesOf(): {0}")
MSG_DEF(JSMSG_INVALID_OPTION_VALUE, 2, JSEXN_RANGEERR, "invalid value {1} for option {0}")
MSG_DEF(JSMSG_INVALID_TIME_ZONE, 1, JSEXN_RANGEERR, "invalid time zone in DateTimeFormat(): {0}")
MSG_DEF(JSMSG_INVALID_DATETIME_OPTION, 2, JSEXN_TYPEERR, "can't set option {0} when {1} is used")
MSG_DEF(JSMSG_INVALID_DATETIME_STYLE, 2, JSEXN_TYPEERR, "can't set option {0} in Date.{1}()")
MSG_DEF(JSMSG_UNDEFINED_CURRENCY, 0, JSEXN_TYPEERR, "undefined currency in NumberFormat() with currency style")
// RegExp

View file

@ -5327,8 +5327,8 @@ JS_ResetDefaultLocale(JSContext* cx);
* Locale specific string conversion and error message callbacks.
*/
struct JSLocaleCallbacks {
JSLocaleToUpperCase localeToUpperCase;
JSLocaleToLowerCase localeToLowerCase;
JSLocaleToUpperCase localeToUpperCase; // not used
JSLocaleToLowerCase localeToLowerCase; // not used
JSLocaleCompare localeCompare; // not used
JSLocaleToUnicode localeToUnicode;
};

View file

@ -245,6 +245,13 @@ js::ReportOutOfMemory(ExclusiveContext* cxArg)
cx->setPendingException(oomMessage, nullptr);
}
mozilla::GenericErrorResult<OOM&>
js::ReportOutOfMemoryResult(ExclusiveContext* cx)
{
ReportOutOfMemory(cx);
return cx->alreadyReportedOOM();
}
void
js::ReportOverRecursed(JSContext* maybecx, unsigned errorNumber)
{
@ -1009,6 +1016,34 @@ ExclusiveContext::recoverFromOutOfMemory()
task->outOfMemory = false;
}
JS::Error ExclusiveContext::reportedError;
JS::OOM ExclusiveContext::reportedOOM;
mozilla::GenericErrorResult<OOM&>
ExclusiveContext::alreadyReportedOOM()
{
#ifdef DEBUG
if (JSContext* maybecx = maybeJSContext()) {
MOZ_ASSERT(maybecx->isThrowingOutOfMemory());
} else {
// Keep in sync with addPendingOutOfMemory.
if (ParseTask* task = helperThread()->parseTask())
MOZ_ASSERT(task->outOfMemory);
}
#endif
return mozilla::MakeGenericErrorResult(reportedOOM);
}
mozilla::GenericErrorResult<JS::Error&>
ExclusiveContext::alreadyReportedError()
{
#ifdef DEBUG
if (JSContext* maybecx = maybeJSContext())
MOZ_ASSERT(maybecx->isExceptionPending());
#endif
return mozilla::MakeGenericErrorResult(reportedError);
}
JSContext::JSContext(JSRuntime* parentRuntime)
: ExclusiveContext(this, &this->JSRuntime::mainThread, Context_JS, JS::ContextOptions()),
JSRuntime(parentRuntime),

View file

@ -12,6 +12,7 @@
#include "js/CharacterEncoding.h"
#include "js/GCVector.h"
#include "js/Result.h"
#include "js/Utility.h"
#include "js/Vector.h"
#include "vm/Caches.h"
@ -314,6 +315,30 @@ class ExclusiveContext : public ContextFriendFields,
bool addPendingCompileError(frontend::CompileError** err);
void addPendingOverRecursed();
void addPendingOutOfMemory();
private:
static JS::Error reportedError;
static JS::OOM reportedOOM;
public:
inline JS::Result<> boolToResult(bool ok);
/**
* Intentionally awkward signpost method that is stationed on the
* boundary between Result-using and non-Result-using code.
*/
template <typename V, typename E>
bool resultToBool(JS::Result<V, E> result) {
return result.isOk();
}
template <typename V, typename E>
V* resultToPtr(JS::Result<V*, E> result) {
return result.isOk() ? result.unwrap() : nullptr;
}
mozilla::GenericErrorResult<JS::OOM&> alreadyReportedOOM();
mozilla::GenericErrorResult<JS::Error&> alreadyReportedError();
};
void ReportOverRecursed(JSContext* cx, unsigned errorNumber);
@ -340,6 +365,7 @@ struct JSContext : public js::ExclusiveContext,
using ExclusiveContext::permanentAtoms;
using ExclusiveContext::pod_calloc;
using ExclusiveContext::pod_malloc;
using ExclusiveContext::pod_realloc;
using ExclusiveContext::staticStrings;
using ExclusiveContext::updateMallocCounter;
using ExclusiveContext::wellKnownSymbols;
@ -490,7 +516,7 @@ struct JSContext : public js::ExclusiveContext,
}
public:
bool isExceptionPending() {
bool isExceptionPending() const {
return throwing;
}
@ -540,6 +566,17 @@ struct JSContext : public js::ExclusiveContext,
namespace js {
inline JS::Result<>
ExclusiveContext::boolToResult(bool ok)
{
if (MOZ_LIKELY(ok)) {
MOZ_ASSERT_IF(isJSContext(), !asJSContext()->isExceptionPending());
MOZ_ASSERT_IF(isJSContext(), !asJSContext()->isPropagatingForcedReturn());
return JS::Ok();
}
return JS::Result<>(reportedError);
}
struct MOZ_RAII AutoResolving {
public:
enum Kind {

View file

@ -2862,6 +2862,20 @@ ToWindowProxyIfWindow(JSObject* obj);
extern JS_FRIEND_API(JSObject*)
ToWindowIfWindowProxy(JSObject* obj);
/*
* This custom date/time formatter constructor gives users the ability
* to specify a custom format pattern. This pattern is passed *directly*
* to ICU with NO SYNTAX PARSING OR VALIDATION WHATSOEVER. ICU appears to
* have a a modicum of testing of this, and it won't fall over completely
* if passed bad input. But the current behavior is entirely under-specified
* and emphatically not shippable on the web, and it *must* be fixed before
* this functionality can be exposed in the real world. (There are also some
* questions about whether the format exposed here is the *right* one to
* standardize, that will also need to be resolved to ship this.)
*/
extern bool
AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
} /* namespace js */
class NativeProfiler

View file

@ -384,6 +384,42 @@ static const FinalizePhase BackgroundFinalizePhases[] = {
}
};
// Incremental sweeping is controlled by a list of actions that describe what
// happens and in what order. Due to the incremental nature of sweeping an
// action does not necessarily run to completion so the current state is tracked
// in the GCRuntime by the performSweepActions() method.
//
// Actions are performed in phases run per sweep group, and each action is run
// for every zone in the group, i.e. as if by the following pseudocode:
//
// for each sweep group:
// for each phase:
// for each zone in sweep group:
// for each action in phase:
// perform_action
struct SweepAction
{
using Func = IncrementalProgress (*)(GCRuntime* gc, FreeOp* fop, Zone* zone,
SliceBudget& budget, AllocKind kind);
Func func;
AllocKind kind;
SweepAction(Func func, AllocKind kind) : func(func), kind(kind) {}
};
using SweepActionVector = Vector<SweepAction, 0, SystemAllocPolicy>;
using SweepPhaseVector = Vector<SweepActionVector, 0, SystemAllocPolicy>;
static SweepPhaseVector SweepPhases;
bool
js::gc::InitializeStaticData()
{
return GCRuntime::initializeSweepActions();
}
template<>
JSObject*
ArenaCellIterImpl::get<JSObject>() const
@ -841,8 +877,9 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
zoneGroupIndex(0),
zoneGroups(nullptr),
currentZoneGroup(nullptr),
sweepPhaseIndex(0),
sweepZone(nullptr),
sweepKind(AllocKind::FIRST),
sweepActionIndex(0),
abortSweepAfterCurrentGroup(false),
arenasAllocatedDuringSweep(nullptr),
startedCompacting(false),
@ -883,7 +920,7 @@ static const uint64_t JIT_SCRIPT_RELEASE_TYPES_PERIOD = 20;
bool
GCRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
{
InitMemorySubsystem();
MOZ_ASSERT(SystemPageSize());
if (!rootsHash.init(256))
return false;
@ -3915,7 +3952,7 @@ struct AddOutgoingEdgeFunctor {
*/
if (needsEdge_) {
JS::Zone* zone = other.zone();
if (zone->isGCMarking())
if (zone->shouldMarkInZone())
finder_.addEdgeTo(zone);
}
}
@ -3946,14 +3983,14 @@ Zone::findOutgoingEdges(ZoneComponentFinder& finder)
*/
JSRuntime* rt = runtimeFromMainThread();
Zone* atomsZone = rt->atomsCompartment(finder.lock)->zone();
if (atomsZone->isGCMarking())
if (atomsZone->shouldMarkInZone())
finder.addEdgeTo(atomsZone);
for (CompartmentsInZoneIter comp(this); !comp.done(); comp.next())
comp->findOutgoingEdges(finder);
for (ZoneSet::Range r = gcZoneGroupEdges.all(); !r.empty(); r.popFront()) {
if (r.front()->isGCMarking())
if (r.front()->shouldMarkInZone())
finder.addEdgeTo(r.front());
}
@ -3995,7 +4032,7 @@ GCRuntime::findZoneGroups(AutoLockForExclusiveAccess& lock)
finder.useOneComponent();
for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
MOZ_ASSERT(zone->isGCMarking());
MOZ_ASSERT(zone->shouldMarkInZone());
finder.addNode(zone);
}
zoneGroups = finder.getResultsList();
@ -4008,7 +4045,7 @@ GCRuntime::findZoneGroups(AutoLockForExclusiveAccess& lock)
#ifdef DEBUG
for (Zone* head = currentZoneGroup; head; head = head->nextGroup()) {
for (Zone* zone = head; zone; zone = zone->nextNodeInGroup())
MOZ_ASSERT(zone->isGCMarking());
MOZ_ASSERT(zone->shouldMarkInZone());
}
MOZ_ASSERT_IF(!isIncremental, !currentZoneGroup->nextGroup());
@ -4031,7 +4068,7 @@ GCRuntime::getNextZoneGroup()
}
for (Zone* zone = currentZoneGroup; zone; zone = zone->nextNodeInGroup()) {
MOZ_ASSERT(zone->isGCMarking());
MOZ_ASSERT(zone->shouldMarkInZone());
MOZ_ASSERT(!zone->isQueuedForBackgroundSweep());
}
@ -4042,7 +4079,7 @@ GCRuntime::getNextZoneGroup()
MOZ_ASSERT(!isIncremental);
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
MOZ_ASSERT(!zone->gcNextGraphComponent);
MOZ_ASSERT(zone->isGCMarking());
MOZ_ASSERT(zone->shouldMarkInZone());
zone->setNeedsIncrementalBarrier(false, Zone::UpdateJit);
zone->setGCState(Zone::NoGC);
zone->gcGrayRoots.clearAndFree();
@ -4485,7 +4522,7 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
bool sweepingAtoms = false;
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
/* Set the GC state to sweeping. */
MOZ_ASSERT(zone->isGCMarking());
MOZ_ASSERT(zone->shouldMarkInZone());
zone->setGCState(Zone::Sweep);
/* Purge the ArenaLists before sweeping. */
@ -4667,11 +4704,9 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
zone->arenas.queueForegroundThingsForSweep(&fop);
}
sweepingTypes = true;
finalizePhase = 0;
sweepPhaseIndex = 0;
sweepZone = currentZoneGroup;
sweepKind = AllocKind::FIRST;
sweepActionIndex = 0;
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_END);
@ -4765,7 +4800,7 @@ ArenaLists::foregroundFinalize(FreeOp* fop, AllocKind thingKind, SliceBudget& sl
return true;
}
GCRuntime::IncrementalProgress
IncrementalProgress
GCRuntime::drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase)
{
/* Run a marking slice and return whether the stack is now empty. */
@ -4810,105 +4845,160 @@ SweepArenaList(Arena** arenasToSweep, SliceBudget& sliceBudget, Args... args)
return true;
}
GCRuntime::IncrementalProgress
GCRuntime::sweepPhase(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock)
/* static */ IncrementalProgress
GCRuntime::sweepTypeInformation(GCRuntime* gc, FreeOp* fop, Zone* zone, SliceBudget& budget,
AllocKind kind)
{
// Sweep dead type information stored in scripts and object groups, but
// don't finalize them yet. We have to sweep dead information from both live
// and dead scripts and object groups, so that no dead references remain in
// them. Type inference can end up crawling these zones again, such as for
// TypeCompartment::markSetsUnknown, and if this happens after sweeping for
// the sweep group finishes we won't be able to determine which things in
// the zone are live.
MOZ_ASSERT(kind == AllocKind::LIMIT);
gcstats::AutoPhase ap1(gc->stats, gcstats::PHASE_SWEEP_COMPARTMENTS);
gcstats::AutoPhase ap2(gc->stats, gcstats::PHASE_SWEEP_TYPES);
ArenaLists& al = zone->arenas;
AutoClearTypeInferenceStateOnOOM oom(zone);
if (!SweepArenaList<JSScript>(&al.gcScriptArenasToUpdate, budget, &oom))
return NotFinished;
if (!SweepArenaList<ObjectGroup>(&al.gcObjectGroupArenasToUpdate, budget, &oom))
return NotFinished;
// Finish sweeping type information in the zone.
{
gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_SWEEP_TYPES_END);
zone->types.endSweep(gc->rt);
}
return Finished;
}
/* static */ IncrementalProgress
GCRuntime::mergeSweptObjectArenas(GCRuntime* gc, FreeOp* fop, Zone* zone, SliceBudget& budget,
AllocKind kind)
{
// Foreground finalized objects have already been finalized, and now their
// arenas can be reclaimed by freeing empty ones and making non-empty ones
// available for allocation.
MOZ_ASSERT(kind == AllocKind::LIMIT);
zone->arenas.mergeForegroundSweptObjectArenas();
return Finished;
}
/* static */ IncrementalProgress
GCRuntime::finalizeAllocKind(GCRuntime* gc, FreeOp* fop, Zone* zone, SliceBudget& budget,
AllocKind kind)
{
// Set the number of things per arena for this AllocKind.
size_t thingsPerArena = Arena::thingsPerArena(kind);
auto& sweepList = gc->incrementalSweepList;
sweepList.setThingsPerArena(thingsPerArena);
if (!zone->arenas.foregroundFinalize(fop, kind, budget, sweepList))
return NotFinished;
// Reset the slots of the sweep list that we used.
sweepList.reset(thingsPerArena);
return Finished;
}
/* static */ IncrementalProgress
GCRuntime::sweepShapeTree(GCRuntime* gc, FreeOp* fop, Zone* zone, SliceBudget& budget,
AllocKind kind)
{
// Remove dead shapes from the shape tree, but don't finalize them yet.
MOZ_ASSERT(kind == AllocKind::LIMIT);
gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_SWEEP_SHAPE);
ArenaLists& al = zone->arenas;
if (!SweepArenaList<Shape>(&al.gcShapeArenasToUpdate, budget))
return NotFinished;
if (!SweepArenaList<AccessorShape>(&al.gcAccessorShapeArenasToUpdate, budget))
return NotFinished;
return Finished;
}
static void
AddSweepPhase(bool* ok)
{
if (*ok)
*ok = SweepPhases.emplaceBack();
}
static void
AddSweepAction(bool* ok, SweepAction::Func func, AllocKind kind = AllocKind::LIMIT)
{
if (*ok)
*ok = SweepPhases.back().emplaceBack(func, kind);
}
/* static */ bool
GCRuntime::initializeSweepActions()
{
bool ok = true;
AddSweepPhase(&ok);
AddSweepAction(&ok, GCRuntime::sweepTypeInformation);
AddSweepAction(&ok, GCRuntime::mergeSweptObjectArenas);
for (const auto& finalizePhase : IncrementalFinalizePhases) {
AddSweepPhase(&ok);
for (auto kind : finalizePhase.kinds)
AddSweepAction(&ok, GCRuntime::finalizeAllocKind, kind);
}
AddSweepPhase(&ok);
AddSweepAction(&ok, GCRuntime::sweepShapeTree);
return ok;
}
IncrementalProgress
GCRuntime::performSweepActions(SliceBudget& budget, AutoLockForExclusiveAccess& lock)
{
AutoSetThreadIsSweeping threadIsSweeping;
gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP);
FreeOp fop(rt);
if (drainMarkStack(sliceBudget, gcstats::PHASE_SWEEP_MARK) == NotFinished)
if (drainMarkStack(budget, gcstats::PHASE_SWEEP_MARK) == NotFinished)
return NotFinished;
for (;;) {
// Sweep dead type information stored in scripts and object groups, but
// don't finalize them yet. We have to sweep dead information from both
// live and dead scripts and object groups, so that no dead references
// remain in them. Type inference can end up crawling these zones
// again, such as for TypeCompartment::markSetsUnknown, and if this
// happens after sweeping for the zone group finishes we won't be able
// to determine which things in the zone are live.
if (sweepingTypes) {
gcstats::AutoPhase ap1(stats, gcstats::PHASE_SWEEP_COMPARTMENTS);
gcstats::AutoPhase ap2(stats, gcstats::PHASE_SWEEP_TYPES);
for (; sweepPhaseIndex < SweepPhases.length(); sweepPhaseIndex++) {
const auto& actions = SweepPhases[sweepPhaseIndex];
for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) {
ArenaLists& al = sweepZone->arenas;
AutoClearTypeInferenceStateOnOOM oom(sweepZone);
if (!SweepArenaList<JSScript>(&al.gcScriptArenasToUpdate, sliceBudget, &oom))
return NotFinished;
if (!SweepArenaList<ObjectGroup>(
&al.gcObjectGroupArenasToUpdate, sliceBudget, &oom))
{
return NotFinished;
}
// Finish sweeping type information in the zone.
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_TYPES_END);
sweepZone->types.endSweep(rt);
}
// Foreground finalized objects have already been finalized,
// and now their arenas can be reclaimed by freeing empty ones
// and making non-empty ones available for allocation.
al.mergeForegroundSweptObjectArenas();
}
sweepZone = currentZoneGroup;
sweepingTypes = false;
}
/* Finalize foreground finalized things. */
for (; finalizePhase < ArrayLength(IncrementalFinalizePhases) ; ++finalizePhase) {
gcstats::AutoPhase ap(stats, IncrementalFinalizePhases[finalizePhase].statsPhase);
for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) {
Zone* zone = sweepZone;
for (auto kind : SomeAllocKinds(sweepKind, AllocKind::LIMIT)) {
if (!IncrementalFinalizePhases[finalizePhase].kinds.contains(kind))
continue;
/* Set the number of things per arena for this AllocKind. */
size_t thingsPerArena = Arena::thingsPerArena(kind);
incrementalSweepList.setThingsPerArena(thingsPerArena);
if (!zone->arenas.foregroundFinalize(&fop, kind, sliceBudget,
incrementalSweepList))
{
sweepKind = kind;
for (; sweepActionIndex < actions.length(); sweepActionIndex++) {
const auto& action = actions[sweepActionIndex];
if (action.func(this, &fop, sweepZone, budget, action.kind) == NotFinished)
return NotFinished;
}
/* Reset the slots of the sweep list that we used. */
incrementalSweepList.reset(thingsPerArena);
}
sweepKind = AllocKind::FIRST;
// Reset action index to first.
sweepActionIndex = 0;
}
sweepZone = currentZoneGroup;
}
/* Remove dead shapes from the shape tree, but don't finalize them yet. */
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_SHAPE);
for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) {
ArenaLists& al = sweepZone->arenas;
if (!SweepArenaList<Shape>(&al.gcShapeArenasToUpdate, sliceBudget))
return NotFinished;
if (!SweepArenaList<AccessorShape>(&al.gcAccessorShapeArenasToUpdate, sliceBudget))
return NotFinished;
}
}
// Reset phase index.
sweepPhaseIndex = 0;
endSweepingZoneGroup();
getNextZoneGroup();
if (!currentZoneGroup)
@ -4999,7 +5089,7 @@ GCRuntime::beginCompactPhase()
startedCompacting = true;
}
GCRuntime::IncrementalProgress
IncrementalProgress
GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
AutoLockForExclusiveAccess& lock)
{
@ -5141,7 +5231,7 @@ GCRuntime::resetIncrementalGC(gc::AbortReason reason, AutoLockForExclusiveAccess
ResetGrayList(c);
for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
MOZ_ASSERT(zone->isGCMarking());
MOZ_ASSERT(zone->shouldMarkInZone());
zone->setNeedsIncrementalBarrier(false, Zone::UpdateJit);
zone->setGCState(Zone::NoGC);
}
@ -5265,7 +5355,7 @@ AutoGCSlice::AutoGCSlice(JSRuntime* rt)
* is expensive) because Ion code doesn't run during GC. If need be,
* we'll update the Ion barriers in ~AutoGCSlice.
*/
if (zone->isGCMarking()) {
if (zone->shouldMarkInZone()) {
MOZ_ASSERT(zone->needsIncrementalBarrier());
zone->setNeedsIncrementalBarrier(false, Zone::DontUpdateJit);
} else {
@ -5278,7 +5368,7 @@ AutoGCSlice::~AutoGCSlice()
{
/* We can't use GCZonesIter if this is the end of the last slice. */
for (ZonesIter zone(runtime, WithAtoms); !zone.done(); zone.next()) {
if (zone->isGCMarking()) {
if (zone->shouldMarkInZone()) {
zone->setNeedsIncrementalBarrier(true, Zone::UpdateJit);
zone->arenas.purge();
} else {
@ -5381,7 +5471,7 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
MOZ_FALLTHROUGH;
case State::Sweep:
if (sweepPhase(budget, lock) == NotFinished)
if (performSweepActions(budget, lock) == NotFinished)
break;
endSweepPhase(destroyingRuntime, lock);

View file

@ -838,6 +838,9 @@ class ArenaLists
/* The number of GC cycles an empty chunk can survive before been released. */
const size_t MAX_EMPTY_CHUNK_AGE = 4;
extern bool
InitializeStaticData();
} /* namespace gc */
class InterpreterFrame;

View file

@ -553,10 +553,10 @@ NewPropertyIteratorObject(JSContext* cx, unsigned flags)
if (!shape)
return nullptr;
JSObject* obj = JSObject::create(cx, ITERATOR_FINALIZE_KIND,
GetInitialHeap(GenericObject, clasp), shape, group);
if (!obj)
return nullptr;
JSObject* obj;
JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, ITERATOR_FINALIZE_KIND,
GetInitialHeap(GenericObject, clasp),
shape, group));
PropertyIteratorObject* res = &obj->as<PropertyIteratorObject>();

View file

@ -257,15 +257,15 @@ js::Throw(JSContext* cx, JSObject* obj, unsigned errorNumber)
/*** PropertyDescriptor operations and DefineProperties ******************************************/
bool
static Result<>
CheckCallable(JSContext* cx, JSObject* obj, const char* fieldName)
{
if (obj && !obj->isCallable()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD,
fieldName);
return false;
return cx->alreadyReportedError();
}
return true;
return Ok();
}
bool
@ -335,8 +335,8 @@ js::ToPropertyDescriptor(JSContext* cx, HandleValue descval, bool checkAccessors
hasGetOrSet = found;
if (found) {
if (v.isObject()) {
if (checkAccessors && !CheckCallable(cx, &v.toObject(), js_getter_str))
return false;
if (checkAccessors)
JS_TRY_OR_RETURN_FALSE(cx, CheckCallable(cx, &v.toObject(), js_getter_str));
desc.setGetterObject(&v.toObject());
} else if (!v.isUndefined()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD,
@ -353,8 +353,8 @@ js::ToPropertyDescriptor(JSContext* cx, HandleValue descval, bool checkAccessors
hasGetOrSet |= found;
if (found) {
if (v.isObject()) {
if (checkAccessors && !CheckCallable(cx, &v.toObject(), js_setter_str))
return false;
if (checkAccessors)
JS_TRY_OR_RETURN_FALSE(cx, CheckCallable(cx, &v.toObject(), js_setter_str));
desc.setSetterObject(&v.toObject());
} else if (!v.isUndefined()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD,
@ -381,18 +381,16 @@ js::ToPropertyDescriptor(JSContext* cx, HandleValue descval, bool checkAccessors
return true;
}
bool
Result<>
js::CheckPropertyDescriptorAccessors(JSContext* cx, Handle<PropertyDescriptor> desc)
{
if (desc.hasGetterObject()) {
if (!CheckCallable(cx, desc.getterObject(), js_getter_str))
return false;
}
if (desc.hasSetterObject()) {
if (!CheckCallable(cx, desc.setterObject(), js_setter_str))
return false;
}
return true;
if (desc.hasGetterObject())
MOZ_TRY(CheckCallable(cx, desc.getterObject(), js_getter_str));
if (desc.hasSetterObject())
MOZ_TRY(CheckCallable(cx, desc.setterObject(), js_setter_str));
return Ok();
}
void
@ -646,9 +644,8 @@ NewObject(ExclusiveContext* cx, HandleObjectGroup group, gc::AllocKind kind,
return nullptr;
gc::InitialHeap heap = GetInitialHeap(newKind, clasp);
JSObject* obj = JSObject::create(cx, kind, heap, shape, group);
if (!obj)
return nullptr;
JSObject* obj;
JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, heap, shape, group));
if (newKind == SingletonObject) {
RootedObject nobj(cx, obj);

View file

@ -179,11 +179,9 @@ class JSObject : public js::gc::Cell
* Make a non-array object with the specified initial state. This method
* takes ownership of any extantSlots it is passed.
*/
static inline JSObject* create(js::ExclusiveContext* cx,
js::gc::AllocKind kind,
js::gc::InitialHeap heap,
js::HandleShape shape,
js::HandleObjectGroup group);
static inline JS::Result<JSObject*, JS::OOM&>
create(js::ExclusiveContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
js::HandleShape shape, js::HandleObjectGroup group);
// Set the initial slots and elements of an object. These pointers are only
// valid for native objects, but during initialization are set for all
@ -1173,7 +1171,7 @@ ToPropertyDescriptor(JSContext* cx, HandleValue descval, bool checkAccessors,
* callable. This performs exactly the checks omitted by ToPropertyDescriptor
* when checkAccessors is false.
*/
bool
Result<>
CheckPropertyDescriptorAccessors(JSContext* cx, Handle<JS::PropertyDescriptor> desc);
void

View file

@ -319,7 +319,7 @@ SetNewObjectMetadata(ExclusiveContext* cxArg, JSObject* obj)
} // namespace js
/* static */ inline JSObject*
/* static */ inline JS::Result<JSObject*, JS::OOM&>
JSObject::create(js::ExclusiveContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
js::HandleShape shape, js::HandleObjectGroup group)
{
@ -375,7 +375,7 @@ JSObject::create(js::ExclusiveContext* cx, js::gc::AllocKind kind, js::gc::Initi
JSObject* obj = js::Allocate<JSObject>(cx, kind, nDynamicSlots, heap, clasp);
if (!obj)
return nullptr;
return cx->alreadyReportedOOM();
obj->group_.init(group);

View file

@ -18,6 +18,7 @@
#include "jsprototypes.h"
#include "jstypes.h"
#include "js/Result.h"
#include "js/TraceKind.h"
#include "js/TypeDecls.h"

View file

@ -31,10 +31,12 @@
#include "jsutil.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/RegExp.h"
#include "jit/InlinableNatives.h"
#include "js/Conversions.h"
#include "js/UniquePtr.h"
#include "unicode/uchar.h"
#include "unicode/unorm2.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
@ -598,19 +600,210 @@ js::SubstringKernel(JSContext* cx, HandleString str, int32_t beginInt, int32_t l
return NewDependentString(cx, str, begin, len);
}
template <typename CharT>
static auto
ReallocChars(JSContext* cx, UniquePtr<CharT[], JS::FreePolicy> chars, size_t oldLength,
size_t newLength)
-> decltype(chars)
{
using AnyCharPtr = decltype(chars);
CharT* oldChars = chars.release();
CharT* newChars = cx->pod_realloc<CharT>(oldChars, oldLength, newLength);
if (!newChars) {
js_free(oldChars);
return AnyCharPtr();
}
return AnyCharPtr(newChars);
}
/**
* U+03A3 GREEK CAPITAL LETTER SIGMA has two different lower case mappings
* depending on its context:
* When it's preceded by a cased character and not followed by another cased
* character, its lower case form is U+03C2 GREEK SMALL LETTER FINAL SIGMA.
* Otherwise its lower case mapping is U+03C3 GREEK SMALL LETTER SIGMA.
*
* Unicode 9.0, §3.13 Default Case Algorithms
*/
static char16_t
Final_Sigma(const char16_t* chars, size_t length, size_t index)
{
MOZ_ASSERT(index < length);
MOZ_ASSERT(chars[index] == unicode::GREEK_CAPITAL_LETTER_SIGMA);
MOZ_ASSERT(unicode::ToLowerCase(unicode::GREEK_CAPITAL_LETTER_SIGMA) ==
unicode::GREEK_SMALL_LETTER_SIGMA);
// Tell the analysis the BinaryProperty.contains function pointer called by
// u_hasBinaryProperty cannot GC.
JS::AutoSuppressGCAnalysis nogc;
bool precededByCased = false;
for (size_t i = index; i > 0; ) {
char16_t c = chars[--i];
uint32_t codePoint = c;
if (unicode::IsTrailSurrogate(c) && i > 0) {
char16_t lead = chars[i - 1];
if (unicode::IsLeadSurrogate(lead)) {
codePoint = unicode::UTF16Decode(lead, c);
i--;
}
}
// Ignore any characters with the property Case_Ignorable.
// NB: We need to skip over all Case_Ignorable characters, even when
// they also have the Cased binary property.
if (u_hasBinaryProperty(codePoint, UCHAR_CASE_IGNORABLE))
continue;
precededByCased = u_hasBinaryProperty(codePoint, UCHAR_CASED);
break;
}
if (!precededByCased)
return unicode::GREEK_SMALL_LETTER_SIGMA;
bool followedByCased = false;
for (size_t i = index + 1; i < length; ) {
char16_t c = chars[i++];
uint32_t codePoint = c;
if (unicode::IsLeadSurrogate(c) && i < length) {
char16_t trail = chars[i];
if (unicode::IsTrailSurrogate(trail)) {
codePoint = unicode::UTF16Decode(c, trail);
i++;
}
}
// Ignore any characters with the property Case_Ignorable.
// NB: We need to skip over all Case_Ignorable characters, even when
// they also have the Cased binary property.
if (u_hasBinaryProperty(codePoint, UCHAR_CASE_IGNORABLE))
continue;
followedByCased = u_hasBinaryProperty(codePoint, UCHAR_CASED);
break;
}
if (!followedByCased)
return unicode::GREEK_SMALL_LETTER_FINAL_SIGMA;
return unicode::GREEK_SMALL_LETTER_SIGMA;
}
static Latin1Char
Final_Sigma(const Latin1Char* chars, size_t length, size_t index)
{
MOZ_ASSERT_UNREACHABLE("U+03A3 is not a Latin-1 character");
return 0;
}
// If |srcLength == destLength| is true, the destination buffer was allocated
// with the same size as the source buffer. When we append characters which
// have special casing mappings, we test |srcLength == destLength| to decide
// if we need to back out and reallocate a sufficiently large destination
// buffer. Otherwise the destination buffer was allocated with the correct
// size to hold all lower case mapped characters, i.e.
// |destLength == ToLowerCaseLength(srcChars, 0, srcLength)| is true.
template <typename CharT>
static size_t
ToLowerCaseImpl(CharT* destChars, const CharT* srcChars, size_t startIndex, size_t srcLength,
size_t destLength)
{
MOZ_ASSERT(startIndex < srcLength);
MOZ_ASSERT(srcLength <= destLength);
MOZ_ASSERT_IF((IsSame<CharT, Latin1Char>::value), srcLength == destLength);
size_t j = startIndex;
for (size_t i = startIndex; i < srcLength; i++) {
char16_t c = srcChars[i];
if (!IsSame<CharT, Latin1Char>::value) {
if (unicode::IsLeadSurrogate(c) && i + 1 < srcLength) {
char16_t trail = srcChars[i + 1];
if (unicode::IsTrailSurrogate(trail)) {
trail = unicode::ToLowerCaseNonBMPTrail(c, trail);
destChars[j++] = c;
destChars[j++] = trail;
i++;
continue;
}
}
// Special case: U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE
// lowercases to <U+0069 U+0307>.
if (c == unicode::LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE) {
// Return if the output buffer is too small.
if (srcLength == destLength)
return i;
destChars[j++] = CharT('i');
destChars[j++] = CharT(unicode::COMBINING_DOT_ABOVE);
continue;
}
// Special case: U+03A3 GREEK CAPITAL LETTER SIGMA lowercases to
// one of two codepoints depending on context.
if (c == unicode::GREEK_CAPITAL_LETTER_SIGMA) {
destChars[j++] = Final_Sigma(srcChars, srcLength, i);
continue;
}
}
c = unicode::ToLowerCase(c);
MOZ_ASSERT_IF((IsSame<CharT, Latin1Char>::value), c <= JSString::MAX_LATIN1_CHAR);
destChars[j++] = c;
}
MOZ_ASSERT(j == destLength);
destChars[destLength] = '\0';
return srcLength;
}
static size_t
ToLowerCaseLength(const char16_t* chars, size_t startIndex, size_t length)
{
size_t lowerLength = length;
for (size_t i = startIndex; i < length; i++) {
char16_t c = chars[i];
// U+0130 is lowercased to the two-element sequence <U+0069 U+0307>.
if (c == unicode::LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE)
lowerLength += 1;
}
return lowerLength;
}
static size_t
ToLowerCaseLength(const Latin1Char* chars, size_t startIndex, size_t length)
{
MOZ_ASSERT_UNREACHABLE("never called for Latin-1 strings");
return 0;
}
template <typename CharT>
static JSString*
ToLowerCase(JSContext* cx, JSLinearString* str)
{
// Unlike toUpperCase, toLowerCase has the nice invariant that if the input
// is a Latin1 string, the output is also a Latin1 string.
UniquePtr<CharT[], JS::FreePolicy> newChars;
size_t length = str->length();
// Unlike toUpperCase, toLowerCase has the nice invariant that if the
// input is a Latin-1 string, the output is also a Latin-1 string.
using AnyCharPtr = UniquePtr<CharT[], JS::FreePolicy>;
AnyCharPtr newChars;
const size_t length = str->length();
size_t resultLength;
{
AutoCheckCannotGC nogc;
const CharT* chars = str->chars<CharT>(nogc);
// Look for the first upper case character.
// We don't need extra special casing checks in the loop below,
// because U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE and U+03A3
// GREEK CAPITAL LETTER SIGMA already have simple lower case mappings.
MOZ_ASSERT(unicode::CanLowerCase(unicode::LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE),
"U+0130 has a simple lower case mapping");
MOZ_ASSERT(unicode::CanLowerCase(unicode::GREEK_CAPITAL_LETTER_SIGMA),
"U+03A3 has a simple lower case mapping");
// Look for the first character that changes when lowercased.
size_t i = 0;
for (; i < length; i++) {
char16_t c = chars[i];
@ -630,40 +823,35 @@ ToLowerCase(JSContext* cx, JSLinearString* str)
break;
}
// If all characters are lower case, return the input string.
// If no character needs to change, return the input string.
if (i == length)
return str;
newChars = cx->make_pod_array<CharT>(length + 1);
resultLength = length;
newChars = cx->make_pod_array<CharT>(resultLength + 1);
if (!newChars)
return nullptr;
PodCopy(newChars.get(), chars, i);
for (; i < length; i++) {
char16_t c = chars[i];
if (!IsSame<CharT, Latin1Char>::value) {
if (unicode::IsLeadSurrogate(c) && i + 1 < length) {
char16_t trail = chars[i + 1];
if (unicode::IsTrailSurrogate(trail)) {
trail = unicode::ToLowerCaseNonBMPTrail(c, trail);
newChars[i] = c;
newChars[i + 1] = trail;
i++;
continue;
}
}
}
size_t readChars = ToLowerCaseImpl(newChars.get(), chars, i, length, resultLength);
if (readChars < length) {
MOZ_ASSERT((!IsSame<CharT, Latin1Char>::value),
"Latin-1 strings don't have special lower case mappings");
resultLength = ToLowerCaseLength(chars, readChars, length);
c = unicode::ToLowerCase(c);
MOZ_ASSERT_IF((IsSame<CharT, Latin1Char>::value), c <= JSString::MAX_LATIN1_CHAR);
newChars[i] = c;
AnyCharPtr buf = ReallocChars(cx, Move(newChars), length + 1, resultLength + 1);
if (!buf)
return nullptr;
newChars = Move(buf);
MOZ_ALWAYS_TRUE(length ==
ToLowerCaseImpl(newChars.get(), chars, readChars, length, resultLength));
}
newChars[length] = 0;
}
JSString* res = NewStringDontDeflate<CanGC>(cx, newChars.get(), length);
JSString* res = NewStringDontDeflate<CanGC>(cx, newChars.get(), resultLength);
if (!res)
return nullptr;
@ -671,21 +859,102 @@ ToLowerCase(JSContext* cx, JSLinearString* str)
return res;
}
static inline bool
ToLowerCaseHelper(JSContext* cx, const CallArgs& args)
JSString*
js::StringToLowerCase(JSContext* cx, HandleLinearString string)
{
if (string->hasLatin1Chars())
return ToLowerCase<Latin1Char>(cx, string);
return ToLowerCase<char16_t>(cx, string);
}
bool
js::str_toLowerCase(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
if (!str)
return false;
JSLinearString* linear = str->ensureLinear(cx);
RootedLinearString linear(cx, str->ensureLinear(cx));
if (!linear)
return false;
if (linear->hasLatin1Chars())
str = ToLowerCase<Latin1Char>(cx, linear);
else
str = ToLowerCase<char16_t>(cx, linear);
JSString* result = StringToLowerCase(cx, linear);
if (!result)
return false;
args.rval().setString(result);
return true;
}
static const char*
CaseMappingLocale(JSContext* cx, JSString* str)
{
JSLinearString* locale = str->ensureLinear(cx);
if (!locale)
return nullptr;
MOZ_ASSERT(locale->length() >= 2, "locale is a valid language tag");
// Lithuanian, Turkish, and Azeri have language dependent case mappings.
static const char languagesWithSpecialCasing[][3] = { "lt", "tr", "az" };
// All strings in |languagesWithSpecialCasing| are of length two, so we
// only need to compare the first two characters to find a matching locale.
// ES2017 Intl, §9.2.2 BestAvailableLocale
if (locale->length() == 2 || locale->latin1OrTwoByteChar(2) == '-') {
for (const auto& language : languagesWithSpecialCasing) {
if (locale->latin1OrTwoByteChar(0) == language[0] &&
locale->latin1OrTwoByteChar(1) == language[1])
{
return language;
}
}
}
return ""; // ICU root locale
}
bool
js::intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].isString());
MOZ_ASSERT(args[1].isString());
RootedLinearString linear(cx, args[0].toString()->ensureLinear(cx));
if (!linear)
return false;
const char* locale = CaseMappingLocale(cx, args[1].toString());
if (!locale)
return false;
// Call String.prototype.toLowerCase() for language independent casing.
if (intl::StringsAreEqual(locale, "")) {
JSString* str = StringToLowerCase(cx, linear);
if (!str)
return false;
args.rval().setString(str);
return true;
}
AutoStableStringChars inputChars(cx);
if (!inputChars.initTwoByte(cx, linear))
return false;
mozilla::Range<const char16_t> input = inputChars.twoByteRange();
// Maximum case mapping length is three characters.
static_assert(JSString::MAX_LENGTH < INT32_MAX / 3,
"Case conversion doesn't overflow int32_t indices");
JSString* str = intl::CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
return u_strToLower(chars, size, Char16ToUChar(input.begin().get()), input.length(),
locale, status);
});
if (!str)
return false;
@ -693,82 +962,192 @@ ToLowerCaseHelper(JSContext* cx, const CallArgs& args)
return true;
}
bool
js::str_toLowerCase(JSContext* cx, unsigned argc, Value* vp)
static inline bool
CanUpperCaseSpecialCasing(Latin1Char charCode)
{
return ToLowerCaseHelper(cx, CallArgsFromVp(argc, vp));
// Handle U+00DF LATIN SMALL LETTER SHARP S inline, all other Latin-1
// characters don't have special casing rules.
MOZ_ASSERT_IF(charCode != unicode::LATIN_SMALL_LETTER_SHARP_S,
!unicode::CanUpperCaseSpecialCasing(charCode));
return charCode == unicode::LATIN_SMALL_LETTER_SHARP_S;
}
bool
js::str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
static inline bool
CanUpperCaseSpecialCasing(char16_t charCode)
{
CallArgs args = CallArgsFromVp(argc, vp);
/*
* Forcefully ignore the first (or any) argument and return toLowerCase(),
* ECMA has reserved that argument, presumably for defining the locale.
*/
if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToLowerCase) {
RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
if (!str)
return false;
RootedValue result(cx);
if (!cx->runtime()->localeCallbacks->localeToLowerCase(cx, str, &result))
return false;
args.rval().set(result);
return true;
}
return ToLowerCaseHelper(cx, args);
return unicode::CanUpperCaseSpecialCasing(charCode);
}
static inline size_t
LengthUpperCaseSpecialCasing(Latin1Char charCode)
{
// U+00DF LATIN SMALL LETTER SHARP S is uppercased to two 'S'.
MOZ_ASSERT(charCode == unicode::LATIN_SMALL_LETTER_SHARP_S);
return 2;
}
static inline size_t
LengthUpperCaseSpecialCasing(char16_t charCode)
{
MOZ_ASSERT(CanUpperCaseSpecialCasing(charCode));
return unicode::LengthUpperCaseSpecialCasing(charCode);
}
static inline void
AppendUpperCaseSpecialCasing(char16_t charCode, Latin1Char* elements, size_t* index)
{
// U+00DF LATIN SMALL LETTER SHARP S is uppercased to two 'S'.
MOZ_ASSERT(charCode == unicode::LATIN_SMALL_LETTER_SHARP_S);
static_assert('S' <= JSString::MAX_LATIN1_CHAR, "'S' is a Latin-1 character");
elements[(*index)++] = 'S';
elements[(*index)++] = 'S';
}
static inline void
AppendUpperCaseSpecialCasing(char16_t charCode, char16_t* elements, size_t* index)
{
unicode::AppendUpperCaseSpecialCasing(charCode, elements, index);
}
// See ToLowerCaseImpl for an explanation of the parameters.
template <typename DestChar, typename SrcChar>
static void
ToUpperCaseImpl(DestChar* destChars, const SrcChar* srcChars, size_t firstLowerCase, size_t length)
static size_t
ToUpperCaseImpl(DestChar* destChars, const SrcChar* srcChars, size_t startIndex, size_t srcLength,
size_t destLength)
{
MOZ_ASSERT(firstLowerCase < length);
static_assert(IsSame<SrcChar, Latin1Char>::value || !IsSame<DestChar, Latin1Char>::value,
"cannot write non-Latin-1 characters into Latin-1 string");
MOZ_ASSERT(startIndex < srcLength);
MOZ_ASSERT(srcLength <= destLength);
for (size_t i = 0; i < firstLowerCase; i++)
destChars[i] = srcChars[i];
for (size_t i = firstLowerCase; i < length; i++) {
size_t j = startIndex;
for (size_t i = startIndex; i < srcLength; i++) {
char16_t c = srcChars[i];
if (!IsSame<DestChar, Latin1Char>::value) {
if (unicode::IsLeadSurrogate(c) && i + 1 < length) {
if (unicode::IsLeadSurrogate(c) && i + 1 < srcLength) {
char16_t trail = srcChars[i + 1];
if (unicode::IsTrailSurrogate(trail)) {
trail = unicode::ToUpperCaseNonBMPTrail(c, trail);
destChars[i] = c;
destChars[i + 1] = trail;
destChars[j++] = c;
destChars[j++] = trail;
i++;
continue;
}
}
}
if (MOZ_UNLIKELY(c > 0x7f && CanUpperCaseSpecialCasing(static_cast<SrcChar>(c)))) {
// Return if the output buffer is too small.
if (srcLength == destLength)
return i;
AppendUpperCaseSpecialCasing(c, destChars, &j);
continue;
}
c = unicode::ToUpperCase(c);
MOZ_ASSERT_IF((IsSame<DestChar, Latin1Char>::value), c <= JSString::MAX_LATIN1_CHAR);
destChars[i] = c;
destChars[j++] = c;
}
destChars[length] = '\0';
MOZ_ASSERT(j == destLength);
destChars[destLength] = '\0';
return srcLength;
}
// Explicit instantiation so we don't hit the static_assert from above.
static bool
ToUpperCaseImpl(Latin1Char* destChars, const char16_t* srcChars, size_t startIndex,
size_t srcLength, size_t destLength)
{
MOZ_ASSERT_UNREACHABLE("cannot write non-Latin-1 characters into Latin-1 string");
return false;
}
template <typename CharT>
static size_t
ToUpperCaseLength(const CharT* chars, size_t startIndex, size_t length)
{
size_t upperLength = length;
for (size_t i = startIndex; i < length; i++) {
char16_t c = chars[i];
if (c > 0x7f && CanUpperCaseSpecialCasing(static_cast<CharT>(c)))
upperLength += LengthUpperCaseSpecialCasing(static_cast<CharT>(c)) - 1;
}
return upperLength;
}
template <typename DestChar, typename SrcChar>
static inline void
CopyChars(DestChar* destChars, const SrcChar* srcChars, size_t length)
{
static_assert(!IsSame<DestChar, SrcChar>::value, "PodCopy is used for the same type case");
for (size_t i = 0; i < length; i++)
destChars[i] = srcChars[i];
}
template <typename CharT>
static inline void
CopyChars(CharT* destChars, const CharT* srcChars, size_t length)
{
PodCopy(destChars, srcChars, length);
}
template <typename DestChar, typename SrcChar>
static inline UniquePtr<DestChar[], JS::FreePolicy>
ToUpperCase(JSContext* cx, const SrcChar* chars, size_t startIndex, size_t length,
size_t* resultLength)
{
MOZ_ASSERT(startIndex < length);
using DestCharPtr = UniquePtr<DestChar[], JS::FreePolicy>;
*resultLength = length;
DestCharPtr buf = cx->make_pod_array<DestChar>(length + 1);
if (!buf)
return buf;
CopyChars(buf.get(), chars, startIndex);
size_t readChars = ToUpperCaseImpl(buf.get(), chars, startIndex, length, length);
if (readChars < length) {
size_t actualLength = ToUpperCaseLength(chars, readChars, length);
*resultLength = actualLength;
DestCharPtr buf2 = ReallocChars(cx, Move(buf), length + 1, actualLength + 1);
if (!buf2)
return buf2;
buf = Move(buf2);
MOZ_ALWAYS_TRUE(length ==
ToUpperCaseImpl(buf.get(), chars, readChars, length, actualLength));
}
return buf;
}
template <typename CharT>
static JSString*
ToUpperCase(JSContext* cx, JSLinearString* str)
{
typedef UniquePtr<Latin1Char[], JS::FreePolicy> Latin1CharPtr;
typedef UniquePtr<char16_t[], JS::FreePolicy> TwoByteCharPtr;
using Latin1CharPtr = UniquePtr<Latin1Char[], JS::FreePolicy>;
using TwoByteCharPtr = UniquePtr<char16_t[], JS::FreePolicy>;
mozilla::MaybeOneOf<Latin1CharPtr, TwoByteCharPtr> newChars;
size_t length = str->length();
const size_t length = str->length();
size_t resultLength;
{
AutoCheckCannotGC nogc;
const CharT* chars = str->chars<CharT>(nogc);
// Look for the first lower case character.
// Look for the first character that changes when uppercased.
size_t i = 0;
for (; i < length; i++) {
char16_t c = chars[i];
@ -786,21 +1165,33 @@ ToUpperCase(JSContext* cx, JSLinearString* str)
}
if (unicode::CanUpperCase(c))
break;
if (MOZ_UNLIKELY(c > 0x7f && CanUpperCaseSpecialCasing(static_cast<CharT>(c))))
break;
}
// If all characters are upper case, return the input string.
// If no character needs to change, return the input string.
if (i == length)
return str;
// If the string is Latin1, check if it contains the MICRO SIGN (0xb5)
// or SMALL LETTER Y WITH DIAERESIS (0xff) character. The corresponding
// upper case characters are not in the Latin1 range.
// The string changes when uppercased, so we must create a new string.
// Can it be Latin-1?
//
// If the original string is Latin-1, it can -- unless the string
// contains U+00B5 MICRO SIGN or U+00FF SMALL LETTER Y WITH DIAERESIS,
// the only Latin-1 codepoints that don't uppercase within Latin-1.
// Search for those codepoints to decide whether the new string can be
// Latin-1.
// If the original string is a two-byte string, its uppercase form is
// so rarely Latin-1 that we don't even consider creating a new
// Latin-1 string.
bool resultIsLatin1;
if (IsSame<CharT, Latin1Char>::value) {
resultIsLatin1 = true;
for (size_t j = i; j < length; j++) {
Latin1Char c = chars[j];
if (c == 0xb5 || c == 0xff) {
if (c == unicode::MICRO_SIGN ||
c == unicode::LATIN_SMALL_LETTER_Y_WITH_DIAERESIS)
{
MOZ_ASSERT(unicode::ToUpperCase(c) > JSString::MAX_LATIN1_CHAR);
resultIsLatin1 = false;
break;
@ -813,31 +1204,29 @@ ToUpperCase(JSContext* cx, JSLinearString* str)
}
if (resultIsLatin1) {
Latin1CharPtr buf = cx->make_pod_array<Latin1Char>(length + 1);
Latin1CharPtr buf = ToUpperCase<Latin1Char>(cx, chars, i, length, &resultLength);
if (!buf)
return nullptr;
ToUpperCaseImpl(buf.get(), chars, i, length);
newChars.construct<Latin1CharPtr>(Move(buf));
} else {
TwoByteCharPtr buf = cx->make_pod_array<char16_t>(length + 1);
TwoByteCharPtr buf = ToUpperCase<char16_t>(cx, chars, i, length, &resultLength);
if (!buf)
return nullptr;
ToUpperCaseImpl(buf.get(), chars, i, length);
newChars.construct<TwoByteCharPtr>(Move(buf));
}
}
JSString* res;
if (newChars.constructed<Latin1CharPtr>()) {
res = NewStringDontDeflate<CanGC>(cx, newChars.ref<Latin1CharPtr>().get(), length);
res = NewStringDontDeflate<CanGC>(cx, newChars.ref<Latin1CharPtr>().get(), resultLength);
if (!res)
return nullptr;
mozilla::Unused << newChars.ref<Latin1CharPtr>().release();
} else {
res = NewStringDontDeflate<CanGC>(cx, newChars.ref<TwoByteCharPtr>().get(), length);
res = NewStringDontDeflate<CanGC>(cx, newChars.ref<TwoByteCharPtr>().get(), resultLength);
if (!res)
return nullptr;
@ -847,57 +1236,79 @@ ToUpperCase(JSContext* cx, JSLinearString* str)
return res;
}
static bool
ToUpperCaseHelper(JSContext* cx, const CallArgs& args)
JSString*
js::StringToUpperCase(JSContext* cx, HandleLinearString string)
{
RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
if (!str)
return false;
JSLinearString* linear = str->ensureLinear(cx);
if (!linear)
return false;
if (linear->hasLatin1Chars())
str = ToUpperCase<Latin1Char>(cx, linear);
else
str = ToUpperCase<char16_t>(cx, linear);
if (!str)
return false;
args.rval().setString(str);
return true;
if (string->hasLatin1Chars())
return ToUpperCase<Latin1Char>(cx, string);
return ToUpperCase<char16_t>(cx, string);
}
bool
js::str_toUpperCase(JSContext* cx, unsigned argc, Value* vp)
{
return ToUpperCaseHelper(cx, CallArgsFromVp(argc, vp));
CallArgs args = CallArgsFromVp(argc, vp);
RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
if (!str)
return false;
RootedLinearString linear(cx, str->ensureLinear(cx));
if (!linear)
return false;
JSString* result = StringToUpperCase(cx, linear);
if (!result)
return false;
args.rval().setString(result);
return true;
}
bool
js::str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
js::intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].isString());
MOZ_ASSERT(args[1].isString());
/*
* Forcefully ignore the first (or any) argument and return toUpperCase(),
* ECMA has reserved that argument, presumably for defining the locale.
*/
if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUpperCase) {
RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
RootedLinearString linear(cx, args[0].toString()->ensureLinear(cx));
if (!linear)
return false;
const char* locale = CaseMappingLocale(cx, args[1].toString());
if (!locale)
return false;
// Call String.prototype.toUpperCase() for language independent casing.
if (intl::StringsAreEqual(locale, "")) {
JSString* str = StringToUpperCase(cx, linear);
if (!str)
return false;
RootedValue result(cx);
if (!cx->runtime()->localeCallbacks->localeToUpperCase(cx, str, &result))
return false;
args.rval().set(result);
args.rval().setString(str);
return true;
}
return ToUpperCaseHelper(cx, args);
AutoStableStringChars inputChars(cx);
if (!inputChars.initTwoByte(cx, linear))
return false;
mozilla::Range<const char16_t> input = inputChars.twoByteRange();
// Maximum case mapping length is three characters.
static_assert(JSString::MAX_LENGTH < INT32_MAX / 3,
"Case conversion doesn't overflow int32_t indices");
JSString* str = intl::CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
return u_strToUpper(chars, size, Char16ToUChar(input.begin().get()), input.length(),
locale, status);
});
if (!str)
return false;
args.rval().setString(str);
return true;
}
/* ES2017 21.1.3.12. */
@ -944,7 +1355,7 @@ js::str_normalize(JSContext* cx, unsigned argc, Value* vp)
if (!linear)
return false;
// Latin1 strings are already in Normalization Form C.
// Latin-1 strings are already in Normalization Form C.
if (form == NFC && linear->hasLatin1Chars()) {
// Step 7.
args.rval().setString(str);
@ -1359,7 +1770,7 @@ StringMatch(const TextChar* text, uint32_t textLen, const PatChar* pat, uint32_t
/*
* For big patterns with large potential overlap we want the SIMD-optimized
* speed of memcmp. For small patterns, a simple loop is faster. We also can't
* use memcmp if one of the strings is TwoByte and the other is Latin1.
* use memcmp if one of the strings is TwoByte and the other is Latin-1.
*
* FIXME: Linux memcmp performance is sad and the manual loop is faster.
*/
@ -1555,7 +1966,7 @@ RopeMatch(JSContext* cx, JSRope* text, JSLinearString* pat, int* match)
* need to build the list of leaf nodes. Do both here: iterate over the
* nodes so long as there are not too many.
*
* We also don't use rope matching if the rope contains both Latin1 and
* We also don't use rope matching if the rope contains both Latin-1 and
* TwoByte nodes, to simplify the match algorithm.
*/
{
@ -2890,8 +3301,8 @@ static const JSFunctionSpec string_methods[] = {
JS_FN("trimStart", str_trimStart, 0,0),
JS_FN("trimRight", str_trimEnd, 0,0),
JS_FN("trimEnd", str_trimEnd, 0,0),
JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,0),
JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,0),
JS_SELF_HOSTED_FN("toLocaleLowerCase", "String_toLocaleLowerCase", 0,0),
JS_SELF_HOSTED_FN("toLocaleUpperCase", "String_toLocaleUpperCase", 0,0),
JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
JS_SELF_HOSTED_FN("repeat", "String_repeat", 1,0),
JS_FN("normalize", str_normalize, 0,0),
@ -3000,7 +3411,7 @@ js::str_fromCharCode(JSContext* cx, unsigned argc, Value* vp)
// string (thin or fat) and so we don't need to malloc the chars. (We could
// cover some cases where args.length() goes up to
// JSFatInlineString::MAX_LENGTH_LATIN1 if we also checked if the chars are
// all Latin1, but it doesn't seem worth the effort.)
// all Latin-1, but it doesn't seem worth the effort.)
if (args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE)
return str_fromCharCode_few_args(cx, args);
@ -3143,7 +3554,7 @@ js::str_fromCodePoint(JSContext* cx, unsigned argc, Value* vp)
// string (thin or fat) and so we don't need to malloc the chars. (We could
// cover some cases where |args.length()| goes up to
// JSFatInlineString::MAX_LENGTH_LATIN1 / 2 if we also checked if the chars
// are all Latin1, but it doesn't seem worth the effort.)
// are all Latin-1, but it doesn't seem worth the effort.)
if (args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE / 2)
return str_fromCodePoint_few_args(cx, args);

View file

@ -371,11 +371,24 @@ str_trimStart(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_trimEnd(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns the input string converted to lower case based on the language
* specific case mappings for the input locale.
*
* Usage: lowerCase = intl_toLocaleLowerCase(string, locale)
*/
extern MOZ_MUST_USE bool
intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns the input string converted to upper case based on the language
* specific case mappings for the input locale.
*
* Usage: upperCase = intl_toLocaleUpperCase(string, locale)
*/
extern MOZ_MUST_USE bool
intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_normalize(JSContext* cx, unsigned argc, Value* vp);
@ -480,6 +493,12 @@ JSString*
str_replaceAll_string_raw(JSContext* cx, HandleString string, HandleString pattern,
HandleString replacement);
extern JSString*
StringToLowerCase(JSContext* cx, HandleLinearString string);
extern JSString*
StringToUpperCase(JSContext* cx, HandleLinearString string);
extern bool
StringConstructor(JSContext* cx, unsigned argc, Value* vp);

View file

@ -121,7 +121,7 @@ WeakMapBase::restoreMarkedWeakMaps(WeakMapSet& markedWeakMaps)
{
for (WeakMapSet::Range r = markedWeakMaps.all(); !r.empty(); r.popFront()) {
WeakMapBase* map = r.front();
MOZ_ASSERT(map->zone->isGCMarking());
MOZ_ASSERT(map->zone->shouldMarkInZone());
MOZ_ASSERT(!map->marked);
map->marked = true;
}
@ -144,7 +144,7 @@ ObjectValueMap::findZoneEdges()
if (!delegate)
continue;
Zone* delegateZone = delegate->zone();
if (delegateZone == zone || !delegateZone->isGCMarking())
if (delegateZone == zone || !delegateZone->shouldMarkInZone())
continue;
if (!delegateZone->gcZoneGroupEdges.put(key->zone()))
return false;

View file

@ -89,6 +89,7 @@ EXPORTS.js += [
'../public/Proxy.h',
'../public/Realm.h',
'../public/RequiredDefines.h',
'../public/Result.h',
'../public/RootingAPI.h',
'../public/SliceBudget.h',
'../public/StructuredClone.h',
@ -117,6 +118,9 @@ main_deunified_sources = [
'builtin/intl/CommonFunctions.cpp',
'builtin/intl/DateTimeFormat.cpp',
'builtin/intl/IntlObject.cpp',
'builtin/intl/LanguageTag.cpp',
'builtin/intl/LanguageTagGenerated.cpp',
'builtin/intl/Locale.cpp',
'builtin/intl/NumberFormat.cpp',
'builtin/intl/PluralRules.cpp',
'builtin/intl/RelativeTimeFormat.cpp',
@ -708,7 +712,6 @@ selfhosted.inputs = [
'builtin/intl/CommonFunctions.js',
'builtin/intl/DateTimeFormat.js',
'builtin/intl/IntlObject.js',
'builtin/intl/LangTagMappingsGenerated.js',
'builtin/intl/NumberFormat.js',
'builtin/intl/PluralRules.js',
'builtin/intl/RelativeTimeFormat.js',

View file

@ -927,6 +927,9 @@ AddIntlExtras(JSContext* cx, unsigned argc, Value* vp)
if (!JS_DefineFunctions(cx, intl, funcs))
return false;
if (!js::AddMozDateTimeFormatConstructor(cx, intl))
return false;
args.rval().setUndefined();
return true;
}

View file

@ -0,0 +1,69 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
function IsConstructor(o) {
try {
new (new Proxy(o, {construct: () => ({})}));
return true;
} catch (e) {
return false;
}
}
function IsObject(o) {
return Object(o) === o;
}
function thisValues() {
const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
return [
// Primitive values.
...[undefined, null, true, "abc", Symbol(), 123],
// Object values.
...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
// Intl objects.
...[].concat(...intlConstructors.map(ctor => [
// Instance of an Intl constructor.
new ctor(),
// Instance of a subclassed Intl constructor.
new class extends ctor {},
// Object inheriting from an Intl constructor prototype.
Object.create(ctor.prototype),
// Intl object not inheriting from its default prototype.
Object.setPrototypeOf(new ctor(), Object.prototype),
])),
];
}
// Invoking [[Call]] for Intl.Collator always returns a new Collator instance.
for (let thisValue of thisValues()) {
let obj = Intl.Collator.call(thisValue);
assertEq(Object.is(obj, thisValue), false);
assertEq(obj instanceof Intl.Collator, true);
// Ensure Intl.[[FallbackSymbol]] wasn't installed on |thisValue|.
if (IsObject(thisValue))
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
}
// Intl.Collator doesn't use the legacy Intl constructor compromise semantics.
for (let thisValue of thisValues()) {
// Ensure instanceof operator isn't invoked for Intl.Collator.
Object.defineProperty(Intl.Collator, Symbol.hasInstance, {
get() {
assertEq(false, true, "@@hasInstance operator called");
}, configurable: true
});
let obj = Intl.Collator.call(thisValue);
delete Intl.Collator[Symbol.hasInstance];
assertEq(Object.is(obj, thisValue), false);
assertEq(obj instanceof Intl.Collator, true);
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -0,0 +1,197 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Locales which use caseFirst=off for the standard (sort) collation type.
const defaultLocales = Intl.Collator.supportedLocalesOf(["en", "de", "es", "sv", "ar", "zh", "ja"]);
// Locales which use caseFirst=upper for the standard (sort) collation type.
const upperFirstLocales = Intl.Collator.supportedLocalesOf(["cu", "da", "mt"]);
// Default collation for zh (pinyin) reorders "á" before "a" at secondary strength level.
const accentReordered = ["zh"];
const allLocales = [...defaultLocales, ...upperFirstLocales];
// Check default "caseFirst" option is resolved correctly.
for (let locale of defaultLocales) {
let col = new Intl.Collator(locale, {usage: "sort"});
assertEq(col.resolvedOptions().caseFirst, "false");
}
for (let locale of upperFirstLocales) {
let col = new Intl.Collator(locale, {usage: "sort"});
assertEq(col.resolvedOptions().caseFirst, "upper");
}
for (let locale of allLocales) {
let col = new Intl.Collator(locale, {usage: "search"});
assertEq(col.resolvedOptions().caseFirst, "false");
}
const collOptions = {usage: "sort"};
const primary = {sensitivity: "base"};
const secondary = {sensitivity: "accent"};
const tertiary = {sensitivity: "variant"};
const caseLevel = {sensitivity: "case"};
const strengths = [primary, secondary, tertiary, caseLevel];
// "A" is sorted after "a" when caseFirst=off is the default and strength is tertiary.
for (let locale of defaultLocales) {
let col = new Intl.Collator(locale, Object.assign({}, collOptions, tertiary));
assertEq(col.compare("A", "a"), 1);
assertEq(col.compare("a", "A"), -1);
}
for (let locale of defaultLocales.filter(loc => !accentReordered.includes(loc))) {
let col = new Intl.Collator(locale, Object.assign({}, collOptions, tertiary));
assertEq(col.compare("A", "á"), -1);
assertEq(col.compare("á", "A"), 1);
}
// Also sorted after "a" with the sensitivity=case collator.
for (let locale of defaultLocales) {
let col = new Intl.Collator(locale, Object.assign({}, collOptions, caseLevel));
assertEq(col.compare("A", "a"), 1);
assertEq(col.compare("a", "A"), -1);
assertEq(col.compare("A", "á"), 1);
assertEq(col.compare("á", "A"), -1);
}
// "A" is sorted before "a" when caseFirst=upper is the default and strength is tertiary.
for (let locale of upperFirstLocales) {
let col = new Intl.Collator(locale, Object.assign({}, collOptions, tertiary));
assertEq(col.compare("A", "a"), -1);
assertEq(col.compare("a", "A"), 1);
assertEq(col.compare("A", "á"), -1);
assertEq(col.compare("á", "A"), 1);
}
// Also sorted before "a" with the sensitivity=case collator.
for (let locale of upperFirstLocales) {
let col = new Intl.Collator(locale, Object.assign({}, collOptions, caseLevel));
assertEq(col.compare("A", "a"), -1);
assertEq(col.compare("a", "A"), 1);
assertEq(col.compare("A", "á"), -1);
assertEq(col.compare("á", "A"), 1);
}
// caseFirst=upper doesn't change the sort order when strength is below tertiary.
for (let locale of allLocales) {
let col = new Intl.Collator(locale, Object.assign({}, collOptions, secondary));
assertEq(col.compare("A", "a"), 0);
assertEq(col.compare("a", "A"), 0);
}
for (let locale of allLocales.filter(loc => !accentReordered.includes(loc))) {
let col = new Intl.Collator(locale, Object.assign({}, collOptions, secondary));
assertEq(col.compare("A", "á"), -1);
assertEq(col.compare("á", "A"), 1);
}
for (let locale of allLocales) {
let col = new Intl.Collator(locale, Object.assign({}, collOptions, primary));
assertEq(col.compare("A", "a"), 0);
assertEq(col.compare("a", "A"), 0);
assertEq(col.compare("A", "á"), 0);
assertEq(col.compare("á", "A"), 0);
}
// caseFirst=upper doesn't change the sort order when there's a primary difference.
for (let locale of allLocales) {
for (let strength of strengths) {
let col = new Intl.Collator(locale, Object.assign({}, collOptions, strength));
assertEq(col.compare("A", "b"), -1);
assertEq(col.compare("a", "B"), -1);
}
}
// caseFirst set through Unicode extension tag.
for (let locale of allLocales) {
let colKfFalse = new Intl.Collator(locale + "-u-kf-false", {});
let colKfLower = new Intl.Collator(locale + "-u-kf-lower", {});
let colKfUpper = new Intl.Collator(locale + "-u-kf-upper", {});
assertEq(colKfFalse.resolvedOptions().caseFirst, "false");
assertEq(colKfFalse.compare("A", "a"), 1);
assertEq(colKfFalse.compare("a", "A"), -1);
assertEq(colKfLower.resolvedOptions().caseFirst, "lower");
assertEq(colKfLower.compare("A", "a"), 1);
assertEq(colKfLower.compare("a", "A"), -1);
assertEq(colKfUpper.resolvedOptions().caseFirst, "upper");
assertEq(colKfUpper.compare("A", "a"), -1);
assertEq(colKfUpper.compare("a", "A"), 1);
}
// caseFirst set through options value.
for (let locale of allLocales) {
let colKfFalse = new Intl.Collator(locale, {caseFirst: "false"});
let colKfLower = new Intl.Collator(locale, {caseFirst: "lower"});
let colKfUpper = new Intl.Collator(locale, {caseFirst: "upper"});
assertEq(colKfFalse.resolvedOptions().caseFirst, "false");
assertEq(colKfFalse.compare("A", "a"), 1);
assertEq(colKfFalse.compare("a", "A"), -1);
assertEq(colKfLower.resolvedOptions().caseFirst, "lower");
assertEq(colKfLower.compare("A", "a"), 1);
assertEq(colKfLower.compare("a", "A"), -1);
assertEq(colKfUpper.resolvedOptions().caseFirst, "upper");
assertEq(colKfUpper.compare("A", "a"), -1);
assertEq(colKfUpper.compare("a", "A"), 1);
}
// Test Unicode extension tag and options value, the latter should win.
for (let locale of allLocales) {
let colKfFalse = new Intl.Collator(locale + "-u-kf-upper", {caseFirst: "false"});
let colKfLower = new Intl.Collator(locale + "-u-kf-upper", {caseFirst: "lower"});
let colKfUpper = new Intl.Collator(locale + "-u-kf-lower", {caseFirst: "upper"});
assertEq(colKfFalse.resolvedOptions().caseFirst, "false");
assertEq(colKfFalse.compare("A", "a"), 1);
assertEq(colKfFalse.compare("a", "A"), -1);
assertEq(colKfLower.resolvedOptions().caseFirst, "lower");
assertEq(colKfLower.compare("A", "a"), 1);
assertEq(colKfLower.compare("a", "A"), -1);
assertEq(colKfUpper.resolvedOptions().caseFirst, "upper");
assertEq(colKfUpper.compare("A", "a"), -1);
assertEq(colKfUpper.compare("a", "A"), 1);
}
// Ensure languages are properly detected when additional subtags are present.
if (Intl.Collator.supportedLocalesOf("da").length !== 0) {
assertEq(new Intl.Collator("da-DK", {usage: "sort"}).resolvedOptions().caseFirst, "upper");
assertEq(new Intl.Collator("da-Latn-DK", {usage: "sort"}).resolvedOptions().caseFirst, "upper");
}
if (Intl.Collator.supportedLocalesOf("mt").length !== 0) {
assertEq(new Intl.Collator("mt-MT", {usage: "sort"}).resolvedOptions().caseFirst, "upper");
assertEq(new Intl.Collator("mt-Latn-MT", {usage: "sort"}).resolvedOptions().caseFirst, "upper");
}
if (typeof reportCompare === "function")
reportCompare(0, 0, "ok");

View file

@ -0,0 +1,167 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
function IsConstructor(o) {
try {
new (new Proxy(o, {construct: () => ({})}));
return true;
} catch (e) {
return false;
}
}
function IsObject(o) {
return Object(o) === o;
}
function thisValues() {
const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
return [
// Primitive values.
...[undefined, null, true, "abc", Symbol(), 123],
// Object values.
...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
// Intl objects.
...[].concat(...intlConstructors.map(ctor => [
// Instance of an Intl constructor.
new ctor(),
// Instance of a subclassed Intl constructor.
new class extends ctor {},
// Object inheriting from an Intl constructor prototype.
Object.create(ctor.prototype),
// Intl object not inheriting from its default prototype.
Object.setPrototypeOf(new ctor(), Object.prototype),
])),
];
}
const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.DateTimeFormat.call(Object.create(Intl.DateTimeFormat.prototype)))[0];
// Invoking [[Call]] for Intl.DateTimeFormat returns a new instance unless called
// with an instance inheriting from Intl.DateTimeFormat.prototype.
for (let thisValue of thisValues()) {
let obj = Intl.DateTimeFormat.call(thisValue);
if (!Intl.DateTimeFormat.prototype.isPrototypeOf(thisValue)) {
assertEq(Object.is(obj, thisValue), false);
assertEq(obj instanceof Intl.DateTimeFormat, true);
if (IsObject(thisValue))
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
} else {
assertEq(Object.is(obj, thisValue), true);
assertEq(obj instanceof Intl.DateTimeFormat, true);
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
}
}
// Intl.DateTimeFormat uses the legacy Intl constructor compromise semantics.
// - Test when InstanceofOperator(thisValue, %DateTimeFormat%) returns true.
for (let thisValue of thisValues()) {
let hasInstanceCalled = false;
Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
value() {
assertEq(hasInstanceCalled, false);
hasInstanceCalled = true;
return true;
}, configurable: true
});
if (!IsObject(thisValue)) {
// A TypeError is thrown when Intl.DateTimeFormat tries to install the
// [[FallbackSymbol]] property on |thisValue|.
assertThrowsInstanceOf(() => Intl.DateTimeFormat.call(thisValue), TypeError);
delete Intl.DateTimeFormat[Symbol.hasInstance];
} else {
let obj = Intl.DateTimeFormat.call(thisValue);
delete Intl.DateTimeFormat[Symbol.hasInstance];
assertEq(Object.is(obj, thisValue), true);
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
}
assertEq(hasInstanceCalled, true);
}
// - Test when InstanceofOperator(thisValue, %DateTimeFormat%) returns false.
for (let thisValue of thisValues()) {
let hasInstanceCalled = false;
Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
value() {
assertEq(hasInstanceCalled, false);
hasInstanceCalled = true;
return false;
}, configurable: true
});
let obj = Intl.DateTimeFormat.call(thisValue);
delete Intl.DateTimeFormat[Symbol.hasInstance];
assertEq(Object.is(obj, thisValue), false);
assertEq(obj instanceof Intl.DateTimeFormat, true);
if (IsObject(thisValue))
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
assertEq(hasInstanceCalled, true);
}
// Throws an error when attempting to install [[FallbackSymbol]] twice.
{
let thisValue = Object.create(Intl.DateTimeFormat.prototype);
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
assertEq(Intl.DateTimeFormat.call(thisValue), thisValue);
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
assertThrowsInstanceOf(() => Intl.DateTimeFormat.call(thisValue), TypeError);
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
}
// Throws an error when the thisValue is non-extensible.
{
let thisValue = Object.create(Intl.DateTimeFormat.prototype);
Object.preventExtensions(thisValue);
assertThrowsInstanceOf(() => Intl.DateTimeFormat.call(thisValue), TypeError);
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
}
// [[FallbackSymbol]] is installed as a frozen property holding an Intl.DateTimeFormat instance.
{
let thisValue = Object.create(Intl.DateTimeFormat.prototype);
Intl.DateTimeFormat.call(thisValue);
let desc = Object.getOwnPropertyDescriptor(thisValue, intlFallbackSymbol);
assertEq(desc !== undefined, true);
assertEq(desc.writable, false);
assertEq(desc.enumerable, false);
assertEq(desc.configurable, false);
assertEq(desc.value instanceof Intl.DateTimeFormat, true);
}
// Ensure [[FallbackSymbol]] is installed last by changing the [[Prototype]]
// during initialization.
{
let thisValue = {};
let options = {
get hour12() {
Object.setPrototypeOf(thisValue, Intl.DateTimeFormat.prototype);
return false;
}
};
let obj = Intl.DateTimeFormat.call(thisValue, undefined, options);
assertEq(Object.is(obj, thisValue), true);
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
}
{
let thisValue = Object.create(Intl.DateTimeFormat.prototype);
let options = {
get hour12() {
Object.setPrototypeOf(thisValue, Object.prototype);
return false;
}
};
let obj = Intl.DateTimeFormat.call(thisValue, undefined, options);
assertEq(Object.is(obj, thisValue), false);
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -0,0 +1,145 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
const hourCycleToH12Map = {
"h11": true,
"h12": true,
"h23": false,
"h24": false,
};
for (const key of Object.keys(hourCycleToH12Map)) {
const langTag = "en-US";
const loc = `${langTag}-u-hc-${key}`;
const dtf = new Intl.DateTimeFormat(loc, {hour: "numeric"});
const dtf2 = new Intl.DateTimeFormat(langTag, {hour: "numeric", hourCycle: key});
assertEq(dtf.resolvedOptions().hourCycle, dtf2.resolvedOptions().hourCycle);
}
/* Legacy hour12 compatibility */
// When constructed with hourCycle option, resolvedOptions' hour12 is correct.
for (const key of Object.keys(hourCycleToH12Map)) {
const dtf = new Intl.DateTimeFormat("en-US", {hour: "numeric", hourCycle: key});
assertEq(dtf.resolvedOptions().hour12, hourCycleToH12Map[key]);
}
// When constructed with hour12 option, resolvedOptions' hourCycle is correct
for (const [key, value] of Object.entries(hourCycleToH12Map)) {
const dtf = new Intl.DateTimeFormat("en-US", {hour: "numeric", hour12: value});
assertEq(hourCycleToH12Map[dtf.resolvedOptions().hourCycle], value);
}
// When constructed with both hour12 and hourCycle options that don't match
// hour12 takes a precedence.
for (const [key, value] of Object.entries(hourCycleToH12Map)) {
const dtf = new Intl.DateTimeFormat("en-US", {
hour: "numeric",
hourCycle: key,
hour12: !value
});
assertEq(hourCycleToH12Map[dtf.resolvedOptions().hourCycle], !value);
assertEq(dtf.resolvedOptions().hour12, !value);
}
// When constructed with hourCycle as extkey, resolvedOptions' hour12 is correct.
for (const [key, value] of Object.entries(hourCycleToH12Map)) {
const langTag = "en-US";
const loc = `${langTag}-u-hc-${key}`;
const dtf = new Intl.DateTimeFormat(loc, {hour: "numeric"});
assertEq(dtf.resolvedOptions().hour12, value);
}
const expectedValuesENUS = {
h11: "0 AM",
h12: "12 AM",
h23: "00",
h24: "24"
};
const exampleDate = new Date(2017, 10-1, 10, 0);
for (const [key, val] of Object.entries(expectedValuesENUS)) {
assertEq(
Intl.DateTimeFormat("en-US", {hour: "numeric", hourCycle: key}).format(exampleDate),
val
);
}
const invalidHourCycleValues = [
"h5",
"h0",
"h28",
"f28",
"23",
];
for (const key of invalidHourCycleValues) {
const langTag = "en-US";
const loc = `${langTag}-u-hc-${key}`;
const dtf = new Intl.DateTimeFormat(loc, {hour: "numeric"});
assertEq(dtf.resolvedOptions().hour12, true); // default value for en-US
assertEq(dtf.resolvedOptions().hourCycle, "h12"); //default value for en-US
}
{
// hourCycle is not present in resolvedOptions when the formatter has no hour field
const options = Intl.DateTimeFormat("en-US", {hourCycle:"h11"}).resolvedOptions();
assertEq("hourCycle" in options, false);
assertEq("hour12" in options, false);
}
{
// Make sure that hourCycle option overrides the unicode extension
let dtf = Intl.DateTimeFormat("en-US-u-hc-h23", {hourCycle: "h24", hour: "numeric"});
assertEq(
dtf.resolvedOptions().hourCycle,
"h24"
);
}
{
// Make sure that hour12 option overrides the unicode extension
let dtf = Intl.DateTimeFormat("en-US-u-hc-h23", {hour12: true, hour: "numeric"});
assertEq(
dtf.resolvedOptions().hourCycle,
"h12"
);
}
{
// Make sure that hour12 option overrides hourCycle options
let dtf = Intl.DateTimeFormat("en-US",
{hourCycle: "h12", hour12: false, hour: "numeric"});
assertEq(
dtf.resolvedOptions().hourCycle,
"h23"
);
}
{
// Make sure that hour12 option overrides hourCycle options
let dtf = Intl.DateTimeFormat("en-u-hc-h11", {hour: "numeric"});
assertEq(
dtf.resolvedOptions().locale,
"en-u-hc-h11"
);
}
{
// Make sure that hour12 option overrides unicode extension
let dtf = Intl.DateTimeFormat("en-u-hc-h11", {hour: "numeric", hourCycle: "h24"});
assertEq(
dtf.resolvedOptions().locale,
"en"
);
assertEq(
dtf.resolvedOptions().hourCycle,
"h24"
);
}
if (typeof reportCompare === "function")
reportCompare(0, 0, "ok");

View file

@ -0,0 +1,58 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Tests the format function with a diverse set of locales and options.
// Always use UTC to avoid dependencies on test environment.
let mozIntl = {};
addIntlExtras(mozIntl);
// Pattern
var dtf = new Intl.DateTimeFormat("en-US", {pattern: "HH:mm MM/dd/YYYY"});
var mozDtf = new mozIntl.DateTimeFormat("en-US", {pattern: "HH:mm MM/dd/YYYY"});
assertEq(dtf.resolvedOptions().hasOwnProperty('pattern'), false);
assertEq(mozDtf.resolvedOptions().pattern, "HH:mm MM/dd/YYYY");
// Date style
var dtf = new Intl.DateTimeFormat("en-US", {dateStyle: 'long'});
assertEq(mozDtf.resolvedOptions().hasOwnProperty('dateStyle'), false);
var mozDtf = new mozIntl.DateTimeFormat("en-US", {dateStyle: 'long'});
assertEq(mozDtf.resolvedOptions().dateStyle, 'long');
assertEq(mozDtf.resolvedOptions().hasOwnProperty('year'), true);
assertEq(mozDtf.resolvedOptions().hasOwnProperty('month'), true);
assertEq(mozDtf.resolvedOptions().hasOwnProperty('day'), true);
// Time style
var dtf = new Intl.DateTimeFormat("en-US", {timeStyle: 'long'});
assertEq(dtf.resolvedOptions().hasOwnProperty('dateStyle'), false);
var mozDtf = new mozIntl.DateTimeFormat("en-US", {timeStyle: 'long'});
assertEq(mozDtf.resolvedOptions().timeStyle, 'long');
assertEq(mozDtf.resolvedOptions().hasOwnProperty('hour'), true);
assertEq(mozDtf.resolvedOptions().hasOwnProperty('minute'), true);
assertEq(mozDtf.resolvedOptions().hasOwnProperty('second'), true);
// Date/Time style
var dtf = new Intl.DateTimeFormat("en-US", {timeStyle: 'medium', dateStyle: 'medium'});
assertEq(dtf.resolvedOptions().hasOwnProperty('dateStyle'), false);
assertEq(dtf.resolvedOptions().hasOwnProperty('timeStyle'), false);
var mozDtf = new mozIntl.DateTimeFormat("en-US", {dateStyle: 'medium', timeStyle: 'medium'});
assertEq(mozDtf.resolvedOptions().timeStyle, 'medium');
assertEq(mozDtf.resolvedOptions().dateStyle, 'medium');
assertEq(mozDtf.resolvedOptions().hasOwnProperty('hour'), true);
assertEq(mozDtf.resolvedOptions().hasOwnProperty('minute'), true);
assertEq(mozDtf.resolvedOptions().hasOwnProperty('second'), true);
assertEq(mozDtf.resolvedOptions().hasOwnProperty('year'), true);
assertEq(mozDtf.resolvedOptions().hasOwnProperty('month'), true);
assertEq(mozDtf.resolvedOptions().hasOwnProperty('day'), true);
reportCompare(0, 0, 'ok');

View file

@ -0,0 +1,224 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
// Test UnwrapDateTimeFormat operation.
const dateTimeFormatFunctions = [];
dateTimeFormatFunctions.push(Intl.DateTimeFormat.prototype.resolvedOptions);
dateTimeFormatFunctions.push(Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, "format").get);
dateTimeFormatFunctions.push(Intl.DateTimeFormat.prototype.formatToParts);
function IsConstructor(o) {
try {
new (new Proxy(o, {construct: () => ({})}));
return true;
} catch (e) {
return false;
}
}
function IsObject(o) {
return Object(o) === o;
}
function intlObjects(ctor) {
return [
// Instance of an Intl constructor.
new ctor(),
// Instance of a subclassed Intl constructor.
new class extends ctor {},
// Intl object not inheriting from its default prototype.
Object.setPrototypeOf(new ctor(), Object.prototype),
];
}
function thisValues(C) {
const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
return [
// Primitive values.
...[undefined, null, true, "abc", Symbol(), 123],
// Object values.
...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
// Intl objects.
...[].concat(...intlConstructors.filter(ctor => ctor !== C).map(intlObjects)),
// Object inheriting from an Intl constructor prototype.
...intlConstructors.map(ctor => Object.create(ctor.prototype)),
];
}
const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.DateTimeFormat.call(Object.create(Intl.DateTimeFormat.prototype)))[0];
// Test Intl.DateTimeFormat.prototype methods.
for (let dateTimeFormatFunction of dateTimeFormatFunctions) {
// Test a TypeError is thrown when the this-value isn't an initialized
// Intl.DateTimeFormat instance.
for (let thisValue of thisValues(Intl.DateTimeFormat)) {
assertThrowsInstanceOf(() => dateTimeFormatFunction.call(thisValue), TypeError);
}
// And test no error is thrown for initialized Intl.DateTimeFormat instances.
for (let thisValue of intlObjects(Intl.DateTimeFormat)) {
dateTimeFormatFunction.call(thisValue);
}
// Manually add [[FallbackSymbol]] to objects and then repeat the tests from above.
for (let thisValue of thisValues(Intl.DateTimeFormat)) {
assertThrowsInstanceOf(() => dateTimeFormatFunction.call({
__proto__: Intl.DateTimeFormat.prototype,
[intlFallbackSymbol]: thisValue,
}), TypeError);
}
for (let thisValue of intlObjects(Intl.DateTimeFormat)) {
dateTimeFormatFunction.call({
__proto__: Intl.DateTimeFormat.prototype,
[intlFallbackSymbol]: thisValue,
});
}
// Ensure [[FallbackSymbol]] isn't retrieved for Intl.DateTimeFormat instances.
for (let thisValue of intlObjects(Intl.DateTimeFormat)) {
Object.defineProperty(thisValue, intlFallbackSymbol, {
get() { assertEq(false, true); }
});
dateTimeFormatFunction.call(thisValue);
}
// Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.DateTimeFormat.prototype.
for (let thisValue of thisValues(Intl.DateTimeFormat)) {
if (!IsObject(thisValue) || Intl.DateTimeFormat.prototype.isPrototypeOf(thisValue))
continue;
Object.defineProperty(thisValue, intlFallbackSymbol, {
get() { assertEq(false, true); }
});
assertThrowsInstanceOf(() => dateTimeFormatFunction.call(thisValue), TypeError);
}
// Repeat the test from above, but also change Intl.DateTimeFormat[@@hasInstance]
// so it always returns |null|.
for (let thisValue of thisValues(Intl.DateTimeFormat)) {
let hasInstanceCalled = false, symbolGetterCalled = false;
Object.defineProperty(Intl.DateTimeFormat, Symbol.hasInstance, {
value() {
assertEq(hasInstanceCalled, false);
hasInstanceCalled = true;
return true;
}, configurable: true
});
let isUndefinedOrNull = thisValue !== undefined || thisValue !== null;
let symbolHolder;
if (!isUndefinedOrNull) {
symbolHolder = IsObject(thisValue) ? thisValue : Object.getPrototypeOf(thisValue);
Object.defineProperty(symbolHolder, intlFallbackSymbol, {
get() {
assertEq(symbolGetterCalled, false);
symbolGetterCalled = true;
return null;
}, configurable: true
});
}
assertThrowsInstanceOf(() => dateTimeFormatFunction.call(thisValue), TypeError);
delete Intl.DateTimeFormat[Symbol.hasInstance];
if (!isUndefinedOrNull && !IsObject(thisValue))
delete symbolHolder[intlFallbackSymbol];
assertEq(hasInstanceCalled, true);
assertEq(symbolGetterCalled, !isUndefinedOrNull);
}
}
// Test format() returns the correct result for objects initialized as Intl.DateTimeFormat instances.
{
// An actual Intl.DateTimeFormat instance.
let dateTimeFormat = new Intl.DateTimeFormat();
// An object initialized as a DateTimeFormat instance.
let thisValue = Object.create(Intl.DateTimeFormat.prototype);
Intl.DateTimeFormat.call(thisValue);
// Object with [[FallbackSymbol]] set to DateTimeFormat instance.
let fakeObj = {
__proto__: Intl.DateTimeFormat.prototype,
[intlFallbackSymbol]: dateTimeFormat,
};
for (let number of [0, Date.now(), -Date.now()]) {
let expected = dateTimeFormat.format(number);
assertEq(thisValue.format(number), expected);
assertEq(thisValue[intlFallbackSymbol].format(number), expected);
assertEq(fakeObj.format(number), expected);
}
}
// Test formatToParts() returns the correct result for objects initialized as Intl.DateTimeFormat instances.
if ("formatToParts" in Intl.DateTimeFormat.prototype) {
// An actual Intl.DateTimeFormat instance.
let dateTimeFormat = new Intl.DateTimeFormat();
// An object initialized as a DateTimeFormat instance.
let thisValue = Object.create(Intl.DateTimeFormat.prototype);
Intl.DateTimeFormat.call(thisValue);
// Object with [[FallbackSymbol]] set to DateTimeFormat instance.
let fakeObj = {
__proto__: Intl.DateTimeFormat.prototype,
[intlFallbackSymbol]: dateTimeFormat,
};
function assertEqParts(actual, expected) {
assertEq(actual.length, expected.length, "parts count mismatch");
for (var i = 0; i < expected.length; i++) {
assertEq(actual[i].type, expected[i].type, "type mismatch at " + i);
assertEq(actual[i].value, expected[i].value, "value mismatch at " + i);
}
}
for (let number of [0, Date.now(), -Date.now()]) {
let expected = dateTimeFormat.formatToParts(number);
assertEqParts(thisValue.formatToParts(number), expected);
assertEqParts(thisValue[intlFallbackSymbol].formatToParts(number), expected);
assertEqParts(fakeObj.formatToParts(number), expected);
}
}
// Test resolvedOptions() returns the same results.
{
// An actual Intl.DateTimeFormat instance.
let dateTimeFormat = new Intl.DateTimeFormat();
// An object initialized as a DateTimeFormat instance.
let thisValue = Object.create(Intl.DateTimeFormat.prototype);
Intl.DateTimeFormat.call(thisValue);
// Object with [[FallbackSymbol]] set to DateTimeFormat instance.
let fakeObj = {
__proto__: Intl.DateTimeFormat.prototype,
[intlFallbackSymbol]: dateTimeFormat,
};
function assertEqOptions(actual, expected) {
actual = Object.entries(actual);
expected = Object.entries(expected);
assertEq(actual.length, expected.length, "options count mismatch");
for (var i = 0; i < expected.length; i++) {
assertEq(actual[i][0], expected[i][0], "key mismatch at " + i);
assertEq(actual[i][1], expected[i][1], "value mismatch at " + i);
}
}
let expected = dateTimeFormat.resolvedOptions();
assertEqOptions(thisValue.resolvedOptions(), expected);
assertEqOptions(thisValue[intlFallbackSymbol].resolvedOptions(), expected);
assertEqOptions(fakeObj.resolvedOptions(), expected);
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -0,0 +1,167 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
function IsConstructor(o) {
try {
new (new Proxy(o, {construct: () => ({})}));
return true;
} catch (e) {
return false;
}
}
function IsObject(o) {
return Object(o) === o;
}
function thisValues() {
const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
return [
// Primitive values.
...[undefined, null, true, "abc", Symbol(), 123],
// Object values.
...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
// Intl objects.
...[].concat(...intlConstructors.map(ctor => [
// Instance of an Intl constructor.
new ctor(),
// Instance of a subclassed Intl constructor.
new class extends ctor {},
// Object inheriting from an Intl constructor prototype.
Object.create(ctor.prototype),
// Intl object not inheriting from its default prototype.
Object.setPrototypeOf(new ctor(), Object.prototype),
])),
];
}
const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0];
// Invoking [[Call]] for Intl.NumberFormat returns a new instance unless called
// with an instance inheriting from Intl.NumberFormat.prototype.
for (let thisValue of thisValues()) {
let obj = Intl.NumberFormat.call(thisValue);
if (!Intl.NumberFormat.prototype.isPrototypeOf(thisValue)) {
assertEq(Object.is(obj, thisValue), false);
assertEq(obj instanceof Intl.NumberFormat, true);
if (IsObject(thisValue))
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
} else {
assertEq(Object.is(obj, thisValue), true);
assertEq(obj instanceof Intl.NumberFormat, true);
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
}
}
// Intl.NumberFormat uses the legacy Intl constructor compromise semantics.
// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns true.
for (let thisValue of thisValues()) {
let hasInstanceCalled = false;
Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
value() {
assertEq(hasInstanceCalled, false);
hasInstanceCalled = true;
return true;
}, configurable: true
});
if (!IsObject(thisValue)) {
// A TypeError is thrown when Intl.NumberFormat tries to install the
// [[FallbackSymbol]] property on |thisValue|.
assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
delete Intl.NumberFormat[Symbol.hasInstance];
} else {
let obj = Intl.NumberFormat.call(thisValue);
delete Intl.NumberFormat[Symbol.hasInstance];
assertEq(Object.is(obj, thisValue), true);
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
}
assertEq(hasInstanceCalled, true);
}
// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns false.
for (let thisValue of thisValues()) {
let hasInstanceCalled = false;
Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
value() {
assertEq(hasInstanceCalled, false);
hasInstanceCalled = true;
return false;
}, configurable: true
});
let obj = Intl.NumberFormat.call(thisValue);
delete Intl.NumberFormat[Symbol.hasInstance];
assertEq(Object.is(obj, thisValue), false);
assertEq(obj instanceof Intl.NumberFormat, true);
if (IsObject(thisValue))
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
assertEq(hasInstanceCalled, true);
}
// Throws an error when attempting to install [[FallbackSymbol]] twice.
{
let thisValue = Object.create(Intl.NumberFormat.prototype);
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
assertEq(Intl.NumberFormat.call(thisValue), thisValue);
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
}
// Throws an error when the thisValue is non-extensible.
{
let thisValue = Object.create(Intl.NumberFormat.prototype);
Object.preventExtensions(thisValue);
assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
}
// [[FallbackSymbol]] is installed as a frozen property holding an Intl.NumberFormat instance.
{
let thisValue = Object.create(Intl.NumberFormat.prototype);
Intl.NumberFormat.call(thisValue);
let desc = Object.getOwnPropertyDescriptor(thisValue, intlFallbackSymbol);
assertEq(desc !== undefined, true);
assertEq(desc.writable, false);
assertEq(desc.enumerable, false);
assertEq(desc.configurable, false);
assertEq(desc.value instanceof Intl.NumberFormat, true);
}
// Ensure [[FallbackSymbol]] is installed last by changing the [[Prototype]]
// during initialization.
{
let thisValue = {};
let options = {
get useGrouping() {
Object.setPrototypeOf(thisValue, Intl.NumberFormat.prototype);
return false;
}
};
let obj = Intl.NumberFormat.call(thisValue, undefined, options);
assertEq(Object.is(obj, thisValue), true);
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
}
{
let thisValue = Object.create(Intl.NumberFormat.prototype);
let options = {
get useGrouping() {
Object.setPrototypeOf(thisValue, Object.prototype);
return false;
}
};
let obj = Intl.NumberFormat.call(thisValue, undefined, options);
assertEq(Object.is(obj, thisValue), false);
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -0,0 +1,226 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
// Test UnwrapNumberFormat operation.
const numberFormatFunctions = [];
numberFormatFunctions.push(Intl.NumberFormat.prototype.resolvedOptions);
numberFormatFunctions.push(Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get);
// "formatToParts" isn't yet enabled by default.
if ("formatToParts" in Intl.NumberFormat.prototype)
numberFormatFunctions.push(Intl.NumberFormat.prototype.formatToParts);
function IsConstructor(o) {
try {
new (new Proxy(o, {construct: () => ({})}));
return true;
} catch (e) {
return false;
}
}
function IsObject(o) {
return Object(o) === o;
}
function intlObjects(ctor) {
return [
// Instance of an Intl constructor.
new ctor(),
// Instance of a subclassed Intl constructor.
new class extends ctor {},
// Intl object not inheriting from its default prototype.
Object.setPrototypeOf(new ctor(), Object.prototype),
];
}
function thisValues(C) {
const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
return [
// Primitive values.
...[undefined, null, true, "abc", Symbol(), 123],
// Object values.
...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
// Intl objects.
...[].concat(...intlConstructors.filter(ctor => ctor !== C).map(intlObjects)),
// Object inheriting from an Intl constructor prototype.
...intlConstructors.map(ctor => Object.create(ctor.prototype)),
];
}
const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0];
// Test Intl.NumberFormat.prototype methods.
for (let numberFormatFunction of numberFormatFunctions) {
// Test a TypeError is thrown when the this-value isn't an initialized
// Intl.NumberFormat instance.
for (let thisValue of thisValues(Intl.NumberFormat)) {
assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
}
// And test no error is thrown for initialized Intl.NumberFormat instances.
for (let thisValue of intlObjects(Intl.NumberFormat)) {
numberFormatFunction.call(thisValue);
}
// Manually add [[FallbackSymbol]] to objects and then repeat the tests from above.
for (let thisValue of thisValues(Intl.NumberFormat)) {
assertThrowsInstanceOf(() => numberFormatFunction.call({
__proto__: Intl.NumberFormat.prototype,
[intlFallbackSymbol]: thisValue,
}), TypeError);
}
for (let thisValue of intlObjects(Intl.NumberFormat)) {
numberFormatFunction.call({
__proto__: Intl.NumberFormat.prototype,
[intlFallbackSymbol]: thisValue,
});
}
// Ensure [[FallbackSymbol]] isn't retrieved for Intl.NumberFormat instances.
for (let thisValue of intlObjects(Intl.NumberFormat)) {
Object.defineProperty(thisValue, intlFallbackSymbol, {
get() { assertEq(false, true); }
});
numberFormatFunction.call(thisValue);
}
// Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.NumberFormat.prototype.
for (let thisValue of thisValues(Intl.NumberFormat)) {
if (!IsObject(thisValue) || Intl.NumberFormat.prototype.isPrototypeOf(thisValue))
continue;
Object.defineProperty(thisValue, intlFallbackSymbol, {
get() { assertEq(false, true); }
});
assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
}
// Repeat the test from above, but also change Intl.NumberFormat[@@hasInstance]
// so it always returns |null|.
for (let thisValue of thisValues(Intl.NumberFormat)) {
let hasInstanceCalled = false, symbolGetterCalled = false;
Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
value() {
assertEq(hasInstanceCalled, false);
hasInstanceCalled = true;
return true;
}, configurable: true
});
let isUndefinedOrNull = thisValue !== undefined || thisValue !== null;
let symbolHolder;
if (!isUndefinedOrNull) {
symbolHolder = IsObject(thisValue) ? thisValue : Object.getPrototypeOf(thisValue);
Object.defineProperty(symbolHolder, intlFallbackSymbol, {
get() {
assertEq(symbolGetterCalled, false);
symbolGetterCalled = true;
return null;
}, configurable: true
});
}
assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
delete Intl.NumberFormat[Symbol.hasInstance];
if (!isUndefinedOrNull && !IsObject(thisValue))
delete symbolHolder[intlFallbackSymbol];
assertEq(hasInstanceCalled, true);
assertEq(symbolGetterCalled, !isUndefinedOrNull);
}
}
// Test format() returns the correct result for objects initialized as Intl.NumberFormat instances.
{
// An actual Intl.NumberFormat instance.
let numberFormat = new Intl.NumberFormat();
// An object initialized as a NumberFormat instance.
let thisValue = Object.create(Intl.NumberFormat.prototype);
Intl.NumberFormat.call(thisValue);
// Object with [[FallbackSymbol]] set to NumberFormat instance.
let fakeObj = {
__proto__: Intl.NumberFormat.prototype,
[intlFallbackSymbol]: numberFormat,
};
for (let number of [0, 1, 1.5, Infinity, NaN]) {
let expected = numberFormat.format(number);
assertEq(thisValue.format(number), expected);
assertEq(thisValue[intlFallbackSymbol].format(number), expected);
assertEq(fakeObj.format(number), expected);
}
}
// Test formatToParts() returns the correct result for objects initialized as Intl.NumberFormat instances.
if ("formatToParts" in Intl.NumberFormat.prototype) {
// An actual Intl.NumberFormat instance.
let numberFormat = new Intl.NumberFormat();
// An object initialized as a NumberFormat instance.
let thisValue = Object.create(Intl.NumberFormat.prototype);
Intl.NumberFormat.call(thisValue);
// Object with [[FallbackSymbol]] set to NumberFormat instance.
let fakeObj = {
__proto__: Intl.NumberFormat.prototype,
[intlFallbackSymbol]: numberFormat,
};
function assertEqParts(actual, expected) {
assertEq(actual.length, expected.length, "parts count mismatch");
for (var i = 0; i < expected.length; i++) {
assertEq(actual[i].type, expected[i].type, "type mismatch at " + i);
assertEq(actual[i].value, expected[i].value, "value mismatch at " + i);
}
}
for (let number of [0, 1, 1.5, Infinity, NaN]) {
let expected = numberFormat.formatToParts(number);
assertEqParts(thisValue.formatToParts(number), expected);
assertEqParts(thisValue[intlFallbackSymbol].formatToParts(number), expected);
assertEqParts(fakeObj.formatToParts(number), expected);
}
}
// Test resolvedOptions() returns the same results.
{
// An actual Intl.NumberFormat instance.
let numberFormat = new Intl.NumberFormat();
// An object initialized as a NumberFormat instance.
let thisValue = Object.create(Intl.NumberFormat.prototype);
Intl.NumberFormat.call(thisValue);
// Object with [[FallbackSymbol]] set to NumberFormat instance.
let fakeObj = {
__proto__: Intl.NumberFormat.prototype,
[intlFallbackSymbol]: numberFormat,
};
function assertEqOptions(actual, expected) {
actual = Object.entries(actual);
expected = Object.entries(expected);
assertEq(actual.length, expected.length, "options count mismatch");
for (var i = 0; i < expected.length; i++) {
assertEq(actual[i][0], expected[i][0], "key mismatch at " + i);
assertEq(actual[i][1], expected[i][1], "value mismatch at " + i);
}
}
let expected = numberFormat.resolvedOptions();
assertEqOptions(thisValue.resolvedOptions(), expected);
assertEqOptions(thisValue[intlFallbackSymbol].resolvedOptions(), expected);
assertEqOptions(fakeObj.resolvedOptions(), expected);
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -0,0 +1,71 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
addIntlExtras(Intl);
function IsConstructor(o) {
try {
new (new Proxy(o, {construct: () => ({})}));
return true;
} catch (e) {
return false;
}
}
function IsObject(o) {
return Object(o) === o;
}
function thisValues() {
const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
return [
// Primitive values.
...[undefined, null, true, "abc", Symbol(), 123],
// Object values.
...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
// Intl objects.
...[].concat(...intlConstructors.map(ctor => [
// Instance of an Intl constructor.
new ctor(),
// Instance of a subclassed Intl constructor.
new class extends ctor {},
// Object inheriting from an Intl constructor prototype.
Object.create(ctor.prototype),
// Intl object not inheriting from its default prototype.
Object.setPrototypeOf(new ctor(), Object.prototype),
])),
];
}
// Invoking [[Call]] for Intl.PluralRules always returns a new PluralRules instance.
for (let thisValue of thisValues()) {
let obj = Intl.PluralRules.call(thisValue);
assertEq(Object.is(obj, thisValue), false);
assertEq(obj instanceof Intl.PluralRules, true);
// Ensure Intl.[[FallbackSymbol]] wasn't installed on |thisValue|.
if (IsObject(thisValue))
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
}
// Intl.PluralRules doesn't use the legacy Intl constructor compromise semantics.
for (let thisValue of thisValues()) {
// Ensure instanceof operator isn't invoked for Intl.PluralRules.
Object.defineProperty(Intl.PluralRules, Symbol.hasInstance, {
get() {
assertEq(false, true, "@@hasInstance operator called");
}, configurable: true
});
let obj = Intl.PluralRules.call(thisValue);
delete Intl.PluralRules[Symbol.hasInstance];
assertEq(Object.is(obj, thisValue), false);
assertEq(obj instanceof Intl.PluralRules, true);
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

View file

@ -0,0 +1,69 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
// Test language dependent special casing with different language tags.
for (let locale of ["tr", "TR", "tr-TR", "tr-u-co-search", "tr-x-turkish"]) {
assertEq("\u0130".toLocaleLowerCase(locale), "i");
assertEq("\u0130".toLocaleLowerCase([locale]), "i");
// Additional language tags are ignored.
assertEq("\u0130".toLocaleLowerCase([locale, "und"]), "i");
assertEq("\u0130".toLocaleLowerCase(["und", locale]), "\u0069\u0307");
}
// Ensure "trl" (Traveller Scottish) isn't misrecognized as "tr", even though
// both share the same prefix.
assertEq("\u0130".toLocaleLowerCase("trl"), "\u0069\u0307");
assertEq("\u0130".toLocaleLowerCase(["trl"]), "\u0069\u0307");
// Language tag is always verified.
for (let locale of ["no_locale", "tr-invalid_ext", ["no_locale"], ["en", "no_locale"]]) {
// Empty input string.
assertThrowsInstanceOf(() => "".toLocaleLowerCase(locale), RangeError);
// Non-empty input string.
assertThrowsInstanceOf(() => "x".toLocaleLowerCase(locale), RangeError);
}
// The language tag fast-path for String.prototype.toLocaleLowerCase doesn't
// trip up on three element private-use only language tags.
assertEq("A".toLocaleLowerCase("x-x"), "a");
assertEq("A".toLocaleLowerCase("x-0"), "a");
// No locale argument, undefined as locale, and empty array or array-like all
// return the same result. Testing with "a/A" because it has only simple case
// mappings.
assertEq("A".toLocaleLowerCase(), "a");
assertEq("A".toLocaleLowerCase(undefined), "a");
assertEq("A".toLocaleLowerCase([]), "a");
assertEq("A".toLocaleLowerCase({}), "a");
assertEq("A".toLocaleLowerCase({length: 0}), "a");
assertEq("A".toLocaleLowerCase({length: -1}), "a");
// Test with incorrect locale type.
for (let locale of [null, 0, Math.PI, NaN, Infinity, true, false, Symbol()]) {
// Empty input string.
assertThrowsInstanceOf(() => "".toLocaleLowerCase([locale]), TypeError);
// Non-empty input string.
assertThrowsInstanceOf(() => "A".toLocaleLowerCase([locale]), TypeError);
}
// Primitives are converted with ToObject and then queried for .length property.
for (let locale of [null]) {
// Empty input string.
assertThrowsInstanceOf(() => "".toLocaleLowerCase([locale]), TypeError);
// Non-empty input string.
assertThrowsInstanceOf(() => "A".toLocaleLowerCase([locale]), TypeError);
}
// ToLength(ToObject(<primitive>)) returns 0.
for (let locale of [0, Math.PI, NaN, Infinity, true, false, Symbol()]) {
// Empty input string.
assertEq("".toLocaleLowerCase(locale), "");
// Non-empty input string.
assertEq("A".toLocaleLowerCase(locale), "a");
}
if (typeof reportCompare === "function")
reportCompare(0, 0, "ok");

View file

@ -0,0 +1,69 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
// Test language dependent special casing with different language tags.
for (let locale of ["lt", "LT", "lt-LT", "lt-u-co-phonebk", "lt-x-lietuva"]) {
assertEq("i\u0307".toLocaleUpperCase(locale), "I");
assertEq("i\u0307".toLocaleUpperCase([locale]), "I");
// Additional language tags are ignored.
assertEq("i\u0307".toLocaleUpperCase([locale, "und"]), "I");
assertEq("i\u0307".toLocaleUpperCase(["und", locale]), "I\u0307");
}
// Ensure "lti" (Leti) isn't misrecognized as "lt", even though both share the
// same prefix.
assertEq("i\u0307".toLocaleUpperCase("lti"), "I\u0307");
assertEq("i\u0307".toLocaleUpperCase(["lti"]), "I\u0307");
// Language tag is always verified.
for (let locale of ["no_locale", "lt-invalid_ext", ["no_locale"], ["en", "no_locale"]]) {
// Empty input string.
assertThrowsInstanceOf(() => "".toLocaleUpperCase(locale), RangeError);
// Non-empty input string.
assertThrowsInstanceOf(() => "a".toLocaleUpperCase(locale), RangeError);
}
// The language tag fast-path for String.prototype.toLocaleUpperCase doesn't
// trip up on three element private-use only language tags.
assertEq("a".toLocaleUpperCase("x-x"), "A");
assertEq("a".toLocaleUpperCase("x-0"), "A");
// No locale argument, undefined as locale, and empty array or array-like all
// return the same result. Testing with "a/A" because it has only simple case
// mappings.
assertEq("a".toLocaleUpperCase(), "A");
assertEq("a".toLocaleUpperCase(undefined), "A");
assertEq("a".toLocaleUpperCase([]), "A");
assertEq("a".toLocaleUpperCase({}), "A");
assertEq("a".toLocaleUpperCase({length: 0}), "A");
assertEq("a".toLocaleUpperCase({length: -1}), "A");
// Test with incorrect locale type.
for (let locale of [null, 0, Math.PI, NaN, Infinity, true, false, Symbol()]) {
// Empty input string.
assertThrowsInstanceOf(() => "".toLocaleUpperCase([locale]), TypeError);
// Non-empty input string.
assertThrowsInstanceOf(() => "a".toLocaleUpperCase([locale]), TypeError);
}
// Primitives are converted with ToObject and then queried for .length property.
for (let locale of [null]) {
// Empty input string.
assertThrowsInstanceOf(() => "".toLocaleUpperCase([locale]), TypeError);
// Non-empty input string.
assertThrowsInstanceOf(() => "a".toLocaleUpperCase([locale]), TypeError);
}
// ToLength(ToObject(<primitive>)) returns 0.
for (let locale of [0, Math.PI, NaN, Infinity, true, false, Symbol()]) {
// Empty input string.
assertEq("".toLocaleUpperCase(locale), "");
// Non-empty input string.
assertEq("a".toLocaleUpperCase(locale), "A");
}
if (typeof reportCompare === "function")
reportCompare(0, 0, "ok");

View file

@ -35,6 +35,9 @@ writeHeaderToLog( SECTION + " "+ TITLE);
// Armenian
// Range: U+0530 to U+058F
for ( var i = 0x0530; i <= 0x058F; i++ ) {
// U+0587 (ARMENIAN SMALL LIGATURE ECH YIWN) has special upper casing.
if (i == 0x0587) continue;
var U = new Unicode( i );
/*
new TestCase( SECTION,

View file

@ -5,7 +5,33 @@
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
var onlySpace = String.fromCharCode(0x9, 0xa, 0xb, 0xc, 0xd, 0x20, 0xa0, 0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a, 0x2028, 0x2029, 0x202f, 0x205f, 0x3000, 0xfeff);
var onlySpace = String.fromCharCode(
0x0009 /* <control> (CHARACTER TABULATION) */,
0x000A /* <control> (LINE FEED (LF)) */,
0x000B /* <control> (LINE TABULATION) */,
0x000C /* <control> (FORM FEED (FF)) */,
0x000D /* <control> (CARRIAGE RETURN (CR)) */,
0x0020 /* SPACE */,
0x00A0 /* NO-BREAK SPACE (NON-BREAKING SPACE) */,
0x1680 /* OGHAM SPACE MARK */,
0x2000 /* EN QUAD */,
0x2001 /* EM QUAD */,
0x2002 /* EN SPACE */,
0x2003 /* EM SPACE */,
0x2004 /* THREE-PER-EM SPACE */,
0x2005 /* FOUR-PER-EM SPACE */,
0x2006 /* SIX-PER-EM SPACE */,
0x2007 /* FIGURE SPACE */,
0x2008 /* PUNCTUATION SPACE */,
0x2009 /* THIN SPACE */,
0x200A /* HAIR SPACE */,
0x2028 /* LINE SEPARATOR */,
0x2029 /* PARAGRAPH SEPARATOR */,
0x202F /* NARROW NO-BREAK SPACE */,
0x205F /* MEDIUM MATHEMATICAL SPACE */,
0x3000 /* IDEOGRAPHIC SPACE */,
0xFEFF /* ZERO WIDTH NO-BREAK SPACE (BYTE ORDER MARK) */
);
assertEq(onlySpace.trim(), "");
assertEq((onlySpace + 'aaaa').trim(), 'aaaa');

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -5,456 +5,456 @@
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
assertEq(String.fromCodePoint(0x10428).toUpperCase().codePointAt(0), 0x10400);
assertEq(String.fromCodePoint(0x10429).toUpperCase().codePointAt(0), 0x10401);
assertEq(String.fromCodePoint(0x1042a).toUpperCase().codePointAt(0), 0x10402);
assertEq(String.fromCodePoint(0x1042b).toUpperCase().codePointAt(0), 0x10403);
assertEq(String.fromCodePoint(0x1042c).toUpperCase().codePointAt(0), 0x10404);
assertEq(String.fromCodePoint(0x1042d).toUpperCase().codePointAt(0), 0x10405);
assertEq(String.fromCodePoint(0x1042e).toUpperCase().codePointAt(0), 0x10406);
assertEq(String.fromCodePoint(0x1042f).toUpperCase().codePointAt(0), 0x10407);
assertEq(String.fromCodePoint(0x10430).toUpperCase().codePointAt(0), 0x10408);
assertEq(String.fromCodePoint(0x10431).toUpperCase().codePointAt(0), 0x10409);
assertEq(String.fromCodePoint(0x10432).toUpperCase().codePointAt(0), 0x1040a);
assertEq(String.fromCodePoint(0x10433).toUpperCase().codePointAt(0), 0x1040b);
assertEq(String.fromCodePoint(0x10434).toUpperCase().codePointAt(0), 0x1040c);
assertEq(String.fromCodePoint(0x10435).toUpperCase().codePointAt(0), 0x1040d);
assertEq(String.fromCodePoint(0x10436).toUpperCase().codePointAt(0), 0x1040e);
assertEq(String.fromCodePoint(0x10437).toUpperCase().codePointAt(0), 0x1040f);
assertEq(String.fromCodePoint(0x10438).toUpperCase().codePointAt(0), 0x10410);
assertEq(String.fromCodePoint(0x10439).toUpperCase().codePointAt(0), 0x10411);
assertEq(String.fromCodePoint(0x1043a).toUpperCase().codePointAt(0), 0x10412);
assertEq(String.fromCodePoint(0x1043b).toUpperCase().codePointAt(0), 0x10413);
assertEq(String.fromCodePoint(0x1043c).toUpperCase().codePointAt(0), 0x10414);
assertEq(String.fromCodePoint(0x1043d).toUpperCase().codePointAt(0), 0x10415);
assertEq(String.fromCodePoint(0x1043e).toUpperCase().codePointAt(0), 0x10416);
assertEq(String.fromCodePoint(0x1043f).toUpperCase().codePointAt(0), 0x10417);
assertEq(String.fromCodePoint(0x10440).toUpperCase().codePointAt(0), 0x10418);
assertEq(String.fromCodePoint(0x10441).toUpperCase().codePointAt(0), 0x10419);
assertEq(String.fromCodePoint(0x10442).toUpperCase().codePointAt(0), 0x1041a);
assertEq(String.fromCodePoint(0x10443).toUpperCase().codePointAt(0), 0x1041b);
assertEq(String.fromCodePoint(0x10444).toUpperCase().codePointAt(0), 0x1041c);
assertEq(String.fromCodePoint(0x10445).toUpperCase().codePointAt(0), 0x1041d);
assertEq(String.fromCodePoint(0x10446).toUpperCase().codePointAt(0), 0x1041e);
assertEq(String.fromCodePoint(0x10447).toUpperCase().codePointAt(0), 0x1041f);
assertEq(String.fromCodePoint(0x10448).toUpperCase().codePointAt(0), 0x10420);
assertEq(String.fromCodePoint(0x10449).toUpperCase().codePointAt(0), 0x10421);
assertEq(String.fromCodePoint(0x1044a).toUpperCase().codePointAt(0), 0x10422);
assertEq(String.fromCodePoint(0x1044b).toUpperCase().codePointAt(0), 0x10423);
assertEq(String.fromCodePoint(0x1044c).toUpperCase().codePointAt(0), 0x10424);
assertEq(String.fromCodePoint(0x1044d).toUpperCase().codePointAt(0), 0x10425);
assertEq(String.fromCodePoint(0x1044e).toUpperCase().codePointAt(0), 0x10426);
assertEq(String.fromCodePoint(0x1044f).toUpperCase().codePointAt(0), 0x10427);
assertEq(String.fromCodePoint(0x104d8).toUpperCase().codePointAt(0), 0x104b0);
assertEq(String.fromCodePoint(0x104d9).toUpperCase().codePointAt(0), 0x104b1);
assertEq(String.fromCodePoint(0x104da).toUpperCase().codePointAt(0), 0x104b2);
assertEq(String.fromCodePoint(0x104db).toUpperCase().codePointAt(0), 0x104b3);
assertEq(String.fromCodePoint(0x104dc).toUpperCase().codePointAt(0), 0x104b4);
assertEq(String.fromCodePoint(0x104dd).toUpperCase().codePointAt(0), 0x104b5);
assertEq(String.fromCodePoint(0x104de).toUpperCase().codePointAt(0), 0x104b6);
assertEq(String.fromCodePoint(0x104df).toUpperCase().codePointAt(0), 0x104b7);
assertEq(String.fromCodePoint(0x104e0).toUpperCase().codePointAt(0), 0x104b8);
assertEq(String.fromCodePoint(0x104e1).toUpperCase().codePointAt(0), 0x104b9);
assertEq(String.fromCodePoint(0x104e2).toUpperCase().codePointAt(0), 0x104ba);
assertEq(String.fromCodePoint(0x104e3).toUpperCase().codePointAt(0), 0x104bb);
assertEq(String.fromCodePoint(0x104e4).toUpperCase().codePointAt(0), 0x104bc);
assertEq(String.fromCodePoint(0x104e5).toUpperCase().codePointAt(0), 0x104bd);
assertEq(String.fromCodePoint(0x104e6).toUpperCase().codePointAt(0), 0x104be);
assertEq(String.fromCodePoint(0x104e7).toUpperCase().codePointAt(0), 0x104bf);
assertEq(String.fromCodePoint(0x104e8).toUpperCase().codePointAt(0), 0x104c0);
assertEq(String.fromCodePoint(0x104e9).toUpperCase().codePointAt(0), 0x104c1);
assertEq(String.fromCodePoint(0x104ea).toUpperCase().codePointAt(0), 0x104c2);
assertEq(String.fromCodePoint(0x104eb).toUpperCase().codePointAt(0), 0x104c3);
assertEq(String.fromCodePoint(0x104ec).toUpperCase().codePointAt(0), 0x104c4);
assertEq(String.fromCodePoint(0x104ed).toUpperCase().codePointAt(0), 0x104c5);
assertEq(String.fromCodePoint(0x104ee).toUpperCase().codePointAt(0), 0x104c6);
assertEq(String.fromCodePoint(0x104ef).toUpperCase().codePointAt(0), 0x104c7);
assertEq(String.fromCodePoint(0x104f0).toUpperCase().codePointAt(0), 0x104c8);
assertEq(String.fromCodePoint(0x104f1).toUpperCase().codePointAt(0), 0x104c9);
assertEq(String.fromCodePoint(0x104f2).toUpperCase().codePointAt(0), 0x104ca);
assertEq(String.fromCodePoint(0x104f3).toUpperCase().codePointAt(0), 0x104cb);
assertEq(String.fromCodePoint(0x104f4).toUpperCase().codePointAt(0), 0x104cc);
assertEq(String.fromCodePoint(0x104f5).toUpperCase().codePointAt(0), 0x104cd);
assertEq(String.fromCodePoint(0x104f6).toUpperCase().codePointAt(0), 0x104ce);
assertEq(String.fromCodePoint(0x104f7).toUpperCase().codePointAt(0), 0x104cf);
assertEq(String.fromCodePoint(0x104f8).toUpperCase().codePointAt(0), 0x104d0);
assertEq(String.fromCodePoint(0x104f9).toUpperCase().codePointAt(0), 0x104d1);
assertEq(String.fromCodePoint(0x104fa).toUpperCase().codePointAt(0), 0x104d2);
assertEq(String.fromCodePoint(0x104fb).toUpperCase().codePointAt(0), 0x104d3);
assertEq(String.fromCodePoint(0x10cc0).toUpperCase().codePointAt(0), 0x10c80);
assertEq(String.fromCodePoint(0x10cc1).toUpperCase().codePointAt(0), 0x10c81);
assertEq(String.fromCodePoint(0x10cc2).toUpperCase().codePointAt(0), 0x10c82);
assertEq(String.fromCodePoint(0x10cc3).toUpperCase().codePointAt(0), 0x10c83);
assertEq(String.fromCodePoint(0x10cc4).toUpperCase().codePointAt(0), 0x10c84);
assertEq(String.fromCodePoint(0x10cc5).toUpperCase().codePointAt(0), 0x10c85);
assertEq(String.fromCodePoint(0x10cc6).toUpperCase().codePointAt(0), 0x10c86);
assertEq(String.fromCodePoint(0x10cc7).toUpperCase().codePointAt(0), 0x10c87);
assertEq(String.fromCodePoint(0x10cc8).toUpperCase().codePointAt(0), 0x10c88);
assertEq(String.fromCodePoint(0x10cc9).toUpperCase().codePointAt(0), 0x10c89);
assertEq(String.fromCodePoint(0x10cca).toUpperCase().codePointAt(0), 0x10c8a);
assertEq(String.fromCodePoint(0x10ccb).toUpperCase().codePointAt(0), 0x10c8b);
assertEq(String.fromCodePoint(0x10ccc).toUpperCase().codePointAt(0), 0x10c8c);
assertEq(String.fromCodePoint(0x10ccd).toUpperCase().codePointAt(0), 0x10c8d);
assertEq(String.fromCodePoint(0x10cce).toUpperCase().codePointAt(0), 0x10c8e);
assertEq(String.fromCodePoint(0x10ccf).toUpperCase().codePointAt(0), 0x10c8f);
assertEq(String.fromCodePoint(0x10cd0).toUpperCase().codePointAt(0), 0x10c90);
assertEq(String.fromCodePoint(0x10cd1).toUpperCase().codePointAt(0), 0x10c91);
assertEq(String.fromCodePoint(0x10cd2).toUpperCase().codePointAt(0), 0x10c92);
assertEq(String.fromCodePoint(0x10cd3).toUpperCase().codePointAt(0), 0x10c93);
assertEq(String.fromCodePoint(0x10cd4).toUpperCase().codePointAt(0), 0x10c94);
assertEq(String.fromCodePoint(0x10cd5).toUpperCase().codePointAt(0), 0x10c95);
assertEq(String.fromCodePoint(0x10cd6).toUpperCase().codePointAt(0), 0x10c96);
assertEq(String.fromCodePoint(0x10cd7).toUpperCase().codePointAt(0), 0x10c97);
assertEq(String.fromCodePoint(0x10cd8).toUpperCase().codePointAt(0), 0x10c98);
assertEq(String.fromCodePoint(0x10cd9).toUpperCase().codePointAt(0), 0x10c99);
assertEq(String.fromCodePoint(0x10cda).toUpperCase().codePointAt(0), 0x10c9a);
assertEq(String.fromCodePoint(0x10cdb).toUpperCase().codePointAt(0), 0x10c9b);
assertEq(String.fromCodePoint(0x10cdc).toUpperCase().codePointAt(0), 0x10c9c);
assertEq(String.fromCodePoint(0x10cdd).toUpperCase().codePointAt(0), 0x10c9d);
assertEq(String.fromCodePoint(0x10cde).toUpperCase().codePointAt(0), 0x10c9e);
assertEq(String.fromCodePoint(0x10cdf).toUpperCase().codePointAt(0), 0x10c9f);
assertEq(String.fromCodePoint(0x10ce0).toUpperCase().codePointAt(0), 0x10ca0);
assertEq(String.fromCodePoint(0x10ce1).toUpperCase().codePointAt(0), 0x10ca1);
assertEq(String.fromCodePoint(0x10ce2).toUpperCase().codePointAt(0), 0x10ca2);
assertEq(String.fromCodePoint(0x10ce3).toUpperCase().codePointAt(0), 0x10ca3);
assertEq(String.fromCodePoint(0x10ce4).toUpperCase().codePointAt(0), 0x10ca4);
assertEq(String.fromCodePoint(0x10ce5).toUpperCase().codePointAt(0), 0x10ca5);
assertEq(String.fromCodePoint(0x10ce6).toUpperCase().codePointAt(0), 0x10ca6);
assertEq(String.fromCodePoint(0x10ce7).toUpperCase().codePointAt(0), 0x10ca7);
assertEq(String.fromCodePoint(0x10ce8).toUpperCase().codePointAt(0), 0x10ca8);
assertEq(String.fromCodePoint(0x10ce9).toUpperCase().codePointAt(0), 0x10ca9);
assertEq(String.fromCodePoint(0x10cea).toUpperCase().codePointAt(0), 0x10caa);
assertEq(String.fromCodePoint(0x10ceb).toUpperCase().codePointAt(0), 0x10cab);
assertEq(String.fromCodePoint(0x10cec).toUpperCase().codePointAt(0), 0x10cac);
assertEq(String.fromCodePoint(0x10ced).toUpperCase().codePointAt(0), 0x10cad);
assertEq(String.fromCodePoint(0x10cee).toUpperCase().codePointAt(0), 0x10cae);
assertEq(String.fromCodePoint(0x10cef).toUpperCase().codePointAt(0), 0x10caf);
assertEq(String.fromCodePoint(0x10cf0).toUpperCase().codePointAt(0), 0x10cb0);
assertEq(String.fromCodePoint(0x10cf1).toUpperCase().codePointAt(0), 0x10cb1);
assertEq(String.fromCodePoint(0x10cf2).toUpperCase().codePointAt(0), 0x10cb2);
assertEq(String.fromCodePoint(0x118c0).toUpperCase().codePointAt(0), 0x118a0);
assertEq(String.fromCodePoint(0x118c1).toUpperCase().codePointAt(0), 0x118a1);
assertEq(String.fromCodePoint(0x118c2).toUpperCase().codePointAt(0), 0x118a2);
assertEq(String.fromCodePoint(0x118c3).toUpperCase().codePointAt(0), 0x118a3);
assertEq(String.fromCodePoint(0x118c4).toUpperCase().codePointAt(0), 0x118a4);
assertEq(String.fromCodePoint(0x118c5).toUpperCase().codePointAt(0), 0x118a5);
assertEq(String.fromCodePoint(0x118c6).toUpperCase().codePointAt(0), 0x118a6);
assertEq(String.fromCodePoint(0x118c7).toUpperCase().codePointAt(0), 0x118a7);
assertEq(String.fromCodePoint(0x118c8).toUpperCase().codePointAt(0), 0x118a8);
assertEq(String.fromCodePoint(0x118c9).toUpperCase().codePointAt(0), 0x118a9);
assertEq(String.fromCodePoint(0x118ca).toUpperCase().codePointAt(0), 0x118aa);
assertEq(String.fromCodePoint(0x118cb).toUpperCase().codePointAt(0), 0x118ab);
assertEq(String.fromCodePoint(0x118cc).toUpperCase().codePointAt(0), 0x118ac);
assertEq(String.fromCodePoint(0x118cd).toUpperCase().codePointAt(0), 0x118ad);
assertEq(String.fromCodePoint(0x118ce).toUpperCase().codePointAt(0), 0x118ae);
assertEq(String.fromCodePoint(0x118cf).toUpperCase().codePointAt(0), 0x118af);
assertEq(String.fromCodePoint(0x118d0).toUpperCase().codePointAt(0), 0x118b0);
assertEq(String.fromCodePoint(0x118d1).toUpperCase().codePointAt(0), 0x118b1);
assertEq(String.fromCodePoint(0x118d2).toUpperCase().codePointAt(0), 0x118b2);
assertEq(String.fromCodePoint(0x118d3).toUpperCase().codePointAt(0), 0x118b3);
assertEq(String.fromCodePoint(0x118d4).toUpperCase().codePointAt(0), 0x118b4);
assertEq(String.fromCodePoint(0x118d5).toUpperCase().codePointAt(0), 0x118b5);
assertEq(String.fromCodePoint(0x118d6).toUpperCase().codePointAt(0), 0x118b6);
assertEq(String.fromCodePoint(0x118d7).toUpperCase().codePointAt(0), 0x118b7);
assertEq(String.fromCodePoint(0x118d8).toUpperCase().codePointAt(0), 0x118b8);
assertEq(String.fromCodePoint(0x118d9).toUpperCase().codePointAt(0), 0x118b9);
assertEq(String.fromCodePoint(0x118da).toUpperCase().codePointAt(0), 0x118ba);
assertEq(String.fromCodePoint(0x118db).toUpperCase().codePointAt(0), 0x118bb);
assertEq(String.fromCodePoint(0x118dc).toUpperCase().codePointAt(0), 0x118bc);
assertEq(String.fromCodePoint(0x118dd).toUpperCase().codePointAt(0), 0x118bd);
assertEq(String.fromCodePoint(0x118de).toUpperCase().codePointAt(0), 0x118be);
assertEq(String.fromCodePoint(0x118df).toUpperCase().codePointAt(0), 0x118bf);
assertEq(String.fromCodePoint(0x16e60).toUpperCase().codePointAt(0), 0x16e40);
assertEq(String.fromCodePoint(0x16e61).toUpperCase().codePointAt(0), 0x16e41);
assertEq(String.fromCodePoint(0x16e62).toUpperCase().codePointAt(0), 0x16e42);
assertEq(String.fromCodePoint(0x16e63).toUpperCase().codePointAt(0), 0x16e43);
assertEq(String.fromCodePoint(0x16e64).toUpperCase().codePointAt(0), 0x16e44);
assertEq(String.fromCodePoint(0x16e65).toUpperCase().codePointAt(0), 0x16e45);
assertEq(String.fromCodePoint(0x16e66).toUpperCase().codePointAt(0), 0x16e46);
assertEq(String.fromCodePoint(0x16e67).toUpperCase().codePointAt(0), 0x16e47);
assertEq(String.fromCodePoint(0x16e68).toUpperCase().codePointAt(0), 0x16e48);
assertEq(String.fromCodePoint(0x16e69).toUpperCase().codePointAt(0), 0x16e49);
assertEq(String.fromCodePoint(0x16e6a).toUpperCase().codePointAt(0), 0x16e4a);
assertEq(String.fromCodePoint(0x16e6b).toUpperCase().codePointAt(0), 0x16e4b);
assertEq(String.fromCodePoint(0x16e6c).toUpperCase().codePointAt(0), 0x16e4c);
assertEq(String.fromCodePoint(0x16e6d).toUpperCase().codePointAt(0), 0x16e4d);
assertEq(String.fromCodePoint(0x16e6e).toUpperCase().codePointAt(0), 0x16e4e);
assertEq(String.fromCodePoint(0x16e6f).toUpperCase().codePointAt(0), 0x16e4f);
assertEq(String.fromCodePoint(0x16e70).toUpperCase().codePointAt(0), 0x16e50);
assertEq(String.fromCodePoint(0x16e71).toUpperCase().codePointAt(0), 0x16e51);
assertEq(String.fromCodePoint(0x16e72).toUpperCase().codePointAt(0), 0x16e52);
assertEq(String.fromCodePoint(0x16e73).toUpperCase().codePointAt(0), 0x16e53);
assertEq(String.fromCodePoint(0x16e74).toUpperCase().codePointAt(0), 0x16e54);
assertEq(String.fromCodePoint(0x16e75).toUpperCase().codePointAt(0), 0x16e55);
assertEq(String.fromCodePoint(0x16e76).toUpperCase().codePointAt(0), 0x16e56);
assertEq(String.fromCodePoint(0x16e77).toUpperCase().codePointAt(0), 0x16e57);
assertEq(String.fromCodePoint(0x16e78).toUpperCase().codePointAt(0), 0x16e58);
assertEq(String.fromCodePoint(0x16e79).toUpperCase().codePointAt(0), 0x16e59);
assertEq(String.fromCodePoint(0x16e7a).toUpperCase().codePointAt(0), 0x16e5a);
assertEq(String.fromCodePoint(0x16e7b).toUpperCase().codePointAt(0), 0x16e5b);
assertEq(String.fromCodePoint(0x16e7c).toUpperCase().codePointAt(0), 0x16e5c);
assertEq(String.fromCodePoint(0x16e7d).toUpperCase().codePointAt(0), 0x16e5d);
assertEq(String.fromCodePoint(0x16e7e).toUpperCase().codePointAt(0), 0x16e5e);
assertEq(String.fromCodePoint(0x16e7f).toUpperCase().codePointAt(0), 0x16e5f);
assertEq(String.fromCodePoint(0x1e922).toUpperCase().codePointAt(0), 0x1e900);
assertEq(String.fromCodePoint(0x1e923).toUpperCase().codePointAt(0), 0x1e901);
assertEq(String.fromCodePoint(0x1e924).toUpperCase().codePointAt(0), 0x1e902);
assertEq(String.fromCodePoint(0x1e925).toUpperCase().codePointAt(0), 0x1e903);
assertEq(String.fromCodePoint(0x1e926).toUpperCase().codePointAt(0), 0x1e904);
assertEq(String.fromCodePoint(0x1e927).toUpperCase().codePointAt(0), 0x1e905);
assertEq(String.fromCodePoint(0x1e928).toUpperCase().codePointAt(0), 0x1e906);
assertEq(String.fromCodePoint(0x1e929).toUpperCase().codePointAt(0), 0x1e907);
assertEq(String.fromCodePoint(0x1e92a).toUpperCase().codePointAt(0), 0x1e908);
assertEq(String.fromCodePoint(0x1e92b).toUpperCase().codePointAt(0), 0x1e909);
assertEq(String.fromCodePoint(0x1e92c).toUpperCase().codePointAt(0), 0x1e90a);
assertEq(String.fromCodePoint(0x1e92d).toUpperCase().codePointAt(0), 0x1e90b);
assertEq(String.fromCodePoint(0x1e92e).toUpperCase().codePointAt(0), 0x1e90c);
assertEq(String.fromCodePoint(0x1e92f).toUpperCase().codePointAt(0), 0x1e90d);
assertEq(String.fromCodePoint(0x1e930).toUpperCase().codePointAt(0), 0x1e90e);
assertEq(String.fromCodePoint(0x1e931).toUpperCase().codePointAt(0), 0x1e90f);
assertEq(String.fromCodePoint(0x1e932).toUpperCase().codePointAt(0), 0x1e910);
assertEq(String.fromCodePoint(0x1e933).toUpperCase().codePointAt(0), 0x1e911);
assertEq(String.fromCodePoint(0x1e934).toUpperCase().codePointAt(0), 0x1e912);
assertEq(String.fromCodePoint(0x1e935).toUpperCase().codePointAt(0), 0x1e913);
assertEq(String.fromCodePoint(0x1e936).toUpperCase().codePointAt(0), 0x1e914);
assertEq(String.fromCodePoint(0x1e937).toUpperCase().codePointAt(0), 0x1e915);
assertEq(String.fromCodePoint(0x1e938).toUpperCase().codePointAt(0), 0x1e916);
assertEq(String.fromCodePoint(0x1e939).toUpperCase().codePointAt(0), 0x1e917);
assertEq(String.fromCodePoint(0x1e93a).toUpperCase().codePointAt(0), 0x1e918);
assertEq(String.fromCodePoint(0x1e93b).toUpperCase().codePointAt(0), 0x1e919);
assertEq(String.fromCodePoint(0x1e93c).toUpperCase().codePointAt(0), 0x1e91a);
assertEq(String.fromCodePoint(0x1e93d).toUpperCase().codePointAt(0), 0x1e91b);
assertEq(String.fromCodePoint(0x1e93e).toUpperCase().codePointAt(0), 0x1e91c);
assertEq(String.fromCodePoint(0x1e93f).toUpperCase().codePointAt(0), 0x1e91d);
assertEq(String.fromCodePoint(0x1e940).toUpperCase().codePointAt(0), 0x1e91e);
assertEq(String.fromCodePoint(0x1e941).toUpperCase().codePointAt(0), 0x1e91f);
assertEq(String.fromCodePoint(0x1e942).toUpperCase().codePointAt(0), 0x1e920);
assertEq(String.fromCodePoint(0x1e943).toUpperCase().codePointAt(0), 0x1e921);
assertEq(String.fromCodePoint(0x10400).toLowerCase().codePointAt(0), 0x10428);
assertEq(String.fromCodePoint(0x10401).toLowerCase().codePointAt(0), 0x10429);
assertEq(String.fromCodePoint(0x10402).toLowerCase().codePointAt(0), 0x1042a);
assertEq(String.fromCodePoint(0x10403).toLowerCase().codePointAt(0), 0x1042b);
assertEq(String.fromCodePoint(0x10404).toLowerCase().codePointAt(0), 0x1042c);
assertEq(String.fromCodePoint(0x10405).toLowerCase().codePointAt(0), 0x1042d);
assertEq(String.fromCodePoint(0x10406).toLowerCase().codePointAt(0), 0x1042e);
assertEq(String.fromCodePoint(0x10407).toLowerCase().codePointAt(0), 0x1042f);
assertEq(String.fromCodePoint(0x10408).toLowerCase().codePointAt(0), 0x10430);
assertEq(String.fromCodePoint(0x10409).toLowerCase().codePointAt(0), 0x10431);
assertEq(String.fromCodePoint(0x1040a).toLowerCase().codePointAt(0), 0x10432);
assertEq(String.fromCodePoint(0x1040b).toLowerCase().codePointAt(0), 0x10433);
assertEq(String.fromCodePoint(0x1040c).toLowerCase().codePointAt(0), 0x10434);
assertEq(String.fromCodePoint(0x1040d).toLowerCase().codePointAt(0), 0x10435);
assertEq(String.fromCodePoint(0x1040e).toLowerCase().codePointAt(0), 0x10436);
assertEq(String.fromCodePoint(0x1040f).toLowerCase().codePointAt(0), 0x10437);
assertEq(String.fromCodePoint(0x10410).toLowerCase().codePointAt(0), 0x10438);
assertEq(String.fromCodePoint(0x10411).toLowerCase().codePointAt(0), 0x10439);
assertEq(String.fromCodePoint(0x10412).toLowerCase().codePointAt(0), 0x1043a);
assertEq(String.fromCodePoint(0x10413).toLowerCase().codePointAt(0), 0x1043b);
assertEq(String.fromCodePoint(0x10414).toLowerCase().codePointAt(0), 0x1043c);
assertEq(String.fromCodePoint(0x10415).toLowerCase().codePointAt(0), 0x1043d);
assertEq(String.fromCodePoint(0x10416).toLowerCase().codePointAt(0), 0x1043e);
assertEq(String.fromCodePoint(0x10417).toLowerCase().codePointAt(0), 0x1043f);
assertEq(String.fromCodePoint(0x10418).toLowerCase().codePointAt(0), 0x10440);
assertEq(String.fromCodePoint(0x10419).toLowerCase().codePointAt(0), 0x10441);
assertEq(String.fromCodePoint(0x1041a).toLowerCase().codePointAt(0), 0x10442);
assertEq(String.fromCodePoint(0x1041b).toLowerCase().codePointAt(0), 0x10443);
assertEq(String.fromCodePoint(0x1041c).toLowerCase().codePointAt(0), 0x10444);
assertEq(String.fromCodePoint(0x1041d).toLowerCase().codePointAt(0), 0x10445);
assertEq(String.fromCodePoint(0x1041e).toLowerCase().codePointAt(0), 0x10446);
assertEq(String.fromCodePoint(0x1041f).toLowerCase().codePointAt(0), 0x10447);
assertEq(String.fromCodePoint(0x10420).toLowerCase().codePointAt(0), 0x10448);
assertEq(String.fromCodePoint(0x10421).toLowerCase().codePointAt(0), 0x10449);
assertEq(String.fromCodePoint(0x10422).toLowerCase().codePointAt(0), 0x1044a);
assertEq(String.fromCodePoint(0x10423).toLowerCase().codePointAt(0), 0x1044b);
assertEq(String.fromCodePoint(0x10424).toLowerCase().codePointAt(0), 0x1044c);
assertEq(String.fromCodePoint(0x10425).toLowerCase().codePointAt(0), 0x1044d);
assertEq(String.fromCodePoint(0x10426).toLowerCase().codePointAt(0), 0x1044e);
assertEq(String.fromCodePoint(0x10427).toLowerCase().codePointAt(0), 0x1044f);
assertEq(String.fromCodePoint(0x104b0).toLowerCase().codePointAt(0), 0x104d8);
assertEq(String.fromCodePoint(0x104b1).toLowerCase().codePointAt(0), 0x104d9);
assertEq(String.fromCodePoint(0x104b2).toLowerCase().codePointAt(0), 0x104da);
assertEq(String.fromCodePoint(0x104b3).toLowerCase().codePointAt(0), 0x104db);
assertEq(String.fromCodePoint(0x104b4).toLowerCase().codePointAt(0), 0x104dc);
assertEq(String.fromCodePoint(0x104b5).toLowerCase().codePointAt(0), 0x104dd);
assertEq(String.fromCodePoint(0x104b6).toLowerCase().codePointAt(0), 0x104de);
assertEq(String.fromCodePoint(0x104b7).toLowerCase().codePointAt(0), 0x104df);
assertEq(String.fromCodePoint(0x104b8).toLowerCase().codePointAt(0), 0x104e0);
assertEq(String.fromCodePoint(0x104b9).toLowerCase().codePointAt(0), 0x104e1);
assertEq(String.fromCodePoint(0x104ba).toLowerCase().codePointAt(0), 0x104e2);
assertEq(String.fromCodePoint(0x104bb).toLowerCase().codePointAt(0), 0x104e3);
assertEq(String.fromCodePoint(0x104bc).toLowerCase().codePointAt(0), 0x104e4);
assertEq(String.fromCodePoint(0x104bd).toLowerCase().codePointAt(0), 0x104e5);
assertEq(String.fromCodePoint(0x104be).toLowerCase().codePointAt(0), 0x104e6);
assertEq(String.fromCodePoint(0x104bf).toLowerCase().codePointAt(0), 0x104e7);
assertEq(String.fromCodePoint(0x104c0).toLowerCase().codePointAt(0), 0x104e8);
assertEq(String.fromCodePoint(0x104c1).toLowerCase().codePointAt(0), 0x104e9);
assertEq(String.fromCodePoint(0x104c2).toLowerCase().codePointAt(0), 0x104ea);
assertEq(String.fromCodePoint(0x104c3).toLowerCase().codePointAt(0), 0x104eb);
assertEq(String.fromCodePoint(0x104c4).toLowerCase().codePointAt(0), 0x104ec);
assertEq(String.fromCodePoint(0x104c5).toLowerCase().codePointAt(0), 0x104ed);
assertEq(String.fromCodePoint(0x104c6).toLowerCase().codePointAt(0), 0x104ee);
assertEq(String.fromCodePoint(0x104c7).toLowerCase().codePointAt(0), 0x104ef);
assertEq(String.fromCodePoint(0x104c8).toLowerCase().codePointAt(0), 0x104f0);
assertEq(String.fromCodePoint(0x104c9).toLowerCase().codePointAt(0), 0x104f1);
assertEq(String.fromCodePoint(0x104ca).toLowerCase().codePointAt(0), 0x104f2);
assertEq(String.fromCodePoint(0x104cb).toLowerCase().codePointAt(0), 0x104f3);
assertEq(String.fromCodePoint(0x104cc).toLowerCase().codePointAt(0), 0x104f4);
assertEq(String.fromCodePoint(0x104cd).toLowerCase().codePointAt(0), 0x104f5);
assertEq(String.fromCodePoint(0x104ce).toLowerCase().codePointAt(0), 0x104f6);
assertEq(String.fromCodePoint(0x104cf).toLowerCase().codePointAt(0), 0x104f7);
assertEq(String.fromCodePoint(0x104d0).toLowerCase().codePointAt(0), 0x104f8);
assertEq(String.fromCodePoint(0x104d1).toLowerCase().codePointAt(0), 0x104f9);
assertEq(String.fromCodePoint(0x104d2).toLowerCase().codePointAt(0), 0x104fa);
assertEq(String.fromCodePoint(0x104d3).toLowerCase().codePointAt(0), 0x104fb);
assertEq(String.fromCodePoint(0x10c80).toLowerCase().codePointAt(0), 0x10cc0);
assertEq(String.fromCodePoint(0x10c81).toLowerCase().codePointAt(0), 0x10cc1);
assertEq(String.fromCodePoint(0x10c82).toLowerCase().codePointAt(0), 0x10cc2);
assertEq(String.fromCodePoint(0x10c83).toLowerCase().codePointAt(0), 0x10cc3);
assertEq(String.fromCodePoint(0x10c84).toLowerCase().codePointAt(0), 0x10cc4);
assertEq(String.fromCodePoint(0x10c85).toLowerCase().codePointAt(0), 0x10cc5);
assertEq(String.fromCodePoint(0x10c86).toLowerCase().codePointAt(0), 0x10cc6);
assertEq(String.fromCodePoint(0x10c87).toLowerCase().codePointAt(0), 0x10cc7);
assertEq(String.fromCodePoint(0x10c88).toLowerCase().codePointAt(0), 0x10cc8);
assertEq(String.fromCodePoint(0x10c89).toLowerCase().codePointAt(0), 0x10cc9);
assertEq(String.fromCodePoint(0x10c8a).toLowerCase().codePointAt(0), 0x10cca);
assertEq(String.fromCodePoint(0x10c8b).toLowerCase().codePointAt(0), 0x10ccb);
assertEq(String.fromCodePoint(0x10c8c).toLowerCase().codePointAt(0), 0x10ccc);
assertEq(String.fromCodePoint(0x10c8d).toLowerCase().codePointAt(0), 0x10ccd);
assertEq(String.fromCodePoint(0x10c8e).toLowerCase().codePointAt(0), 0x10cce);
assertEq(String.fromCodePoint(0x10c8f).toLowerCase().codePointAt(0), 0x10ccf);
assertEq(String.fromCodePoint(0x10c90).toLowerCase().codePointAt(0), 0x10cd0);
assertEq(String.fromCodePoint(0x10c91).toLowerCase().codePointAt(0), 0x10cd1);
assertEq(String.fromCodePoint(0x10c92).toLowerCase().codePointAt(0), 0x10cd2);
assertEq(String.fromCodePoint(0x10c93).toLowerCase().codePointAt(0), 0x10cd3);
assertEq(String.fromCodePoint(0x10c94).toLowerCase().codePointAt(0), 0x10cd4);
assertEq(String.fromCodePoint(0x10c95).toLowerCase().codePointAt(0), 0x10cd5);
assertEq(String.fromCodePoint(0x10c96).toLowerCase().codePointAt(0), 0x10cd6);
assertEq(String.fromCodePoint(0x10c97).toLowerCase().codePointAt(0), 0x10cd7);
assertEq(String.fromCodePoint(0x10c98).toLowerCase().codePointAt(0), 0x10cd8);
assertEq(String.fromCodePoint(0x10c99).toLowerCase().codePointAt(0), 0x10cd9);
assertEq(String.fromCodePoint(0x10c9a).toLowerCase().codePointAt(0), 0x10cda);
assertEq(String.fromCodePoint(0x10c9b).toLowerCase().codePointAt(0), 0x10cdb);
assertEq(String.fromCodePoint(0x10c9c).toLowerCase().codePointAt(0), 0x10cdc);
assertEq(String.fromCodePoint(0x10c9d).toLowerCase().codePointAt(0), 0x10cdd);
assertEq(String.fromCodePoint(0x10c9e).toLowerCase().codePointAt(0), 0x10cde);
assertEq(String.fromCodePoint(0x10c9f).toLowerCase().codePointAt(0), 0x10cdf);
assertEq(String.fromCodePoint(0x10ca0).toLowerCase().codePointAt(0), 0x10ce0);
assertEq(String.fromCodePoint(0x10ca1).toLowerCase().codePointAt(0), 0x10ce1);
assertEq(String.fromCodePoint(0x10ca2).toLowerCase().codePointAt(0), 0x10ce2);
assertEq(String.fromCodePoint(0x10ca3).toLowerCase().codePointAt(0), 0x10ce3);
assertEq(String.fromCodePoint(0x10ca4).toLowerCase().codePointAt(0), 0x10ce4);
assertEq(String.fromCodePoint(0x10ca5).toLowerCase().codePointAt(0), 0x10ce5);
assertEq(String.fromCodePoint(0x10ca6).toLowerCase().codePointAt(0), 0x10ce6);
assertEq(String.fromCodePoint(0x10ca7).toLowerCase().codePointAt(0), 0x10ce7);
assertEq(String.fromCodePoint(0x10ca8).toLowerCase().codePointAt(0), 0x10ce8);
assertEq(String.fromCodePoint(0x10ca9).toLowerCase().codePointAt(0), 0x10ce9);
assertEq(String.fromCodePoint(0x10caa).toLowerCase().codePointAt(0), 0x10cea);
assertEq(String.fromCodePoint(0x10cab).toLowerCase().codePointAt(0), 0x10ceb);
assertEq(String.fromCodePoint(0x10cac).toLowerCase().codePointAt(0), 0x10cec);
assertEq(String.fromCodePoint(0x10cad).toLowerCase().codePointAt(0), 0x10ced);
assertEq(String.fromCodePoint(0x10cae).toLowerCase().codePointAt(0), 0x10cee);
assertEq(String.fromCodePoint(0x10caf).toLowerCase().codePointAt(0), 0x10cef);
assertEq(String.fromCodePoint(0x10cb0).toLowerCase().codePointAt(0), 0x10cf0);
assertEq(String.fromCodePoint(0x10cb1).toLowerCase().codePointAt(0), 0x10cf1);
assertEq(String.fromCodePoint(0x10cb2).toLowerCase().codePointAt(0), 0x10cf2);
assertEq(String.fromCodePoint(0x118a0).toLowerCase().codePointAt(0), 0x118c0);
assertEq(String.fromCodePoint(0x118a1).toLowerCase().codePointAt(0), 0x118c1);
assertEq(String.fromCodePoint(0x118a2).toLowerCase().codePointAt(0), 0x118c2);
assertEq(String.fromCodePoint(0x118a3).toLowerCase().codePointAt(0), 0x118c3);
assertEq(String.fromCodePoint(0x118a4).toLowerCase().codePointAt(0), 0x118c4);
assertEq(String.fromCodePoint(0x118a5).toLowerCase().codePointAt(0), 0x118c5);
assertEq(String.fromCodePoint(0x118a6).toLowerCase().codePointAt(0), 0x118c6);
assertEq(String.fromCodePoint(0x118a7).toLowerCase().codePointAt(0), 0x118c7);
assertEq(String.fromCodePoint(0x118a8).toLowerCase().codePointAt(0), 0x118c8);
assertEq(String.fromCodePoint(0x118a9).toLowerCase().codePointAt(0), 0x118c9);
assertEq(String.fromCodePoint(0x118aa).toLowerCase().codePointAt(0), 0x118ca);
assertEq(String.fromCodePoint(0x118ab).toLowerCase().codePointAt(0), 0x118cb);
assertEq(String.fromCodePoint(0x118ac).toLowerCase().codePointAt(0), 0x118cc);
assertEq(String.fromCodePoint(0x118ad).toLowerCase().codePointAt(0), 0x118cd);
assertEq(String.fromCodePoint(0x118ae).toLowerCase().codePointAt(0), 0x118ce);
assertEq(String.fromCodePoint(0x118af).toLowerCase().codePointAt(0), 0x118cf);
assertEq(String.fromCodePoint(0x118b0).toLowerCase().codePointAt(0), 0x118d0);
assertEq(String.fromCodePoint(0x118b1).toLowerCase().codePointAt(0), 0x118d1);
assertEq(String.fromCodePoint(0x118b2).toLowerCase().codePointAt(0), 0x118d2);
assertEq(String.fromCodePoint(0x118b3).toLowerCase().codePointAt(0), 0x118d3);
assertEq(String.fromCodePoint(0x118b4).toLowerCase().codePointAt(0), 0x118d4);
assertEq(String.fromCodePoint(0x118b5).toLowerCase().codePointAt(0), 0x118d5);
assertEq(String.fromCodePoint(0x118b6).toLowerCase().codePointAt(0), 0x118d6);
assertEq(String.fromCodePoint(0x118b7).toLowerCase().codePointAt(0), 0x118d7);
assertEq(String.fromCodePoint(0x118b8).toLowerCase().codePointAt(0), 0x118d8);
assertEq(String.fromCodePoint(0x118b9).toLowerCase().codePointAt(0), 0x118d9);
assertEq(String.fromCodePoint(0x118ba).toLowerCase().codePointAt(0), 0x118da);
assertEq(String.fromCodePoint(0x118bb).toLowerCase().codePointAt(0), 0x118db);
assertEq(String.fromCodePoint(0x118bc).toLowerCase().codePointAt(0), 0x118dc);
assertEq(String.fromCodePoint(0x118bd).toLowerCase().codePointAt(0), 0x118dd);
assertEq(String.fromCodePoint(0x118be).toLowerCase().codePointAt(0), 0x118de);
assertEq(String.fromCodePoint(0x118bf).toLowerCase().codePointAt(0), 0x118df);
assertEq(String.fromCodePoint(0x16e40).toLowerCase().codePointAt(0), 0x16e60);
assertEq(String.fromCodePoint(0x16e41).toLowerCase().codePointAt(0), 0x16e61);
assertEq(String.fromCodePoint(0x16e42).toLowerCase().codePointAt(0), 0x16e62);
assertEq(String.fromCodePoint(0x16e43).toLowerCase().codePointAt(0), 0x16e63);
assertEq(String.fromCodePoint(0x16e44).toLowerCase().codePointAt(0), 0x16e64);
assertEq(String.fromCodePoint(0x16e45).toLowerCase().codePointAt(0), 0x16e65);
assertEq(String.fromCodePoint(0x16e46).toLowerCase().codePointAt(0), 0x16e66);
assertEq(String.fromCodePoint(0x16e47).toLowerCase().codePointAt(0), 0x16e67);
assertEq(String.fromCodePoint(0x16e48).toLowerCase().codePointAt(0), 0x16e68);
assertEq(String.fromCodePoint(0x16e49).toLowerCase().codePointAt(0), 0x16e69);
assertEq(String.fromCodePoint(0x16e4a).toLowerCase().codePointAt(0), 0x16e6a);
assertEq(String.fromCodePoint(0x16e4b).toLowerCase().codePointAt(0), 0x16e6b);
assertEq(String.fromCodePoint(0x16e4c).toLowerCase().codePointAt(0), 0x16e6c);
assertEq(String.fromCodePoint(0x16e4d).toLowerCase().codePointAt(0), 0x16e6d);
assertEq(String.fromCodePoint(0x16e4e).toLowerCase().codePointAt(0), 0x16e6e);
assertEq(String.fromCodePoint(0x16e4f).toLowerCase().codePointAt(0), 0x16e6f);
assertEq(String.fromCodePoint(0x16e50).toLowerCase().codePointAt(0), 0x16e70);
assertEq(String.fromCodePoint(0x16e51).toLowerCase().codePointAt(0), 0x16e71);
assertEq(String.fromCodePoint(0x16e52).toLowerCase().codePointAt(0), 0x16e72);
assertEq(String.fromCodePoint(0x16e53).toLowerCase().codePointAt(0), 0x16e73);
assertEq(String.fromCodePoint(0x16e54).toLowerCase().codePointAt(0), 0x16e74);
assertEq(String.fromCodePoint(0x16e55).toLowerCase().codePointAt(0), 0x16e75);
assertEq(String.fromCodePoint(0x16e56).toLowerCase().codePointAt(0), 0x16e76);
assertEq(String.fromCodePoint(0x16e57).toLowerCase().codePointAt(0), 0x16e77);
assertEq(String.fromCodePoint(0x16e58).toLowerCase().codePointAt(0), 0x16e78);
assertEq(String.fromCodePoint(0x16e59).toLowerCase().codePointAt(0), 0x16e79);
assertEq(String.fromCodePoint(0x16e5a).toLowerCase().codePointAt(0), 0x16e7a);
assertEq(String.fromCodePoint(0x16e5b).toLowerCase().codePointAt(0), 0x16e7b);
assertEq(String.fromCodePoint(0x16e5c).toLowerCase().codePointAt(0), 0x16e7c);
assertEq(String.fromCodePoint(0x16e5d).toLowerCase().codePointAt(0), 0x16e7d);
assertEq(String.fromCodePoint(0x16e5e).toLowerCase().codePointAt(0), 0x16e7e);
assertEq(String.fromCodePoint(0x16e5f).toLowerCase().codePointAt(0), 0x16e7f);
assertEq(String.fromCodePoint(0x1e900).toLowerCase().codePointAt(0), 0x1e922);
assertEq(String.fromCodePoint(0x1e901).toLowerCase().codePointAt(0), 0x1e923);
assertEq(String.fromCodePoint(0x1e902).toLowerCase().codePointAt(0), 0x1e924);
assertEq(String.fromCodePoint(0x1e903).toLowerCase().codePointAt(0), 0x1e925);
assertEq(String.fromCodePoint(0x1e904).toLowerCase().codePointAt(0), 0x1e926);
assertEq(String.fromCodePoint(0x1e905).toLowerCase().codePointAt(0), 0x1e927);
assertEq(String.fromCodePoint(0x1e906).toLowerCase().codePointAt(0), 0x1e928);
assertEq(String.fromCodePoint(0x1e907).toLowerCase().codePointAt(0), 0x1e929);
assertEq(String.fromCodePoint(0x1e908).toLowerCase().codePointAt(0), 0x1e92a);
assertEq(String.fromCodePoint(0x1e909).toLowerCase().codePointAt(0), 0x1e92b);
assertEq(String.fromCodePoint(0x1e90a).toLowerCase().codePointAt(0), 0x1e92c);
assertEq(String.fromCodePoint(0x1e90b).toLowerCase().codePointAt(0), 0x1e92d);
assertEq(String.fromCodePoint(0x1e90c).toLowerCase().codePointAt(0), 0x1e92e);
assertEq(String.fromCodePoint(0x1e90d).toLowerCase().codePointAt(0), 0x1e92f);
assertEq(String.fromCodePoint(0x1e90e).toLowerCase().codePointAt(0), 0x1e930);
assertEq(String.fromCodePoint(0x1e90f).toLowerCase().codePointAt(0), 0x1e931);
assertEq(String.fromCodePoint(0x1e910).toLowerCase().codePointAt(0), 0x1e932);
assertEq(String.fromCodePoint(0x1e911).toLowerCase().codePointAt(0), 0x1e933);
assertEq(String.fromCodePoint(0x1e912).toLowerCase().codePointAt(0), 0x1e934);
assertEq(String.fromCodePoint(0x1e913).toLowerCase().codePointAt(0), 0x1e935);
assertEq(String.fromCodePoint(0x1e914).toLowerCase().codePointAt(0), 0x1e936);
assertEq(String.fromCodePoint(0x1e915).toLowerCase().codePointAt(0), 0x1e937);
assertEq(String.fromCodePoint(0x1e916).toLowerCase().codePointAt(0), 0x1e938);
assertEq(String.fromCodePoint(0x1e917).toLowerCase().codePointAt(0), 0x1e939);
assertEq(String.fromCodePoint(0x1e918).toLowerCase().codePointAt(0), 0x1e93a);
assertEq(String.fromCodePoint(0x1e919).toLowerCase().codePointAt(0), 0x1e93b);
assertEq(String.fromCodePoint(0x1e91a).toLowerCase().codePointAt(0), 0x1e93c);
assertEq(String.fromCodePoint(0x1e91b).toLowerCase().codePointAt(0), 0x1e93d);
assertEq(String.fromCodePoint(0x1e91c).toLowerCase().codePointAt(0), 0x1e93e);
assertEq(String.fromCodePoint(0x1e91d).toLowerCase().codePointAt(0), 0x1e93f);
assertEq(String.fromCodePoint(0x1e91e).toLowerCase().codePointAt(0), 0x1e940);
assertEq(String.fromCodePoint(0x1e91f).toLowerCase().codePointAt(0), 0x1e941);
assertEq(String.fromCodePoint(0x1e920).toLowerCase().codePointAt(0), 0x1e942);
assertEq(String.fromCodePoint(0x1e921).toLowerCase().codePointAt(0), 0x1e943);
assertEq(String.fromCodePoint(0x10428).toUpperCase().codePointAt(0), 0x10400); // DESERET SMALL LETTER LONG I, DESERET CAPITAL LETTER LONG I
assertEq(String.fromCodePoint(0x10429).toUpperCase().codePointAt(0), 0x10401); // DESERET SMALL LETTER LONG E, DESERET CAPITAL LETTER LONG E
assertEq(String.fromCodePoint(0x1042A).toUpperCase().codePointAt(0), 0x10402); // DESERET SMALL LETTER LONG A, DESERET CAPITAL LETTER LONG A
assertEq(String.fromCodePoint(0x1042B).toUpperCase().codePointAt(0), 0x10403); // DESERET SMALL LETTER LONG AH, DESERET CAPITAL LETTER LONG AH
assertEq(String.fromCodePoint(0x1042C).toUpperCase().codePointAt(0), 0x10404); // DESERET SMALL LETTER LONG O, DESERET CAPITAL LETTER LONG O
assertEq(String.fromCodePoint(0x1042D).toUpperCase().codePointAt(0), 0x10405); // DESERET SMALL LETTER LONG OO, DESERET CAPITAL LETTER LONG OO
assertEq(String.fromCodePoint(0x1042E).toUpperCase().codePointAt(0), 0x10406); // DESERET SMALL LETTER SHORT I, DESERET CAPITAL LETTER SHORT I
assertEq(String.fromCodePoint(0x1042F).toUpperCase().codePointAt(0), 0x10407); // DESERET SMALL LETTER SHORT E, DESERET CAPITAL LETTER SHORT E
assertEq(String.fromCodePoint(0x10430).toUpperCase().codePointAt(0), 0x10408); // DESERET SMALL LETTER SHORT A, DESERET CAPITAL LETTER SHORT A
assertEq(String.fromCodePoint(0x10431).toUpperCase().codePointAt(0), 0x10409); // DESERET SMALL LETTER SHORT AH, DESERET CAPITAL LETTER SHORT AH
assertEq(String.fromCodePoint(0x10432).toUpperCase().codePointAt(0), 0x1040A); // DESERET SMALL LETTER SHORT O, DESERET CAPITAL LETTER SHORT O
assertEq(String.fromCodePoint(0x10433).toUpperCase().codePointAt(0), 0x1040B); // DESERET SMALL LETTER SHORT OO, DESERET CAPITAL LETTER SHORT OO
assertEq(String.fromCodePoint(0x10434).toUpperCase().codePointAt(0), 0x1040C); // DESERET SMALL LETTER AY, DESERET CAPITAL LETTER AY
assertEq(String.fromCodePoint(0x10435).toUpperCase().codePointAt(0), 0x1040D); // DESERET SMALL LETTER OW, DESERET CAPITAL LETTER OW
assertEq(String.fromCodePoint(0x10436).toUpperCase().codePointAt(0), 0x1040E); // DESERET SMALL LETTER WU, DESERET CAPITAL LETTER WU
assertEq(String.fromCodePoint(0x10437).toUpperCase().codePointAt(0), 0x1040F); // DESERET SMALL LETTER YEE, DESERET CAPITAL LETTER YEE
assertEq(String.fromCodePoint(0x10438).toUpperCase().codePointAt(0), 0x10410); // DESERET SMALL LETTER H, DESERET CAPITAL LETTER H
assertEq(String.fromCodePoint(0x10439).toUpperCase().codePointAt(0), 0x10411); // DESERET SMALL LETTER PEE, DESERET CAPITAL LETTER PEE
assertEq(String.fromCodePoint(0x1043A).toUpperCase().codePointAt(0), 0x10412); // DESERET SMALL LETTER BEE, DESERET CAPITAL LETTER BEE
assertEq(String.fromCodePoint(0x1043B).toUpperCase().codePointAt(0), 0x10413); // DESERET SMALL LETTER TEE, DESERET CAPITAL LETTER TEE
assertEq(String.fromCodePoint(0x1043C).toUpperCase().codePointAt(0), 0x10414); // DESERET SMALL LETTER DEE, DESERET CAPITAL LETTER DEE
assertEq(String.fromCodePoint(0x1043D).toUpperCase().codePointAt(0), 0x10415); // DESERET SMALL LETTER CHEE, DESERET CAPITAL LETTER CHEE
assertEq(String.fromCodePoint(0x1043E).toUpperCase().codePointAt(0), 0x10416); // DESERET SMALL LETTER JEE, DESERET CAPITAL LETTER JEE
assertEq(String.fromCodePoint(0x1043F).toUpperCase().codePointAt(0), 0x10417); // DESERET SMALL LETTER KAY, DESERET CAPITAL LETTER KAY
assertEq(String.fromCodePoint(0x10440).toUpperCase().codePointAt(0), 0x10418); // DESERET SMALL LETTER GAY, DESERET CAPITAL LETTER GAY
assertEq(String.fromCodePoint(0x10441).toUpperCase().codePointAt(0), 0x10419); // DESERET SMALL LETTER EF, DESERET CAPITAL LETTER EF
assertEq(String.fromCodePoint(0x10442).toUpperCase().codePointAt(0), 0x1041A); // DESERET SMALL LETTER VEE, DESERET CAPITAL LETTER VEE
assertEq(String.fromCodePoint(0x10443).toUpperCase().codePointAt(0), 0x1041B); // DESERET SMALL LETTER ETH, DESERET CAPITAL LETTER ETH
assertEq(String.fromCodePoint(0x10444).toUpperCase().codePointAt(0), 0x1041C); // DESERET SMALL LETTER THEE, DESERET CAPITAL LETTER THEE
assertEq(String.fromCodePoint(0x10445).toUpperCase().codePointAt(0), 0x1041D); // DESERET SMALL LETTER ES, DESERET CAPITAL LETTER ES
assertEq(String.fromCodePoint(0x10446).toUpperCase().codePointAt(0), 0x1041E); // DESERET SMALL LETTER ZEE, DESERET CAPITAL LETTER ZEE
assertEq(String.fromCodePoint(0x10447).toUpperCase().codePointAt(0), 0x1041F); // DESERET SMALL LETTER ESH, DESERET CAPITAL LETTER ESH
assertEq(String.fromCodePoint(0x10448).toUpperCase().codePointAt(0), 0x10420); // DESERET SMALL LETTER ZHEE, DESERET CAPITAL LETTER ZHEE
assertEq(String.fromCodePoint(0x10449).toUpperCase().codePointAt(0), 0x10421); // DESERET SMALL LETTER ER, DESERET CAPITAL LETTER ER
assertEq(String.fromCodePoint(0x1044A).toUpperCase().codePointAt(0), 0x10422); // DESERET SMALL LETTER EL, DESERET CAPITAL LETTER EL
assertEq(String.fromCodePoint(0x1044B).toUpperCase().codePointAt(0), 0x10423); // DESERET SMALL LETTER EM, DESERET CAPITAL LETTER EM
assertEq(String.fromCodePoint(0x1044C).toUpperCase().codePointAt(0), 0x10424); // DESERET SMALL LETTER EN, DESERET CAPITAL LETTER EN
assertEq(String.fromCodePoint(0x1044D).toUpperCase().codePointAt(0), 0x10425); // DESERET SMALL LETTER ENG, DESERET CAPITAL LETTER ENG
assertEq(String.fromCodePoint(0x1044E).toUpperCase().codePointAt(0), 0x10426); // DESERET SMALL LETTER OI, DESERET CAPITAL LETTER OI
assertEq(String.fromCodePoint(0x1044F).toUpperCase().codePointAt(0), 0x10427); // DESERET SMALL LETTER EW, DESERET CAPITAL LETTER EW
assertEq(String.fromCodePoint(0x104D8).toUpperCase().codePointAt(0), 0x104B0); // OSAGE SMALL LETTER A, OSAGE CAPITAL LETTER A
assertEq(String.fromCodePoint(0x104D9).toUpperCase().codePointAt(0), 0x104B1); // OSAGE SMALL LETTER AI, OSAGE CAPITAL LETTER AI
assertEq(String.fromCodePoint(0x104DA).toUpperCase().codePointAt(0), 0x104B2); // OSAGE SMALL LETTER AIN, OSAGE CAPITAL LETTER AIN
assertEq(String.fromCodePoint(0x104DB).toUpperCase().codePointAt(0), 0x104B3); // OSAGE SMALL LETTER AH, OSAGE CAPITAL LETTER AH
assertEq(String.fromCodePoint(0x104DC).toUpperCase().codePointAt(0), 0x104B4); // OSAGE SMALL LETTER BRA, OSAGE CAPITAL LETTER BRA
assertEq(String.fromCodePoint(0x104DD).toUpperCase().codePointAt(0), 0x104B5); // OSAGE SMALL LETTER CHA, OSAGE CAPITAL LETTER CHA
assertEq(String.fromCodePoint(0x104DE).toUpperCase().codePointAt(0), 0x104B6); // OSAGE SMALL LETTER EHCHA, OSAGE CAPITAL LETTER EHCHA
assertEq(String.fromCodePoint(0x104DF).toUpperCase().codePointAt(0), 0x104B7); // OSAGE SMALL LETTER E, OSAGE CAPITAL LETTER E
assertEq(String.fromCodePoint(0x104E0).toUpperCase().codePointAt(0), 0x104B8); // OSAGE SMALL LETTER EIN, OSAGE CAPITAL LETTER EIN
assertEq(String.fromCodePoint(0x104E1).toUpperCase().codePointAt(0), 0x104B9); // OSAGE SMALL LETTER HA, OSAGE CAPITAL LETTER HA
assertEq(String.fromCodePoint(0x104E2).toUpperCase().codePointAt(0), 0x104BA); // OSAGE SMALL LETTER HYA, OSAGE CAPITAL LETTER HYA
assertEq(String.fromCodePoint(0x104E3).toUpperCase().codePointAt(0), 0x104BB); // OSAGE SMALL LETTER I, OSAGE CAPITAL LETTER I
assertEq(String.fromCodePoint(0x104E4).toUpperCase().codePointAt(0), 0x104BC); // OSAGE SMALL LETTER KA, OSAGE CAPITAL LETTER KA
assertEq(String.fromCodePoint(0x104E5).toUpperCase().codePointAt(0), 0x104BD); // OSAGE SMALL LETTER EHKA, OSAGE CAPITAL LETTER EHKA
assertEq(String.fromCodePoint(0x104E6).toUpperCase().codePointAt(0), 0x104BE); // OSAGE SMALL LETTER KYA, OSAGE CAPITAL LETTER KYA
assertEq(String.fromCodePoint(0x104E7).toUpperCase().codePointAt(0), 0x104BF); // OSAGE SMALL LETTER LA, OSAGE CAPITAL LETTER LA
assertEq(String.fromCodePoint(0x104E8).toUpperCase().codePointAt(0), 0x104C0); // OSAGE SMALL LETTER MA, OSAGE CAPITAL LETTER MA
assertEq(String.fromCodePoint(0x104E9).toUpperCase().codePointAt(0), 0x104C1); // OSAGE SMALL LETTER NA, OSAGE CAPITAL LETTER NA
assertEq(String.fromCodePoint(0x104EA).toUpperCase().codePointAt(0), 0x104C2); // OSAGE SMALL LETTER O, OSAGE CAPITAL LETTER O
assertEq(String.fromCodePoint(0x104EB).toUpperCase().codePointAt(0), 0x104C3); // OSAGE SMALL LETTER OIN, OSAGE CAPITAL LETTER OIN
assertEq(String.fromCodePoint(0x104EC).toUpperCase().codePointAt(0), 0x104C4); // OSAGE SMALL LETTER PA, OSAGE CAPITAL LETTER PA
assertEq(String.fromCodePoint(0x104ED).toUpperCase().codePointAt(0), 0x104C5); // OSAGE SMALL LETTER EHPA, OSAGE CAPITAL LETTER EHPA
assertEq(String.fromCodePoint(0x104EE).toUpperCase().codePointAt(0), 0x104C6); // OSAGE SMALL LETTER SA, OSAGE CAPITAL LETTER SA
assertEq(String.fromCodePoint(0x104EF).toUpperCase().codePointAt(0), 0x104C7); // OSAGE SMALL LETTER SHA, OSAGE CAPITAL LETTER SHA
assertEq(String.fromCodePoint(0x104F0).toUpperCase().codePointAt(0), 0x104C8); // OSAGE SMALL LETTER TA, OSAGE CAPITAL LETTER TA
assertEq(String.fromCodePoint(0x104F1).toUpperCase().codePointAt(0), 0x104C9); // OSAGE SMALL LETTER EHTA, OSAGE CAPITAL LETTER EHTA
assertEq(String.fromCodePoint(0x104F2).toUpperCase().codePointAt(0), 0x104CA); // OSAGE SMALL LETTER TSA, OSAGE CAPITAL LETTER TSA
assertEq(String.fromCodePoint(0x104F3).toUpperCase().codePointAt(0), 0x104CB); // OSAGE SMALL LETTER EHTSA, OSAGE CAPITAL LETTER EHTSA
assertEq(String.fromCodePoint(0x104F4).toUpperCase().codePointAt(0), 0x104CC); // OSAGE SMALL LETTER TSHA, OSAGE CAPITAL LETTER TSHA
assertEq(String.fromCodePoint(0x104F5).toUpperCase().codePointAt(0), 0x104CD); // OSAGE SMALL LETTER DHA, OSAGE CAPITAL LETTER DHA
assertEq(String.fromCodePoint(0x104F6).toUpperCase().codePointAt(0), 0x104CE); // OSAGE SMALL LETTER U, OSAGE CAPITAL LETTER U
assertEq(String.fromCodePoint(0x104F7).toUpperCase().codePointAt(0), 0x104CF); // OSAGE SMALL LETTER WA, OSAGE CAPITAL LETTER WA
assertEq(String.fromCodePoint(0x104F8).toUpperCase().codePointAt(0), 0x104D0); // OSAGE SMALL LETTER KHA, OSAGE CAPITAL LETTER KHA
assertEq(String.fromCodePoint(0x104F9).toUpperCase().codePointAt(0), 0x104D1); // OSAGE SMALL LETTER GHA, OSAGE CAPITAL LETTER GHA
assertEq(String.fromCodePoint(0x104FA).toUpperCase().codePointAt(0), 0x104D2); // OSAGE SMALL LETTER ZA, OSAGE CAPITAL LETTER ZA
assertEq(String.fromCodePoint(0x104FB).toUpperCase().codePointAt(0), 0x104D3); // OSAGE SMALL LETTER ZHA, OSAGE CAPITAL LETTER ZHA
assertEq(String.fromCodePoint(0x10CC0).toUpperCase().codePointAt(0), 0x10C80); // OLD HUNGARIAN SMALL LETTER A, OLD HUNGARIAN CAPITAL LETTER A
assertEq(String.fromCodePoint(0x10CC1).toUpperCase().codePointAt(0), 0x10C81); // OLD HUNGARIAN SMALL LETTER AA, OLD HUNGARIAN CAPITAL LETTER AA
assertEq(String.fromCodePoint(0x10CC2).toUpperCase().codePointAt(0), 0x10C82); // OLD HUNGARIAN SMALL LETTER EB, OLD HUNGARIAN CAPITAL LETTER EB
assertEq(String.fromCodePoint(0x10CC3).toUpperCase().codePointAt(0), 0x10C83); // OLD HUNGARIAN SMALL LETTER AMB, OLD HUNGARIAN CAPITAL LETTER AMB
assertEq(String.fromCodePoint(0x10CC4).toUpperCase().codePointAt(0), 0x10C84); // OLD HUNGARIAN SMALL LETTER EC, OLD HUNGARIAN CAPITAL LETTER EC
assertEq(String.fromCodePoint(0x10CC5).toUpperCase().codePointAt(0), 0x10C85); // OLD HUNGARIAN SMALL LETTER ENC, OLD HUNGARIAN CAPITAL LETTER ENC
assertEq(String.fromCodePoint(0x10CC6).toUpperCase().codePointAt(0), 0x10C86); // OLD HUNGARIAN SMALL LETTER ECS, OLD HUNGARIAN CAPITAL LETTER ECS
assertEq(String.fromCodePoint(0x10CC7).toUpperCase().codePointAt(0), 0x10C87); // OLD HUNGARIAN SMALL LETTER ED, OLD HUNGARIAN CAPITAL LETTER ED
assertEq(String.fromCodePoint(0x10CC8).toUpperCase().codePointAt(0), 0x10C88); // OLD HUNGARIAN SMALL LETTER AND, OLD HUNGARIAN CAPITAL LETTER AND
assertEq(String.fromCodePoint(0x10CC9).toUpperCase().codePointAt(0), 0x10C89); // OLD HUNGARIAN SMALL LETTER E, OLD HUNGARIAN CAPITAL LETTER E
assertEq(String.fromCodePoint(0x10CCA).toUpperCase().codePointAt(0), 0x10C8A); // OLD HUNGARIAN SMALL LETTER CLOSE E, OLD HUNGARIAN CAPITAL LETTER CLOSE E
assertEq(String.fromCodePoint(0x10CCB).toUpperCase().codePointAt(0), 0x10C8B); // OLD HUNGARIAN SMALL LETTER EE, OLD HUNGARIAN CAPITAL LETTER EE
assertEq(String.fromCodePoint(0x10CCC).toUpperCase().codePointAt(0), 0x10C8C); // OLD HUNGARIAN SMALL LETTER EF, OLD HUNGARIAN CAPITAL LETTER EF
assertEq(String.fromCodePoint(0x10CCD).toUpperCase().codePointAt(0), 0x10C8D); // OLD HUNGARIAN SMALL LETTER EG, OLD HUNGARIAN CAPITAL LETTER EG
assertEq(String.fromCodePoint(0x10CCE).toUpperCase().codePointAt(0), 0x10C8E); // OLD HUNGARIAN SMALL LETTER EGY, OLD HUNGARIAN CAPITAL LETTER EGY
assertEq(String.fromCodePoint(0x10CCF).toUpperCase().codePointAt(0), 0x10C8F); // OLD HUNGARIAN SMALL LETTER EH, OLD HUNGARIAN CAPITAL LETTER EH
assertEq(String.fromCodePoint(0x10CD0).toUpperCase().codePointAt(0), 0x10C90); // OLD HUNGARIAN SMALL LETTER I, OLD HUNGARIAN CAPITAL LETTER I
assertEq(String.fromCodePoint(0x10CD1).toUpperCase().codePointAt(0), 0x10C91); // OLD HUNGARIAN SMALL LETTER II, OLD HUNGARIAN CAPITAL LETTER II
assertEq(String.fromCodePoint(0x10CD2).toUpperCase().codePointAt(0), 0x10C92); // OLD HUNGARIAN SMALL LETTER EJ, OLD HUNGARIAN CAPITAL LETTER EJ
assertEq(String.fromCodePoint(0x10CD3).toUpperCase().codePointAt(0), 0x10C93); // OLD HUNGARIAN SMALL LETTER EK, OLD HUNGARIAN CAPITAL LETTER EK
assertEq(String.fromCodePoint(0x10CD4).toUpperCase().codePointAt(0), 0x10C94); // OLD HUNGARIAN SMALL LETTER AK, OLD HUNGARIAN CAPITAL LETTER AK
assertEq(String.fromCodePoint(0x10CD5).toUpperCase().codePointAt(0), 0x10C95); // OLD HUNGARIAN SMALL LETTER UNK, OLD HUNGARIAN CAPITAL LETTER UNK
assertEq(String.fromCodePoint(0x10CD6).toUpperCase().codePointAt(0), 0x10C96); // OLD HUNGARIAN SMALL LETTER EL, OLD HUNGARIAN CAPITAL LETTER EL
assertEq(String.fromCodePoint(0x10CD7).toUpperCase().codePointAt(0), 0x10C97); // OLD HUNGARIAN SMALL LETTER ELY, OLD HUNGARIAN CAPITAL LETTER ELY
assertEq(String.fromCodePoint(0x10CD8).toUpperCase().codePointAt(0), 0x10C98); // OLD HUNGARIAN SMALL LETTER EM, OLD HUNGARIAN CAPITAL LETTER EM
assertEq(String.fromCodePoint(0x10CD9).toUpperCase().codePointAt(0), 0x10C99); // OLD HUNGARIAN SMALL LETTER EN, OLD HUNGARIAN CAPITAL LETTER EN
assertEq(String.fromCodePoint(0x10CDA).toUpperCase().codePointAt(0), 0x10C9A); // OLD HUNGARIAN SMALL LETTER ENY, OLD HUNGARIAN CAPITAL LETTER ENY
assertEq(String.fromCodePoint(0x10CDB).toUpperCase().codePointAt(0), 0x10C9B); // OLD HUNGARIAN SMALL LETTER O, OLD HUNGARIAN CAPITAL LETTER O
assertEq(String.fromCodePoint(0x10CDC).toUpperCase().codePointAt(0), 0x10C9C); // OLD HUNGARIAN SMALL LETTER OO, OLD HUNGARIAN CAPITAL LETTER OO
assertEq(String.fromCodePoint(0x10CDD).toUpperCase().codePointAt(0), 0x10C9D); // OLD HUNGARIAN SMALL LETTER NIKOLSBURG OE, OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG OE
assertEq(String.fromCodePoint(0x10CDE).toUpperCase().codePointAt(0), 0x10C9E); // OLD HUNGARIAN SMALL LETTER RUDIMENTA OE, OLD HUNGARIAN CAPITAL LETTER RUDIMENTA OE
assertEq(String.fromCodePoint(0x10CDF).toUpperCase().codePointAt(0), 0x10C9F); // OLD HUNGARIAN SMALL LETTER OEE, OLD HUNGARIAN CAPITAL LETTER OEE
assertEq(String.fromCodePoint(0x10CE0).toUpperCase().codePointAt(0), 0x10CA0); // OLD HUNGARIAN SMALL LETTER EP, OLD HUNGARIAN CAPITAL LETTER EP
assertEq(String.fromCodePoint(0x10CE1).toUpperCase().codePointAt(0), 0x10CA1); // OLD HUNGARIAN SMALL LETTER EMP, OLD HUNGARIAN CAPITAL LETTER EMP
assertEq(String.fromCodePoint(0x10CE2).toUpperCase().codePointAt(0), 0x10CA2); // OLD HUNGARIAN SMALL LETTER ER, OLD HUNGARIAN CAPITAL LETTER ER
assertEq(String.fromCodePoint(0x10CE3).toUpperCase().codePointAt(0), 0x10CA3); // OLD HUNGARIAN SMALL LETTER SHORT ER, OLD HUNGARIAN CAPITAL LETTER SHORT ER
assertEq(String.fromCodePoint(0x10CE4).toUpperCase().codePointAt(0), 0x10CA4); // OLD HUNGARIAN SMALL LETTER ES, OLD HUNGARIAN CAPITAL LETTER ES
assertEq(String.fromCodePoint(0x10CE5).toUpperCase().codePointAt(0), 0x10CA5); // OLD HUNGARIAN SMALL LETTER ESZ, OLD HUNGARIAN CAPITAL LETTER ESZ
assertEq(String.fromCodePoint(0x10CE6).toUpperCase().codePointAt(0), 0x10CA6); // OLD HUNGARIAN SMALL LETTER ET, OLD HUNGARIAN CAPITAL LETTER ET
assertEq(String.fromCodePoint(0x10CE7).toUpperCase().codePointAt(0), 0x10CA7); // OLD HUNGARIAN SMALL LETTER ENT, OLD HUNGARIAN CAPITAL LETTER ENT
assertEq(String.fromCodePoint(0x10CE8).toUpperCase().codePointAt(0), 0x10CA8); // OLD HUNGARIAN SMALL LETTER ETY, OLD HUNGARIAN CAPITAL LETTER ETY
assertEq(String.fromCodePoint(0x10CE9).toUpperCase().codePointAt(0), 0x10CA9); // OLD HUNGARIAN SMALL LETTER ECH, OLD HUNGARIAN CAPITAL LETTER ECH
assertEq(String.fromCodePoint(0x10CEA).toUpperCase().codePointAt(0), 0x10CAA); // OLD HUNGARIAN SMALL LETTER U, OLD HUNGARIAN CAPITAL LETTER U
assertEq(String.fromCodePoint(0x10CEB).toUpperCase().codePointAt(0), 0x10CAB); // OLD HUNGARIAN SMALL LETTER UU, OLD HUNGARIAN CAPITAL LETTER UU
assertEq(String.fromCodePoint(0x10CEC).toUpperCase().codePointAt(0), 0x10CAC); // OLD HUNGARIAN SMALL LETTER NIKOLSBURG UE, OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG UE
assertEq(String.fromCodePoint(0x10CED).toUpperCase().codePointAt(0), 0x10CAD); // OLD HUNGARIAN SMALL LETTER RUDIMENTA UE, OLD HUNGARIAN CAPITAL LETTER RUDIMENTA UE
assertEq(String.fromCodePoint(0x10CEE).toUpperCase().codePointAt(0), 0x10CAE); // OLD HUNGARIAN SMALL LETTER EV, OLD HUNGARIAN CAPITAL LETTER EV
assertEq(String.fromCodePoint(0x10CEF).toUpperCase().codePointAt(0), 0x10CAF); // OLD HUNGARIAN SMALL LETTER EZ, OLD HUNGARIAN CAPITAL LETTER EZ
assertEq(String.fromCodePoint(0x10CF0).toUpperCase().codePointAt(0), 0x10CB0); // OLD HUNGARIAN SMALL LETTER EZS, OLD HUNGARIAN CAPITAL LETTER EZS
assertEq(String.fromCodePoint(0x10CF1).toUpperCase().codePointAt(0), 0x10CB1); // OLD HUNGARIAN SMALL LETTER ENT-SHAPED SIGN, OLD HUNGARIAN CAPITAL LETTER ENT-SHAPED SIGN
assertEq(String.fromCodePoint(0x10CF2).toUpperCase().codePointAt(0), 0x10CB2); // OLD HUNGARIAN SMALL LETTER US, OLD HUNGARIAN CAPITAL LETTER US
assertEq(String.fromCodePoint(0x118C0).toUpperCase().codePointAt(0), 0x118A0); // WARANG CITI SMALL LETTER NGAA, WARANG CITI CAPITAL LETTER NGAA
assertEq(String.fromCodePoint(0x118C1).toUpperCase().codePointAt(0), 0x118A1); // WARANG CITI SMALL LETTER A, WARANG CITI CAPITAL LETTER A
assertEq(String.fromCodePoint(0x118C2).toUpperCase().codePointAt(0), 0x118A2); // WARANG CITI SMALL LETTER WI, WARANG CITI CAPITAL LETTER WI
assertEq(String.fromCodePoint(0x118C3).toUpperCase().codePointAt(0), 0x118A3); // WARANG CITI SMALL LETTER YU, WARANG CITI CAPITAL LETTER YU
assertEq(String.fromCodePoint(0x118C4).toUpperCase().codePointAt(0), 0x118A4); // WARANG CITI SMALL LETTER YA, WARANG CITI CAPITAL LETTER YA
assertEq(String.fromCodePoint(0x118C5).toUpperCase().codePointAt(0), 0x118A5); // WARANG CITI SMALL LETTER YO, WARANG CITI CAPITAL LETTER YO
assertEq(String.fromCodePoint(0x118C6).toUpperCase().codePointAt(0), 0x118A6); // WARANG CITI SMALL LETTER II, WARANG CITI CAPITAL LETTER II
assertEq(String.fromCodePoint(0x118C7).toUpperCase().codePointAt(0), 0x118A7); // WARANG CITI SMALL LETTER UU, WARANG CITI CAPITAL LETTER UU
assertEq(String.fromCodePoint(0x118C8).toUpperCase().codePointAt(0), 0x118A8); // WARANG CITI SMALL LETTER E, WARANG CITI CAPITAL LETTER E
assertEq(String.fromCodePoint(0x118C9).toUpperCase().codePointAt(0), 0x118A9); // WARANG CITI SMALL LETTER O, WARANG CITI CAPITAL LETTER O
assertEq(String.fromCodePoint(0x118CA).toUpperCase().codePointAt(0), 0x118AA); // WARANG CITI SMALL LETTER ANG, WARANG CITI CAPITAL LETTER ANG
assertEq(String.fromCodePoint(0x118CB).toUpperCase().codePointAt(0), 0x118AB); // WARANG CITI SMALL LETTER GA, WARANG CITI CAPITAL LETTER GA
assertEq(String.fromCodePoint(0x118CC).toUpperCase().codePointAt(0), 0x118AC); // WARANG CITI SMALL LETTER KO, WARANG CITI CAPITAL LETTER KO
assertEq(String.fromCodePoint(0x118CD).toUpperCase().codePointAt(0), 0x118AD); // WARANG CITI SMALL LETTER ENY, WARANG CITI CAPITAL LETTER ENY
assertEq(String.fromCodePoint(0x118CE).toUpperCase().codePointAt(0), 0x118AE); // WARANG CITI SMALL LETTER YUJ, WARANG CITI CAPITAL LETTER YUJ
assertEq(String.fromCodePoint(0x118CF).toUpperCase().codePointAt(0), 0x118AF); // WARANG CITI SMALL LETTER UC, WARANG CITI CAPITAL LETTER UC
assertEq(String.fromCodePoint(0x118D0).toUpperCase().codePointAt(0), 0x118B0); // WARANG CITI SMALL LETTER ENN, WARANG CITI CAPITAL LETTER ENN
assertEq(String.fromCodePoint(0x118D1).toUpperCase().codePointAt(0), 0x118B1); // WARANG CITI SMALL LETTER ODD, WARANG CITI CAPITAL LETTER ODD
assertEq(String.fromCodePoint(0x118D2).toUpperCase().codePointAt(0), 0x118B2); // WARANG CITI SMALL LETTER TTE, WARANG CITI CAPITAL LETTER TTE
assertEq(String.fromCodePoint(0x118D3).toUpperCase().codePointAt(0), 0x118B3); // WARANG CITI SMALL LETTER NUNG, WARANG CITI CAPITAL LETTER NUNG
assertEq(String.fromCodePoint(0x118D4).toUpperCase().codePointAt(0), 0x118B4); // WARANG CITI SMALL LETTER DA, WARANG CITI CAPITAL LETTER DA
assertEq(String.fromCodePoint(0x118D5).toUpperCase().codePointAt(0), 0x118B5); // WARANG CITI SMALL LETTER AT, WARANG CITI CAPITAL LETTER AT
assertEq(String.fromCodePoint(0x118D6).toUpperCase().codePointAt(0), 0x118B6); // WARANG CITI SMALL LETTER AM, WARANG CITI CAPITAL LETTER AM
assertEq(String.fromCodePoint(0x118D7).toUpperCase().codePointAt(0), 0x118B7); // WARANG CITI SMALL LETTER BU, WARANG CITI CAPITAL LETTER BU
assertEq(String.fromCodePoint(0x118D8).toUpperCase().codePointAt(0), 0x118B8); // WARANG CITI SMALL LETTER PU, WARANG CITI CAPITAL LETTER PU
assertEq(String.fromCodePoint(0x118D9).toUpperCase().codePointAt(0), 0x118B9); // WARANG CITI SMALL LETTER HIYO, WARANG CITI CAPITAL LETTER HIYO
assertEq(String.fromCodePoint(0x118DA).toUpperCase().codePointAt(0), 0x118BA); // WARANG CITI SMALL LETTER HOLO, WARANG CITI CAPITAL LETTER HOLO
assertEq(String.fromCodePoint(0x118DB).toUpperCase().codePointAt(0), 0x118BB); // WARANG CITI SMALL LETTER HORR, WARANG CITI CAPITAL LETTER HORR
assertEq(String.fromCodePoint(0x118DC).toUpperCase().codePointAt(0), 0x118BC); // WARANG CITI SMALL LETTER HAR, WARANG CITI CAPITAL LETTER HAR
assertEq(String.fromCodePoint(0x118DD).toUpperCase().codePointAt(0), 0x118BD); // WARANG CITI SMALL LETTER SSUU, WARANG CITI CAPITAL LETTER SSUU
assertEq(String.fromCodePoint(0x118DE).toUpperCase().codePointAt(0), 0x118BE); // WARANG CITI SMALL LETTER SII, WARANG CITI CAPITAL LETTER SII
assertEq(String.fromCodePoint(0x118DF).toUpperCase().codePointAt(0), 0x118BF); // WARANG CITI SMALL LETTER VIYO, WARANG CITI CAPITAL LETTER VIYO
assertEq(String.fromCodePoint(0x16E60).toUpperCase().codePointAt(0), 0x16E40); // MEDEFAIDRIN SMALL LETTER M, MEDEFAIDRIN CAPITAL LETTER M
assertEq(String.fromCodePoint(0x16E61).toUpperCase().codePointAt(0), 0x16E41); // MEDEFAIDRIN SMALL LETTER S, MEDEFAIDRIN CAPITAL LETTER S
assertEq(String.fromCodePoint(0x16E62).toUpperCase().codePointAt(0), 0x16E42); // MEDEFAIDRIN SMALL LETTER V, MEDEFAIDRIN CAPITAL LETTER V
assertEq(String.fromCodePoint(0x16E63).toUpperCase().codePointAt(0), 0x16E43); // MEDEFAIDRIN SMALL LETTER W, MEDEFAIDRIN CAPITAL LETTER W
assertEq(String.fromCodePoint(0x16E64).toUpperCase().codePointAt(0), 0x16E44); // MEDEFAIDRIN SMALL LETTER ATIU, MEDEFAIDRIN CAPITAL LETTER ATIU
assertEq(String.fromCodePoint(0x16E65).toUpperCase().codePointAt(0), 0x16E45); // MEDEFAIDRIN SMALL LETTER Z, MEDEFAIDRIN CAPITAL LETTER Z
assertEq(String.fromCodePoint(0x16E66).toUpperCase().codePointAt(0), 0x16E46); // MEDEFAIDRIN SMALL LETTER KP, MEDEFAIDRIN CAPITAL LETTER KP
assertEq(String.fromCodePoint(0x16E67).toUpperCase().codePointAt(0), 0x16E47); // MEDEFAIDRIN SMALL LETTER P, MEDEFAIDRIN CAPITAL LETTER P
assertEq(String.fromCodePoint(0x16E68).toUpperCase().codePointAt(0), 0x16E48); // MEDEFAIDRIN SMALL LETTER T, MEDEFAIDRIN CAPITAL LETTER T
assertEq(String.fromCodePoint(0x16E69).toUpperCase().codePointAt(0), 0x16E49); // MEDEFAIDRIN SMALL LETTER G, MEDEFAIDRIN CAPITAL LETTER G
assertEq(String.fromCodePoint(0x16E6A).toUpperCase().codePointAt(0), 0x16E4A); // MEDEFAIDRIN SMALL LETTER F, MEDEFAIDRIN CAPITAL LETTER F
assertEq(String.fromCodePoint(0x16E6B).toUpperCase().codePointAt(0), 0x16E4B); // MEDEFAIDRIN SMALL LETTER I, MEDEFAIDRIN CAPITAL LETTER I
assertEq(String.fromCodePoint(0x16E6C).toUpperCase().codePointAt(0), 0x16E4C); // MEDEFAIDRIN SMALL LETTER K, MEDEFAIDRIN CAPITAL LETTER K
assertEq(String.fromCodePoint(0x16E6D).toUpperCase().codePointAt(0), 0x16E4D); // MEDEFAIDRIN SMALL LETTER A, MEDEFAIDRIN CAPITAL LETTER A
assertEq(String.fromCodePoint(0x16E6E).toUpperCase().codePointAt(0), 0x16E4E); // MEDEFAIDRIN SMALL LETTER J, MEDEFAIDRIN CAPITAL LETTER J
assertEq(String.fromCodePoint(0x16E6F).toUpperCase().codePointAt(0), 0x16E4F); // MEDEFAIDRIN SMALL LETTER E, MEDEFAIDRIN CAPITAL LETTER E
assertEq(String.fromCodePoint(0x16E70).toUpperCase().codePointAt(0), 0x16E50); // MEDEFAIDRIN SMALL LETTER B, MEDEFAIDRIN CAPITAL LETTER B
assertEq(String.fromCodePoint(0x16E71).toUpperCase().codePointAt(0), 0x16E51); // MEDEFAIDRIN SMALL LETTER C, MEDEFAIDRIN CAPITAL LETTER C
assertEq(String.fromCodePoint(0x16E72).toUpperCase().codePointAt(0), 0x16E52); // MEDEFAIDRIN SMALL LETTER U, MEDEFAIDRIN CAPITAL LETTER U
assertEq(String.fromCodePoint(0x16E73).toUpperCase().codePointAt(0), 0x16E53); // MEDEFAIDRIN SMALL LETTER YU, MEDEFAIDRIN CAPITAL LETTER YU
assertEq(String.fromCodePoint(0x16E74).toUpperCase().codePointAt(0), 0x16E54); // MEDEFAIDRIN SMALL LETTER L, MEDEFAIDRIN CAPITAL LETTER L
assertEq(String.fromCodePoint(0x16E75).toUpperCase().codePointAt(0), 0x16E55); // MEDEFAIDRIN SMALL LETTER Q, MEDEFAIDRIN CAPITAL LETTER Q
assertEq(String.fromCodePoint(0x16E76).toUpperCase().codePointAt(0), 0x16E56); // MEDEFAIDRIN SMALL LETTER HP, MEDEFAIDRIN CAPITAL LETTER HP
assertEq(String.fromCodePoint(0x16E77).toUpperCase().codePointAt(0), 0x16E57); // MEDEFAIDRIN SMALL LETTER NY, MEDEFAIDRIN CAPITAL LETTER NY
assertEq(String.fromCodePoint(0x16E78).toUpperCase().codePointAt(0), 0x16E58); // MEDEFAIDRIN SMALL LETTER X, MEDEFAIDRIN CAPITAL LETTER X
assertEq(String.fromCodePoint(0x16E79).toUpperCase().codePointAt(0), 0x16E59); // MEDEFAIDRIN SMALL LETTER D, MEDEFAIDRIN CAPITAL LETTER D
assertEq(String.fromCodePoint(0x16E7A).toUpperCase().codePointAt(0), 0x16E5A); // MEDEFAIDRIN SMALL LETTER OE, MEDEFAIDRIN CAPITAL LETTER OE
assertEq(String.fromCodePoint(0x16E7B).toUpperCase().codePointAt(0), 0x16E5B); // MEDEFAIDRIN SMALL LETTER N, MEDEFAIDRIN CAPITAL LETTER N
assertEq(String.fromCodePoint(0x16E7C).toUpperCase().codePointAt(0), 0x16E5C); // MEDEFAIDRIN SMALL LETTER R, MEDEFAIDRIN CAPITAL LETTER R
assertEq(String.fromCodePoint(0x16E7D).toUpperCase().codePointAt(0), 0x16E5D); // MEDEFAIDRIN SMALL LETTER O, MEDEFAIDRIN CAPITAL LETTER O
assertEq(String.fromCodePoint(0x16E7E).toUpperCase().codePointAt(0), 0x16E5E); // MEDEFAIDRIN SMALL LETTER AI, MEDEFAIDRIN CAPITAL LETTER AI
assertEq(String.fromCodePoint(0x16E7F).toUpperCase().codePointAt(0), 0x16E5F); // MEDEFAIDRIN SMALL LETTER Y, MEDEFAIDRIN CAPITAL LETTER Y
assertEq(String.fromCodePoint(0x1E922).toUpperCase().codePointAt(0), 0x1E900); // ADLAM SMALL LETTER ALIF, ADLAM CAPITAL LETTER ALIF
assertEq(String.fromCodePoint(0x1E923).toUpperCase().codePointAt(0), 0x1E901); // ADLAM SMALL LETTER DAALI, ADLAM CAPITAL LETTER DAALI
assertEq(String.fromCodePoint(0x1E924).toUpperCase().codePointAt(0), 0x1E902); // ADLAM SMALL LETTER LAAM, ADLAM CAPITAL LETTER LAAM
assertEq(String.fromCodePoint(0x1E925).toUpperCase().codePointAt(0), 0x1E903); // ADLAM SMALL LETTER MIIM, ADLAM CAPITAL LETTER MIIM
assertEq(String.fromCodePoint(0x1E926).toUpperCase().codePointAt(0), 0x1E904); // ADLAM SMALL LETTER BA, ADLAM CAPITAL LETTER BA
assertEq(String.fromCodePoint(0x1E927).toUpperCase().codePointAt(0), 0x1E905); // ADLAM SMALL LETTER SINNYIIYHE, ADLAM CAPITAL LETTER SINNYIIYHE
assertEq(String.fromCodePoint(0x1E928).toUpperCase().codePointAt(0), 0x1E906); // ADLAM SMALL LETTER PE, ADLAM CAPITAL LETTER PE
assertEq(String.fromCodePoint(0x1E929).toUpperCase().codePointAt(0), 0x1E907); // ADLAM SMALL LETTER BHE, ADLAM CAPITAL LETTER BHE
assertEq(String.fromCodePoint(0x1E92A).toUpperCase().codePointAt(0), 0x1E908); // ADLAM SMALL LETTER RA, ADLAM CAPITAL LETTER RA
assertEq(String.fromCodePoint(0x1E92B).toUpperCase().codePointAt(0), 0x1E909); // ADLAM SMALL LETTER E, ADLAM CAPITAL LETTER E
assertEq(String.fromCodePoint(0x1E92C).toUpperCase().codePointAt(0), 0x1E90A); // ADLAM SMALL LETTER FA, ADLAM CAPITAL LETTER FA
assertEq(String.fromCodePoint(0x1E92D).toUpperCase().codePointAt(0), 0x1E90B); // ADLAM SMALL LETTER I, ADLAM CAPITAL LETTER I
assertEq(String.fromCodePoint(0x1E92E).toUpperCase().codePointAt(0), 0x1E90C); // ADLAM SMALL LETTER O, ADLAM CAPITAL LETTER O
assertEq(String.fromCodePoint(0x1E92F).toUpperCase().codePointAt(0), 0x1E90D); // ADLAM SMALL LETTER DHA, ADLAM CAPITAL LETTER DHA
assertEq(String.fromCodePoint(0x1E930).toUpperCase().codePointAt(0), 0x1E90E); // ADLAM SMALL LETTER YHE, ADLAM CAPITAL LETTER YHE
assertEq(String.fromCodePoint(0x1E931).toUpperCase().codePointAt(0), 0x1E90F); // ADLAM SMALL LETTER WAW, ADLAM CAPITAL LETTER WAW
assertEq(String.fromCodePoint(0x1E932).toUpperCase().codePointAt(0), 0x1E910); // ADLAM SMALL LETTER NUN, ADLAM CAPITAL LETTER NUN
assertEq(String.fromCodePoint(0x1E933).toUpperCase().codePointAt(0), 0x1E911); // ADLAM SMALL LETTER KAF, ADLAM CAPITAL LETTER KAF
assertEq(String.fromCodePoint(0x1E934).toUpperCase().codePointAt(0), 0x1E912); // ADLAM SMALL LETTER YA, ADLAM CAPITAL LETTER YA
assertEq(String.fromCodePoint(0x1E935).toUpperCase().codePointAt(0), 0x1E913); // ADLAM SMALL LETTER U, ADLAM CAPITAL LETTER U
assertEq(String.fromCodePoint(0x1E936).toUpperCase().codePointAt(0), 0x1E914); // ADLAM SMALL LETTER JIIM, ADLAM CAPITAL LETTER JIIM
assertEq(String.fromCodePoint(0x1E937).toUpperCase().codePointAt(0), 0x1E915); // ADLAM SMALL LETTER CHI, ADLAM CAPITAL LETTER CHI
assertEq(String.fromCodePoint(0x1E938).toUpperCase().codePointAt(0), 0x1E916); // ADLAM SMALL LETTER HA, ADLAM CAPITAL LETTER HA
assertEq(String.fromCodePoint(0x1E939).toUpperCase().codePointAt(0), 0x1E917); // ADLAM SMALL LETTER QAAF, ADLAM CAPITAL LETTER QAAF
assertEq(String.fromCodePoint(0x1E93A).toUpperCase().codePointAt(0), 0x1E918); // ADLAM SMALL LETTER GA, ADLAM CAPITAL LETTER GA
assertEq(String.fromCodePoint(0x1E93B).toUpperCase().codePointAt(0), 0x1E919); // ADLAM SMALL LETTER NYA, ADLAM CAPITAL LETTER NYA
assertEq(String.fromCodePoint(0x1E93C).toUpperCase().codePointAt(0), 0x1E91A); // ADLAM SMALL LETTER TU, ADLAM CAPITAL LETTER TU
assertEq(String.fromCodePoint(0x1E93D).toUpperCase().codePointAt(0), 0x1E91B); // ADLAM SMALL LETTER NHA, ADLAM CAPITAL LETTER NHA
assertEq(String.fromCodePoint(0x1E93E).toUpperCase().codePointAt(0), 0x1E91C); // ADLAM SMALL LETTER VA, ADLAM CAPITAL LETTER VA
assertEq(String.fromCodePoint(0x1E93F).toUpperCase().codePointAt(0), 0x1E91D); // ADLAM SMALL LETTER KHA, ADLAM CAPITAL LETTER KHA
assertEq(String.fromCodePoint(0x1E940).toUpperCase().codePointAt(0), 0x1E91E); // ADLAM SMALL LETTER GBE, ADLAM CAPITAL LETTER GBE
assertEq(String.fromCodePoint(0x1E941).toUpperCase().codePointAt(0), 0x1E91F); // ADLAM SMALL LETTER ZAL, ADLAM CAPITAL LETTER ZAL
assertEq(String.fromCodePoint(0x1E942).toUpperCase().codePointAt(0), 0x1E920); // ADLAM SMALL LETTER KPO, ADLAM CAPITAL LETTER KPO
assertEq(String.fromCodePoint(0x1E943).toUpperCase().codePointAt(0), 0x1E921); // ADLAM SMALL LETTER SHA, ADLAM CAPITAL LETTER SHA
assertEq(String.fromCodePoint(0x10400).toLowerCase().codePointAt(0), 0x10428); // DESERET CAPITAL LETTER LONG I, DESERET SMALL LETTER LONG I
assertEq(String.fromCodePoint(0x10401).toLowerCase().codePointAt(0), 0x10429); // DESERET CAPITAL LETTER LONG E, DESERET SMALL LETTER LONG E
assertEq(String.fromCodePoint(0x10402).toLowerCase().codePointAt(0), 0x1042A); // DESERET CAPITAL LETTER LONG A, DESERET SMALL LETTER LONG A
assertEq(String.fromCodePoint(0x10403).toLowerCase().codePointAt(0), 0x1042B); // DESERET CAPITAL LETTER LONG AH, DESERET SMALL LETTER LONG AH
assertEq(String.fromCodePoint(0x10404).toLowerCase().codePointAt(0), 0x1042C); // DESERET CAPITAL LETTER LONG O, DESERET SMALL LETTER LONG O
assertEq(String.fromCodePoint(0x10405).toLowerCase().codePointAt(0), 0x1042D); // DESERET CAPITAL LETTER LONG OO, DESERET SMALL LETTER LONG OO
assertEq(String.fromCodePoint(0x10406).toLowerCase().codePointAt(0), 0x1042E); // DESERET CAPITAL LETTER SHORT I, DESERET SMALL LETTER SHORT I
assertEq(String.fromCodePoint(0x10407).toLowerCase().codePointAt(0), 0x1042F); // DESERET CAPITAL LETTER SHORT E, DESERET SMALL LETTER SHORT E
assertEq(String.fromCodePoint(0x10408).toLowerCase().codePointAt(0), 0x10430); // DESERET CAPITAL LETTER SHORT A, DESERET SMALL LETTER SHORT A
assertEq(String.fromCodePoint(0x10409).toLowerCase().codePointAt(0), 0x10431); // DESERET CAPITAL LETTER SHORT AH, DESERET SMALL LETTER SHORT AH
assertEq(String.fromCodePoint(0x1040A).toLowerCase().codePointAt(0), 0x10432); // DESERET CAPITAL LETTER SHORT O, DESERET SMALL LETTER SHORT O
assertEq(String.fromCodePoint(0x1040B).toLowerCase().codePointAt(0), 0x10433); // DESERET CAPITAL LETTER SHORT OO, DESERET SMALL LETTER SHORT OO
assertEq(String.fromCodePoint(0x1040C).toLowerCase().codePointAt(0), 0x10434); // DESERET CAPITAL LETTER AY, DESERET SMALL LETTER AY
assertEq(String.fromCodePoint(0x1040D).toLowerCase().codePointAt(0), 0x10435); // DESERET CAPITAL LETTER OW, DESERET SMALL LETTER OW
assertEq(String.fromCodePoint(0x1040E).toLowerCase().codePointAt(0), 0x10436); // DESERET CAPITAL LETTER WU, DESERET SMALL LETTER WU
assertEq(String.fromCodePoint(0x1040F).toLowerCase().codePointAt(0), 0x10437); // DESERET CAPITAL LETTER YEE, DESERET SMALL LETTER YEE
assertEq(String.fromCodePoint(0x10410).toLowerCase().codePointAt(0), 0x10438); // DESERET CAPITAL LETTER H, DESERET SMALL LETTER H
assertEq(String.fromCodePoint(0x10411).toLowerCase().codePointAt(0), 0x10439); // DESERET CAPITAL LETTER PEE, DESERET SMALL LETTER PEE
assertEq(String.fromCodePoint(0x10412).toLowerCase().codePointAt(0), 0x1043A); // DESERET CAPITAL LETTER BEE, DESERET SMALL LETTER BEE
assertEq(String.fromCodePoint(0x10413).toLowerCase().codePointAt(0), 0x1043B); // DESERET CAPITAL LETTER TEE, DESERET SMALL LETTER TEE
assertEq(String.fromCodePoint(0x10414).toLowerCase().codePointAt(0), 0x1043C); // DESERET CAPITAL LETTER DEE, DESERET SMALL LETTER DEE
assertEq(String.fromCodePoint(0x10415).toLowerCase().codePointAt(0), 0x1043D); // DESERET CAPITAL LETTER CHEE, DESERET SMALL LETTER CHEE
assertEq(String.fromCodePoint(0x10416).toLowerCase().codePointAt(0), 0x1043E); // DESERET CAPITAL LETTER JEE, DESERET SMALL LETTER JEE
assertEq(String.fromCodePoint(0x10417).toLowerCase().codePointAt(0), 0x1043F); // DESERET CAPITAL LETTER KAY, DESERET SMALL LETTER KAY
assertEq(String.fromCodePoint(0x10418).toLowerCase().codePointAt(0), 0x10440); // DESERET CAPITAL LETTER GAY, DESERET SMALL LETTER GAY
assertEq(String.fromCodePoint(0x10419).toLowerCase().codePointAt(0), 0x10441); // DESERET CAPITAL LETTER EF, DESERET SMALL LETTER EF
assertEq(String.fromCodePoint(0x1041A).toLowerCase().codePointAt(0), 0x10442); // DESERET CAPITAL LETTER VEE, DESERET SMALL LETTER VEE
assertEq(String.fromCodePoint(0x1041B).toLowerCase().codePointAt(0), 0x10443); // DESERET CAPITAL LETTER ETH, DESERET SMALL LETTER ETH
assertEq(String.fromCodePoint(0x1041C).toLowerCase().codePointAt(0), 0x10444); // DESERET CAPITAL LETTER THEE, DESERET SMALL LETTER THEE
assertEq(String.fromCodePoint(0x1041D).toLowerCase().codePointAt(0), 0x10445); // DESERET CAPITAL LETTER ES, DESERET SMALL LETTER ES
assertEq(String.fromCodePoint(0x1041E).toLowerCase().codePointAt(0), 0x10446); // DESERET CAPITAL LETTER ZEE, DESERET SMALL LETTER ZEE
assertEq(String.fromCodePoint(0x1041F).toLowerCase().codePointAt(0), 0x10447); // DESERET CAPITAL LETTER ESH, DESERET SMALL LETTER ESH
assertEq(String.fromCodePoint(0x10420).toLowerCase().codePointAt(0), 0x10448); // DESERET CAPITAL LETTER ZHEE, DESERET SMALL LETTER ZHEE
assertEq(String.fromCodePoint(0x10421).toLowerCase().codePointAt(0), 0x10449); // DESERET CAPITAL LETTER ER, DESERET SMALL LETTER ER
assertEq(String.fromCodePoint(0x10422).toLowerCase().codePointAt(0), 0x1044A); // DESERET CAPITAL LETTER EL, DESERET SMALL LETTER EL
assertEq(String.fromCodePoint(0x10423).toLowerCase().codePointAt(0), 0x1044B); // DESERET CAPITAL LETTER EM, DESERET SMALL LETTER EM
assertEq(String.fromCodePoint(0x10424).toLowerCase().codePointAt(0), 0x1044C); // DESERET CAPITAL LETTER EN, DESERET SMALL LETTER EN
assertEq(String.fromCodePoint(0x10425).toLowerCase().codePointAt(0), 0x1044D); // DESERET CAPITAL LETTER ENG, DESERET SMALL LETTER ENG
assertEq(String.fromCodePoint(0x10426).toLowerCase().codePointAt(0), 0x1044E); // DESERET CAPITAL LETTER OI, DESERET SMALL LETTER OI
assertEq(String.fromCodePoint(0x10427).toLowerCase().codePointAt(0), 0x1044F); // DESERET CAPITAL LETTER EW, DESERET SMALL LETTER EW
assertEq(String.fromCodePoint(0x104B0).toLowerCase().codePointAt(0), 0x104D8); // OSAGE CAPITAL LETTER A, OSAGE SMALL LETTER A
assertEq(String.fromCodePoint(0x104B1).toLowerCase().codePointAt(0), 0x104D9); // OSAGE CAPITAL LETTER AI, OSAGE SMALL LETTER AI
assertEq(String.fromCodePoint(0x104B2).toLowerCase().codePointAt(0), 0x104DA); // OSAGE CAPITAL LETTER AIN, OSAGE SMALL LETTER AIN
assertEq(String.fromCodePoint(0x104B3).toLowerCase().codePointAt(0), 0x104DB); // OSAGE CAPITAL LETTER AH, OSAGE SMALL LETTER AH
assertEq(String.fromCodePoint(0x104B4).toLowerCase().codePointAt(0), 0x104DC); // OSAGE CAPITAL LETTER BRA, OSAGE SMALL LETTER BRA
assertEq(String.fromCodePoint(0x104B5).toLowerCase().codePointAt(0), 0x104DD); // OSAGE CAPITAL LETTER CHA, OSAGE SMALL LETTER CHA
assertEq(String.fromCodePoint(0x104B6).toLowerCase().codePointAt(0), 0x104DE); // OSAGE CAPITAL LETTER EHCHA, OSAGE SMALL LETTER EHCHA
assertEq(String.fromCodePoint(0x104B7).toLowerCase().codePointAt(0), 0x104DF); // OSAGE CAPITAL LETTER E, OSAGE SMALL LETTER E
assertEq(String.fromCodePoint(0x104B8).toLowerCase().codePointAt(0), 0x104E0); // OSAGE CAPITAL LETTER EIN, OSAGE SMALL LETTER EIN
assertEq(String.fromCodePoint(0x104B9).toLowerCase().codePointAt(0), 0x104E1); // OSAGE CAPITAL LETTER HA, OSAGE SMALL LETTER HA
assertEq(String.fromCodePoint(0x104BA).toLowerCase().codePointAt(0), 0x104E2); // OSAGE CAPITAL LETTER HYA, OSAGE SMALL LETTER HYA
assertEq(String.fromCodePoint(0x104BB).toLowerCase().codePointAt(0), 0x104E3); // OSAGE CAPITAL LETTER I, OSAGE SMALL LETTER I
assertEq(String.fromCodePoint(0x104BC).toLowerCase().codePointAt(0), 0x104E4); // OSAGE CAPITAL LETTER KA, OSAGE SMALL LETTER KA
assertEq(String.fromCodePoint(0x104BD).toLowerCase().codePointAt(0), 0x104E5); // OSAGE CAPITAL LETTER EHKA, OSAGE SMALL LETTER EHKA
assertEq(String.fromCodePoint(0x104BE).toLowerCase().codePointAt(0), 0x104E6); // OSAGE CAPITAL LETTER KYA, OSAGE SMALL LETTER KYA
assertEq(String.fromCodePoint(0x104BF).toLowerCase().codePointAt(0), 0x104E7); // OSAGE CAPITAL LETTER LA, OSAGE SMALL LETTER LA
assertEq(String.fromCodePoint(0x104C0).toLowerCase().codePointAt(0), 0x104E8); // OSAGE CAPITAL LETTER MA, OSAGE SMALL LETTER MA
assertEq(String.fromCodePoint(0x104C1).toLowerCase().codePointAt(0), 0x104E9); // OSAGE CAPITAL LETTER NA, OSAGE SMALL LETTER NA
assertEq(String.fromCodePoint(0x104C2).toLowerCase().codePointAt(0), 0x104EA); // OSAGE CAPITAL LETTER O, OSAGE SMALL LETTER O
assertEq(String.fromCodePoint(0x104C3).toLowerCase().codePointAt(0), 0x104EB); // OSAGE CAPITAL LETTER OIN, OSAGE SMALL LETTER OIN
assertEq(String.fromCodePoint(0x104C4).toLowerCase().codePointAt(0), 0x104EC); // OSAGE CAPITAL LETTER PA, OSAGE SMALL LETTER PA
assertEq(String.fromCodePoint(0x104C5).toLowerCase().codePointAt(0), 0x104ED); // OSAGE CAPITAL LETTER EHPA, OSAGE SMALL LETTER EHPA
assertEq(String.fromCodePoint(0x104C6).toLowerCase().codePointAt(0), 0x104EE); // OSAGE CAPITAL LETTER SA, OSAGE SMALL LETTER SA
assertEq(String.fromCodePoint(0x104C7).toLowerCase().codePointAt(0), 0x104EF); // OSAGE CAPITAL LETTER SHA, OSAGE SMALL LETTER SHA
assertEq(String.fromCodePoint(0x104C8).toLowerCase().codePointAt(0), 0x104F0); // OSAGE CAPITAL LETTER TA, OSAGE SMALL LETTER TA
assertEq(String.fromCodePoint(0x104C9).toLowerCase().codePointAt(0), 0x104F1); // OSAGE CAPITAL LETTER EHTA, OSAGE SMALL LETTER EHTA
assertEq(String.fromCodePoint(0x104CA).toLowerCase().codePointAt(0), 0x104F2); // OSAGE CAPITAL LETTER TSA, OSAGE SMALL LETTER TSA
assertEq(String.fromCodePoint(0x104CB).toLowerCase().codePointAt(0), 0x104F3); // OSAGE CAPITAL LETTER EHTSA, OSAGE SMALL LETTER EHTSA
assertEq(String.fromCodePoint(0x104CC).toLowerCase().codePointAt(0), 0x104F4); // OSAGE CAPITAL LETTER TSHA, OSAGE SMALL LETTER TSHA
assertEq(String.fromCodePoint(0x104CD).toLowerCase().codePointAt(0), 0x104F5); // OSAGE CAPITAL LETTER DHA, OSAGE SMALL LETTER DHA
assertEq(String.fromCodePoint(0x104CE).toLowerCase().codePointAt(0), 0x104F6); // OSAGE CAPITAL LETTER U, OSAGE SMALL LETTER U
assertEq(String.fromCodePoint(0x104CF).toLowerCase().codePointAt(0), 0x104F7); // OSAGE CAPITAL LETTER WA, OSAGE SMALL LETTER WA
assertEq(String.fromCodePoint(0x104D0).toLowerCase().codePointAt(0), 0x104F8); // OSAGE CAPITAL LETTER KHA, OSAGE SMALL LETTER KHA
assertEq(String.fromCodePoint(0x104D1).toLowerCase().codePointAt(0), 0x104F9); // OSAGE CAPITAL LETTER GHA, OSAGE SMALL LETTER GHA
assertEq(String.fromCodePoint(0x104D2).toLowerCase().codePointAt(0), 0x104FA); // OSAGE CAPITAL LETTER ZA, OSAGE SMALL LETTER ZA
assertEq(String.fromCodePoint(0x104D3).toLowerCase().codePointAt(0), 0x104FB); // OSAGE CAPITAL LETTER ZHA, OSAGE SMALL LETTER ZHA
assertEq(String.fromCodePoint(0x10C80).toLowerCase().codePointAt(0), 0x10CC0); // OLD HUNGARIAN CAPITAL LETTER A, OLD HUNGARIAN SMALL LETTER A
assertEq(String.fromCodePoint(0x10C81).toLowerCase().codePointAt(0), 0x10CC1); // OLD HUNGARIAN CAPITAL LETTER AA, OLD HUNGARIAN SMALL LETTER AA
assertEq(String.fromCodePoint(0x10C82).toLowerCase().codePointAt(0), 0x10CC2); // OLD HUNGARIAN CAPITAL LETTER EB, OLD HUNGARIAN SMALL LETTER EB
assertEq(String.fromCodePoint(0x10C83).toLowerCase().codePointAt(0), 0x10CC3); // OLD HUNGARIAN CAPITAL LETTER AMB, OLD HUNGARIAN SMALL LETTER AMB
assertEq(String.fromCodePoint(0x10C84).toLowerCase().codePointAt(0), 0x10CC4); // OLD HUNGARIAN CAPITAL LETTER EC, OLD HUNGARIAN SMALL LETTER EC
assertEq(String.fromCodePoint(0x10C85).toLowerCase().codePointAt(0), 0x10CC5); // OLD HUNGARIAN CAPITAL LETTER ENC, OLD HUNGARIAN SMALL LETTER ENC
assertEq(String.fromCodePoint(0x10C86).toLowerCase().codePointAt(0), 0x10CC6); // OLD HUNGARIAN CAPITAL LETTER ECS, OLD HUNGARIAN SMALL LETTER ECS
assertEq(String.fromCodePoint(0x10C87).toLowerCase().codePointAt(0), 0x10CC7); // OLD HUNGARIAN CAPITAL LETTER ED, OLD HUNGARIAN SMALL LETTER ED
assertEq(String.fromCodePoint(0x10C88).toLowerCase().codePointAt(0), 0x10CC8); // OLD HUNGARIAN CAPITAL LETTER AND, OLD HUNGARIAN SMALL LETTER AND
assertEq(String.fromCodePoint(0x10C89).toLowerCase().codePointAt(0), 0x10CC9); // OLD HUNGARIAN CAPITAL LETTER E, OLD HUNGARIAN SMALL LETTER E
assertEq(String.fromCodePoint(0x10C8A).toLowerCase().codePointAt(0), 0x10CCA); // OLD HUNGARIAN CAPITAL LETTER CLOSE E, OLD HUNGARIAN SMALL LETTER CLOSE E
assertEq(String.fromCodePoint(0x10C8B).toLowerCase().codePointAt(0), 0x10CCB); // OLD HUNGARIAN CAPITAL LETTER EE, OLD HUNGARIAN SMALL LETTER EE
assertEq(String.fromCodePoint(0x10C8C).toLowerCase().codePointAt(0), 0x10CCC); // OLD HUNGARIAN CAPITAL LETTER EF, OLD HUNGARIAN SMALL LETTER EF
assertEq(String.fromCodePoint(0x10C8D).toLowerCase().codePointAt(0), 0x10CCD); // OLD HUNGARIAN CAPITAL LETTER EG, OLD HUNGARIAN SMALL LETTER EG
assertEq(String.fromCodePoint(0x10C8E).toLowerCase().codePointAt(0), 0x10CCE); // OLD HUNGARIAN CAPITAL LETTER EGY, OLD HUNGARIAN SMALL LETTER EGY
assertEq(String.fromCodePoint(0x10C8F).toLowerCase().codePointAt(0), 0x10CCF); // OLD HUNGARIAN CAPITAL LETTER EH, OLD HUNGARIAN SMALL LETTER EH
assertEq(String.fromCodePoint(0x10C90).toLowerCase().codePointAt(0), 0x10CD0); // OLD HUNGARIAN CAPITAL LETTER I, OLD HUNGARIAN SMALL LETTER I
assertEq(String.fromCodePoint(0x10C91).toLowerCase().codePointAt(0), 0x10CD1); // OLD HUNGARIAN CAPITAL LETTER II, OLD HUNGARIAN SMALL LETTER II
assertEq(String.fromCodePoint(0x10C92).toLowerCase().codePointAt(0), 0x10CD2); // OLD HUNGARIAN CAPITAL LETTER EJ, OLD HUNGARIAN SMALL LETTER EJ
assertEq(String.fromCodePoint(0x10C93).toLowerCase().codePointAt(0), 0x10CD3); // OLD HUNGARIAN CAPITAL LETTER EK, OLD HUNGARIAN SMALL LETTER EK
assertEq(String.fromCodePoint(0x10C94).toLowerCase().codePointAt(0), 0x10CD4); // OLD HUNGARIAN CAPITAL LETTER AK, OLD HUNGARIAN SMALL LETTER AK
assertEq(String.fromCodePoint(0x10C95).toLowerCase().codePointAt(0), 0x10CD5); // OLD HUNGARIAN CAPITAL LETTER UNK, OLD HUNGARIAN SMALL LETTER UNK
assertEq(String.fromCodePoint(0x10C96).toLowerCase().codePointAt(0), 0x10CD6); // OLD HUNGARIAN CAPITAL LETTER EL, OLD HUNGARIAN SMALL LETTER EL
assertEq(String.fromCodePoint(0x10C97).toLowerCase().codePointAt(0), 0x10CD7); // OLD HUNGARIAN CAPITAL LETTER ELY, OLD HUNGARIAN SMALL LETTER ELY
assertEq(String.fromCodePoint(0x10C98).toLowerCase().codePointAt(0), 0x10CD8); // OLD HUNGARIAN CAPITAL LETTER EM, OLD HUNGARIAN SMALL LETTER EM
assertEq(String.fromCodePoint(0x10C99).toLowerCase().codePointAt(0), 0x10CD9); // OLD HUNGARIAN CAPITAL LETTER EN, OLD HUNGARIAN SMALL LETTER EN
assertEq(String.fromCodePoint(0x10C9A).toLowerCase().codePointAt(0), 0x10CDA); // OLD HUNGARIAN CAPITAL LETTER ENY, OLD HUNGARIAN SMALL LETTER ENY
assertEq(String.fromCodePoint(0x10C9B).toLowerCase().codePointAt(0), 0x10CDB); // OLD HUNGARIAN CAPITAL LETTER O, OLD HUNGARIAN SMALL LETTER O
assertEq(String.fromCodePoint(0x10C9C).toLowerCase().codePointAt(0), 0x10CDC); // OLD HUNGARIAN CAPITAL LETTER OO, OLD HUNGARIAN SMALL LETTER OO
assertEq(String.fromCodePoint(0x10C9D).toLowerCase().codePointAt(0), 0x10CDD); // OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG OE, OLD HUNGARIAN SMALL LETTER NIKOLSBURG OE
assertEq(String.fromCodePoint(0x10C9E).toLowerCase().codePointAt(0), 0x10CDE); // OLD HUNGARIAN CAPITAL LETTER RUDIMENTA OE, OLD HUNGARIAN SMALL LETTER RUDIMENTA OE
assertEq(String.fromCodePoint(0x10C9F).toLowerCase().codePointAt(0), 0x10CDF); // OLD HUNGARIAN CAPITAL LETTER OEE, OLD HUNGARIAN SMALL LETTER OEE
assertEq(String.fromCodePoint(0x10CA0).toLowerCase().codePointAt(0), 0x10CE0); // OLD HUNGARIAN CAPITAL LETTER EP, OLD HUNGARIAN SMALL LETTER EP
assertEq(String.fromCodePoint(0x10CA1).toLowerCase().codePointAt(0), 0x10CE1); // OLD HUNGARIAN CAPITAL LETTER EMP, OLD HUNGARIAN SMALL LETTER EMP
assertEq(String.fromCodePoint(0x10CA2).toLowerCase().codePointAt(0), 0x10CE2); // OLD HUNGARIAN CAPITAL LETTER ER, OLD HUNGARIAN SMALL LETTER ER
assertEq(String.fromCodePoint(0x10CA3).toLowerCase().codePointAt(0), 0x10CE3); // OLD HUNGARIAN CAPITAL LETTER SHORT ER, OLD HUNGARIAN SMALL LETTER SHORT ER
assertEq(String.fromCodePoint(0x10CA4).toLowerCase().codePointAt(0), 0x10CE4); // OLD HUNGARIAN CAPITAL LETTER ES, OLD HUNGARIAN SMALL LETTER ES
assertEq(String.fromCodePoint(0x10CA5).toLowerCase().codePointAt(0), 0x10CE5); // OLD HUNGARIAN CAPITAL LETTER ESZ, OLD HUNGARIAN SMALL LETTER ESZ
assertEq(String.fromCodePoint(0x10CA6).toLowerCase().codePointAt(0), 0x10CE6); // OLD HUNGARIAN CAPITAL LETTER ET, OLD HUNGARIAN SMALL LETTER ET
assertEq(String.fromCodePoint(0x10CA7).toLowerCase().codePointAt(0), 0x10CE7); // OLD HUNGARIAN CAPITAL LETTER ENT, OLD HUNGARIAN SMALL LETTER ENT
assertEq(String.fromCodePoint(0x10CA8).toLowerCase().codePointAt(0), 0x10CE8); // OLD HUNGARIAN CAPITAL LETTER ETY, OLD HUNGARIAN SMALL LETTER ETY
assertEq(String.fromCodePoint(0x10CA9).toLowerCase().codePointAt(0), 0x10CE9); // OLD HUNGARIAN CAPITAL LETTER ECH, OLD HUNGARIAN SMALL LETTER ECH
assertEq(String.fromCodePoint(0x10CAA).toLowerCase().codePointAt(0), 0x10CEA); // OLD HUNGARIAN CAPITAL LETTER U, OLD HUNGARIAN SMALL LETTER U
assertEq(String.fromCodePoint(0x10CAB).toLowerCase().codePointAt(0), 0x10CEB); // OLD HUNGARIAN CAPITAL LETTER UU, OLD HUNGARIAN SMALL LETTER UU
assertEq(String.fromCodePoint(0x10CAC).toLowerCase().codePointAt(0), 0x10CEC); // OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG UE, OLD HUNGARIAN SMALL LETTER NIKOLSBURG UE
assertEq(String.fromCodePoint(0x10CAD).toLowerCase().codePointAt(0), 0x10CED); // OLD HUNGARIAN CAPITAL LETTER RUDIMENTA UE, OLD HUNGARIAN SMALL LETTER RUDIMENTA UE
assertEq(String.fromCodePoint(0x10CAE).toLowerCase().codePointAt(0), 0x10CEE); // OLD HUNGARIAN CAPITAL LETTER EV, OLD HUNGARIAN SMALL LETTER EV
assertEq(String.fromCodePoint(0x10CAF).toLowerCase().codePointAt(0), 0x10CEF); // OLD HUNGARIAN CAPITAL LETTER EZ, OLD HUNGARIAN SMALL LETTER EZ
assertEq(String.fromCodePoint(0x10CB0).toLowerCase().codePointAt(0), 0x10CF0); // OLD HUNGARIAN CAPITAL LETTER EZS, OLD HUNGARIAN SMALL LETTER EZS
assertEq(String.fromCodePoint(0x10CB1).toLowerCase().codePointAt(0), 0x10CF1); // OLD HUNGARIAN CAPITAL LETTER ENT-SHAPED SIGN, OLD HUNGARIAN SMALL LETTER ENT-SHAPED SIGN
assertEq(String.fromCodePoint(0x10CB2).toLowerCase().codePointAt(0), 0x10CF2); // OLD HUNGARIAN CAPITAL LETTER US, OLD HUNGARIAN SMALL LETTER US
assertEq(String.fromCodePoint(0x118A0).toLowerCase().codePointAt(0), 0x118C0); // WARANG CITI CAPITAL LETTER NGAA, WARANG CITI SMALL LETTER NGAA
assertEq(String.fromCodePoint(0x118A1).toLowerCase().codePointAt(0), 0x118C1); // WARANG CITI CAPITAL LETTER A, WARANG CITI SMALL LETTER A
assertEq(String.fromCodePoint(0x118A2).toLowerCase().codePointAt(0), 0x118C2); // WARANG CITI CAPITAL LETTER WI, WARANG CITI SMALL LETTER WI
assertEq(String.fromCodePoint(0x118A3).toLowerCase().codePointAt(0), 0x118C3); // WARANG CITI CAPITAL LETTER YU, WARANG CITI SMALL LETTER YU
assertEq(String.fromCodePoint(0x118A4).toLowerCase().codePointAt(0), 0x118C4); // WARANG CITI CAPITAL LETTER YA, WARANG CITI SMALL LETTER YA
assertEq(String.fromCodePoint(0x118A5).toLowerCase().codePointAt(0), 0x118C5); // WARANG CITI CAPITAL LETTER YO, WARANG CITI SMALL LETTER YO
assertEq(String.fromCodePoint(0x118A6).toLowerCase().codePointAt(0), 0x118C6); // WARANG CITI CAPITAL LETTER II, WARANG CITI SMALL LETTER II
assertEq(String.fromCodePoint(0x118A7).toLowerCase().codePointAt(0), 0x118C7); // WARANG CITI CAPITAL LETTER UU, WARANG CITI SMALL LETTER UU
assertEq(String.fromCodePoint(0x118A8).toLowerCase().codePointAt(0), 0x118C8); // WARANG CITI CAPITAL LETTER E, WARANG CITI SMALL LETTER E
assertEq(String.fromCodePoint(0x118A9).toLowerCase().codePointAt(0), 0x118C9); // WARANG CITI CAPITAL LETTER O, WARANG CITI SMALL LETTER O
assertEq(String.fromCodePoint(0x118AA).toLowerCase().codePointAt(0), 0x118CA); // WARANG CITI CAPITAL LETTER ANG, WARANG CITI SMALL LETTER ANG
assertEq(String.fromCodePoint(0x118AB).toLowerCase().codePointAt(0), 0x118CB); // WARANG CITI CAPITAL LETTER GA, WARANG CITI SMALL LETTER GA
assertEq(String.fromCodePoint(0x118AC).toLowerCase().codePointAt(0), 0x118CC); // WARANG CITI CAPITAL LETTER KO, WARANG CITI SMALL LETTER KO
assertEq(String.fromCodePoint(0x118AD).toLowerCase().codePointAt(0), 0x118CD); // WARANG CITI CAPITAL LETTER ENY, WARANG CITI SMALL LETTER ENY
assertEq(String.fromCodePoint(0x118AE).toLowerCase().codePointAt(0), 0x118CE); // WARANG CITI CAPITAL LETTER YUJ, WARANG CITI SMALL LETTER YUJ
assertEq(String.fromCodePoint(0x118AF).toLowerCase().codePointAt(0), 0x118CF); // WARANG CITI CAPITAL LETTER UC, WARANG CITI SMALL LETTER UC
assertEq(String.fromCodePoint(0x118B0).toLowerCase().codePointAt(0), 0x118D0); // WARANG CITI CAPITAL LETTER ENN, WARANG CITI SMALL LETTER ENN
assertEq(String.fromCodePoint(0x118B1).toLowerCase().codePointAt(0), 0x118D1); // WARANG CITI CAPITAL LETTER ODD, WARANG CITI SMALL LETTER ODD
assertEq(String.fromCodePoint(0x118B2).toLowerCase().codePointAt(0), 0x118D2); // WARANG CITI CAPITAL LETTER TTE, WARANG CITI SMALL LETTER TTE
assertEq(String.fromCodePoint(0x118B3).toLowerCase().codePointAt(0), 0x118D3); // WARANG CITI CAPITAL LETTER NUNG, WARANG CITI SMALL LETTER NUNG
assertEq(String.fromCodePoint(0x118B4).toLowerCase().codePointAt(0), 0x118D4); // WARANG CITI CAPITAL LETTER DA, WARANG CITI SMALL LETTER DA
assertEq(String.fromCodePoint(0x118B5).toLowerCase().codePointAt(0), 0x118D5); // WARANG CITI CAPITAL LETTER AT, WARANG CITI SMALL LETTER AT
assertEq(String.fromCodePoint(0x118B6).toLowerCase().codePointAt(0), 0x118D6); // WARANG CITI CAPITAL LETTER AM, WARANG CITI SMALL LETTER AM
assertEq(String.fromCodePoint(0x118B7).toLowerCase().codePointAt(0), 0x118D7); // WARANG CITI CAPITAL LETTER BU, WARANG CITI SMALL LETTER BU
assertEq(String.fromCodePoint(0x118B8).toLowerCase().codePointAt(0), 0x118D8); // WARANG CITI CAPITAL LETTER PU, WARANG CITI SMALL LETTER PU
assertEq(String.fromCodePoint(0x118B9).toLowerCase().codePointAt(0), 0x118D9); // WARANG CITI CAPITAL LETTER HIYO, WARANG CITI SMALL LETTER HIYO
assertEq(String.fromCodePoint(0x118BA).toLowerCase().codePointAt(0), 0x118DA); // WARANG CITI CAPITAL LETTER HOLO, WARANG CITI SMALL LETTER HOLO
assertEq(String.fromCodePoint(0x118BB).toLowerCase().codePointAt(0), 0x118DB); // WARANG CITI CAPITAL LETTER HORR, WARANG CITI SMALL LETTER HORR
assertEq(String.fromCodePoint(0x118BC).toLowerCase().codePointAt(0), 0x118DC); // WARANG CITI CAPITAL LETTER HAR, WARANG CITI SMALL LETTER HAR
assertEq(String.fromCodePoint(0x118BD).toLowerCase().codePointAt(0), 0x118DD); // WARANG CITI CAPITAL LETTER SSUU, WARANG CITI SMALL LETTER SSUU
assertEq(String.fromCodePoint(0x118BE).toLowerCase().codePointAt(0), 0x118DE); // WARANG CITI CAPITAL LETTER SII, WARANG CITI SMALL LETTER SII
assertEq(String.fromCodePoint(0x118BF).toLowerCase().codePointAt(0), 0x118DF); // WARANG CITI CAPITAL LETTER VIYO, WARANG CITI SMALL LETTER VIYO
assertEq(String.fromCodePoint(0x16E40).toLowerCase().codePointAt(0), 0x16E60); // MEDEFAIDRIN CAPITAL LETTER M, MEDEFAIDRIN SMALL LETTER M
assertEq(String.fromCodePoint(0x16E41).toLowerCase().codePointAt(0), 0x16E61); // MEDEFAIDRIN CAPITAL LETTER S, MEDEFAIDRIN SMALL LETTER S
assertEq(String.fromCodePoint(0x16E42).toLowerCase().codePointAt(0), 0x16E62); // MEDEFAIDRIN CAPITAL LETTER V, MEDEFAIDRIN SMALL LETTER V
assertEq(String.fromCodePoint(0x16E43).toLowerCase().codePointAt(0), 0x16E63); // MEDEFAIDRIN CAPITAL LETTER W, MEDEFAIDRIN SMALL LETTER W
assertEq(String.fromCodePoint(0x16E44).toLowerCase().codePointAt(0), 0x16E64); // MEDEFAIDRIN CAPITAL LETTER ATIU, MEDEFAIDRIN SMALL LETTER ATIU
assertEq(String.fromCodePoint(0x16E45).toLowerCase().codePointAt(0), 0x16E65); // MEDEFAIDRIN CAPITAL LETTER Z, MEDEFAIDRIN SMALL LETTER Z
assertEq(String.fromCodePoint(0x16E46).toLowerCase().codePointAt(0), 0x16E66); // MEDEFAIDRIN CAPITAL LETTER KP, MEDEFAIDRIN SMALL LETTER KP
assertEq(String.fromCodePoint(0x16E47).toLowerCase().codePointAt(0), 0x16E67); // MEDEFAIDRIN CAPITAL LETTER P, MEDEFAIDRIN SMALL LETTER P
assertEq(String.fromCodePoint(0x16E48).toLowerCase().codePointAt(0), 0x16E68); // MEDEFAIDRIN CAPITAL LETTER T, MEDEFAIDRIN SMALL LETTER T
assertEq(String.fromCodePoint(0x16E49).toLowerCase().codePointAt(0), 0x16E69); // MEDEFAIDRIN CAPITAL LETTER G, MEDEFAIDRIN SMALL LETTER G
assertEq(String.fromCodePoint(0x16E4A).toLowerCase().codePointAt(0), 0x16E6A); // MEDEFAIDRIN CAPITAL LETTER F, MEDEFAIDRIN SMALL LETTER F
assertEq(String.fromCodePoint(0x16E4B).toLowerCase().codePointAt(0), 0x16E6B); // MEDEFAIDRIN CAPITAL LETTER I, MEDEFAIDRIN SMALL LETTER I
assertEq(String.fromCodePoint(0x16E4C).toLowerCase().codePointAt(0), 0x16E6C); // MEDEFAIDRIN CAPITAL LETTER K, MEDEFAIDRIN SMALL LETTER K
assertEq(String.fromCodePoint(0x16E4D).toLowerCase().codePointAt(0), 0x16E6D); // MEDEFAIDRIN CAPITAL LETTER A, MEDEFAIDRIN SMALL LETTER A
assertEq(String.fromCodePoint(0x16E4E).toLowerCase().codePointAt(0), 0x16E6E); // MEDEFAIDRIN CAPITAL LETTER J, MEDEFAIDRIN SMALL LETTER J
assertEq(String.fromCodePoint(0x16E4F).toLowerCase().codePointAt(0), 0x16E6F); // MEDEFAIDRIN CAPITAL LETTER E, MEDEFAIDRIN SMALL LETTER E
assertEq(String.fromCodePoint(0x16E50).toLowerCase().codePointAt(0), 0x16E70); // MEDEFAIDRIN CAPITAL LETTER B, MEDEFAIDRIN SMALL LETTER B
assertEq(String.fromCodePoint(0x16E51).toLowerCase().codePointAt(0), 0x16E71); // MEDEFAIDRIN CAPITAL LETTER C, MEDEFAIDRIN SMALL LETTER C
assertEq(String.fromCodePoint(0x16E52).toLowerCase().codePointAt(0), 0x16E72); // MEDEFAIDRIN CAPITAL LETTER U, MEDEFAIDRIN SMALL LETTER U
assertEq(String.fromCodePoint(0x16E53).toLowerCase().codePointAt(0), 0x16E73); // MEDEFAIDRIN CAPITAL LETTER YU, MEDEFAIDRIN SMALL LETTER YU
assertEq(String.fromCodePoint(0x16E54).toLowerCase().codePointAt(0), 0x16E74); // MEDEFAIDRIN CAPITAL LETTER L, MEDEFAIDRIN SMALL LETTER L
assertEq(String.fromCodePoint(0x16E55).toLowerCase().codePointAt(0), 0x16E75); // MEDEFAIDRIN CAPITAL LETTER Q, MEDEFAIDRIN SMALL LETTER Q
assertEq(String.fromCodePoint(0x16E56).toLowerCase().codePointAt(0), 0x16E76); // MEDEFAIDRIN CAPITAL LETTER HP, MEDEFAIDRIN SMALL LETTER HP
assertEq(String.fromCodePoint(0x16E57).toLowerCase().codePointAt(0), 0x16E77); // MEDEFAIDRIN CAPITAL LETTER NY, MEDEFAIDRIN SMALL LETTER NY
assertEq(String.fromCodePoint(0x16E58).toLowerCase().codePointAt(0), 0x16E78); // MEDEFAIDRIN CAPITAL LETTER X, MEDEFAIDRIN SMALL LETTER X
assertEq(String.fromCodePoint(0x16E59).toLowerCase().codePointAt(0), 0x16E79); // MEDEFAIDRIN CAPITAL LETTER D, MEDEFAIDRIN SMALL LETTER D
assertEq(String.fromCodePoint(0x16E5A).toLowerCase().codePointAt(0), 0x16E7A); // MEDEFAIDRIN CAPITAL LETTER OE, MEDEFAIDRIN SMALL LETTER OE
assertEq(String.fromCodePoint(0x16E5B).toLowerCase().codePointAt(0), 0x16E7B); // MEDEFAIDRIN CAPITAL LETTER N, MEDEFAIDRIN SMALL LETTER N
assertEq(String.fromCodePoint(0x16E5C).toLowerCase().codePointAt(0), 0x16E7C); // MEDEFAIDRIN CAPITAL LETTER R, MEDEFAIDRIN SMALL LETTER R
assertEq(String.fromCodePoint(0x16E5D).toLowerCase().codePointAt(0), 0x16E7D); // MEDEFAIDRIN CAPITAL LETTER O, MEDEFAIDRIN SMALL LETTER O
assertEq(String.fromCodePoint(0x16E5E).toLowerCase().codePointAt(0), 0x16E7E); // MEDEFAIDRIN CAPITAL LETTER AI, MEDEFAIDRIN SMALL LETTER AI
assertEq(String.fromCodePoint(0x16E5F).toLowerCase().codePointAt(0), 0x16E7F); // MEDEFAIDRIN CAPITAL LETTER Y, MEDEFAIDRIN SMALL LETTER Y
assertEq(String.fromCodePoint(0x1E900).toLowerCase().codePointAt(0), 0x1E922); // ADLAM CAPITAL LETTER ALIF, ADLAM SMALL LETTER ALIF
assertEq(String.fromCodePoint(0x1E901).toLowerCase().codePointAt(0), 0x1E923); // ADLAM CAPITAL LETTER DAALI, ADLAM SMALL LETTER DAALI
assertEq(String.fromCodePoint(0x1E902).toLowerCase().codePointAt(0), 0x1E924); // ADLAM CAPITAL LETTER LAAM, ADLAM SMALL LETTER LAAM
assertEq(String.fromCodePoint(0x1E903).toLowerCase().codePointAt(0), 0x1E925); // ADLAM CAPITAL LETTER MIIM, ADLAM SMALL LETTER MIIM
assertEq(String.fromCodePoint(0x1E904).toLowerCase().codePointAt(0), 0x1E926); // ADLAM CAPITAL LETTER BA, ADLAM SMALL LETTER BA
assertEq(String.fromCodePoint(0x1E905).toLowerCase().codePointAt(0), 0x1E927); // ADLAM CAPITAL LETTER SINNYIIYHE, ADLAM SMALL LETTER SINNYIIYHE
assertEq(String.fromCodePoint(0x1E906).toLowerCase().codePointAt(0), 0x1E928); // ADLAM CAPITAL LETTER PE, ADLAM SMALL LETTER PE
assertEq(String.fromCodePoint(0x1E907).toLowerCase().codePointAt(0), 0x1E929); // ADLAM CAPITAL LETTER BHE, ADLAM SMALL LETTER BHE
assertEq(String.fromCodePoint(0x1E908).toLowerCase().codePointAt(0), 0x1E92A); // ADLAM CAPITAL LETTER RA, ADLAM SMALL LETTER RA
assertEq(String.fromCodePoint(0x1E909).toLowerCase().codePointAt(0), 0x1E92B); // ADLAM CAPITAL LETTER E, ADLAM SMALL LETTER E
assertEq(String.fromCodePoint(0x1E90A).toLowerCase().codePointAt(0), 0x1E92C); // ADLAM CAPITAL LETTER FA, ADLAM SMALL LETTER FA
assertEq(String.fromCodePoint(0x1E90B).toLowerCase().codePointAt(0), 0x1E92D); // ADLAM CAPITAL LETTER I, ADLAM SMALL LETTER I
assertEq(String.fromCodePoint(0x1E90C).toLowerCase().codePointAt(0), 0x1E92E); // ADLAM CAPITAL LETTER O, ADLAM SMALL LETTER O
assertEq(String.fromCodePoint(0x1E90D).toLowerCase().codePointAt(0), 0x1E92F); // ADLAM CAPITAL LETTER DHA, ADLAM SMALL LETTER DHA
assertEq(String.fromCodePoint(0x1E90E).toLowerCase().codePointAt(0), 0x1E930); // ADLAM CAPITAL LETTER YHE, ADLAM SMALL LETTER YHE
assertEq(String.fromCodePoint(0x1E90F).toLowerCase().codePointAt(0), 0x1E931); // ADLAM CAPITAL LETTER WAW, ADLAM SMALL LETTER WAW
assertEq(String.fromCodePoint(0x1E910).toLowerCase().codePointAt(0), 0x1E932); // ADLAM CAPITAL LETTER NUN, ADLAM SMALL LETTER NUN
assertEq(String.fromCodePoint(0x1E911).toLowerCase().codePointAt(0), 0x1E933); // ADLAM CAPITAL LETTER KAF, ADLAM SMALL LETTER KAF
assertEq(String.fromCodePoint(0x1E912).toLowerCase().codePointAt(0), 0x1E934); // ADLAM CAPITAL LETTER YA, ADLAM SMALL LETTER YA
assertEq(String.fromCodePoint(0x1E913).toLowerCase().codePointAt(0), 0x1E935); // ADLAM CAPITAL LETTER U, ADLAM SMALL LETTER U
assertEq(String.fromCodePoint(0x1E914).toLowerCase().codePointAt(0), 0x1E936); // ADLAM CAPITAL LETTER JIIM, ADLAM SMALL LETTER JIIM
assertEq(String.fromCodePoint(0x1E915).toLowerCase().codePointAt(0), 0x1E937); // ADLAM CAPITAL LETTER CHI, ADLAM SMALL LETTER CHI
assertEq(String.fromCodePoint(0x1E916).toLowerCase().codePointAt(0), 0x1E938); // ADLAM CAPITAL LETTER HA, ADLAM SMALL LETTER HA
assertEq(String.fromCodePoint(0x1E917).toLowerCase().codePointAt(0), 0x1E939); // ADLAM CAPITAL LETTER QAAF, ADLAM SMALL LETTER QAAF
assertEq(String.fromCodePoint(0x1E918).toLowerCase().codePointAt(0), 0x1E93A); // ADLAM CAPITAL LETTER GA, ADLAM SMALL LETTER GA
assertEq(String.fromCodePoint(0x1E919).toLowerCase().codePointAt(0), 0x1E93B); // ADLAM CAPITAL LETTER NYA, ADLAM SMALL LETTER NYA
assertEq(String.fromCodePoint(0x1E91A).toLowerCase().codePointAt(0), 0x1E93C); // ADLAM CAPITAL LETTER TU, ADLAM SMALL LETTER TU
assertEq(String.fromCodePoint(0x1E91B).toLowerCase().codePointAt(0), 0x1E93D); // ADLAM CAPITAL LETTER NHA, ADLAM SMALL LETTER NHA
assertEq(String.fromCodePoint(0x1E91C).toLowerCase().codePointAt(0), 0x1E93E); // ADLAM CAPITAL LETTER VA, ADLAM SMALL LETTER VA
assertEq(String.fromCodePoint(0x1E91D).toLowerCase().codePointAt(0), 0x1E93F); // ADLAM CAPITAL LETTER KHA, ADLAM SMALL LETTER KHA
assertEq(String.fromCodePoint(0x1E91E).toLowerCase().codePointAt(0), 0x1E940); // ADLAM CAPITAL LETTER GBE, ADLAM SMALL LETTER GBE
assertEq(String.fromCodePoint(0x1E91F).toLowerCase().codePointAt(0), 0x1E941); // ADLAM CAPITAL LETTER ZAL, ADLAM SMALL LETTER ZAL
assertEq(String.fromCodePoint(0x1E920).toLowerCase().codePointAt(0), 0x1E942); // ADLAM CAPITAL LETTER KPO, ADLAM SMALL LETTER KPO
assertEq(String.fromCodePoint(0x1E921).toLowerCase().codePointAt(0), 0x1E943); // ADLAM CAPITAL LETTER SHA, ADLAM SMALL LETTER SHA
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -56,6 +56,29 @@ skip script test262/ch10/10.6/10.6-14-b-4-s.js
skip script test262/ch10/10.6/10.6-13-b-1-s.js
skip script test262/ch10/10.6/10.6-13-b-2-s.js
# ES2017 Intl legacy constructor semantics changes made these tests invalid
# (bug 1328386).
skip script test262/intl402/ch10/10.1/10.1.1_1.js
skip script test262/intl402/ch10/10.1/10.1.2_a.js
skip script test262/intl402/ch11/11.1/11.1.1_1.js
skip script test262/intl402/ch12/12.1/12.1.1_1.js
# Intl.{Collator,DateTimeFormat,NumberFormat}.prototype are now plain objects.
skip script test262/intl402/ch10/10.3/10.3_a.js
skip script test262/intl402/ch11/11.3/11.3_a.js
skip script test262/intl402/ch12/12.3/12.3_a.js
# Tests already removed upstream (https://github.com/tc39/test262/pull/807/files),
# but apparently for the wrong reason.
skip script test262/intl402/ch10/10.1/10.1.2.1_4.js
skip script test262/intl402/ch11/11.1/11.1.2.1_4.js
skip script test262/intl402/ch12/12.1/12.1.2.1_4.js
# Tests not updated to follow new language tag canonicalisation.
skip script test262/intl402/Locale/constructor-non-iana-canon.js
skip script test262/intl402/Intl/getCanonicalLocales/preferred-variant.js
skip script test262/intl402/Intl/getCanonicalLocales/non-iana-canon.js
#######################################################################
# Tests disabled due to jstest limitations wrt imported test262 tests #
#######################################################################

View file

@ -0,0 +1,69 @@
// |reftest| skip-if(release_or_beta)
const defaultLocale = "en";
const defaultCalendar = new Intl.DateTimeFormat(defaultLocale).resolvedOptions().calendar;
function createWithLocale(locale, calendar) {
return new Intl.DateTimeFormat(locale, {calendar});
}
function create(calendar) {
return createWithLocale(defaultLocale, calendar);
}
// Empty string should throw.
assertThrowsInstanceOf(() => create(""), RangeError);
// Trailing \0 should throw.
assertThrowsInstanceOf(() => create("gregory\0"), RangeError);
// Too short or too long strings should throw.
assertThrowsInstanceOf(() => create("a"), RangeError);
assertThrowsInstanceOf(() => create("toolongstring"), RangeError);
// Throw even when prefix is valid.
assertThrowsInstanceOf(() => create("gregory-toolongstring"), RangeError);
// |calendar| can be set to |undefined|.
let dtf = create(undefined);
assertEq(dtf.resolvedOptions().calendar, defaultCalendar);
// Unsupported calendars are ignored.
dtf = create("xxxxxxxx");
assertEq(dtf.resolvedOptions().calendar, defaultCalendar);
// Calendars in options overwrite Unicode extension keyword.
dtf = createWithLocale(`${defaultLocale}-u-ca-iso8601`, "japanese");
assertEq(dtf.resolvedOptions().locale, defaultLocale);
assertEq(dtf.resolvedOptions().calendar, "japanese");
// |calendar| option ignores case.
dtf = create("CHINESE");
assertEq(dtf.resolvedOptions().locale, defaultLocale);
assertEq(dtf.resolvedOptions().calendar, "chinese");
const calendars = [
"buddhist", "chinese", "coptic", "dangi", "ethioaa", "ethiopic-amete-alem",
"ethiopic", "gregory", "hebrew", "indian", "islamic", "islamic-umalqura",
"islamic-tbla", "islamic-civil", "islamic-rgsa", "iso8601", "japanese",
"persian", "roc", "islamicc",
];
// https://github.com/tc39/proposal-intl-locale/issues/96
const canonical = {
"islamicc": "islamic-civil",
"ethiopic-amete-alem": "ethioaa",
};
for (let calendar of calendars) {
let dtf1 = new Intl.DateTimeFormat(`${defaultLocale}-u-ca-${calendar}`);
let dtf2 = new Intl.DateTimeFormat(defaultLocale, {calendar});
assertEq(dtf1.resolvedOptions().calendar, canonical[calendar] ?? calendar);
assertEq(dtf2.resolvedOptions().calendar, canonical[calendar] ?? calendar);
assertEq(dtf2.format(0), dtf1.format(0));
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -0,0 +1,64 @@
// |reftest| skip-if(release_or_beta)
const defaultLocale = "en";
const defaultNumberingSystem = new Intl.DateTimeFormat(defaultLocale).resolvedOptions().numberingSystem;
function createWithLocale(locale, numberingSystem) {
return new Intl.DateTimeFormat(locale, {numberingSystem});
}
function create(numberingSystem) {
return createWithLocale(defaultLocale, numberingSystem);
}
// Empty string should throw.
assertThrowsInstanceOf(() => create(""), RangeError);
// Trailing \0 should throw.
assertThrowsInstanceOf(() => create("latn\0"), RangeError);
// Too short or too long strings should throw.
assertThrowsInstanceOf(() => create("a"), RangeError);
assertThrowsInstanceOf(() => create("toolongstring"), RangeError);
// Throw even when prefix is valid.
assertThrowsInstanceOf(() => create("latn-toolongstring"), RangeError);
// |numberingSystem| can be set to |undefined|.
let dtf = create(undefined);
assertEq(dtf.resolvedOptions().numberingSystem, defaultNumberingSystem);
// Unsupported numbering systems are ignored.
dtf = create("xxxxxxxx");
assertEq(dtf.resolvedOptions().numberingSystem, defaultNumberingSystem);
// Numbering system in options overwrite Unicode extension keyword.
dtf = createWithLocale(`${defaultLocale}-u-nu-thai`, "arab");
assertEq(dtf.resolvedOptions().locale, defaultLocale);
assertEq(dtf.resolvedOptions().numberingSystem, "arab");
// |numberingSystem| option ignores case.
dtf = create("ARAB");
assertEq(dtf.resolvedOptions().locale, defaultLocale);
assertEq(dtf.resolvedOptions().numberingSystem, "arab");
const numberingSystems = [
"arab", "arabext", "bali", "beng", "deva",
"fullwide", "gujr", "guru", "hanidec", "khmr",
"knda", "laoo", "latn", "limb", "mlym",
"mong", "mymr", "orya", "tamldec", "telu",
"thai", "tibt",
];
for (let numberingSystem of numberingSystems) {
let dtf1 = new Intl.DateTimeFormat(`${defaultLocale}-u-nu-${numberingSystem}`);
let dtf2 = new Intl.DateTimeFormat(defaultLocale, {numberingSystem});
assertEq(dtf1.resolvedOptions().numberingSystem, numberingSystem);
assertEq(dtf2.resolvedOptions().numberingSystem, numberingSystem);
assertEq(dtf2.format(0), dtf1.format(0));
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -0,0 +1,11 @@
// |reftest| skip-if(!this.hasOwnProperty('Intl'))
// ApplyOptionsToTag canonicalises the locale identifier before applying the
// options. That means "und-Armn-SU" is first canonicalised to "und-Armn-AM",
// then the language is changed to "ru". If "ru" were applied first, the result
// would be "ru-Armn-RU" instead.
assertEq(new Intl.Locale("und-Armn-SU", {language:"ru"}).toString(),
"ru-Armn-AM");
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -0,0 +1,28 @@
// |reftest| skip-if(!this.hasOwnProperty('Intl'))
var g = newGlobal();
var tag = "de-Latn-AT-u-ca-gregory-nu-latn-co-phonebk-kf-false-kn-hc-h23";
var locale = new Intl.Locale(tag);
var ccwLocale = new g.Intl.Locale(tag);
for (var [key, {get, value = get}] of Object.entries(Object.getOwnPropertyDescriptors(Intl.Locale.prototype))) {
if (typeof value === "function") {
if (key !== "constructor") {
var expectedValue = value.call(locale);
if (typeof expectedValue === "string" || typeof expectedValue === "boolean") {
assertEq(value.call(ccwLocale), expectedValue, key);
} else if (expectedValue instanceof Intl.Locale) {
assertEq(value.call(ccwLocale).toString(), expectedValue.toString(), key);
} else {
throw new Error("unexpected result value");
}
} else {
assertEq(new value(ccwLocale).toString(), new value(locale).toString(), key);
}
}
}
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -0,0 +1,75 @@
// |reftest| skip-if(!this.hasOwnProperty('Intl'))
var testData = [
{
tag: "cel-gaulish",
options: {
numberingSystem: "latn",
},
canonical: "xtg-u-nu-latn-x-cel-gaulish",
extensions: {
numberingSystem: "latn",
},
},
{
tag: "cel-gaulish",
options: {
region: "FR",
numberingSystem: "latn",
},
canonical: "xtg-FR-u-nu-latn-x-cel-gaulish",
extensions: {
numberingSystem: "latn",
},
},
{
tag: "art-lojban",
options: {
numberingSystem: "latn",
},
canonical: "jbo-u-nu-latn",
extensions: {
numberingSystem: "latn",
},
},
{
tag: "art-lojban",
options: {
region: "ZZ",
numberingSystem: "latn",
},
canonical: "jbo-ZZ-u-nu-latn",
extensions: {
numberingSystem: "latn",
},
},
];
for (var {tag, options, canonical, extensions} of testData) {
var loc = new Intl.Locale(tag, options);
assertEq(loc.toString(), canonical);
for (var [name, value] of Object.entries(extensions)) {
assertEq(loc[name], value);
}
}
var errorTestData = [
"en-gb-oed",
"i-default",
"sgn-ch-de",
"zh-min",
"zh-min-nan",
"zh-hakka-hakka",
];
for (var tag of errorTestData) {
assertThrowsInstanceOf(() => new Intl.Locale(tag), RangeError);
assertThrowsInstanceOf(() => new Intl.Locale(tag, {}), RangeError);
}
if (typeof reportCompare === "function")
reportCompare(0, 0);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,61 @@
// |reftest| skip-if(!this.hasOwnProperty('Intl'))
var testDataMaximal = {
// Keeps "und" primary language.
"und-AQ": "und-Latn-AQ",
// Modifies primary language.
"und-Cyrl-RO": "bg-Cyrl-RO",
}
var testDataMinimal = {
// Undefined primary language.
"und": "en",
"und-Thai": "th",
"und-419": "es-419",
"und-150": "ru",
"und-AT": "de-AT",
// https://ssl.icu-project.org/trac/ticket/13786
"aae-Latn-IT": "aae-Latn-IT",
"aae-Thai-CO": "aae-Thai-CO",
// https://ssl.icu-project.org/trac/ticket/10220
// https://ssl.icu-project.org/trac/ticket/12345
"und-CW": "pap-CW",
"und-US": "en",
"zh-Hant": "zh-TW",
"zh-Hani": "zh-Hani",
};
// Add variants, extensions, and privateuse subtags and ensure they don't
// modify the result of the likely subtags algorithms.
var extras = [
"fonipa",
"a-not-assigned",
"u-attr",
"u-co",
"u-co-phonebk",
"x-private",
];
for (var [tag, maximal] of Object.entries(testDataMaximal)) {
assertEq(new Intl.Locale(tag).maximize().toString(), maximal);
assertEq(new Intl.Locale(maximal).maximize().toString(), maximal);
for (var extra of extras) {
assertEq(new Intl.Locale(tag + "-" + extra).maximize().toString(), maximal + "-" + extra);
}
}
for (var [tag, minimal] of Object.entries(testDataMinimal)) {
assertEq(new Intl.Locale(tag).minimize().toString(), minimal);
assertEq(new Intl.Locale(minimal).minimize().toString(), minimal);
for (var extra of extras) {
assertEq(new Intl.Locale(tag + "-" + extra).minimize().toString(), minimal + "-" + extra);
}
}
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -0,0 +1,26 @@
// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.wrapWithProto)
var tag = "de-Latn-AT-u-ca-gregory-nu-latn-co-phonebk-kf-false-kn-hc-h23";
var locale = new Intl.Locale(tag);
var scwLocale = wrapWithProto(locale, Intl.Locale.prototype);
for (var [key, {get, value = get}] of Object.entries(Object.getOwnPropertyDescriptors(Intl.Locale.prototype))) {
if (typeof value === "function") {
if (key !== "constructor") {
var expectedValue = value.call(locale);
if (typeof expectedValue === "string" || typeof expectedValue === "boolean") {
assertEq(value.call(scwLocale), expectedValue, key);
} else if (expectedValue instanceof Intl.Locale) {
assertEq(value.call(scwLocale).toString(), expectedValue.toString(), key);
} else {
throw new Error("unexpected result value");
}
} else {
assertEq(new value(scwLocale).toString(), new value(locale).toString(), key);
}
}
}
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

View file

@ -0,0 +1,98 @@
// |reftest| skip-if(!this.hasOwnProperty('Intl'))
function assertProperty(object, name, desc) {
assertEq(desc === undefined || (typeof desc === "object" && desc !== null), true,
"desc is a property descriptor");
var actual = Object.getOwnPropertyDescriptor(object, name);
if (desc === undefined) {
assertEq(actual, desc, `property ${String(name)} is absent`);
return;
}
assertEq(actual !== undefined, true, `property ${String(name)} is present`);
var fields = ["value", "writable", "enumerable", "configurable", "get", "set"];
for (var field of fields) {
if (Object.prototype.hasOwnProperty.call(desc, field)) {
assertEq(actual[field], desc[field], `unexpected value for ${field}`);
}
}
}
function assertBuiltinFunction(fn, length, name) {
assertProperty(fn, "length", {
value: length, writable: false, enumerable: false, configurable: true,
});
}
function assertBuiltinMethod(object, propName, length, name) {
var desc = Object.getOwnPropertyDescriptor(object, propName);
assertProperty(object, propName, {
value: desc.value, writable: true, enumerable: false, configurable: true
});
assertBuiltinFunction(desc.value, length, name);
}
function assertBuiltinGetter(object, propName, length, name) {
var desc = Object.getOwnPropertyDescriptor(object, propName);
assertBuiltinFunction(desc.get, length, name);
}
// Intl.Locale( tag[, options] )
assertBuiltinFunction(Intl.Locale, 1, "Locale");
// Properties of the Intl.Locale Constructor
// Intl.Locale.prototype
assertProperty(Intl.Locale, "prototype", {
value: Intl.Locale.prototype, writable: false, enumerable: false, configurable: false,
});
// Properties of the Intl.Locale Prototype Object
// Intl.Locale.prototype.constructor
assertProperty(Intl.Locale.prototype, "constructor", {
value: Intl.Locale, writable: true, enumerable: false, configurable: true,
});
// Intl.Locale.prototype[ @@toStringTag ]
assertProperty(Intl.Locale.prototype, Symbol.toStringTag, {
value: "Intl.Locale", writable: false, enumerable: false, configurable: true,
});
// Intl.Locale.prototype.toString ()
assertBuiltinMethod(Intl.Locale.prototype, "toString", 0, "toString");
// get Intl.Locale.prototype.baseName
assertBuiltinGetter(Intl.Locale.prototype, "baseName", 0, "get baseName");
// get Intl.Locale.prototype.calendar
assertBuiltinGetter(Intl.Locale.prototype, "calendar", 0, "get calendar");
// get Intl.Locale.prototype.collation
assertBuiltinGetter(Intl.Locale.prototype, "collation", 0, "get collation");
// get Intl.Locale.prototype.hourCycle
assertBuiltinGetter(Intl.Locale.prototype, "hourCycle", 0, "get hourCycle");
// get Intl.Locale.prototype.caseFirst
assertBuiltinGetter(Intl.Locale.prototype, "caseFirst", 0, "get caseFirst");
// get Intl.Locale.prototype.numeric
assertBuiltinGetter(Intl.Locale.prototype, "numeric", 0, "get numeric");
// get Intl.Locale.prototype.numberingSystem
assertBuiltinGetter(Intl.Locale.prototype, "numberingSystem", 0, "get numberingSystem");
// get Intl.Locale.prototype.language
assertBuiltinGetter(Intl.Locale.prototype, "language", 0, "get language");
// get Intl.Locale.prototype.script
assertBuiltinGetter(Intl.Locale.prototype, "script", 0, "get script");
// get Intl.Locale.prototype.region
assertBuiltinGetter(Intl.Locale.prototype, "region", 0, "get region");
if (typeof reportCompare === "function")
reportCompare(0, 0);

Some files were not shown because too many files have changed in this diff Show more