Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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/. */
/* Implementation of the Intl object and its non-constructor properties. */
#include "builtin/intl/IntlObject.h"
#include "mozilla/Assertions.h"
#include "mozilla/Likely.h"
#include "mozilla/Range.h"
#include <algorithm>
#include <iterator>
#include "jsapi.h"
#include "builtin/Array.h"
#include "builtin/intl/Collator.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/DateTimeFormat.h"
#include "builtin/intl/LanguageTag.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/CharacterEncoding.h"
#include "js/Class.h"
#include "js/PropertySpec.h"
#include "js/Result.h"
#include "js/StableStringChars.h"
#include "unicode/ucal.h"
#include "unicode/udat.h"
#include "unicode/udatpg.h"
#include "unicode/uloc.h"
#include "unicode/utypes.h"
#include "vm/GlobalObject.h"
#include "vm/JSAtom.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/StringType.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using mozilla::Range;
using mozilla::RangedPtr;
using JS::AutoStableStringChars;
using js::intl::CallICU;
using js::intl::DateTimeFormatOptions;
using js::intl::IcuLocale;
/******************** Intl ********************/
bool js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
if (!locale) {
return false;
}
UErrorCode status = U_ZERO_ERROR;
const UChar* uTimeZone = nullptr;
int32_t uTimeZoneLength = 0;
UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, locale.get(),
UCAL_DEFAULT, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UCalendar, ucal_close> toClose(cal);
RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!info) {
return false;
}
RootedValue v(cx);
int32_t firstDayOfWeek = ucal_getAttribute(cal, UCAL_FIRST_DAY_OF_WEEK);
v.setInt32(firstDayOfWeek);
if (!DefineDataProperty(cx, info, cx->names().firstDayOfWeek, v)) {
return false;
}
int32_t minDays = ucal_getAttribute(cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK);
v.setInt32(minDays);
if (!DefineDataProperty(cx, info, cx->names().minDays, v)) {
return false;
}
UCalendarWeekdayType prevDayType =
ucal_getDayOfWeekType(cal, UCAL_SATURDAY, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
RootedValue weekendStart(cx), weekendEnd(cx);
for (int i = UCAL_SUNDAY; i <= UCAL_SATURDAY; i++) {
UCalendarDaysOfWeek dayOfWeek = static_cast<UCalendarDaysOfWeek>(i);
UCalendarWeekdayType type = ucal_getDayOfWeekType(cal, dayOfWeek, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
if (prevDayType != type) {
switch (type) {
case UCAL_WEEKDAY:
// If the first Weekday after Weekend is Sunday (1),
// then the last Weekend day is Saturday (7).
// Otherwise we'll just take the previous days number.
weekendEnd.setInt32(i == 1 ? 7 : i - 1);
break;
case UCAL_WEEKEND:
weekendStart.setInt32(i);
break;
case UCAL_WEEKEND_ONSET:
case UCAL_WEEKEND_CEASE:
// At the time this code was added, ICU apparently never behaves this
// way, so just throw, so that users will report a bug and we can
// decide what to do.
intl::ReportInternalError(cx);
return false;
default:
break;
}
}
prevDayType = type;
}
MOZ_ASSERT(weekendStart.isInt32());
MOZ_ASSERT(weekendEnd.isInt32());
if (!DefineDataProperty(cx, info, cx->names().weekendStart, weekendStart)) {
return false;
}
if (!DefineDataProperty(cx, info, cx->names().weekendEnd, weekendEnd)) {
return false;
}
args.rval().setObject(*info);
return true;
}
static void ReportBadKey(JSContext* cx, HandleString key) {
if (UniqueChars chars = QuoteString(cx, key, '"')) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY,
chars.get());
}
}
template <typename ConstChar>
static bool MatchPart(RangedPtr<ConstChar> iter, const RangedPtr<ConstChar> end,
const char* part, size_t partlen) {
for (size_t i = 0; i < partlen; iter++, i++) {
if (iter == end || *iter != part[i]) {
return false;
}
}
return true;
}
template <typename ConstChar, size_t N>
inline bool MatchPart(RangedPtr<ConstChar>* iter,
const RangedPtr<ConstChar> end, const char (&part)[N]) {
if (!MatchPart(*iter, end, part, N - 1)) {
return false;
}
*iter += N - 1;
return true;
}
enum class DisplayNameStyle {
Narrow,
Short,
Long,
};
template <typename ConstChar>
static JSString* ComputeSingleDisplayName(JSContext* cx, UDateFormat* fmt,
UDateTimePatternGenerator* dtpg,
DisplayNameStyle style,
const Range<ConstChar>& pattern,
HandleString patternString) {
RangedPtr<ConstChar> iter = pattern.begin();
const RangedPtr<ConstChar> end = pattern.end();
auto MatchSlash = [cx, patternString, &iter, end]() {
if (MOZ_LIKELY(iter != end && *iter == '/')) {
iter++;
return true;
}
ReportBadKey(cx, patternString);
return false;
};
if (!MatchPart(&iter, end, "dates")) {
ReportBadKey(cx, patternString);
return nullptr;
}
if (!MatchSlash()) {
return nullptr;
}
if (MatchPart(&iter, end, "fields")) {
if (!MatchSlash()) {
return nullptr;
}
UDateTimePatternField fieldType;
if (MatchPart(&iter, end, "year")) {
fieldType = UDATPG_YEAR_FIELD;
} else if (MatchPart(&iter, end, "month")) {
fieldType = UDATPG_MONTH_FIELD;
} else if (MatchPart(&iter, end, "week")) {
fieldType = UDATPG_WEEK_OF_YEAR_FIELD;
} else if (MatchPart(&iter, end, "day")) {
fieldType = UDATPG_DAY_FIELD;
} else {
ReportBadKey(cx, patternString);
return nullptr;
}
// This part must be the final part with no trailing data.
if (iter != end) {
ReportBadKey(cx, patternString);
return nullptr;
}
int32_t resultSize;
const UChar* value = udatpg_getAppendItemName(dtpg, fieldType, &resultSize);
MOZ_ASSERT(resultSize >= 0);
return NewStringCopyN<CanGC>(cx, value, size_t(resultSize));
}
if (MatchPart(&iter, end, "gregorian")) {
if (!MatchSlash()) {
return nullptr;
}
UDateFormatSymbolType symbolType;
int32_t index;
if (MatchPart(&iter, end, "months")) {
if (!MatchSlash()) {
return nullptr;
}
switch (style) {
case DisplayNameStyle::Narrow:
symbolType = UDAT_STANDALONE_NARROW_MONTHS;
break;
case DisplayNameStyle::Short:
symbolType = UDAT_STANDALONE_SHORT_MONTHS;
break;
case DisplayNameStyle::Long:
symbolType = UDAT_STANDALONE_MONTHS;
break;
}
if (MatchPart(&iter, end, "january")) {
index = UCAL_JANUARY;
} else if (MatchPart(&iter, end, "february")) {
index = UCAL_FEBRUARY;
} else if (MatchPart(&iter, end, "march")) {
index = UCAL_MARCH;
} else if (MatchPart(&iter, end, "april")) {
index = UCAL_APRIL;
} else if (MatchPart(&iter, end, "may")) {
index = UCAL_MAY;
} else if (MatchPart(&iter, end, "june")) {
index = UCAL_JUNE;
} else if (MatchPart(&iter, end, "july")) {
index = UCAL_JULY;
} else if (MatchPart(&iter, end, "august")) {
index = UCAL_AUGUST;
} else if (MatchPart(&iter, end, "september")) {
index = UCAL_SEPTEMBER;
} else if (MatchPart(&iter, end, "october")) {
index = UCAL_OCTOBER;
} else if (MatchPart(&iter, end, "november")) {
index = UCAL_NOVEMBER;
} else if (MatchPart(&iter, end, "december")) {
index = UCAL_DECEMBER;
} else {
ReportBadKey(cx, patternString);
return nullptr;
}
} else if (MatchPart(&iter, end, "weekdays")) {
if (!MatchSlash()) {
return nullptr;
}
switch (style) {
case DisplayNameStyle::Narrow:
symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
break;
case DisplayNameStyle::Short:
symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS;
break;
case DisplayNameStyle::Long:
symbolType = UDAT_STANDALONE_WEEKDAYS;
break;
}
if (MatchPart(&iter, end, "monday")) {
index = UCAL_MONDAY;
} else if (MatchPart(&iter, end, "tuesday")) {
index = UCAL_TUESDAY;
} else if (MatchPart(&iter, end, "wednesday")) {
index = UCAL_WEDNESDAY;
} else if (MatchPart(&iter, end, "thursday")) {
index = UCAL_THURSDAY;
} else if (MatchPart(&iter, end, "friday")) {
index = UCAL_FRIDAY;
} else if (MatchPart(&iter, end, "saturday")) {
index = UCAL_SATURDAY;
} else if (MatchPart(&iter, end, "sunday")) {
index = UCAL_SUNDAY;
} else {
ReportBadKey(cx, patternString);
return nullptr;
}
} else if (MatchPart(&iter, end, "dayperiods")) {
if (!MatchSlash()) {
return nullptr;
}
symbolType = UDAT_AM_PMS;
if (MatchPart(&iter, end, "am")) {
index = UCAL_AM;
} else if (MatchPart(&iter, end, "pm")) {
index = UCAL_PM;
} else {
ReportBadKey(cx, patternString);
return nullptr;
}
} else {
ReportBadKey(cx, patternString);
return nullptr;
}
// This part must be the final part with no trailing data.
if (iter != end) {
ReportBadKey(cx, patternString);
return nullptr;
}
return CallICU(cx, [fmt, symbolType, index](UChar* chars, int32_t size,
UErrorCode* status) {
return udat_getSymbols(fmt, symbolType, index, chars, size, status);
});
}
ReportBadKey(cx, patternString);
return nullptr;
}
bool js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
// 1. Assert: locale is a string.
UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
if (!locale) {
return false;
}
// 2. Assert: style is a string.
DisplayNameStyle dnStyle;
{
JSLinearString* style = args[1].toString()->ensureLinear(cx);
if (!style) {
return false;
}
if (StringEqualsLiteral(style, "narrow")) {
dnStyle = DisplayNameStyle::Narrow;
} else if (StringEqualsLiteral(style, "short")) {
dnStyle = DisplayNameStyle::Short;
} else {
MOZ_ASSERT(StringEqualsLiteral(style, "long"));
dnStyle = DisplayNameStyle::Long;
}
}
// 3. Assert: keys is an Array.
RootedArrayObject keys(cx, &args[2].toObject().as<ArrayObject>());
if (!keys) {
return false;
}
// 4. Let result be ArrayCreate(0).
RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, keys->length()));
if (!result) {
return false;
}
result->ensureDenseInitializedLength(cx, 0, keys->length());
UErrorCode status = U_ZERO_ERROR;
UDateFormat* fmt =
udat_open(UDAT_DEFAULT, UDAT_DEFAULT, IcuLocale(locale.get()), nullptr, 0,
nullptr, 0, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UDateFormat, udat_close> datToClose(fmt);
// UDateTimePatternGenerator will be needed for translations of date and
// time fields like "month", "week", "day" etc.
UDateTimePatternGenerator* dtpg =
udatpg_open(IcuLocale(locale.get()), &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
// 5. For each element of keys,
RootedString keyValStr(cx);
RootedValue v(cx);
for (uint32_t i = 0; i < keys->length(); i++) {
if (!GetElement(cx, keys, keys, i, &v)) {
return false;
}
keyValStr = v.toString();
AutoStableStringChars stablePatternChars(cx);
if (!stablePatternChars.init(cx, keyValStr)) {
return false;
}
// 5.a. Perform an implementation dependent algorithm to map a key to a
// corresponding display name.
JSString* displayName =
stablePatternChars.isLatin1()
? ComputeSingleDisplayName(cx, fmt, dtpg, dnStyle,
stablePatternChars.latin1Range(),
keyValStr)
: ComputeSingleDisplayName(cx, fmt, dtpg, dnStyle,
stablePatternChars.twoByteRange(),
keyValStr);
if (!displayName) {
return false;
}
// 5.b. Append the result string to result.
result->setDenseElement(i, StringValue(displayName));
}
// 6. Return result.
args.rval().setObject(*result);
return true;
}
bool js::intl_GetLocaleInfo(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
if (!locale) {
return false;
}
RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!info) {
return false;
}
if (!DefineDataProperty(cx, info, cx->names().locale, args[0])) {
return false;
}
bool rtl = uloc_isRightToLeft(IcuLocale(locale.get()));
RootedValue dir(cx, StringValue(rtl ? cx->names().rtl : cx->names().ltr));
if (!DefineDataProperty(cx, info, cx->names().direction, dir)) {
return false;
}
args.rval().setObject(*info);
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->runtime()->sharedIntlData.ref();
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 correspoding 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 (StringEqualsLiteral(typeStr, "Collator")) {
kind = SupportedLocaleKind::Collator;
} else if (StringEqualsLiteral(typeStr, "DateTimeFormat")) {
kind = SupportedLocaleKind::DateTimeFormat;
} else if (StringEqualsLiteral(typeStr, "DisplayNames")) {
kind = SupportedLocaleKind::DisplayNames;
} else if (StringEqualsLiteral(typeStr, "ListFormat")) {
kind = SupportedLocaleKind::ListFormat;
} else if (StringEqualsLiteral(typeStr, "NumberFormat")) {
kind = SupportedLocaleKind::NumberFormat;
} else if (StringEqualsLiteral(typeStr, "PluralRules")) {
kind = SupportedLocaleKind::PluralRules;
} else {
MOZ_ASSERT(StringEqualsLiteral(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.ListFormat,
// Intl.PluralRules, 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;
}
static bool intl_toSource(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().Intl);
return true;
}
static const JSFunctionSpec intl_static_methods[] = {
JS_FN(js_toSource_str, intl_toSource, 0, 0),
JS_SELF_HOSTED_FN("getCanonicalLocales", "Intl_getCanonicalLocales", 1, 0),
JS_FS_END};
static JSObject* CreateIntlObject(JSContext* cx, JSProtoKey key) {
Handle<GlobalObject*> global = cx->global();
RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
if (!proto) {
return nullptr;
}
// The |Intl| object is just a plain object with some "static" function
// properties and some constructor properties.
return NewSingletonObjectWithGivenProto(cx, &IntlClass, proto);
}
/**
* Initializes the Intl Object and its standard built-in properties.
* Spec: ECMAScript Internationalization API Specification, 8.0, 8.1
*/
static bool IntlClassFinish(JSContext* cx, HandleObject intl,
HandleObject proto) {
// Add the constructor properties.
RootedId ctorId(cx);
RootedValue ctorValue(cx);
for (const auto& protoKey :
{JSProto_Collator, JSProto_DateTimeFormat, JSProto_ListFormat,
JSProto_Locale, JSProto_NumberFormat, JSProto_PluralRules,
JSProto_RelativeTimeFormat}) {
JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, protoKey);
if (!ctor) {
return false;
}
ctorId = NameToId(ClassName(protoKey, cx));
ctorValue.setObject(*ctor);
if (!DefineDataProperty(cx, intl, ctorId, ctorValue, 0)) {
return false;
}
}
return true;
}
static const ClassSpec IntlClassSpec = {
CreateIntlObject, nullptr, intl_static_methods, nullptr,
nullptr, nullptr, IntlClassFinish};
const JSClass js::IntlClass = {js_Object_str,
JSCLASS_HAS_CACHED_PROTO(JSProto_Intl),
JS_NULL_CLASS_OPS, &IntlClassSpec};