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

This commit is contained in:
roytam1 2023-02-21 08:21:33 +08:00
commit fc9eb6a1b3
44 changed files with 8731 additions and 8225 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,531 +0,0 @@
/* -*- 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/. */
#ifndef builtin_Intl_h
#define builtin_Intl_h
#include "mozilla/HashFunctions.h"
#include "mozilla/MemoryReporting.h"
#include "jsalloc.h"
#include "NamespaceImports.h"
#include "js/GCAPI.h"
#include "js/GCHashTable.h"
#include "unicode/utypes.h"
/*
* The Intl module specified by standard ECMA-402,
* ECMAScript Internationalization API Specification.
*/
namespace js {
/**
* Initializes the Intl Object and its standard built-in properties.
* Spec: ECMAScript Internationalization API Specification, 8.0, 8.1
*/
extern JSObject*
InitIntlClass(JSContext* cx, HandleObject obj);
/**
* Stores Intl data which can be shared across compartments (but not contexts).
*
* Used for data which is expensive when computed repeatedly or is not
* available through ICU.
*/
class SharedIntlData
{
/**
* Information tracking the set of the supported time zone names, derived
* from the IANA time zone database <https://www.iana.org/time-zones>.
*
* There are two kinds of IANA time zone names: Zone and Link (denoted as
* such in database source files). Zone names are the canonical, preferred
* name for a time zone, e.g. Asia/Kolkata. Link names simply refer to
* target Zone names for their meaning, e.g. Asia/Calcutta targets
* Asia/Kolkata. That a name is a Link doesn't *necessarily* reflect a
* sense of deprecation: some Link names also exist partly for convenience,
* e.g. UTC and GMT as Link names targeting the Zone name Etc/UTC.
*
* Two data sources determine the time zone names we support: those ICU
* supports and IANA's zone information.
*
* Unfortunately the names ICU and IANA support, and their Link
* relationships from name to target, aren't identical, so we can't simply
* implicitly trust ICU's name handling. We must perform various
* preprocessing of user-provided zone names and post-processing of
* ICU-provided zone names to implement ECMA-402's IANA-consistent behavior.
*
* Also see <https://ssl.icu-project.org/trac/ticket/12044> and
* <http://unicode.org/cldr/trac/ticket/9892>.
*/
using TimeZoneName = JSAtom*;
struct TimeZoneHasher
{
struct Lookup
{
union {
const JS::Latin1Char* latin1Chars;
const char16_t* twoByteChars;
};
bool isLatin1;
size_t length;
JS::AutoCheckCannotGC nogc;
HashNumber hash;
explicit Lookup(JSFlatString* timeZone);
};
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
static bool match(TimeZoneName key, const Lookup& lookup);
};
using TimeZoneSet = js::GCHashSet<TimeZoneName,
TimeZoneHasher,
js::SystemAllocPolicy>;
using TimeZoneMap = js::GCHashMap<TimeZoneName,
TimeZoneName,
TimeZoneHasher,
js::SystemAllocPolicy>;
/**
* As a threshold matter, available time zones are those time zones ICU
* supports, via ucal_openTimeZones. But ICU supports additional non-IANA
* time zones described in intl/icu/source/tools/tzcode/icuzones (listed in
* IntlTimeZoneData.cpp's |legacyICUTimeZones|) for its own backwards
* compatibility purposes. This set consists of ICU's supported time zones,
* minus all backwards-compatibility time zones.
*/
TimeZoneSet availableTimeZones;
/**
* IANA treats some time zone names as Zones, that ICU instead treats as
* Links. For example, IANA considers "America/Indiana/Indianapolis" to be
* a Zone and "America/Fort_Wayne" a Link that targets it, but ICU
* considers the former a Link that targets "America/Indianapolis" (which
* IANA treats as a Link).
*
* ECMA-402 requires that we respect IANA data, so if we're asked to
* canonicalize a time zone name in this set, we must *not* return ICU's
* canonicalization.
*/
TimeZoneSet ianaZonesTreatedAsLinksByICU;
/**
* IANA treats some time zone names as Links to one target, that ICU
* instead treats as either Zones, or Links to different targets. An
* example of the former is "Asia/Calcutta, which IANA assigns the target
* "Asia/Kolkata" but ICU considers its own Zone. An example of the latter
* is "America/Virgin", which IANA assigns the target
* "America/Port_of_Spain" but ICU assigns the target "America/St_Thomas".
*
* ECMA-402 requires that we respect IANA data, so if we're asked to
* canonicalize a time zone name that's a key in this map, we *must* return
* the corresponding value and *must not* return ICU's canonicalization.
*/
TimeZoneMap ianaLinksCanonicalizedDifferentlyByICU;
bool timeZoneDataInitialized = false;
/**
* Precomputes the available time zone names, because it's too expensive to
* call ucal_openTimeZones() repeatedly.
*/
bool ensureTimeZones(JSContext* cx);
public:
/**
* Returns the validated time zone name in |result|. If the input time zone
* isn't a valid IANA time zone name, |result| remains unchanged.
*/
bool validateTimeZoneName(JSContext* cx, JS::HandleString timeZone,
JS::MutableHandleString result);
/**
* Returns the canonical time zone name in |result|. If no canonical name
* was found, |result| remains unchanged.
*
* This method only handles time zones which are canonicalized differently
* by ICU when compared to IANA.
*/
bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
JS::MutableHandleString result);
void destroyInstance();
void trace(JSTracer* trc);
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
};
/*
* The following functions are for use by self-hosted code.
*/
/******************** Collator ********************/
/**
* Returns a new instance of the standard built-in Collator constructor.
* Self-hosted code cannot cache this constructor (as it does for others in
* Utilities.js) because it is initialized after self-hosted code is compiled.
*
* Usage: collator = intl_Collator(locales, options)
*/
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
* collations supported for the given locale. "standard" and "search" are
* excluded.
*
* Usage: collations = intl_availableCollations(locale)
*/
extern MOZ_MUST_USE bool
intl_availableCollations(JSContext* cx, unsigned argc, Value* vp);
/**
* Compares x and y (which must be String values), and returns a number less
* than 0 if x < y, 0 if x = y, or a number greater than 0 if x > y according
* to the sort order for the locale and collation options of the given
* Collator.
*
* Spec: ECMAScript Internationalization API Specification, 10.3.2.
*
* Usage: result = intl_CompareStrings(collator, x, y)
*/
extern MOZ_MUST_USE bool
intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp);
/******************** NumberFormat ********************/
/**
* Returns a new instance of the standard built-in NumberFormat constructor.
* Self-hosted code cannot cache this constructor (as it does for others in
* Utilities.js) because it is initialized after self-hosted code is compiled.
*
* Usage: numberFormat = intl_NumberFormat(locales, options)
*/
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
* default numbering system for the given locale.
*
* Usage: defaultNumberingSystem = intl_numberingSystem(locale)
*/
extern MOZ_MUST_USE bool
intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns a string representing the number x according to the effective
* locale and the formatting options of the given NumberFormat.
*
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
*
* Usage: formatted = intl_FormatNumber(numberFormat, x)
*/
extern MOZ_MUST_USE bool
intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp);
/******************** DateTimeFormat ********************/
/**
* Returns a new instance of the standard built-in DateTimeFormat constructor.
* Self-hosted code cannot cache this constructor (as it does for others in
* Utilities.js) because it is initialized after self-hosted code is compiled.
*
* Usage: dateTimeFormat = intl_DateTimeFormat(locales, options)
*/
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
* supported calendars for the given locale. The default calendar is
* element 0.
*
* Usage: calendars = intl_availableCalendars(locale)
*/
extern MOZ_MUST_USE bool
intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp);
/**
* 6.4.1 IsValidTimeZoneName ( timeZone )
*
* Verifies that the given string is a valid time zone name. If it is a valid
* time zone name, its IANA time zone name is returned. Otherwise returns null.
*
* ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
*
* Usage: ianaTimeZone = intl_IsValidTimeZoneName(timeZone)
*/
extern MOZ_MUST_USE bool
intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp);
/**
* Return the canonicalized time zone name. Canonicalization resolves link
* names to their target time zones.
*
* Usage: ianaTimeZone = intl_canonicalizeTimeZone(timeZone)
*/
extern MOZ_MUST_USE bool
intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp);
/**
* Return the default time zone name. The time zone name is not canonicalized.
*
* Usage: icuDefaultTimeZone = intl_defaultTimeZone()
*/
extern MOZ_MUST_USE bool
intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp);
/**
* Return the raw offset from GMT in milliseconds for the default time zone.
*
* Usage: defaultTimeZoneOffset = intl_defaultTimeZoneOffset()
*/
extern MOZ_MUST_USE bool
intl_defaultTimeZoneOffset(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 format pattern corresponding to skeleton for the
* given locale.
*
* Usage: pattern = intl_patternForSkeleton(locale, skeleton)
*/
extern MOZ_MUST_USE bool
intl_patternForSkeleton(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
* given DateTimeFormat.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
*
* Usage: formatted = intl_FormatDateTime(dateTimeFormat, x)
*/
extern MOZ_MUST_USE bool
intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp);
/******************** PluralRules ********************/
/**
* Returns a new PluralRules instance.
* Self-hosted code cannot cache this constructor (as it does for others in
* Utilities.js) because it is initialized after self-hosted code is compiled.
*
* Usage: pluralRules = intl_PluralRules(locales, options)
*/
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.
*
* A plural rule is a grammatical category that expresses count distinctions
* (such as "one", "two", "few" etc.).
*
* Usage: rule = intl_SelectPluralRule(pluralRules, x)
*/
extern MOZ_MUST_USE bool
intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an array of plural rules categories for a given
* locale and type.
*
* Usage: categories = intl_GetPluralCategories(locale, type)
*
* Example:
*
* intl_getPluralCategories('pl', 'cardinal'); // ['one', 'few', 'many', 'other']
*/
extern MOZ_MUST_USE bool
intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp);
/******************** RelativeTimeFormat ********************/
/**
* 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.
*
* t should be a number representing a number to be formatted.
* unit should be "second", "minute", "hour", "day", "week", "month", "quarter", or "year".
*
* Usage: formatted = intl_FormatRelativeTime(relativeTimeFormat, t, unit)
*/
extern MOZ_MUST_USE bool
intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns a plain object with calendar information for a single valid locale
* (callers must perform this validation). The object will have these
* properties:
*
* firstDayOfWeek
* an integer in the range 1=Sunday to 7=Saturday indicating the day
* considered the first day of the week in calendars, e.g. 1 for en-US,
* 2 for en-GB, 1 for bn-IN
* minDays
* an integer in the range of 1 to 7 indicating the minimum number
* of days required in the first week of the year, e.g. 1 for en-US, 4 for de
* weekendStart
* an integer in the range 1=Sunday to 7=Saturday indicating the day
* considered the beginning of a weekend, e.g. 7 for en-US, 7 for en-GB,
* 1 for bn-IN
* weekendEnd
* an integer in the range 1=Sunday to 7=Saturday indicating the day
* considered the end of a weekend, e.g. 1 for en-US, 1 for en-GB,
* 1 for bn-IN (note that "weekend" is *not* necessarily two days)
*
* NOTE: "calendar" and "locale" properties are *not* added to the object.
*/
extern MOZ_MUST_USE bool
intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an Array with CLDR-based fields display names.
* The function takes three arguments:
*
* locale
* BCP47 compliant locale string
* style
* A string with values: long or short or narrow
* keys
* An array or path-like strings that identify keys to be returned
* At the moment the following types of keys are supported:
*
* 'dates/fields/{year|month|week|day}'
* 'dates/gregorian/months/{january|...|december}'
* 'dates/gregorian/weekdays/{sunday|...|saturday}'
* 'dates/gregorian/dayperiods/{am|pm}'
*
* Example:
*
* let info = intl_ComputeDisplayNames(
* 'en-US',
* 'long',
* [
* 'dates/fields/year',
* 'dates/gregorian/months/january',
* 'dates/gregorian/weekdays/monday',
* 'dates/gregorian/dayperiods/am',
* ]
* );
*
* Returned value:
*
* [
* 'year',
* 'January',
* 'Monday',
* 'AM'
* ]
*/
extern MOZ_MUST_USE bool
intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp);
/**
* Cast char16_t* strings to UChar* strings used by ICU.
*/
inline const UChar*
Char16ToUChar(const char16_t* chars)
{
return reinterpret_cast<const UChar*>(chars);
}
inline UChar*
Char16ToUChar(char16_t* chars)
{
return reinterpret_cast<UChar*>(chars);
}
inline char16_t*
UCharToChar16(UChar* chars)
{
return reinterpret_cast<char16_t*>(chars);
}
inline const char16_t*
UCharToChar16(const UChar* chars)
{
return reinterpret_cast<const char16_t*>(chars);
}
} // namespace js
#endif /* builtin_Intl_h */

File diff suppressed because it is too large Load diff

View file

@ -96,6 +96,8 @@
#define REGEXP_STRING_ITERATOR_FLAGS_SLOT 2
#define REGEXP_STRING_ITERATOR_DONE_SLOT 3
#define INTL_INTERNALS_OBJECT_SLOT 0
#define MODULE_OBJECT_ENVIRONMENT_SLOT 1
#define MODULE_OBJECT_STATUS_SLOT 3
#define MODULE_OBJECT_EVALUATION_ERROR_SLOT 4

View file

@ -0,0 +1,530 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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/. */
/* Intl.Collator implementation. */
#include "builtin/intl/Collator.h"
#include "mozilla/Assertions.h"
#include "jsapi.h"
#include "jscntxt.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/intl/SharedIntlData.h"
#include "js/TypeDecls.h"
#include "vm/GlobalObject.h"
#include "vm/Runtime.h"
#include "vm/String.h"
#include "jsobjinlines.h"
using namespace js;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::ReportInternalError;
using js::intl::SharedIntlData;
using js::intl::StringsAreEqual;
/******************** Collator ********************/
const ClassOps CollatorObject::classOps_ = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
CollatorObject::finalize
};
const Class CollatorObject::class_ = {
js_Object_str,
JSCLASS_HAS_RESERVED_SLOTS(CollatorObject::SLOT_COUNT) |
JSCLASS_FOREGROUND_FINALIZE,
&CollatorObject::classOps_
};
#if JS_HAS_TOSOURCE
static bool
collator_toSource(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().Collator);
return true;
}
#endif
static const JSFunctionSpec collator_static_methods[] = {
JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_Collator_supportedLocalesOf", 1, 0),
JS_FS_END
};
static const JSFunctionSpec collator_methods[] = {
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Collator_resolvedOptions", 0, 0),
#if JS_HAS_TOSOURCE
JS_FN(js_toSource_str, collator_toSource, 0, 0),
#endif
JS_FS_END
};
/**
* 10.1.2 Intl.Collator([ locales [, options]])
*
* ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
*/
static bool
Collator(JSContext* cx, const CallArgs& args, bool construct)
{
RootedObject obj(cx);
// 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))
return false;
args.rval().setObject(*obj);
return true;
}
static bool
Collator(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return Collator(cx, args, args.isConstructing());
}
bool
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);
}
void
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);
}
}
JSObject*
js::CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
{
RootedFunction ctor(cx, GlobalObject::createConstructor(cx, &Collator, cx->names().Collator,
0));
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &CollatorObject::class_));
if (!proto)
return nullptr;
proto->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
// 10.2.2
if (!JS_DefineFunctions(cx, ctor, collator_static_methods))
return nullptr;
// 10.3.2 and 10.3.3
if (!JS_DefineFunctions(cx, proto, collator_methods))
return nullptr;
/*
* Install the getter for Collator.prototype.compare, which returns a bound
* comparison function for the specified Collator object (suitable for
* passing to methods like Array.prototype.sort).
*/
RootedValue getter(cx);
if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().CollatorCompareGet, &getter))
return nullptr;
if (!DefineProperty(cx, proto, cx->names().compare, UndefinedHandleValue,
JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()),
nullptr, JSPROP_GETTER | JSPROP_SHARED))
{
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))
return nullptr;
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)
{
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;
UErrorCode status = U_ZERO_ERROR;
UEnumeration* values = ucol_getKeywordValuesForLocale("co", locale.ptr(), false, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UEnumeration, uenum_close> toClose(values);
uint32_t count = uenum_count(values, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
RootedObject collations(cx, NewDenseEmptyArray(cx));
if (!collations)
return false;
uint32_t index = 0;
for (uint32_t i = 0; i < count; i++) {
const char* collation = uenum_next(values, nullptr, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
// Per ECMA-402, 10.2.3, we don't include standard and search:
// "The values 'standard' and 'search' must not be used as elements in
// any [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co
// array."
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));
if (!jscollation)
return false;
RootedValue element(cx, StringValue(jscollation));
if (!DefineElement(cx, collations, index++, element))
return false;
}
args.rval().setObject(*collations);
return true;
}
/**
* Returns a new UCollator with the locale and collation options
* of the given Collator.
*/
static UCollator*
NewUCollator(JSContext* cx, HandleObject collator)
{
RootedValue value(cx);
RootedObject internals(cx, intl::GetInternalsObject(cx, collator));
if (!internals)
return nullptr;
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
return nullptr;
JSAutoByteString locale(cx, value.toString());
if (!locale)
return nullptr;
// UCollator options with default values.
UColAttributeValue uStrength = UCOL_DEFAULT;
UColAttributeValue uCaseLevel = UCOL_OFF;
UColAttributeValue uAlternate = UCOL_DEFAULT;
UColAttributeValue uNumeric = UCOL_OFF;
// Normalization is always on to meet the canonical equivalence requirement.
UColAttributeValue uNormalization = UCOL_ON;
UColAttributeValue uCaseFirst = UCOL_DEFAULT;
if (!GetProperty(cx, internals, internals, cx->names().usage, &value))
return nullptr;
JSAutoByteString usage(cx, value.toString());
if (!usage)
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)
return nullptr;
memcpy(newLocale, oldLocale, index);
memcpy(newLocale + index, insert, insertLen);
memcpy(newLocale + index + insertLen, oldLocale + index, localeLen - index + 1); // '\0'
locale.clear();
locale.initBytes(newLocale);
}
// We don't need to look at the collation property - it can only be set
// via the Unicode locale extension and is therefore already set on
// locale.
if (!GetProperty(cx, internals, internals, cx->names().sensitivity, &value))
return nullptr;
JSAutoByteString sensitivity(cx, value.toString());
if (!sensitivity)
return nullptr;
if (StringsAreEqual(sensitivity, "base")) {
uStrength = UCOL_PRIMARY;
} else if (StringsAreEqual(sensitivity, "accent")) {
uStrength = UCOL_SECONDARY;
} else if (StringsAreEqual(sensitivity, "case")) {
uStrength = UCOL_PRIMARY;
uCaseLevel = UCOL_ON;
} else {
MOZ_ASSERT(StringsAreEqual(sensitivity, "variant"));
uStrength = UCOL_TERTIARY;
}
if (!GetProperty(cx, internals, internals, cx->names().ignorePunctuation, &value))
return nullptr;
// According to the ICU team, UCOL_SHIFTED causes punctuation to be
// ignored. Looking at Unicode Technical Report 35, Unicode Locale Data
// Markup Language, "shifted" causes whitespace and punctuation to be
// ignored - that's a bit more than asked for, but there's no way to get
// less.
if (value.toBoolean())
uAlternate = UCOL_SHIFTED;
if (!GetProperty(cx, internals, internals, cx->names().numeric, &value))
return nullptr;
if (!value.isUndefined() && value.toBoolean())
uNumeric = UCOL_ON;
if (!GetProperty(cx, internals, internals, cx->names().caseFirst, &value))
return nullptr;
if (!value.isUndefined()) {
JSAutoByteString caseFirst(cx, value.toString());
if (!caseFirst)
return nullptr;
if (StringsAreEqual(caseFirst, "upper"))
uCaseFirst = UCOL_UPPER_FIRST;
else if (StringsAreEqual(caseFirst, "lower"))
uCaseFirst = UCOL_LOWER_FIRST;
else
MOZ_ASSERT(StringsAreEqual(caseFirst, "false"));
}
UErrorCode status = U_ZERO_ERROR;
UCollator* coll = ucol_open(IcuLocale(locale.ptr()), &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return nullptr;
}
ucol_setAttribute(coll, UCOL_STRENGTH, uStrength, &status);
ucol_setAttribute(coll, UCOL_CASE_LEVEL, uCaseLevel, &status);
ucol_setAttribute(coll, UCOL_ALTERNATE_HANDLING, uAlternate, &status);
ucol_setAttribute(coll, UCOL_NUMERIC_COLLATION, uNumeric, &status);
ucol_setAttribute(coll, UCOL_NORMALIZATION_MODE, uNormalization, &status);
ucol_setAttribute(coll, UCOL_CASE_FIRST, uCaseFirst, &status);
if (U_FAILURE(status)) {
ucol_close(coll);
intl::ReportInternalError(cx);
return nullptr;
}
return coll;
}
static bool
intl_CompareStrings(JSContext* cx, UCollator* coll, HandleString str1, HandleString str2,
MutableHandleValue result)
{
MOZ_ASSERT(str1);
MOZ_ASSERT(str2);
if (str1 == str2) {
result.setInt32(0);
return true;
}
AutoStableStringChars stableChars1(cx);
if (!stableChars1.initTwoByte(cx, str1))
return false;
AutoStableStringChars stableChars2(cx);
if (!stableChars2.initTwoByte(cx, str2))
return false;
mozilla::Range<const char16_t> chars1 = stableChars1.twoByteRange();
mozilla::Range<const char16_t> chars2 = stableChars2.twoByteRange();
UCollationResult uresult = ucol_strcoll(coll,
Char16ToUChar(chars1.begin().get()), chars1.length(),
Char16ToUChar(chars2.begin().get()), chars2.length());
int32_t res;
switch (uresult) {
case UCOL_LESS: res = -1; break;
case UCOL_EQUAL: res = 0; break;
case UCOL_GREATER: res = 1; break;
default: MOZ_CRASH("ucol_strcoll returned bad UCollationResult");
}
result.setInt32(res);
return true;
}
bool
js::intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(args[0].isObject());
MOZ_ASSERT(args[1].isString());
MOZ_ASSERT(args[2].isString());
Rooted<CollatorObject*> collator(cx, &args[0].toObject().as<CollatorObject>());
// Obtain a UCollator object, cached if possible.
// 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.
coll = NewUCollator(cx, collator);
if (!coll)
return false;
}
// 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);
if (!isCollatorInstance)
ucol_close(coll);
if (!success)
return false;
args.rval().set(result);
return true;
}

View file

@ -0,0 +1,94 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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_Collator_h
#define builtin_intl_Collator_h
#include "mozilla/Attributes.h"
#include <stdint.h>
#include "builtin/SelfHostingDefines.h"
#include "js/Class.h"
#include "vm/NativeObject.h"
namespace js {
class FreeOp;
class GlobalObject;
/******************** Collator ********************/
class CollatorObject : public NativeObject
{
public:
static const Class class_;
static constexpr uint32_t INTERNALS_SLOT = 0;
static constexpr uint32_t UCOLLATOR_SLOT = 1;
static constexpr uint32_t SLOT_COUNT = 2;
static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
"INTERNALS_SLOT must match self-hosting define for internals object slot");
private:
static const ClassOps classOps_;
static void finalize(FreeOp* fop, JSObject* obj);
};
extern JSObject*
CreateCollatorPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
JS::Handle<GlobalObject*> global);
/**
* Returns a new instance of the standard built-in Collator constructor.
* Self-hosted code cannot cache this constructor (as it does for others in
* Utilities.js) because it is initialized after self-hosted code is compiled.
*
* Usage: collator = intl_Collator(locales, options)
*/
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
* collations supported for the given locale. "standard" and "search" are
* excluded.
*
* Usage: collations = intl_availableCollations(locale)
*/
extern MOZ_MUST_USE bool
intl_availableCollations(JSContext* cx, unsigned argc, Value* vp);
/**
* Compares x and y (which must be String values), and returns a number less
* than 0 if x < y, 0 if x = y, or a number greater than 0 if x > y according
* to the sort order for the locale and collation options of the given
* Collator.
*
* Spec: ECMAScript Internationalization API Specification, 10.3.2.
*
* Usage: result = intl_CompareStrings(collator, x, y)
*/
extern MOZ_MUST_USE bool
intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp);
} // namespace js
#endif /* builtin_intl_Collator_h */

View file

@ -0,0 +1,355 @@
/* 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/. */
/********** 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|.
*/
function resolveCollatorInternals(lazyCollatorData)
{
assert(IsObject(lazyCollatorData), "lazy data not an object?");
var internalProps = std_Object_create(null);
// Step 7.
internalProps.usage = lazyCollatorData.usage;
// Step 8.
var Collator = collatorInternalProperties;
// Step 9.
var collatorIsSorting = lazyCollatorData.usage === "sort";
var localeData = collatorIsSorting
? Collator.sortLocaleData
: Collator.searchLocaleData;
// Compute effective locale.
// Step 14.
var relevantExtensionKeys = Collator.relevantExtensionKeys;
// Step 15.
var r = ResolveLocale(callFunction(Collator.availableLocales, Collator),
lazyCollatorData.requestedLocales,
lazyCollatorData.opt,
relevantExtensionKeys,
localeData);
// Step 16.
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.d.
internalProps[property] = value;
// Step 19.e.
i++;
}
// Compute remaining collation options.
// Steps 21-22.
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;
}
}
internalProps.sensitivity = s;
// Step 24.
internalProps.ignorePunctuation = lazyCollatorData.ignorePunctuation;
// Step 25.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
// object using |setInternalProperties|.
return internalProps;
}
/**
* Returns an object containing the Collator internal properties of |obj|, or
* throws a TypeError if |obj| isn't Collator-initialized.
*/
function getCollatorInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "Collator", methodName);
assert(internals.type === "Collator", "bad type escaped getIntlObjectInternals");
// If internal properties have already been computed, use them.
var internalProps = maybeInternalProperties(internals);
if (internalProps)
return internalProps;
// Otherwise it's time to fully create them.
internalProps = resolveCollatorInternals(internals.lazyData);
setInternalProperties(internals, internalProps);
return internalProps;
}
/**
* Initializes an object as a Collator.
*
* This method is complicated a moderate bit by its implementing initialization
* as a *lazy* concept. Everything that must happen now, does -- but we defer
* all the work we can until the object is actually used as a Collator. This
* later work occurs in |resolveCollatorInternals|; steps not noted here occur
* there.
*
* 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);
// Lazy Collator data has the following structure:
//
// {
// requestedLocales: List of locales,
// usage: "sort" / "search",
// opt: // opt object computed in InitializeCollator
// {
// localeMatcher: "lookup" / "best fit",
// kn: true / false / undefined,
// kf: "upper" / "lower" / "false" / undefined
// }
// rawSensitivity: "base" / "accent" / "case" / "variant" / undefined,
// ignorePunctuation: true / false
// }
//
// Note that lazy data is only installed as a final step of initialization,
// so every Collator lazy data object has *all* these properties, never a
// subset of them.
var lazyCollatorData = std_Object_create(null);
// Step 3.
var requestedLocales = CanonicalizeLocaleList(locales);
lazyCollatorData.requestedLocales = requestedLocales;
// Steps 4-5.
//
// 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
// mucked with. (|options| is fully consumed in this method, so it's not a
// concern that Object.prototype might be touched between now and when
// |resolveCollatorInternals| is called.) For now, just keep it simple.
if (options === undefined)
options = {};
else
options = ToObject(options);
// Compute options that impact interpretation of locale.
// Step 6.
var u = GetOption(options, "usage", "string", ["sort", "search"], "sort");
lazyCollatorData.usage = u;
// Step 10.
var opt = new Record();
lazyCollatorData.opt = opt;
// Steps 11-12.
var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
// Step 13, unrolled.
var numericValue = GetOption(options, "numeric", "boolean", undefined, undefined);
if (numericValue !== undefined)
numericValue = numericValue ? 'true' : 'false';
opt.kn = numericValue;
var caseFirstValue = GetOption(options, "caseFirst", "string", ["upper", "lower", "false"], undefined);
opt.kf = caseFirstValue;
// Compute remaining collation options.
// Step 20.
var s = GetOption(options, "sensitivity", "string",
["base", "accent", "case", "variant"], undefined);
lazyCollatorData.rawSensitivity = s;
// Step 23.
var ip = GetOption(options, "ignorePunctuation", "boolean", undefined, false);
lazyCollatorData.ignorePunctuation = ip;
// Step 26.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
setLazyData(internals, "Collator", lazyCollatorData);
}
/**
* Returns the subset of the given locale list for which this locale list has a
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript Internationalization API Specification, 10.2.2.
*/
function Intl_Collator_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
var availableLocales = callFunction(collatorInternalProperties.availableLocales,
collatorInternalProperties);
var requestedLocales = CanonicalizeLocaleList(locales);
return SupportedLocales(availableLocales, requestedLocales, options);
}
/**
* Collator internal properties.
*
* Spec: ECMAScript Internationalization API Specification, 9.1 and 10.2.3.
*/
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"]
};
function collatorSortLocaleData(locale) {
var collations = intl_availableCollations(locale);
callFunction(std_Array_unshift, collations, null);
return {
co: collations,
kn: ["false", "true"]
};
}
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"
};
}
/**
* Function to be bound and returned by Intl.Collator.prototype.format.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
*/
function collatorCompareToBind(x, y) {
// Steps 1.a.i-ii implemented by ECMAScript declaration binding instantiation,
// ES5.1 10.5, step 4.d.ii.
// Step 1.a.iii-v.
var X = ToString(x);
var Y = ToString(y);
return intl_CompareStrings(this, X, Y);
}
/**
* Returns a function bound to this Collator that compares x (converted to a
* String value) and y (converted to a String value),
* and returns a number less than 0 if x < y, 0 if x = y, or a number greater
* 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.
*/
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;
// Step 1.b-d.
var bc = callFunction(FunctionBind, F, this);
internals.boundCompare = bc;
}
// Step 2.
return internals.boundCompare;
}
/**
* Returns the resolved options for a Collator object.
*
* Spec: ECMAScript Internationalization API Specification, 10.3.3 and 10.4.
*/
function Intl_Collator_resolvedOptions() {
// Check "this Collator object" per introduction of section 10.3.
var internals = getCollatorInternals(this, "resolvedOptions");
var result = {
locale: internals.locale,
usage: internals.usage,
sensitivity: internals.sensitivity,
ignorePunctuation: internals.ignorePunctuation
};
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]);
}
return result;
}

View file

@ -0,0 +1,116 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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/. */
/* Operations used to implement multiple Intl.* classes. */
#include "builtin/intl/CommonFunctions.h"
#include "mozilla/Assertions.h"
#include "jscntxt.h"
#include "jsfriendapi.h" // for GetErrorMessage, JSMSG_INTERNAL_INTL_ERROR
#include "jsobj.h"
#include "js/Value.h"
#include "vm/SelfHosting.h"
#include "vm/Stack.h"
#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);
args[1].set(locales);
args[2].set(options);
RootedValue thisv(cx, NullValue());
RootedValue ignored(cx);
return js::Call(cx, initializerValue, thisv, args, &ignored);
}
/**
* Returns the object holding the internal properties for obj.
*/
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))
return nullptr;
return &v.toObject();
}
void
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;
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

@ -0,0 +1,146 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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_CommonFunctions_h
#define builtin_intl_CommonFunctions_h
#include "mozilla/Assertions.h"
#include "mozilla/TypeTraits.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "builtin/intl/ICUHeader.h"
#include "js/RootingAPI.h"
#include "js/Vector.h"
#include "vm/String.h"
namespace JS { class Value; }
class JSObject;
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.
*/
extern bool
InitializeObject(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
HandleValue locales, HandleValue options);
/**
* Returns the object holding the internal properties for obj.
*/
extern JSObject*
GetInternalsObject(JSContext* cx, JS::Handle<JSObject*> obj);
/** Report an Intl internal error not directly tied to a spec step. */
extern void
ReportInternalError(JSContext* cx);
static inline bool
StringsAreEqual(const char* s1, const char* s2)
{
return !strcmp(s1, s2);
}
static inline bool
StringsAreEqual(JSAutoByteString& s1, const char* s2)
{
return !strcmp(s1.ptr(), s2);
}
static inline const char*
IcuLocale(const char* locale)
{
if (StringsAreEqual(locale, "und"))
return ""; // ICU root locale
return locale;
}
// Starting with ICU 59, UChar defaults to char16_t.
static_assert(mozilla::IsSame<UChar, char16_t>::value,
"SpiderMonkey doesn't support redefining UChar to a different type");
// The inline capacity we use for a Vector<char16_t>. Use this to ensure that
// our uses of ICU string functions, below and elsewhere, will try to fill the
// buffer's entire inline capacity before growing it and heap-allocating.
static const size_t INITIAL_CHAR_BUFFER_SIZE = 32;
template <typename ICUStringFunction, size_t InlineCapacity>
static int32_t
CallICU(JSContext* cx, Vector<char16_t, InlineCapacity>& chars, const ICUStringFunction& strFn)
{
MOZ_ASSERT(chars.length() == 0);
MOZ_ALWAYS_TRUE(chars.resize(InlineCapacity));
UErrorCode status = U_ZERO_ERROR;
int32_t size = strFn(chars.begin(), InlineCapacity, &status);
if (status == U_BUFFER_OVERFLOW_ERROR) {
MOZ_ASSERT(size >= 0);
if (!chars.resize(size_t(size)))
return -1;
status = U_ZERO_ERROR;
strFn(chars.begin(), size, &status);
}
if (U_FAILURE(status)) {
ReportInternalError(cx);
return -1;
}
MOZ_ASSERT(size >= 0);
if (!chars.resize(size_t(size)))
return -1;
return size;
}
template <typename ICUStringFunction>
static JSString*
CallICU(JSContext* cx, const ICUStringFunction& strFn)
{
Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
int32_t size = CallICU(cx, chars, strFn);
if (size < 0)
return nullptr;
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
#endif /* builtin_intl_CommonFunctions_h */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,819 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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/. */
/* Intl.DateTimeFormat implementation. */
#include "builtin/intl/DateTimeFormat.h"
#include "mozilla/Assertions.h"
#include "mozilla/Range.h"
#include "jscntxt.h"
#include "jsfriendapi.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/intl/SharedIntlData.h"
#include "builtin/intl/TimeZoneDataGenerated.h"
#include "vm/GlobalObject.h"
#include "vm/Runtime.h"
#include "jsobjinlines.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using mozilla::IsFinite;
using JS::ClippedTime;
using JS::TimeClip;
using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::SharedIntlData;
using js::intl::StringsAreEqual;
/******************** DateTimeFormat ********************/
const ClassOps DateTimeFormatObject::classOps_ = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
DateTimeFormatObject::finalize
};
const Class DateTimeFormatObject::class_ = {
js_Object_str,
JSCLASS_HAS_RESERVED_SLOTS(DateTimeFormatObject::SLOT_COUNT) |
JSCLASS_FOREGROUND_FINALIZE,
&DateTimeFormatObject::classOps_
};
#if JS_HAS_TOSOURCE
static bool
dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().DateTimeFormat);
return true;
}
#endif
static const JSFunctionSpec dateTimeFormat_static_methods[] = {
JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_DateTimeFormat_supportedLocalesOf", 1, 0),
JS_FS_END
};
static const JSFunctionSpec dateTimeFormat_methods[] = {
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions", 0, 0),
JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 0, 0),
#if JS_HAS_TOSOURCE
JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0),
#endif
JS_FS_END
};
/**
* 12.2.1 Intl.DateTimeFormat([ locales [, options]])
*
* ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
*/
static bool
DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct)
{
RootedObject obj(cx);
// 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))
return false;
args.rval().setObject(*obj);
return true;
}
static bool
DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return DateTimeFormat(cx, args, args.isConstructing());
}
bool
js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(!args.isConstructing());
// 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);
}
void
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);
}
}
JSObject*
js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
{
RootedFunction ctor(cx);
ctor = GlobalObject::createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0);
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global,
&DateTimeFormatObject::class_));
if (!proto)
return nullptr;
proto->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
// 12.2.2
if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods))
return nullptr;
// 12.3.2 and 12.3.3
if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods))
return nullptr;
// Install a getter for DateTimeFormat.prototype.format that returns a
// formatting function bound to a specified DateTimeFormat object (suitable
// for passing to methods like Array.prototype.map).
RootedValue getter(cx);
if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().DateTimeFormatFormatGet,
&getter))
{
return nullptr;
}
if (!DefineProperty(cx, proto, cx->names().format, UndefinedHandleValue,
JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()),
nullptr, JSPROP_GETTER | JSPROP_SHARED))
{
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;
return proto;
}
bool
js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedValue result(cx);
if (!GetAvailableLocales(cx, udat_countAvailable, udat_getAvailable, &result))
return false;
args.rval().set(result);
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)
{
if (StringsAreEqual(icuName, "ethiopic-amete-alem"))
return "ethioaa";
if (StringsAreEqual(icuName, "gregorian"))
return "gregory";
if (StringsAreEqual(icuName, "islamic-civil"))
return "islamicc";
return icuName;
}
bool
js::intl_availableCalendars(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;
RootedObject calendars(cx, NewDenseEmptyArray(cx));
if (!calendars)
return false;
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);
// 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.
UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UEnumeration, uenum_close> toClose(values);
uint32_t count = uenum_count(values, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
for (; count > 0; count--) {
const char* calendar = uenum_next(values, nullptr, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar));
if (!jscalendar)
return false;
element = StringValue(jscalendar);
if (!DefineElement(cx, calendars, index++, element))
return false;
}
args.rval().setObject(*calendars);
return true;
}
bool
js::intl_IsValidTimeZoneName(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 timeZone(cx, args[0].toString());
RootedString validatedTimeZone(cx);
if (!sharedIntlData.validateTimeZoneName(cx, timeZone, &validatedTimeZone))
return false;
if (validatedTimeZone)
args.rval().setString(validatedTimeZone);
else
args.rval().setNull();
return true;
}
bool
js::intl_canonicalizeTimeZone(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;
// Some time zone names are canonicalized differently by ICU -- handle
// those first:
RootedString timeZone(cx, args[0].toString());
RootedString ianaTimeZone(cx);
if (!sharedIntlData.tryCanonicalizeTimeZoneConsistentWithIANA(cx, timeZone, &ianaTimeZone))
return false;
if (ianaTimeZone) {
args.rval().setString(ianaTimeZone);
return true;
}
AutoStableStringChars stableChars(cx);
if (!stableChars.initTwoByte(cx, timeZone))
return false;
mozilla::Range<const char16_t> tzchars = stableChars.twoByteRange();
JSString* str = CallICU(cx, [&tzchars](UChar* chars, uint32_t size, UErrorCode* status) {
return ucal_getCanonicalTimeZoneID(tzchars.begin().get(), tzchars.length(),
chars, size, nullptr, status);
});
if (!str)
return false;
args.rval().setString(str);
return true;
}
bool
js::intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
// The current default might be stale, because JS::ResetTimeZone() doesn't
// immediately update ICU's default time zone. So perform an update if
// needed.
js::ResyncICUDefaultTimeZone();
JSString* str = CallICU(cx, ucal_getDefaultTimeZone);
if (!str)
return false;
args.rval().setString(str);
return true;
}
bool
js::intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
UErrorCode status = U_ZERO_ERROR;
const UChar* uTimeZone = nullptr;
int32_t uTimeZoneLength = 0;
const char* rootLocale = "";
UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, rootLocale, UCAL_DEFAULT, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UCalendar, ucal_close> toClose(cal);
int32_t offset = ucal_get(cal, UCAL_ZONE_OFFSET, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
args.rval().setInt32(offset);
return true;
}
bool
js::intl_patternForSkeleton(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());
JSAutoByteString locale(cx, args[0].toString());
if (!locale)
return false;
JSFlatString* skeletonFlat = args[1].toString()->ensureFlat(cx);
if (!skeletonFlat)
return false;
AutoStableStringChars stableChars(cx);
if (!stableChars.initTwoByte(cx, skeletonFlat))
return false;
mozilla::Range<const char16_t> skeletonChars = stableChars.twoByteRange();
uint32_t skeletonLen = u_strlen(Char16ToUChar(skeletonChars.begin().get()));
UErrorCode status = U_ZERO_ERROR;
UDateTimePatternGenerator* gen = udatpg_open(IcuLocale(locale.ptr()), &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
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);
});
if (!str)
return false;
args.rval().setString(str);
return true;
}
/**
* Returns a new UDateFormat with the locale and date-time formatting options
* of the given DateTimeFormat.
*/
static UDateFormat*
NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat)
{
RootedValue value(cx);
RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat));
if (!internals)
return nullptr;
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
return nullptr;
JSAutoByteString locale(cx, value.toString());
if (!locale)
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.
if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value))
return nullptr;
AutoStableStringChars timeZoneChars(cx);
Rooted<JSFlatString*> timeZoneFlat(cx, value.toString()->ensureFlat(cx));
if (!timeZoneFlat || !timeZoneChars.initTwoByte(cx, timeZoneFlat))
return nullptr;
const UChar* uTimeZone = Char16ToUChar(timeZoneChars.twoByteRange().begin().get());
uint32_t uTimeZoneLength = u_strlen(uTimeZone);
if (!GetProperty(cx, internals, internals, cx->names().pattern, &value))
return nullptr;
AutoStableStringChars patternChars(cx);
Rooted<JSFlatString*> patternFlat(cx, value.toString()->ensureFlat(cx));
if (!patternFlat || !patternChars.initTwoByte(cx, patternFlat))
return nullptr;
const UChar* uPattern = Char16ToUChar(patternChars.twoByteRange().begin().get());
uint32_t uPatternLength = u_strlen(uPattern);
UErrorCode status = U_ZERO_ERROR;
UDateFormat* df =
udat_open(UDAT_PATTERN, UDAT_PATTERN, IcuLocale(locale.ptr()), uTimeZone, uTimeZoneLength,
uPattern, uPatternLength, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return nullptr;
}
// ECMAScript requires the Gregorian calendar to be used from the beginning
// of ECMAScript time.
UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(df));
ucal_setGregorianChange(cal, StartOfTime, &status);
// An error here means the calendar is not Gregorian, so we don't care.
return df;
}
static bool
intl_FormatDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue result)
{
if (!IsFinite(x)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE);
return false;
}
JSString* str = CallICU(cx, [df, x](UChar* chars, int32_t size, UErrorCode* status) {
return udat_format(df, x, chars, size, nullptr, status);
});
if (!str)
return false;
result.setString(str);
return true;
}
using FieldType = ImmutablePropertyNamePtr JSAtomState::*;
static FieldType
GetFieldTypeForFormatField(UDateFormatField fieldName)
{
// See intl/icu/source/i18n/unicode/udat.h for a detailed field list. This
// switch is deliberately exhaustive: cases might have to be added/removed
// if this code is compiled with a different ICU with more
// UDateFormatField enum initializers. Please guard such cases with
// appropriate ICU version-testing #ifdefs, should cross-version divergence
// occur.
switch (fieldName) {
case UDAT_ERA_FIELD:
return &JSAtomState::era;
case UDAT_YEAR_FIELD:
case UDAT_YEAR_WOY_FIELD:
case UDAT_EXTENDED_YEAR_FIELD:
case UDAT_YEAR_NAME_FIELD:
return &JSAtomState::year;
case UDAT_MONTH_FIELD:
case UDAT_STANDALONE_MONTH_FIELD:
return &JSAtomState::month;
case UDAT_DATE_FIELD:
case UDAT_JULIAN_DAY_FIELD:
return &JSAtomState::day;
case UDAT_HOUR_OF_DAY1_FIELD:
case UDAT_HOUR_OF_DAY0_FIELD:
case UDAT_HOUR1_FIELD:
case UDAT_HOUR0_FIELD:
return &JSAtomState::hour;
case UDAT_MINUTE_FIELD:
return &JSAtomState::minute;
case UDAT_SECOND_FIELD:
return &JSAtomState::second;
case UDAT_DAY_OF_WEEK_FIELD:
case UDAT_STANDALONE_DAY_FIELD:
case UDAT_DOW_LOCAL_FIELD:
case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD:
return &JSAtomState::weekday;
case UDAT_AM_PM_FIELD:
return &JSAtomState::dayPeriod;
case UDAT_TIMEZONE_FIELD:
return &JSAtomState::timeZoneName;
case UDAT_FRACTIONAL_SECOND_FIELD:
case UDAT_DAY_OF_YEAR_FIELD:
case UDAT_WEEK_OF_YEAR_FIELD:
case UDAT_WEEK_OF_MONTH_FIELD:
case UDAT_MILLISECONDS_IN_DAY_FIELD:
case UDAT_TIMEZONE_RFC_FIELD:
case UDAT_TIMEZONE_GENERIC_FIELD:
case UDAT_QUARTER_FIELD:
case UDAT_STANDALONE_QUARTER_FIELD:
case UDAT_TIMEZONE_SPECIAL_FIELD:
case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
case UDAT_TIMEZONE_ISO_FIELD:
case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
#ifndef U_HIDE_INTERNAL_API
case UDAT_RELATED_YEAR_FIELD:
#endif
#ifndef U_HIDE_DRAFT_API
case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
#endif
#ifndef U_HIDE_INTERNAL_API
case UDAT_TIME_SEPARATOR_FIELD:
#endif
// These fields are all unsupported.
return nullptr;
case UDAT_FIELD_COUNT:
MOZ_ASSERT_UNREACHABLE("format field sentinel value returned by "
"iterator!");
}
MOZ_ASSERT_UNREACHABLE("unenumerated, undocumented format field returned "
"by iterator");
return nullptr;
}
static bool
intl_FormatToPartsDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue result)
{
if (!IsFinite(x)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE);
return false;
}
Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
return false;
UErrorCode status = U_ZERO_ERROR;
UFieldPositionIterator* fpositer = ufieldpositer_open(&status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(fpositer);
RootedString overallResult(cx);
overallResult = CallICU(cx, [df, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
return udat_formatForFields(df, x, chars, size, fpositer, status);
});
if (!overallResult)
return false;
RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
if (!partsArray)
return false;
if (overallResult->length() == 0) {
// An empty string contains no parts, so avoid extra work below.
result.setObject(*partsArray);
return true;
}
size_t lastEndIndex = 0;
uint32_t partIndex = 0;
RootedObject singlePart(cx);
RootedValue partType(cx);
RootedValue val(cx);
auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) {
singlePart = NewBuiltinClassInstance<PlainObject>(cx);
if (!singlePart)
return false;
partType = StringValue(cx->names().*type);
if (!DefineProperty(cx, singlePart, cx->names().type, partType))
return false;
JSLinearString* partSubstr =
NewDependentString(cx, overallResult, beginIndex, endIndex - beginIndex);
if (!partSubstr)
return false;
val = StringValue(partSubstr);
if (!DefineProperty(cx, singlePart, cx->names().value, val))
return false;
val = ObjectValue(*singlePart);
if (!DefineElement(cx, partsArray, partIndex, val))
return false;
lastEndIndex = endIndex;
partIndex++;
return true;
};
int32_t fieldInt, beginIndexInt, endIndexInt;
while ((fieldInt = ufieldpositer_next(fpositer, &beginIndexInt, &endIndexInt)) >= 0) {
MOZ_ASSERT(beginIndexInt >= 0);
MOZ_ASSERT(endIndexInt >= 0);
MOZ_ASSERT(beginIndexInt <= endIndexInt,
"field iterator returning invalid range");
size_t beginIndex(beginIndexInt);
size_t endIndex(endIndexInt);
// Technically this isn't guaranteed. But it appears true in pratice,
// and http://bugs.icu-project.org/trac/ticket/12024 is expected to
// correct the documentation lapse.
MOZ_ASSERT(lastEndIndex <= beginIndex,
"field iteration didn't return fields in order start to "
"finish as expected");
if (FieldType type = GetFieldTypeForFormatField(static_cast<UDateFormatField>(fieldInt))) {
if (lastEndIndex < beginIndex) {
if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex))
return false;
}
if (!AppendPart(type, beginIndex, endIndex))
return false;
}
}
// Append any final literal.
if (lastEndIndex < overallResult->length()) {
if (!AppendPart(&JSAtomState::literal, lastEndIndex, overallResult->length()))
return false;
}
result.setObject(*partsArray);
return true;
}
bool
js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(args[0].isObject());
MOZ_ASSERT(args[1].isNumber());
MOZ_ASSERT(args[2].isBoolean());
RootedObject dateTimeFormat(cx, &args[0].toObject());
// 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.
df = NewUDateFormat(cx, dateTimeFormat);
if (!df)
return false;
}
// 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;
}

View file

@ -0,0 +1,143 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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_DateTimeFormat_h
#define builtin_intl_DateTimeFormat_h
#include "mozilla/Attributes.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/SelfHostingDefines.h"
#include "js/Class.h"
#include "js/RootingAPI.h"
#include "vm/NativeObject.h"
namespace js {
class FreeOp;
class GlobalObject;
/******************** DateTimeFormat ********************/
class DateTimeFormatObject : public NativeObject
{
public:
static const Class class_;
static constexpr uint32_t INTERNALS_SLOT = 0;
static constexpr uint32_t UDATE_FORMAT_SLOT = 1;
static constexpr uint32_t SLOT_COUNT = 2;
static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
"INTERNALS_SLOT must match self-hosting define for internals object slot");
private:
static const ClassOps classOps_;
static void finalize(FreeOp* fop, JSObject* obj);
};
extern JSObject*
CreateDateTimeFormatPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
JS::Handle<GlobalObject*> global);
/**
* Returns a new instance of the standard built-in DateTimeFormat constructor.
* Self-hosted code cannot cache this constructor (as it does for others in
* Utilities.js) because it is initialized after self-hosted code is compiled.
*
* Usage: dateTimeFormat = intl_DateTimeFormat(locales, options)
*/
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
* supported calendars for the given locale. The default calendar is
* element 0.
*
* Usage: calendars = intl_availableCalendars(locale)
*/
extern MOZ_MUST_USE bool
intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp);
/**
* 6.4.1 IsValidTimeZoneName ( timeZone )
*
* Verifies that the given string is a valid time zone name. If it is a valid
* time zone name, its IANA time zone name is returned. Otherwise returns null.
*
* ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
*
* Usage: ianaTimeZone = intl_IsValidTimeZoneName(timeZone)
*/
extern MOZ_MUST_USE bool
intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp);
/**
* Return the canonicalized time zone name. Canonicalization resolves link
* names to their target time zones.
*
* Usage: ianaTimeZone = intl_canonicalizeTimeZone(timeZone)
*/
extern MOZ_MUST_USE bool
intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp);
/**
* Return the default time zone name. The time zone name is not canonicalized.
*
* Usage: icuDefaultTimeZone = intl_defaultTimeZone()
*/
extern MOZ_MUST_USE bool
intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp);
/**
* Return the raw offset from GMT in milliseconds for the default time zone.
*
* Usage: defaultTimeZoneOffset = intl_defaultTimeZoneOffset()
*/
extern MOZ_MUST_USE bool
intl_defaultTimeZoneOffset(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 format pattern corresponding to skeleton for the
* given locale.
*
* Usage: pattern = intl_patternForSkeleton(locale, skeleton)
*/
extern MOZ_MUST_USE bool
intl_patternForSkeleton(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
* given DateTimeFormat.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
*
* Usage: formatted = intl_FormatDateTime(dateTimeFormat, x)
*/
extern MOZ_MUST_USE bool
intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp);
} // namespace js
#endif /* builtin_intl_DateTimeFormat_h */

View file

@ -0,0 +1,898 @@
/* 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/. */
/********** Intl.DateTimeFormat **********/
/**
* Compute an internal properties object from |lazyDateTimeFormatData|.
*/
function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
assert(IsObject(lazyDateTimeFormatData), "lazy data not an object?");
// Lazy DateTimeFormat data has the following structure:
//
// {
// requestedLocales: List of locales,
//
// localeOpt: // *first* opt computed in InitializeDateTimeFormat
// {
// localeMatcher: "lookup" / "best fit",
//
// hour12: true / false, // optional
// }
//
// timeZone: IANA time zone name,
//
// formatOpt: // *second* opt computed in InitializeDateTimeFormat
// {
// // all the properties/values listed in Table 3
// // (weekday, era, year, month, day, &c.)
// }
//
// formatMatcher: "basic" / "best fit",
// }
//
// Note that lazy data is only installed as a final step of initialization,
// so every DateTimeFormat lazy data object has *all* these properties,
// 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;
// Step 10.
var r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
lazyDateTimeFormatData.requestedLocales,
lazyDateTimeFormatData.localeOpt,
DateTimeFormat.relevantExtensionKeys,
localeData);
// Steps 11-13.
internalProps.locale = r.locale;
internalProps.calendar = r.ca;
internalProps.numberingSystem = r.nu;
// Compute formatting options.
// Step 14.
var dataLocale = r.dataLocale;
// Steps 15-17.
var tz = lazyDateTimeFormatData.timeZone;
if (tz === undefined) {
// Step 16.
tz = DefaultTimeZone();
}
internalProps.timeZone = tz;
// Step 18.
var formatOpt = lazyDateTimeFormatData.formatOpt;
// Steps 27-28, more or less - see comment after this function.
var pattern = toBestICUPattern(dataLocale, formatOpt);
// Step 29.
internalProps.pattern = pattern;
// Step 30.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
// object using |setInternalProperties|.
return internalProps;
}
/**
* Returns an object containing the DateTimeFormat internal properties of |obj|,
* or throws a TypeError if |obj| isn't DateTimeFormat-initialized.
*/
function getDateTimeFormatInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "DateTimeFormat", methodName);
assert(internals.type === "DateTimeFormat", "bad type escaped getIntlObjectInternals");
// If internal properties have already been computed, use them.
var internalProps = maybeInternalProperties(internals);
if (internalProps)
return internalProps;
// Otherwise it's time to fully create them.
internalProps = resolveDateTimeFormatInternals(internals.lazyData);
setInternalProperties(internals, internalProps);
return internalProps;
}
/**
* Components of date and time formats and their values.
*
* Spec: ECMAScript Internationalization API Specification, 12.1.1.
*/
var dateTimeComponentValues = {
weekday: ["narrow", "short", "long"],
era: ["narrow", "short", "long"],
year: ["2-digit", "numeric"],
month: ["2-digit", "numeric", "narrow", "short", "long"],
day: ["2-digit", "numeric"],
hour: ["2-digit", "numeric"],
minute: ["2-digit", "numeric"],
second: ["2-digit", "numeric"],
timeZoneName: ["short", "long"]
};
var dateTimeComponents = std_Object_getOwnPropertyNames(dateTimeComponentValues);
var timeZoneCache = {
icuDefaultTimeZone: undefined,
defaultTimeZone: undefined,
};
/**
* 6.4.2 CanonicalizeTimeZoneName ( timeZone )
*
* Canonicalizes the given IANA time zone name.
*
* ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
*/
function CanonicalizeTimeZoneName(timeZone) {
assert(typeof timeZone === "string", "CanonicalizeTimeZoneName");
// Step 1. (Not applicable, the input is already a valid IANA time zone.)
assert(timeZone !== "Etc/Unknown", "Invalid time zone");
assert(timeZone === intl_IsValidTimeZoneName(timeZone), "Time zone name not normalized");
// Step 2.
var ianaTimeZone = intl_canonicalizeTimeZone(timeZone);
assert(ianaTimeZone !== "Etc/Unknown", "Invalid canonical time zone");
assert(ianaTimeZone === intl_IsValidTimeZoneName(ianaTimeZone), "Unsupported canonical time zone");
// Step 3.
if (ianaTimeZone === "Etc/UTC" || ianaTimeZone === "Etc/GMT") {
// ICU/CLDR canonicalizes Etc/UCT to Etc/GMT, but following IANA and
// ECMA-402 to the letter means Etc/UCT is a separate time zone.
if (timeZone === "Etc/UCT" || timeZone === "UCT")
ianaTimeZone = "Etc/UCT";
else
ianaTimeZone = "UTC";
}
// Step 4.
return ianaTimeZone;
}
/**
* 6.4.3 DefaultTimeZone ()
*
* Returns the IANA time zone name for the host environment's current time zone.
*
* ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
*/
function DefaultTimeZone() {
const icuDefaultTimeZone = intl_defaultTimeZone();
if (timeZoneCache.icuDefaultTimeZone === icuDefaultTimeZone)
return timeZoneCache.defaultTimeZone;
// Verify that the current ICU time zone is a valid ECMA-402 time zone.
var timeZone = intl_IsValidTimeZoneName(icuDefaultTimeZone);
if (timeZone === null) {
// Before defaulting to "UTC", try to represent the default time zone
// using the Etc/GMT + offset format. This format only accepts full
// hour offsets.
const msPerHour = 60 * 60 * 1000;
var offset = intl_defaultTimeZoneOffset();
assert(offset === (offset | 0),
"milliseconds offset shouldn't be able to exceed int32_t range");
var offsetHours = offset / msPerHour, offsetHoursFraction = offset % msPerHour;
if (offsetHoursFraction === 0) {
// Etc/GMT + offset uses POSIX-style signs, i.e. a positive offset
// means a location west of GMT.
timeZone = "Etc/GMT" + (offsetHours < 0 ? "+" : "-") + std_Math_abs(offsetHours);
// Check if the fallback is valid.
timeZone = intl_IsValidTimeZoneName(timeZone);
}
// Fallback to "UTC" if everything else fails.
if (timeZone === null)
timeZone = "UTC";
}
// Canonicalize the ICU time zone, e.g. change Etc/UTC to UTC.
var defaultTimeZone = CanonicalizeTimeZoneName(timeZone);
timeZoneCache.defaultTimeZone = defaultTimeZone;
timeZoneCache.icuDefaultTimeZone = icuDefaultTimeZone;
return defaultTimeZone;
}
/**
* Initializes an object as a DateTimeFormat.
*
* This method is complicated a moderate bit by its implementing initialization
* as a *lazy* concept. Everything that must happen now, does -- but we defer
* all the work we can until the object is actually used as a DateTimeFormat.
* This later work occurs in |resolveDateTimeFormatInternals|; steps not noted
* here occur there.
*
* 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);
// Lazy DateTimeFormat data has the following structure:
//
// {
// requestedLocales: List of locales,
//
// localeOpt: // *first* opt computed in InitializeDateTimeFormat
// {
// localeMatcher: "lookup" / "best fit",
// }
//
// timeZone: IANA time zone name,
//
// formatOpt: // *second* opt computed in InitializeDateTimeFormat
// {
// // all the properties/values listed in Table 3
// // (weekday, era, year, month, day, &c.)
//
// hour12: true / false // optional
// }
//
// formatMatcher: "basic" / "best fit",
// }
//
// Note that lazy data is only installed as a final step of initialization,
// so every DateTimeFormat lazy data object has *all* these properties,
// never a subset of them.
var lazyDateTimeFormatData = std_Object_create(null);
// Step 3.
var requestedLocales = CanonicalizeLocaleList(locales);
lazyDateTimeFormatData.requestedLocales = requestedLocales;
// Step 4.
options = ToDateTimeOptions(options, "any", "date");
// Compute options that impact interpretation of locale.
// Step 5.
var localeOpt = new Record();
lazyDateTimeFormatData.localeOpt = localeOpt;
// Steps 6-7.
var localeMatcher =
GetOption(options, "localeMatcher", "string", ["lookup", "best fit"],
"best fit");
localeOpt.localeMatcher = localeMatcher;
// Steps 15-17.
var tz = options.timeZone;
if (tz !== undefined) {
// Step 15.a.
tz = ToString(tz);
// Step 15.b.
var timeZone = intl_IsValidTimeZoneName(tz);
if (timeZone === null)
ThrowRangeError(JSMSG_INVALID_TIME_ZONE, tz);
// Step 15.c.
tz = CanonicalizeTimeZoneName(timeZone);
}
lazyDateTimeFormatData.timeZone = tz;
// Step 18.
var formatOpt = new Record();
lazyDateTimeFormatData.formatOpt = formatOpt;
// Step 19.
var i, prop;
for (i = 0; i < dateTimeComponents.length; i++) {
prop = dateTimeComponents[i];
var value = GetOption(options, prop, "string", dateTimeComponentValues[prop], undefined);
formatOpt[prop] = value;
}
// Steps 20-21 provided by ICU - see comment after this function.
// Step 22.
//
// For some reason (ICU not exposing enough interface?) we drop the
// requested format matcher on the floor after this. In any case, even if
// doing so is justified, we have to do this work here in case it triggers
// getters or similar. (bug 852837)
var formatMatcher =
GetOption(options, "formatMatcher", "string", ["basic", "best fit"],
"best fit");
// Steps 23-25 provided by ICU, more or less - see comment after this function.
// Step 26.
var hr12 = GetOption(options, "hour12", "boolean", undefined, undefined);
// Pass hr12 on to ICU.
if (hr12 !== undefined)
formatOpt.hour12 = hr12;
// Step 31.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
setLazyData(internals, "DateTimeFormat", lazyDateTimeFormatData);
}
// Intl.DateTimeFormat and ICU skeletons and patterns
// ==================================================
//
// Different locales have different ways to display dates using the same
// basic components. For example, en-US might use "Sept. 24, 2012" while
// fr-FR might use "24 Sept. 2012". The intent of Intl.DateTimeFormat is to
// permit production of a format for the locale that best matches the
// set of date-time components and their desired representation as specified
// by the API client.
//
// ICU supports specification of date and time formats in three ways:
//
// 1) A style is just one of the identifiers FULL, LONG, MEDIUM, or SHORT.
// The date-time components included in each style and their representation
// are defined by ICU using CLDR locale data (CLDR is the Unicode
// Consortium's Common Locale Data Repository).
//
// 2) A skeleton is a string specifying which date-time components to include,
// and which representations to use for them. For example, "yyyyMMMMdd"
// specifies a year with at least four digits, a full month name, and a
// two-digit day. It does not specify in which order the components appear,
// how they are separated, the localized strings for textual components
// (such as weekday or month), whether the month is in format or
// stand-alone form¹, or the numbering system used for numeric components.
// All that information is filled in by ICU using CLDR locale data.
// ¹ The format form is the one used in formatted strings that include a
// day; the stand-alone form is used when not including days, e.g., in
// calendar headers. The two forms differ at least in some Slavic languages,
// e.g. Russian: "22 марта 2013 г." vs. "Март 2013".
//
// 3) A pattern is a string specifying which date-time components to include,
// in which order, with which separators, in which grammatical case. For
// example, "EEEE, d MMMM y" specifies the full localized weekday name,
// followed by comma and space, followed by the day, followed by space,
// followed by the full month name in format form, followed by space,
// followed by the full year. It
// still does not specify localized strings for textual components and the
// numbering system - these are determined by ICU using CLDR locale data or
// possibly API parameters.
//
// All actual formatting in ICU is done with patterns; styles and skeletons
// have to be mapped to patterns before processing.
//
// The options of DateTimeFormat most closely correspond to ICU skeletons. This
// implementation therefore, in the toBestICUPattern function, converts
// DateTimeFormat options to ICU skeletons, and then lets ICU map skeletons to
// actual ICU patterns. The pattern may not directly correspond to what the
// skeleton requests, as the mapper (UDateTimePatternGenerator) is constrained
// by the available locale data for the locale. The resulting ICU pattern is
// kept as the DateTimeFormat's [[pattern]] internal property and passed to ICU
// in the format method.
//
// An ICU pattern represents the information of the following DateTimeFormat
// internal properties described in the specification, which therefore don't
// exist separately in the implementation:
// - [[weekday]], [[era]], [[year]], [[month]], [[day]], [[hour]], [[minute]],
// [[second]], [[timeZoneName]]
// - [[hour12]]
// - [[hourNo0]]
// When needed for the resolvedOptions method, the resolveICUPattern function
// maps the instance's ICU pattern back to the specified properties of the
// object returned by resolvedOptions.
//
// ICU date-time skeletons and patterns aren't fully documented in the ICU
// documentation (see http://bugs.icu-project.org/trac/ticket/9627). The best
// documentation at this point is in UTR 35:
// http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
/**
* Returns an ICU pattern string for the given locale and representing the
* specified options as closely as possible given available locale data.
*/
function toBestICUPattern(locale, options) {
// Create an ICU skeleton representing the specified options. See
// http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
var skeleton = "";
switch (options.weekday) {
case "narrow":
skeleton += "EEEEE";
break;
case "short":
skeleton += "E";
break;
case "long":
skeleton += "EEEE";
}
switch (options.era) {
case "narrow":
skeleton += "GGGGG";
break;
case "short":
skeleton += "G";
break;
case "long":
skeleton += "GGGG";
break;
}
switch (options.year) {
case "2-digit":
skeleton += "yy";
break;
case "numeric":
skeleton += "y";
break;
}
switch (options.month) {
case "2-digit":
skeleton += "MM";
break;
case "numeric":
skeleton += "M";
break;
case "narrow":
skeleton += "MMMMM";
break;
case "short":
skeleton += "MMM";
break;
case "long":
skeleton += "MMMM";
break;
}
switch (options.day) {
case "2-digit":
skeleton += "dd";
break;
case "numeric":
skeleton += "d";
break;
}
var hourSkeletonChar = "j";
if (options.hour12 !== undefined) {
if (options.hour12)
hourSkeletonChar = "h";
else
hourSkeletonChar = "H";
}
switch (options.hour) {
case "2-digit":
skeleton += hourSkeletonChar + hourSkeletonChar;
break;
case "numeric":
skeleton += hourSkeletonChar;
break;
}
switch (options.minute) {
case "2-digit":
skeleton += "mm";
break;
case "numeric":
skeleton += "m";
break;
}
switch (options.second) {
case "2-digit":
skeleton += "ss";
break;
case "numeric":
skeleton += "s";
break;
}
switch (options.timeZoneName) {
case "short":
skeleton += "z";
break;
case "long":
skeleton += "zzzz";
break;
}
// Let ICU convert the ICU skeleton to an ICU pattern for the given locale.
return intl_patternForSkeleton(locale, skeleton);
}
/**
* Returns a new options object that includes the provided options (if any)
* and fills in default components if required components are not defined.
* Required can be "date", "time", or "any".
* Defaults can be "date", "time", or "all".
*
* Spec: ECMAScript Internationalization API Specification, 12.1.1.
*/
function ToDateTimeOptions(options, required, defaults) {
assert(typeof required === "string", "ToDateTimeOptions");
assert(typeof defaults === "string", "ToDateTimeOptions");
// Steps 1-3.
if (options === undefined)
options = null;
else
options = ToObject(options);
options = std_Object_create(options);
// Step 4.
var needDefaults = true;
// Step 5.
if ((required === "date" || required === "any") &&
(options.weekday !== undefined || options.year !== undefined ||
options.month !== undefined || options.day !== undefined))
{
needDefaults = false;
}
// Step 6.
if ((required === "time" || required === "any") &&
(options.hour !== undefined || options.minute !== undefined ||
options.second !== undefined))
{
needDefaults = false;
}
// Step 7.
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
// calls here, the difference doesn't matter because we're adding
// properties to a new object.
_DefineDataProperty(options, "year", "numeric");
_DefineDataProperty(options, "month", "numeric");
_DefineDataProperty(options, "day", "numeric");
}
// Step 8.
if (needDefaults && (defaults === "time" || defaults === "all")) {
// See comment for step 7.
_DefineDataProperty(options, "hour", "numeric");
_DefineDataProperty(options, "minute", "numeric");
_DefineDataProperty(options, "second", "numeric");
}
// Step 9.
return options;
}
/**
* Compares the date and time components requested by options with the available
* date and time formats in formats, and selects the best match according
* to a specified basic matching algorithm.
*
* Spec: ECMAScript Internationalization API Specification, 12.1.1.
*/
function BasicFormatMatcher(options, formats) {
// Steps 1-6.
var removalPenalty = 120,
additionPenalty = 20,
longLessPenalty = 8,
longMorePenalty = 6,
shortLessPenalty = 6,
shortMorePenalty = 3;
// Table 3.
var properties = ["weekday", "era", "year", "month", "day",
"hour", "minute", "second", "timeZoneName"];
// Step 11.c.vi.1.
var values = ["2-digit", "numeric", "narrow", "short", "long"];
// Steps 7-8.
var bestScore = -Infinity;
var bestFormat;
// Steps 9-11.
var i = 0;
var len = formats.length;
while (i < len) {
// Steps 11.a-b.
var format = formats[i];
var score = 0;
// Step 11.c.
var formatProp;
for (var j = 0; j < properties.length; j++) {
var property = properties[j];
// Step 11.c.i.
var optionsProp = options[property];
// Step missing from spec.
// https://bugs.ecmascript.org/show_bug.cgi?id=1254
formatProp = undefined;
// Steps 11.c.ii-iii.
if (callFunction(std_Object_hasOwnProperty, format, property))
formatProp = format[property];
if (optionsProp === undefined && formatProp !== undefined) {
// Step 11.c.iv.
score -= additionPenalty;
} else if (optionsProp !== undefined && formatProp === undefined) {
// Step 11.c.v.
score -= removalPenalty;
} else {
// Step 11.c.vi.
var optionsPropIndex = callFunction(ArrayIndexOf, values, optionsProp);
var formatPropIndex = callFunction(ArrayIndexOf, values, formatProp);
var delta = std_Math_max(std_Math_min(formatPropIndex - optionsPropIndex, 2), -2);
if (delta === 2)
score -= longMorePenalty;
else if (delta === 1)
score -= shortMorePenalty;
else if (delta === -1)
score -= shortLessPenalty;
else if (delta === -2)
score -= longLessPenalty;
}
}
// Step 11.d.
if (score > bestScore) {
bestScore = score;
bestFormat = format;
}
// Step 11.e.
i++;
}
// Step 12.
return bestFormat;
}
/**
* Compares the date and time components requested by options with the available
* date and time formats in formats, and selects the best match according
* to an unspecified best-fit matching algorithm.
*
* Spec: ECMAScript Internationalization API Specification, 12.1.1.
*/
function BestFitFormatMatcher(options, formats) {
// this implementation doesn't have anything better
return BasicFormatMatcher(options, formats);
}
/**
* Returns the subset of the given locale list for which this locale list has a
* 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.
*/
function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
var availableLocales = callFunction(dateTimeFormatInternalProperties.availableLocales,
dateTimeFormatInternalProperties);
var requestedLocales = CanonicalizeLocaleList(locales);
return SupportedLocales(availableLocales, requestedLocales, options);
}
/**
* DateTimeFormat internal properties.
*
* Spec: ECMAScript Internationalization API Specification, 9.1 and 12.2.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"]
};
function dateTimeFormatLocaleData(locale) {
return {
ca: intl_availableCalendars(locale),
nu: getNumberingSystems(locale)
};
}
/**
* Function to be bound and returned by Intl.DateTimeFormat.prototype.format.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
*/
function dateTimeFormatFormatToBind() {
// Steps 1.a.i-ii
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, false);
}
/**
* Returns a function bound to this DateTimeFormat that returns a String value
* 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.
*/
function Intl_DateTimeFormat_format_get() {
// Check "this DateTimeFormat object" per introduction of section 12.3.
var internals = getDateTimeFormatInternals(this, "format");
// Step 1.
if (internals.boundFormat === undefined) {
// Step 1.a.
var F = dateTimeFormatFormatToBind;
// Step 1.b-d.
var bf = callFunction(FunctionBind, F, this);
internals.boundFormat = bf;
}
// Step 2.
return internals.boundFormat;
}
function Intl_DateTimeFormat_formatToParts() {
// Check "this DateTimeFormat object" per introduction of section 12.3.
getDateTimeFormatInternals(this, "formatToParts");
// Steps 1.a.i-ii
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);
}
/**
* Returns the resolved options for a DateTimeFormat object.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4.
*/
function Intl_DateTimeFormat_resolvedOptions() {
// Check "this DateTimeFormat object" per introduction of section 12.3.
var internals = getDateTimeFormatInternals(this, "resolvedOptions");
var result = {
locale: internals.locale,
calendar: internals.calendar,
numberingSystem: internals.numberingSystem,
timeZone: internals.timeZone
};
resolveICUPattern(internals.pattern, result);
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
* object, which will be returned by the resolvedOptions method. For the
* interpretation of ICU pattern characters, see
* http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
*/
function resolveICUPattern(pattern, result) {
assert(IsObject(result), "resolveICUPattern");
var i = 0;
while (i < pattern.length) {
var c = pattern[i++];
if (c === "'") {
while (i < pattern.length && pattern[i] !== "'")
i++;
i++;
} else {
var count = 1;
while (i < pattern.length && pattern[i] === c) {
i++;
count++;
}
var value;
switch (c) {
// "text" cases
case "G":
case "E":
case "z":
case "v":
case "V":
if (count <= 3)
value = "short";
else if (count === 4)
value = "long";
else
value = "narrow";
break;
// "number" cases
case "y":
case "d":
case "h":
case "H":
case "m":
case "s":
case "k":
case "K":
if (count === 2)
value = "2-digit";
else
value = "numeric";
break;
// "text & number" cases
case "M":
case "L":
if (count === 1)
value = "numeric";
else if (count === 2)
value = "2-digit";
else if (count === 3)
value = "short";
else if (count === 4)
value = "long";
else
value = "narrow";
break;
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);
}
}
}

View file

@ -0,0 +1,50 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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_ICUHeader_h
#define builtin_intl_ICUHeader_h
#include "unicode/plurrule.h"
#include "unicode/ucal.h"
#include "unicode/ucol.h"
#include "unicode/udat.h"
#include "unicode/udatpg.h"
#include "unicode/udisplaycontext.h"
#include "unicode/uenum.h"
#include "unicode/unum.h"
#include "unicode/unumsys.h"
#include "unicode/upluralrules.h"
#include "unicode/ureldatefmt.h"
#include "unicode/ustring.h"
/**
* Cast char16_t* strings to UChar* strings used by ICU.
*/
inline const UChar*
Char16ToUChar(const char16_t* chars)
{
return reinterpret_cast<const UChar*>(chars);
}
inline UChar*
Char16ToUChar(char16_t* chars)
{
return reinterpret_cast<UChar*>(chars);
}
inline char16_t*
UCharToChar16(UChar* chars)
{
return reinterpret_cast<char16_t*>(chars);
}
inline const char16_t*
UCharToChar16(const UChar* chars)
{
return reinterpret_cast<const char16_t*>(chars);
}
#endif /* builtin_intl_ICUHeader_h */

View file

@ -0,0 +1,512 @@
/* -*- 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/. */
/* 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 "jsapi.h"
#include "jscntxt.h"
#include "jsobj.h"
#include "builtin/intl/Collator.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/DateTimeFormat.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/intl/PluralRules.h"
#include "builtin/intl/RelativeTimeFormat.h"
#include "builtin/intl/ScopedICUObject.h"
#include "vm/GlobalObject.h"
#include "jsobjinlines.h"
using namespace js;
using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::StringsAreEqual;
/******************** Intl ********************/
bool
js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
JSAutoByteString locale(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.ptr(), 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 (!DefineProperty(cx, info, cx->names().firstDayOfWeek, v))
return false;
int32_t minDays = ucal_getAttribute(cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK);
v.setInt32(minDays);
if (!DefineProperty(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 (!DefineProperty(cx, info, cx->names().weekendStart, weekendStart))
return false;
if (!DefineProperty(cx, info, cx->names().weekendEnd, weekendEnd))
return false;
args.rval().setObject(*info);
return true;
}
template<size_t N>
inline bool
MatchPart(const char** pattern, const char (&part)[N])
{
if (strncmp(*pattern, part, N - 1))
return false;
*pattern += N - 1;
return true;
}
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.
MOZ_ASSERT(args[0].isString());
// 2. Assert: style is a string.
MOZ_ASSERT(args[1].isString());
// 3. Assert: keys is an Array.
MOZ_ASSERT(args[2].isObject());
JSAutoByteString locale(cx, args[0].toString());
if (!locale)
return false;
JSAutoByteString style(cx, args[1].toString());
if (!style)
return false;
RootedArrayObject keys(cx, &args[2].toObject().as<ArrayObject>());
if (!keys)
return false;
// 4. Let result be ArrayCreate(0).
RootedArrayObject result(cx, NewDenseUnallocatedArray(cx, keys->length()));
if (!result)
return false;
UErrorCode status = U_ZERO_ERROR;
UDateFormat* fmt =
udat_open(UDAT_DEFAULT, UDAT_DEFAULT, IcuLocale(locale.ptr()),
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.ptr()), &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
RootedValue keyValue(cx);
RootedString keyValStr(cx);
RootedValue wordVal(cx);
Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
return false;
// 5. For each element of keys,
for (uint32_t i = 0; i < keys->length(); i++) {
/**
* We iterate over keys array looking for paths that we have code
* branches for.
*
* For any unknown path branch, the wordVal will keep NullValue and
* we'll throw at the end.
*/
if (!GetElement(cx, keys, keys, i, &keyValue))
return false;
JSAutoByteString pattern;
keyValStr = keyValue.toString();
if (!pattern.encodeUtf8(cx, keyValStr))
return false;
wordVal.setNull();
// 5.a. Perform an implementation dependent algorithm to map a key to a
// corresponding display name.
const char* pat = pattern.ptr();
if (!MatchPart(&pat, "dates")) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
if (!MatchPart(&pat, "/")) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
if (MatchPart(&pat, "fields")) {
if (!MatchPart(&pat, "/")) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
UDateTimePatternField fieldType;
if (MatchPart(&pat, "year")) {
fieldType = UDATPG_YEAR_FIELD;
} else if (MatchPart(&pat, "month")) {
fieldType = UDATPG_MONTH_FIELD;
} else if (MatchPart(&pat, "week")) {
fieldType = UDATPG_WEEK_OF_YEAR_FIELD;
} else if (MatchPart(&pat, "day")) {
fieldType = UDATPG_DAY_FIELD;
} else {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
// This part must be the final part with no trailing data.
if (*pat != '\0') {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
int32_t resultSize;
const UChar* value = udatpg_getAppendItemName(dtpg, fieldType, &resultSize);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
JSString* word = NewStringCopyN<CanGC>(cx, UCharToChar16(value), resultSize);
if (!word)
return false;
wordVal.setString(word);
} else if (MatchPart(&pat, "gregorian")) {
if (!MatchPart(&pat, "/")) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
UDateFormatSymbolType symbolType;
int32_t index;
if (MatchPart(&pat, "months")) {
if (!MatchPart(&pat, "/")) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
if (StringsAreEqual(style, "narrow")) {
symbolType = UDAT_STANDALONE_NARROW_MONTHS;
} else if (StringsAreEqual(style, "short")) {
symbolType = UDAT_STANDALONE_SHORT_MONTHS;
} else {
MOZ_ASSERT(StringsAreEqual(style, "long"));
symbolType = UDAT_STANDALONE_MONTHS;
}
if (MatchPart(&pat, "january")) {
index = UCAL_JANUARY;
} else if (MatchPart(&pat, "february")) {
index = UCAL_FEBRUARY;
} else if (MatchPart(&pat, "march")) {
index = UCAL_MARCH;
} else if (MatchPart(&pat, "april")) {
index = UCAL_APRIL;
} else if (MatchPart(&pat, "may")) {
index = UCAL_MAY;
} else if (MatchPart(&pat, "june")) {
index = UCAL_JUNE;
} else if (MatchPart(&pat, "july")) {
index = UCAL_JULY;
} else if (MatchPart(&pat, "august")) {
index = UCAL_AUGUST;
} else if (MatchPart(&pat, "september")) {
index = UCAL_SEPTEMBER;
} else if (MatchPart(&pat, "october")) {
index = UCAL_OCTOBER;
} else if (MatchPart(&pat, "november")) {
index = UCAL_NOVEMBER;
} else if (MatchPart(&pat, "december")) {
index = UCAL_DECEMBER;
} else {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
} else if (MatchPart(&pat, "weekdays")) {
if (!MatchPart(&pat, "/")) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
if (StringsAreEqual(style, "narrow")) {
symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
} else if (StringsAreEqual(style, "short")) {
symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS;
} else {
MOZ_ASSERT(StringsAreEqual(style, "long"));
symbolType = UDAT_STANDALONE_WEEKDAYS;
}
if (MatchPart(&pat, "monday")) {
index = UCAL_MONDAY;
} else if (MatchPart(&pat, "tuesday")) {
index = UCAL_TUESDAY;
} else if (MatchPart(&pat, "wednesday")) {
index = UCAL_WEDNESDAY;
} else if (MatchPart(&pat, "thursday")) {
index = UCAL_THURSDAY;
} else if (MatchPart(&pat, "friday")) {
index = UCAL_FRIDAY;
} else if (MatchPart(&pat, "saturday")) {
index = UCAL_SATURDAY;
} else if (MatchPart(&pat, "sunday")) {
index = UCAL_SUNDAY;
} else {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
} else if (MatchPart(&pat, "dayperiods")) {
if (!MatchPart(&pat, "/")) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
symbolType = UDAT_AM_PMS;
if (MatchPart(&pat, "am")) {
index = UCAL_AM;
} else if (MatchPart(&pat, "pm")) {
index = UCAL_PM;
} else {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
} else {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
// This part must be the final part with no trailing data.
if (*pat != '\0') {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
JSString* word = CallICU(cx, [fmt, symbolType, index](UChar* chars, int32_t size, UErrorCode* status) {
return udat_getSymbols(fmt, symbolType, index, chars, size, status);
});
if (!word)
return false;
wordVal.setString(word);
} else {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
return false;
}
MOZ_ASSERT(wordVal.isString());
// 5.b. Append the result string to result.
if (!DefineElement(cx, result, i, wordVal))
return false;
}
// 6. Return result.
args.rval().setObject(*result);
return true;
}
const Class js::IntlClass = {
js_Object_str,
JSCLASS_HAS_CACHED_PROTO(JSProto_Intl)
};
#if JS_HAS_TOSOURCE
static bool
intl_toSource(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().Intl);
return true;
}
#endif
static const JSFunctionSpec intl_static_methods[] = {
#if JS_HAS_TOSOURCE
JS_FN(js_toSource_str, intl_toSource, 0, 0),
#endif
JS_SELF_HOSTED_FN("getCanonicalLocales", "Intl_getCanonicalLocales", 1, 0),
JS_FS_END
};
/**
* Initializes the Intl Object and its standard built-in properties.
* Spec: ECMAScript Internationalization API Specification, 8.0, 8.1
*/
/* static */ bool
GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
{
RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
if (!proto)
return false;
// The |Intl| object is just a plain object with some "static" function
// properties and some constructor properties.
RootedObject intl(cx, NewObjectWithGivenProto(cx, &IntlClass, proto, SingletonObject));
if (!intl)
return false;
// Add the static functions.
if (!JS_DefineFunctions(cx, intl, intl_static_methods))
return false;
// Add the constructor properties, computing and returning the relevant
// prototype objects needed below.
RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global));
if (!collatorProto)
return false;
RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global));
if (!dateTimeFormatProto)
return false;
RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
if (!numberFormatProto)
return false;
RootedObject pluralRulesProto(cx, CreatePluralRulesPrototype(cx, intl, global));
if (!pluralRulesProto)
return false;
RootedObject relativeTimeFmtProto(cx, CreateRelativeTimeFormatPrototype(cx, intl, global));
if (!relativeTimeFmtProto) {
return false;
}
// The |Intl| object is fully set up now, so define the global property.
RootedValue intlValue(cx, ObjectValue(*intl));
if (!DefineProperty(cx, global, cx->names().Intl, intlValue, nullptr, nullptr,
JSPROP_RESOLVING))
{
return false;
}
// Now that the |Intl| object is successfully added, we can OOM-safely fill
// in all relevant reserved global slots.
// Cache the various prototypes, for use in creating instances of these
// objects with the proper [[Prototype]] as "the original value of
// |Intl.Collator.prototype|" and similar. For builtin classes like
// |String.prototype| we have |JSProto_*| that enables
// |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_PROTO, ObjectValue(*dateTimeFormatProto));
global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
global->setReservedSlot(PLURAL_RULES_PROTO, ObjectValue(*pluralRulesProto));
global->setReservedSlot(RELATIVE_TIME_FORMAT_PROTO, ObjectValue(*relativeTimeFmtProto));
// Also cache |Intl| to implement spec language that conditions behavior
// based on values being equal to "the standard built-in |Intl| object".
// Use |setConstructor| to correspond with |JSProto_Intl|.
//
// XXX We should possibly do a one-off reserved slot like above.
global->setConstructor(JSProto_Intl, ObjectValue(*intl));
return true;
}
JSObject*
js::InitIntlClass(JSContext* cx, HandleObject obj)
{
Handle<GlobalObject*> global = obj.as<GlobalObject>();
if (!GlobalObject::initIntlObject(cx, global))
return nullptr;
return &global->getConstructor(JSProto_Intl).toObject();
}

View file

@ -0,0 +1,100 @@
/* -*- 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/. */
#ifndef builtin_intl_IntlObject_h
#define builtin_intl_IntlObject_h
#include "mozilla/Attributes.h"
#include "js/RootingAPI.h"
struct JSContext;
class JSObject;
namespace JS { class Value; }
namespace js {
/**
* Initializes the Intl Object and its standard built-in properties.
* Spec: ECMAScript Internationalization API Specification, 8.0, 8.1
*/
extern JSObject*
InitIntlClass(JSContext* cx, JS::Handle<JSObject*> obj);
/*
* The following functions are for use by self-hosted code.
*/
/**
* Returns a plain object with calendar information for a single valid locale
* (callers must perform this validation). The object will have these
* properties:
*
* firstDayOfWeek
* an integer in the range 1=Sunday to 7=Saturday indicating the day
* considered the first day of the week in calendars, e.g. 1 for en-US,
* 2 for en-GB, 1 for bn-IN
* minDays
* an integer in the range of 1 to 7 indicating the minimum number
* of days required in the first week of the year, e.g. 1 for en-US, 4 for de
* weekendStart
* an integer in the range 1=Sunday to 7=Saturday indicating the day
* considered the beginning of a weekend, e.g. 7 for en-US, 7 for en-GB,
* 1 for bn-IN
* weekendEnd
* an integer in the range 1=Sunday to 7=Saturday indicating the day
* considered the end of a weekend, e.g. 1 for en-US, 1 for en-GB,
* 1 for bn-IN (note that "weekend" is *not* necessarily two days)
*
* NOTE: "calendar" and "locale" properties are *not* added to the object.
*/
extern MOZ_MUST_USE bool
intl_GetCalendarInfo(JSContext* cx, unsigned argc, JS::Value* vp);
/**
* Returns an Array with CLDR-based fields display names.
* The function takes three arguments:
*
* locale
* BCP47 compliant locale string
* style
* A string with values: long or short or narrow
* keys
* An array or path-like strings that identify keys to be returned
* At the moment the following types of keys are supported:
*
* 'dates/fields/{year|month|week|day}'
* 'dates/gregorian/months/{january|...|december}'
* 'dates/gregorian/weekdays/{sunday|...|saturday}'
* 'dates/gregorian/dayperiods/{am|pm}'
*
* Example:
*
* let info = intl_ComputeDisplayNames(
* 'en-US',
* 'long',
* [
* 'dates/fields/year',
* 'dates/gregorian/months/january',
* 'dates/gregorian/weekdays/monday',
* 'dates/gregorian/dayperiods/am',
* ]
* );
*
* Returned value:
*
* [
* 'year',
* 'January',
* 'Monday',
* 'AM'
* ]
*/
extern MOZ_MUST_USE bool
intl_ComputeDisplayNames(JSContext* cx, unsigned argc, JS::Value* vp);
} // namespace js
#endif /* builtin_intl_IntlObject_h */

View file

@ -0,0 +1,162 @@
/* 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/. */
function Intl_getCanonicalLocales(locales) {
let codes = CanonicalizeLocaleList(locales);
let result = [];
let len = codes.length;
let k = 0;
while (k < len) {
_DefineDataProperty(result, k, codes[k]);
k++;
}
return result;
}
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;
}
/**
* This function is a custom method designed after Intl API, but currently
* not part of the spec or spec proposal.
* We want to use it internally to retrieve translated values from CLDR in
* order to ensure they're aligned with what Intl API returns.
*
* This API may one day be a foundation for an ECMA402 API spec proposal.
*
* The function takes two arguments - locales which is a list of locale strings
* and options which is an object with two optional properties:
*
* keys:
* an Array of string values that are paths to individual terms
*
* style:
* a String with a value "long", "short" or "narrow"
*
* It returns an object with properties:
*
* locale:
* a negotiated locale string
*
* style:
* negotiated style
*
* values:
* A key-value pair list of requested keys and corresponding
* translated values
*
*/
function Intl_getDisplayNames(locales, options) {
// 1. Let requestLocales be ? CanonicalizeLocaleList(locales).
const requestedLocales = CanonicalizeLocaleList(locales);
// 2. If options is undefined, then
if (options === undefined)
// a. Let options be ObjectCreate(%ObjectPrototype%).
options = {};
// 3. Else,
else
// a. Let options be ? ToObject(options).
options = ToObject(options);
const DateTimeFormat = dateTimeFormatInternalProperties;
// 4. Let localeData be %DateTimeFormat%.[[localeData]].
const localeData = DateTimeFormat.localeData;
// 5. Let opt 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);
// 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;
// 10. If keys is undefined,
if (keys === undefined) {
// a. Let keys be ArrayCreate(0).
keys = [];
} else if (!IsObject(keys)) {
// 11. Else,
// a. If Type(keys) is not Object, throw a TypeError exception.
ThrowTypeError(JSMSG_INVALID_KEYS_TYPE);
}
// 12. Let processedKeys be ArrayCreate(0).
// (This really should be a List, but we use an Array here in order that
// |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++) {
// a. Let processedKey be ? ToString(? Get(keys, i)).
// b. Perform ? CreateDataPropertyOrThrow(processedKeys, i, processedKey).
callFunction(std_Array_push, processedKeys, ToString(keys[i]));
}
// 16. Let names be ? ComputeDisplayNames(r.[[locale]], style, processedKeys).
const names = intl_ComputeDisplayNames(r.locale, style, processedKeys);
// 17. Let values be ObjectCreate(%ObjectPrototype%).
const values = {};
// 18. Set i to 0.
// 19. Repeat, while i < len
for (let i = 0; i < len; i++) {
// a. Let key be ? Get(processedKeys, i).
const key = processedKeys[i];
// b. Let name be ? Get(names, i).
const name = names[i];
// c. Assert: Type(name) is string.
assert(typeof name === "string", "unexpected non-string value");
// d. Assert: the length of name is greater than zero.
assert(name.length > 0, "empty string value");
// e. Perform ? DefinePropertyOrThrow(values, key, name).
_DefineDataProperty(values, key, name);
}
// 20. Let options be ObjectCreate(%ObjectPrototype%).
// 21. Perform ! DefinePropertyOrThrow(result, "locale", r.[[locale]]).
// 22. Perform ! DefinePropertyOrThrow(result, "style", style).
// 23. Perform ! DefinePropertyOrThrow(result, "values", values).
const result = { locale: r.locale, style, values };
// 24. Return result.
return result;
}

View file

@ -0,0 +1,904 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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/. */
/* Intl.NumberFormat implementation. */
#include "builtin/intl/NumberFormat.h"
#include "mozilla/Assertions.h"
#include "mozilla/FloatingPoint.h"
#include <algorithm>
#include <stddef.h>
#include <stdint.h>
#include "jscntxt.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/ScopedICUObject.h"
#include "ds/Sort.h"
#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
#include "vm/SelfHosting.h"
#include "vm/Stack.h"
#include "jsobjinlines.h"
using namespace js;
using mozilla::AssertedCast;
using mozilla::IsFinite;
using mozilla::IsNaN;
using mozilla::IsNegativeZero;
using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::StringsAreEqual;
/******************** NumberFormat ********************/
const ClassOps NumberFormatObject::classOps_ = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
NumberFormatObject::finalize
};
const Class NumberFormatObject::class_ = {
js_Object_str,
JSCLASS_HAS_RESERVED_SLOTS(NumberFormatObject::SLOT_COUNT) |
JSCLASS_FOREGROUND_FINALIZE,
&NumberFormatObject::classOps_
};
#if JS_HAS_TOSOURCE
static bool
numberFormat_toSource(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().NumberFormat);
return true;
}
#endif
static const JSFunctionSpec numberFormat_static_methods[] = {
JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_NumberFormat_supportedLocalesOf", 1, 0),
JS_FS_END
};
static const JSFunctionSpec numberFormat_methods[] = {
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0, 0),
JS_SELF_HOSTED_FN("formatToParts", "Intl_NumberFormat_formatToParts", 1, 0),
#if JS_HAS_TOSOURCE
JS_FN(js_toSource_str, numberFormat_toSource, 0, 0),
#endif
JS_FS_END
};
/**
* 11.2.1 Intl.NumberFormat([ locales [, options]])
*
* ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
*/
static bool
NumberFormat(JSContext* cx, const CallArgs& args, bool construct)
{
RootedObject obj(cx);
// 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))
return false;
args.rval().setObject(*obj);
return true;
}
static bool
NumberFormat(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return NumberFormat(cx, args, args.isConstructing());
}
bool
js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(!args.isConstructing());
// intl_NumberFormat 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 NumberFormat(cx, args, true);
}
void
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);
}
}
JSObject*
js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
{
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_));
if (!proto)
return nullptr;
proto->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
// 11.2.2
if (!JS_DefineFunctions(cx, ctor, numberFormat_static_methods))
return nullptr;
// 11.3.2 and 11.3.3
if (!JS_DefineFunctions(cx, proto, numberFormat_methods))
return nullptr;
/*
* Install the getter for NumberFormat.prototype.format, which returns a
* bound formatting function for the specified NumberFormat object (suitable
* for passing to methods like Array.prototype.map).
*/
RootedValue getter(cx);
if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().NumberFormatFormatGet,
&getter))
{
return nullptr;
}
if (!DefineProperty(cx, proto, cx->names().format, UndefinedHandleValue,
JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()),
nullptr, JSPROP_GETTER | JSPROP_SHARED))
{
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;
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)
{
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;
UErrorCode status = U_ZERO_ERROR;
UNumberingSystem* numbers = unumsys_open(IcuLocale(locale.ptr()), &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UNumberingSystem, unumsys_close> toClose(numbers);
const char* name = unumsys_getName(numbers);
RootedString jsname(cx, JS_NewStringCopyZ(cx, name));
if (!jsname)
return false;
args.rval().setString(jsname);
return true;
}
/**
* Returns a new UNumberFormat with the locale and number formatting options
* of the given NumberFormat.
*/
static UNumberFormat*
NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
{
RootedValue value(cx);
RootedObject internals(cx, intl::GetInternalsObject(cx, numberFormat));
if (!internals)
return nullptr;
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
return nullptr;
JSAutoByteString locale(cx, value.toString());
if (!locale)
return nullptr;
// UNumberFormat options with default values
UNumberFormatStyle uStyle = UNUM_DECIMAL;
const UChar* uCurrency = nullptr;
uint32_t uMinimumIntegerDigits = 1;
uint32_t uMinimumFractionDigits = 0;
uint32_t uMaximumFractionDigits = 3;
int32_t uMinimumSignificantDigits = -1;
int32_t uMaximumSignificantDigits = -1;
bool uUseGrouping = true;
// Sprinkle appropriate rooting flavor over things the GC might care about.
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());
if (!style)
return nullptr;
if (StringsAreEqual(style, "currency")) {
if (!GetProperty(cx, internals, internals, cx->names().currency, &value))
return nullptr;
currency = value.toString();
MOZ_ASSERT(currency->length() == 3,
"IsWellFormedCurrencyCode permits only length-3 strings");
if (!currency->ensureFlat(cx) || !stableChars.initTwoByte(cx, currency))
return nullptr;
// uCurrency remains owned by stableChars.
uCurrency = Char16ToUChar(stableChars.twoByteRange().begin().get());
if (!uCurrency)
return nullptr;
if (!GetProperty(cx, internals, internals, cx->names().currencyDisplay, &value))
return nullptr;
JSAutoByteString currencyDisplay(cx, value.toString());
if (!currencyDisplay)
return nullptr;
if (StringsAreEqual(currencyDisplay, "code")) {
uStyle = UNUM_CURRENCY_ISO;
} else if (StringsAreEqual(currencyDisplay, "symbol")) {
uStyle = UNUM_CURRENCY;
} else {
MOZ_ASSERT(StringsAreEqual(currencyDisplay, "name"));
uStyle = UNUM_CURRENCY_PLURAL;
}
} else if (StringsAreEqual(style, "percent")) {
uStyle = UNUM_PERCENT;
} else {
MOZ_ASSERT(StringsAreEqual(style, "decimal"));
uStyle = UNUM_DECIMAL;
}
RootedId id(cx, NameToId(cx->names().minimumSignificantDigits));
bool hasP;
if (!HasProperty(cx, internals, id, &hasP))
return nullptr;
if (hasP) {
if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
&value))
return nullptr;
uMinimumSignificantDigits = value.toInt32();
if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
&value))
return nullptr;
uMaximumSignificantDigits = value.toInt32();
} else {
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
&value))
return nullptr;
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
&value))
return nullptr;
uMinimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
&value))
return nullptr;
uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
}
if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value))
return nullptr;
uUseGrouping = value.toBoolean();
UErrorCode status = U_ZERO_ERROR;
UNumberFormat* nf = unum_open(uStyle, nullptr, 0, IcuLocale(locale.ptr()), nullptr, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return nullptr;
}
ScopedICUObject<UNumberFormat, unum_close> toClose(nf);
if (uCurrency) {
unum_setTextAttribute(nf, UNUM_CURRENCY_CODE, uCurrency, 3, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return nullptr;
}
}
if (uMinimumSignificantDigits != -1) {
unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true);
unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits);
unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits);
} else {
unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits);
unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits);
unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits);
}
unum_setAttribute(nf, UNUM_GROUPING_USED, uUseGrouping);
unum_setAttribute(nf, UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP);
return toClose.forget();
}
static JSString*
PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, double* x,
UFieldPositionIterator* fpositer)
{
// PartitionNumberPattern doesn't consider -0.0 to be negative.
if (IsNegativeZero(*x))
*x = 0.0;
return CallICU(cx, [nf, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
return unum_formatDoubleForFields(nf, *x, chars, size, fpositer, status);
});
}
bool
js::intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result)
{
// Passing null for |fpositer| will just not compute partition information,
// letting us common up all ICU number-formatting code.
JSString* str = PartitionNumberPattern(cx, nf, &x, nullptr);
if (!str)
return false;
result.setString(str);
return true;
}
using FieldType = ImmutablePropertyNamePtr JSAtomState::*;
static FieldType
GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d)
{
// See intl/icu/source/i18n/unicode/unum.h for a detailed field list. This
// list is deliberately exhaustive: cases might have to be added/removed if
// this code is compiled with a different ICU with more UNumberFormatFields
// enum initializers. Please guard such cases with appropriate ICU
// version-testing #ifdefs, should cross-version divergence occur.
switch (fieldName) {
case UNUM_INTEGER_FIELD:
if (IsNaN(d))
return &JSAtomState::nan;
if (!IsFinite(d))
return &JSAtomState::infinity;
return &JSAtomState::integer;
case UNUM_GROUPING_SEPARATOR_FIELD:
return &JSAtomState::group;
case UNUM_DECIMAL_SEPARATOR_FIELD:
return &JSAtomState::decimal;
case UNUM_FRACTION_FIELD:
return &JSAtomState::fraction;
case UNUM_SIGN_FIELD: {
MOZ_ASSERT(!IsNegativeZero(d),
"-0 should have been excluded by PartitionNumberPattern");
// Manual trawling through the ICU call graph appears to indicate that
// the basic formatting we request will never include a positive sign.
// But this analysis may be mistaken, so don't absolutely trust it.
return d < 0 ? &JSAtomState::minusSign : &JSAtomState::plusSign;
}
case UNUM_PERCENT_FIELD:
return &JSAtomState::percentSign;
case UNUM_CURRENCY_FIELD:
return &JSAtomState::currency;
case UNUM_PERMILL_FIELD:
MOZ_ASSERT_UNREACHABLE("unexpected permill field found, even though "
"we don't use any user-defined patterns that "
"would require a permill field");
break;
case UNUM_EXPONENT_SYMBOL_FIELD:
case UNUM_EXPONENT_SIGN_FIELD:
case UNUM_EXPONENT_FIELD:
MOZ_ASSERT_UNREACHABLE("exponent field unexpectedly found in "
"formatted number, even though UNUM_SCIENTIFIC "
"and scientific notation were never requested");
break;
case UNUM_FIELD_COUNT:
MOZ_ASSERT_UNREACHABLE("format field sentinel value returned by "
"iterator!");
break;
}
MOZ_ASSERT_UNREACHABLE("unenumerated, undocumented format field returned "
"by iterator");
return nullptr;
}
static bool
intl_FormatNumberToParts(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result)
{
UErrorCode status = U_ZERO_ERROR;
UFieldPositionIterator* fpositer = ufieldpositer_open(&status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
MOZ_ASSERT(fpositer);
ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(fpositer);
RootedString overallResult(cx, PartitionNumberPattern(cx, nf, &x, fpositer));
if (!overallResult)
return false;
RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
if (!partsArray)
return false;
// First, vacuum up fields in the overall formatted string.
struct Field
{
uint32_t begin;
uint32_t end;
FieldType type;
// Needed for vector-resizing scratch space.
Field() = default;
Field(uint32_t begin, uint32_t end, FieldType type)
: begin(begin), end(end), type(type)
{}
};
using FieldsVector = Vector<Field, 16>;
FieldsVector fields(cx);
int32_t fieldInt, beginIndexInt, endIndexInt;
while ((fieldInt = ufieldpositer_next(fpositer, &beginIndexInt, &endIndexInt)) >= 0) {
MOZ_ASSERT(beginIndexInt >= 0);
MOZ_ASSERT(endIndexInt >= 0);
MOZ_ASSERT(beginIndexInt < endIndexInt,
"erm, aren't fields always non-empty?");
FieldType type = GetFieldTypeForNumberField(UNumberFormatFields(fieldInt), x);
if (!fields.emplaceBack(uint32_t(beginIndexInt), uint32_t(endIndexInt), type))
return false;
}
// Second, merge sort the fields vector. Expand the vector to have scratch
// space for performing the sort.
size_t fieldsLen = fields.length();
if (!fields.resizeUninitialized(fieldsLen * 2))
return false;
MOZ_ALWAYS_TRUE(MergeSort(fields.begin(), fieldsLen, fields.begin() + fieldsLen,
[](const Field& left, const Field& right,
bool* lessOrEqual)
{
// Sort first by begin index, then to place
// enclosing fields before nested fields.
*lessOrEqual = left.begin < right.begin ||
(left.begin == right.begin &&
left.end > right.end);
return true;
}));
// Deallocate the scratch space.
if (!fields.resize(fieldsLen))
return false;
// Third, iterate over the sorted field list to generate a sequence of
// parts (what ECMA-402 actually exposes). A part is a maximal character
// sequence entirely within no field or a single most-nested field.
//
// Diagrams may be helpful to illustrate how fields map to parts. Consider
// formatting -28,114,774,228,750.32, the US national surplus (negative
// because it's actually a debt) on March 31, 2021.
//
// var options =
// { style: "currency", currency: "USD", currencyDisplay: "name" };
// var usdFormatter = new Intl.NumberFormat("en-US", options);
// usdFormatter.format(-28114774228750.32);
//
// The formatted result is "-28,114,774,228,750.32 US dollars". ICU
// identifies these fields in the string:
//
// UNUM_GROUPING_SEPARATOR_FIELD
// |
// UNUM_SIGN_FIELD | UNUM_DECIMAL_SEPARATOR_FIELD
// | __________/| |
// | / | | | |
// "-28,114,774,228,750.32 US dollars"
// \________________/ |/ \_______/
// | | |
// UNUM_INTEGER_FIELD | UNUM_CURRENCY_FIELD
// |
// UNUM_FRACTION_FIELD
//
// These fields map to parts as follows:
//
// integer decimal
// _____|________ |
// / /| |\ |\ |\ | literal
// /| / | | \ | \ | \| |
// "-28,114,774,228,750.32 US dollars"
// | \___|___|___/ |/ \________/
// | | | |
// | group | currency
// | |
// minusSign fraction
//
// The sign is a part. Each comma is a part, splitting the integer field
// into parts for trillions/billions/&c. digits. The decimal point is a
// part. Cents are a part. The space between cents and currency is a part
// (outside any field). Last, the currency field is a part.
//
// Because parts fully partition the formatted string, we only track the
// end of each part -- the beginning is implicitly the last part's end.
struct Part
{
uint32_t end;
FieldType type;
};
class PartGenerator
{
// The fields in order from start to end, then least to most nested.
const FieldsVector& fields;
// Index of the current field, in |fields|, being considered to
// determine part boundaries. |lastEnd <= fields[index].begin| is an
// invariant.
size_t index;
// The end index of the last part produced, always less than or equal
// to |limit|, strictly increasing.
uint32_t lastEnd;
// The length of the overall formatted string.
const uint32_t limit;
Vector<size_t, 4> enclosingFields;
void popEnclosingFieldsEndingAt(uint32_t end) {
MOZ_ASSERT_IF(enclosingFields.length() > 0,
fields[enclosingFields.back()].end >= end);
while (enclosingFields.length() > 0 && fields[enclosingFields.back()].end == end)
enclosingFields.popBack();
}
bool nextPartInternal(Part* part) {
size_t len = fields.length();
MOZ_ASSERT(index <= len);
// If we're out of fields, all that remains are part(s) consisting
// of trailing portions of enclosing fields, and maybe a final
// literal part.
if (index == len) {
if (enclosingFields.length() > 0) {
const auto& enclosing = fields[enclosingFields.popCopy()];
part->end = enclosing.end;
part->type = enclosing.type;
// If additional enclosing fields end where this part ends,
// pop them as well.
popEnclosingFieldsEndingAt(part->end);
} else {
part->end = limit;
part->type = &JSAtomState::literal;
}
return true;
}
// Otherwise we still have a field to process.
const Field* current = &fields[index];
MOZ_ASSERT(lastEnd <= current->begin);
MOZ_ASSERT(current->begin < current->end);
// But first, deal with inter-field space.
if (lastEnd < current->begin) {
if (enclosingFields.length() > 0) {
// Space between fields, within an enclosing field, is part
// of that enclosing field, until the start of the current
// field or the end of the enclosing field, whichever is
// earlier.
const auto& enclosing = fields[enclosingFields.back()];
part->end = std::min(enclosing.end, current->begin);
part->type = enclosing.type;
popEnclosingFieldsEndingAt(part->end);
} else {
// If there's no enclosing field, the space is a literal.
part->end = current->begin;
part->type = &JSAtomState::literal;
}
return true;
}
// Otherwise, the part spans a prefix of the current field. Find
// the most-nested field containing that prefix.
const Field* next;
do {
current = &fields[index];
// If the current field is last, the part extends to its end.
if (++index == len) {
part->end = current->end;
part->type = current->type;
return true;
}
next = &fields[index];
MOZ_ASSERT(current->begin <= next->begin);
MOZ_ASSERT(current->begin < next->end);
// If the next field nests within the current field, push an
// enclosing field. (If there are no nested fields, don't
// bother pushing a field that'd be immediately popped.)
if (current->end > next->begin) {
if (!enclosingFields.append(index - 1))
return false;
}
// Do so until the next field begins after this one.
} while (current->begin == next->begin);
part->type = current->type;
if (current->end <= next->begin) {
// The next field begins after the current field ends. Therefore
// the current part ends at the end of the current field.
part->end = current->end;
popEnclosingFieldsEndingAt(part->end);
} else {
// The current field encloses the next one. The current part
// ends where the next field/part will start.
part->end = next->begin;
}
return true;
}
public:
PartGenerator(JSContext* cx, const FieldsVector& vec, uint32_t limit)
: fields(vec), index(0), lastEnd(0), limit(limit), enclosingFields(cx)
{}
bool nextPart(bool* hasPart, Part* part) {
// There are no parts left if we've partitioned the entire string.
if (lastEnd == limit) {
MOZ_ASSERT(enclosingFields.length() == 0);
*hasPart = false;
return true;
}
if (!nextPartInternal(part))
return false;
*hasPart = true;
lastEnd = part->end;
return true;
}
};
// Finally, generate the result array.
size_t lastEndIndex = 0;
uint32_t partIndex = 0;
RootedObject singlePart(cx);
RootedValue propVal(cx);
PartGenerator gen(cx, fields, overallResult->length());
do {
bool hasPart;
Part part;
if (!gen.nextPart(&hasPart, &part))
return false;
if (!hasPart)
break;
FieldType type = part.type;
size_t endIndex = part.end;
MOZ_ASSERT(lastEndIndex < endIndex);
singlePart = NewBuiltinClassInstance<PlainObject>(cx);
if (!singlePart)
return false;
propVal.setString(cx->names().*type);
if (!DefineProperty(cx, singlePart, cx->names().type, propVal))
return false;
JSLinearString* partSubstr =
NewDependentString(cx, overallResult, lastEndIndex, endIndex - lastEndIndex);
if (!partSubstr)
return false;
propVal.setString(partSubstr);
if (!DefineProperty(cx, singlePart, cx->names().value, propVal))
return false;
propVal.setObject(*singlePart);
if (!DefineElement(cx, partsArray, partIndex, propVal))
return false;
lastEndIndex = endIndex;
partIndex++;
} while (true);
MOZ_ASSERT(lastEndIndex == chars.length(),
"result array must partition the entire string");
result.setObject(*partsArray);
return true;
}
bool
js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(args[0].isObject());
MOZ_ASSERT(args[1].isNumber());
MOZ_ASSERT(args[2].isBoolean());
RootedObject numberFormat(cx, &args[0].toObject());
// 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.
nf = NewUNumberFormat(cx, numberFormat);
if (!nf)
return false;
}
// 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);
}
if (!isNumberFormatInstance)
unum_close(nf);
if (!success)
return false;
args.rval().set(result);
return true;
}

View file

@ -0,0 +1,89 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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_NumberFormat_h
#define builtin_intl_NumberFormat_h
#include "mozilla/Attributes.h"
#include <stdint.h>
#include "unicode/unum.h" // for UNumberFormat
#include "builtin/SelfHostingDefines.h"
#include "js/Class.h"
#include "vm/NativeObject.h"
namespace js {
class FreeOp;
class NumberFormatObject : public NativeObject
{
public:
static const Class class_;
static constexpr uint32_t INTERNALS_SLOT = 0;
static constexpr uint32_t UNUMBER_FORMAT_SLOT = 1;
static constexpr uint32_t SLOT_COUNT = 2;
static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
"INTERNALS_SLOT must match self-hosting define for internals object slot");
private:
static const ClassOps classOps_;
static void finalize(FreeOp* fop, JSObject* obj);
};
extern JSObject*
CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global);
/**
* Returns a new instance of the standard built-in NumberFormat constructor.
* Self-hosted code cannot cache this constructor (as it does for others in
* Utilities.js) because it is initialized after self-hosted code is compiled.
*
* Usage: numberFormat = intl_NumberFormat(locales, options)
*/
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
* default numbering system for the given locale.
*
* Usage: defaultNumberingSystem = intl_numberingSystem(locale)
*/
extern MOZ_MUST_USE bool
intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns a string representing the number x according to the effective
* locale and the formatting options of the given NumberFormat.
*
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
*
* Usage: formatted = intl_FormatNumber(numberFormat, x)
*/
extern MOZ_MUST_USE bool
intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool
intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result);
} // namespace js
#endif /* builtin_intl_NumberFormat_h */

View file

@ -0,0 +1,511 @@
/* 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/. */
/********** Intl.NumberFormat **********/
/**
* NumberFormat internal properties.
*
* Spec: ECMAScript Internationalization API Specification, 9.1 and 11.2.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"]
};
/**
* Compute an internal properties object from |lazyNumberFormatData|.
*/
function resolveNumberFormatInternals(lazyNumberFormatData) {
assert(IsObject(lazyNumberFormatData), "lazy data not an object?");
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.
var localeData = NumberFormat.localeData;
// Step 10.
var r = ResolveLocale(callFunction(NumberFormat.availableLocales, NumberFormat),
lazyNumberFormatData.requestedLocales,
lazyNumberFormatData.opt,
NumberFormat.relevantExtensionKeys,
localeData);
// Steps 11-12. (Step 13 is not relevant to our implementation.)
internalProps.locale = r.locale;
internalProps.numberingSystem = r.nu;
// Compute formatting options.
// Step 15.
var s = lazyNumberFormatData.style;
internalProps.style = s;
// Steps 19, 21.
if (s === "currency") {
internalProps.currency = lazyNumberFormatData.currency;
internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay;
}
internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits;
internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits;
internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits;
if ("minimumSignificantDigits" in lazyNumberFormatData) {
// Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the
// actual presence (versus undefined-ness) of these properties.
assert("maximumSignificantDigits" in lazyNumberFormatData, "min/max sig digits mismatch");
internalProps.minimumSignificantDigits = lazyNumberFormatData.minimumSignificantDigits;
internalProps.maximumSignificantDigits = lazyNumberFormatData.maximumSignificantDigits;
}
// Step 27.
internalProps.useGrouping = lazyNumberFormatData.useGrouping;
// Step 34.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
// object using |setInternalProperties|.
return internalProps;
}
/**
* Returns an object containing the NumberFormat internal properties of |obj|,
* or throws a TypeError if |obj| isn't NumberFormat-initialized.
*/
function getNumberFormatInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "NumberFormat", methodName);
assert(internals.type === "NumberFormat", "bad type escaped getIntlObjectInternals");
// If internal properties have already been computed, use them.
var internalProps = maybeInternalProperties(internals);
if (internalProps)
return internalProps;
// Otherwise it's time to fully create them.
internalProps = resolveNumberFormatInternals(internals.lazyData);
setInternalProperties(internals, internalProps);
return internalProps;
}
/**
* 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.
// Step 2-3.
assert(IsObject(options), "SetNumberFormatDigitOptions");
assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions");
// Steps 4-6.
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.
let mnsd = options.minimumSignificantDigits;
let mxsd = options.maximumSignificantDigits;
// Steps 9-11.
lazyData.minimumIntegerDigits = mnid;
lazyData.minimumFractionDigits = mnfd;
lazyData.maximumFractionDigits = mxfd;
// Step 12.
if (mnsd !== undefined || mxsd !== undefined) {
mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1);
mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
lazyData.minimumSignificantDigits = mnsd;
lazyData.maximumSignificantDigits = mxsd;
}
}
/**
* Convert s to upper case, but limited to characters a-z.
*
* Spec: ECMAScript Internationalization API Specification, 6.1.
*/
function toASCIIUpperCase(s) {
assert(typeof s === "string", "toASCIIUpperCase");
// String.prototype.toUpperCase may map non-ASCII characters into ASCII,
// so go character by character (actually code unit by code unit, but
// since we only care about ASCII characters here, that's OK).
var result = "";
for (var i = 0; i < s.length; i++) {
var c = callFunction(std_String_charCodeAt, s, i);
result += (0x61 <= c && c <= 0x7A)
? callFunction(std_String_fromCharCode, null, c & ~0x20)
: s[i];
}
return result;
}
/**
* Verifies that the given string is a well-formed ISO 4217 currency code.
*
* 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);
}
/**
* Initializes an object as a NumberFormat.
*
* This method is complicated a moderate bit by its implementing initialization
* as a *lazy* concept. Everything that must happen now, does -- but we defer
* all the work we can until the object is actually used as a NumberFormat.
* This later work occurs in |resolveNumberFormatInternals|; steps not noted
* here occur there.
*
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
*/
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);
// Lazy NumberFormat data has the following structure:
//
// {
// requestedLocales: List of locales,
// style: "decimal" / "percent" / "currency",
//
// // fields present only if style === "currency":
// currency: a well-formed currency code (IsWellFormedCurrencyCode),
// currencyDisplay: "code" / "symbol" / "name",
//
// opt: // opt object computed in InitializeNumberFormat
// {
// localeMatcher: "lookup" / "best fit",
// }
//
// minimumIntegerDigits: integer ∈ [1, 21],
// minimumFractionDigits: integer ∈ [0, 20],
// maximumFractionDigits: integer ∈ [0, 20],
//
// // optional
// minimumSignificantDigits: integer ∈ [1, 21],
// maximumSignificantDigits: integer ∈ [1, 21],
//
// useGrouping: true / false,
// }
//
// Note that lazy data is only installed as a final step of initialization,
// so every NumberFormat lazy data object has *all* these properties, never a
// subset of them.
var lazyNumberFormatData = std_Object_create(null);
// Step 3.
var requestedLocales = CanonicalizeLocaleList(locales);
lazyNumberFormatData.requestedLocales = requestedLocales;
// Steps 4-5.
//
// 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
// mucked with. (|options| is fully consumed in this method, so it's not a
// concern that Object.prototype might be touched between now and when
// |resolveNumberFormatInternals| is called.) For now just keep it simple.
if (options === undefined)
options = {};
else
options = ToObject(options);
// Compute options that impact interpretation of locale.
// Step 6.
var opt = new Record();
lazyNumberFormatData.opt = opt;
// Steps 7-8.
var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
// Compute formatting options.
// Step 14.
var s = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal");
lazyNumberFormatData.style = s;
// Steps 16-19.
var c = GetOption(options, "currency", "string", undefined, undefined);
if (c !== undefined && !IsWellFormedCurrencyCode(c))
ThrowRangeError(JSMSG_INVALID_CURRENCY_CODE, c);
var cDigits;
if (s === "currency") {
if (c === undefined)
ThrowTypeError(JSMSG_UNDEFINED_CURRENCY);
// Steps 19.a-c.
c = toASCIIUpperCase(c);
lazyNumberFormatData.currency = c;
cDigits = CurrencyDigits(c);
}
// Step 20.
var cd = GetOption(options, "currencyDisplay", "string", ["code", "symbol", "name"], "symbol");
if (s === "currency")
lazyNumberFormatData.currencyDisplay = cd;
// Steps 22-24.
SetNumberFormatDigitOptions(lazyNumberFormatData, options, s === "currency" ? cDigits: 0);
// Step 25.
if (lazyNumberFormatData.maximumFractionDigits === undefined) {
let mxfdDefault = s === "currency"
? cDigits
: s === "percent"
? 0
: 3;
lazyNumberFormatData.maximumFractionDigits =
std_Math_max(lazyNumberFormatData.minimumFractionDigits, mxfdDefault);
}
// Step 26.
var g = GetOption(options, "useGrouping", "boolean", undefined, true);
lazyNumberFormatData.useGrouping = g;
// Steps 35-36.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
setLazyData(internals, "NumberFormat", lazyNumberFormatData);
}
/**
* Mapping from currency codes to the number of decimal digits used for them.
* Default is 2 digits.
*
* Spec: ISO 4217 Currency and Funds Code List.
* http://www.currency-iso.org/en/home/tables/table-a1.html
*/
var currencyDigits = {
BHD: 3,
BIF: 0,
BYR: 0,
CLF: 4,
CLP: 0,
DJF: 0,
GNF: 0,
IQD: 3,
ISK: 0,
JOD: 3,
JPY: 0,
KMF: 0,
KRW: 0,
KWD: 3,
LYD: 3,
OMR: 3,
PYG: 0,
RWF: 0,
TND: 3,
UGX: 0,
UYI: 0,
VND: 0,
VUV: 0,
XAF: 0,
XOF: 0,
XPF: 0
};
/**
* Returns the number of decimal digits to be used for the given currency.
*
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
*/
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");
if (callFunction(std_Object_hasOwnProperty, currencyDigits, currency))
return currencyDigits[currency];
return 2;
}
/**
* Returns the subset of the given locale list for which this locale list has a
* 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.
*/
function Intl_NumberFormat_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
var availableLocales = callFunction(numberFormatInternalProperties.availableLocales,
numberFormatInternalProperties);
var requestedLocales = CanonicalizeLocaleList(locales);
return SupportedLocales(availableLocales, requestedLocales, options);
}
function getNumberingSystems(locale) {
// ICU doesn't have an API to determine the set of numbering systems
// supported for a locale; it generally pretends that any numbering system
// can be used with any locale. Supporting a decimal numbering system
// (where only the digits are replaced) is easy, so we offer them all here.
// 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
// 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.
// The one thing we can find out from ICU is the default numbering system
// for a locale.
var defaultNumberingSystem = intl_numberingSystem(locale);
return [
defaultNumberingSystem,
"arab", "arabext", "bali", "beng", "deva",
"fullwide", "gujr", "guru", "hanidec", "khmr",
"knda", "laoo", "latn", "limb", "mlym",
"mong", "mymr", "orya", "tamldec", "telu",
"thai", "tibt"
];
}
function numberFormatLocaleData(locale) {
return {
nu: getNumberingSystems(locale)
};
}
/**
* Function to be bound and returned by Intl.NumberFormat.prototype.format.
*
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
*/
function numberFormatFormatToBind(value) {
// Steps 1.a.i implemented by ECMAScript declaration binding instantiation,
// ES5.1 10.5, step 4.d.ii.
// Step 1.a.ii-iii.
var x = ToNumber(value);
return intl_FormatNumber(this, x, /* formatToParts = */ false);
}
/**
* Returns a function bound to this NumberFormat that returns a String 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.
*/
function Intl_NumberFormat_format_get() {
// Check "this NumberFormat object" per introduction of section 11.3.
var internals = getNumberFormatInternals(this, "format");
// Step 1.
if (internals.boundFormat === undefined) {
// Step 1.a.
var F = numberFormatFormatToBind;
// Step 1.b-d.
var bf = callFunction(FunctionBind, F, this);
internals.boundFormat = bf;
}
// Step 2.
return internals.boundFormat;
}
function Intl_NumberFormat_formatToParts(value) {
// Step 1.
var nf = this;
// Steps 2-3.
getNumberFormatInternals(nf, "formatToParts");
// Step 4.
var x = ToNumber(value);
// Step 5.
return intl_FormatNumber(nf, x, /* formatToParts = */ true);
}
/**
* Returns the resolved options for a NumberFormat object.
*
* Spec: ECMAScript Internationalization API Specification, 11.3.3 and 11.4.
*/
function Intl_NumberFormat_resolvedOptions() {
// Check "this NumberFormat object" per introduction of section 11.3.
var internals = getNumberFormatInternals(this, "resolvedOptions");
var result = {
locale: internals.locale,
numberingSystem: internals.numberingSystem,
style: internals.style,
minimumIntegerDigits: internals.minimumIntegerDigits,
minimumFractionDigits: internals.minimumFractionDigits,
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]);
}
return result;
}

View file

@ -0,0 +1,473 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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.PluralRules proposal. */
#include "builtin/intl/PluralRules.h"
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "jscntxt.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/ScopedICUObject.h"
#include "vm/GlobalObject.h"
#include "vm/String.h"
#include "jsobjinlines.h"
#include "vm/NativeObject-inl.h"
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;
/**************** PluralRules *****************/
const ClassOps PluralRulesObject::classOps_ = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
PluralRulesObject::finalize
};
const Class PluralRulesObject::class_ = {
js_Object_str,
JSCLASS_HAS_RESERVED_SLOTS(PluralRulesObject::SLOT_COUNT) |
JSCLASS_FOREGROUND_FINALIZE,
&PluralRulesObject::classOps_
};
#if JS_HAS_TOSOURCE
static bool
pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().PluralRules);
return true;
}
#endif
static const JSFunctionSpec pluralRules_static_methods[] = {
JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_PluralRules_supportedLocalesOf", 1, 0),
JS_FS_END
};
static const JSFunctionSpec pluralRules_methods[] = {
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_PluralRules_resolvedOptions", 0, 0),
JS_SELF_HOSTED_FN("select", "Intl_PluralRules_select", 1, 0),
#if JS_HAS_TOSOURCE
JS_FN(js_toSource_str, pluralRules_toSource, 0, 0),
#endif
JS_FS_END
};
/**
* PluralRules constructor.
* Spec: ECMAScript 402 API, PluralRules, 1.1
*/
static bool
PluralRules(JSContext* cx, const CallArgs& args, bool construct)
{
RootedObject obj(cx);
if (!construct) {
JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
if (!intl)
return false;
RootedValue self(cx, args.thisv());
if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
obj = ToObject(cx, self);
if (!obj)
return false;
bool extensible;
if (!IsExtensible(cx, obj, &extensible))
return false;
if (!extensible)
return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
} else {
construct = true;
}
}
if (construct) {
RootedObject proto(cx, GlobalObject::getOrCreatePluralRulesPrototype(cx, cx->global()));
if (!proto)
return false;
obj = NewObjectWithGivenProto<PluralRulesObject>(cx, proto);
if (!obj)
return false;
obj->as<NativeObject>().setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue());
obj->as<NativeObject>().setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
}
RootedValue locales(cx, args.get(0));
RootedValue options(cx, args.get(1));
if (!intl::InitializeObject(cx, obj, cx->names().InitializePluralRules, locales, options))
return false;
args.rval().setObject(*obj);
return true;
}
static bool
PluralRules(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return PluralRules(cx, args, args.isConstructing());
}
bool
js::intl_PluralRules(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
return PluralRules(cx, args, true);
}
void
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);
}
}
JSObject*
js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
{
RootedFunction ctor(cx);
ctor = global->createConstructor(cx, &PluralRules, cx->names().PluralRules, 0);
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &PluralRulesObject::class_));
if (!proto)
return nullptr;
proto->setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
if (!JS_DefineFunctions(cx, ctor, pluralRules_static_methods))
return nullptr;
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;
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
* properties for PluralRules.
*
* This is similar to NewUNumberFormat but doesn't allow for currency or
* percent types.
*
*/
static UNumberFormat*
NewUNumberFormatForPluralRules(JSContext* cx, HandleObject pluralRules)
{
RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
if (!internals)
return nullptr;
RootedValue value(cx);
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
return nullptr;
JSAutoByteString locale(cx, value.toString());
if (!locale)
return nullptr;
uint32_t uMinimumIntegerDigits = 1;
uint32_t uMinimumFractionDigits = 0;
uint32_t uMaximumFractionDigits = 3;
int32_t uMinimumSignificantDigits = -1;
int32_t uMaximumSignificantDigits = -1;
RootedId id(cx, NameToId(cx->names().minimumSignificantDigits));
bool hasP;
if (!HasProperty(cx, internals, id, &hasP))
return nullptr;
if (hasP) {
if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
&value))
return nullptr;
uMinimumSignificantDigits = value.toInt32();
if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
&value))
return nullptr;
uMaximumSignificantDigits = value.toInt32();
} else {
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
&value))
return nullptr;
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
&value))
return nullptr;
uMinimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
&value))
return nullptr;
uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
}
UErrorCode status = U_ZERO_ERROR;
UNumberFormat* nf = unum_open(UNUM_DECIMAL, nullptr, 0, IcuLocale(locale.ptr()), nullptr, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return nullptr;
}
ScopedICUObject<UNumberFormat, unum_close> toClose(nf);
if (uMinimumSignificantDigits != -1) {
unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true);
unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits);
unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits);
} else {
unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits);
unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits);
unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits);
}
return toClose.forget();
}
bool
js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject pluralRules(cx, &args[0].toObject());
UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
if (!nf)
return false;
ScopedICUObject<UNumberFormat, unum_close> closeNumberFormat(nf);
RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
if (!internals)
return false;
RootedValue value(cx);
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
return false;
JSAutoByteString locale(cx, value.toString());
if (!locale)
return false;
if (!GetProperty(cx, internals, internals, cx->names().type, &value))
return false;
JSAutoByteString type(cx, value.toString());
if (!type)
return false;
double x = args[1].toNumber();
// We need a NumberFormat in order to format the number
// using the number formatting options (minimum/maximum*Digits)
// before we push the result to PluralRules
//
// This should be fixed in ICU 59 and we'll be able to switch to that
// API: http://bugs.icu-project.org/trac/ticket/12763
//
RootedValue fmtNumValue(cx);
if (!intl_FormatNumber(cx, nf, x, &fmtNumValue))
return false;
RootedString fmtNumValueString(cx, fmtNumValue.toString());
AutoStableStringChars stableChars(cx);
if (!stableChars.initTwoByte(cx, fmtNumValueString))
return false;
const UChar* uFmtNumValue = Char16ToUChar(stableChars.twoByteRange().begin().get());
UErrorCode status = U_ZERO_ERROR;
UFormattable* fmt = unum_parseToUFormattable(nf, nullptr, uFmtNumValue,
stableChars.twoByteRange().length(), 0, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UFormattable, ufmt_close> closeUFormattable(fmt);
double y = ufmt_getDouble(fmt, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
UPluralType category;
if (StringsAreEqual(type, "cardinal")) {
category = UPLURAL_TYPE_CARDINAL;
} else {
MOZ_ASSERT(StringsAreEqual(type, "ordinal"));
category = UPLURAL_TYPE_ORDINAL;
}
UPluralRules* pr = uplrules_openForType(IcuLocale(locale.ptr()), category, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
JSString* str = CallICU(cx, [pr, y](UChar* chars, int32_t size, UErrorCode* status) {
return uplrules_select(pr, y, chars, size, status);
});
if (!str)
return false;
args.rval().setString(str);
return true;
}
bool
js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
JSAutoByteString locale(cx, args[0].toString());
if (!locale)
return false;
JSAutoByteString type(cx, args[1].toString());
if (!type)
return false;
UErrorCode status = U_ZERO_ERROR;
UPluralType category;
if (StringsAreEqual(type, "cardinal")) {
category = UPLURAL_TYPE_CARDINAL;
} else {
MOZ_ASSERT(StringsAreEqual(type, "ordinal"));
category = UPLURAL_TYPE_ORDINAL;
}
UPluralRules* pr = uplrules_openForType(
IcuLocale(locale.ptr()),
category,
&status
);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
// We should get a C API for that in ICU 59 and switch to it
// https://ssl.icu-project.org/trac/ticket/12772
//
icu::StringEnumeration* kwenum =
reinterpret_cast<icu::PluralRules*>(pr)->getKeywords(status);
UEnumeration* ue = uenum_openFromStringEnumeration(kwenum, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UEnumeration, uenum_close> closeEnum(ue);
RootedObject res(cx, NewDenseEmptyArray(cx));
if (!res)
return false;
RootedValue element(cx);
uint32_t i = 0;
int32_t catSize;
const char* cat;
do {
cat = uenum_next(ue, &catSize, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
if (!cat)
break;
JSString* str = NewStringCopyN<CanGC>(cx, cat, catSize);
if (!str)
return false;
element.setString(str);
if (!DefineElement(cx, res, i, element))
return false;
i++;
} while (true);
args.rval().setObject(*res);
return true;
}

View file

@ -0,0 +1,91 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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_PluralRules_h
#define builtin_intl_PluralRules_h
#include "mozilla/Attributes.h"
#include "builtin/SelfHostingDefines.h"
#include "js/Class.h"
#include "js/RootingAPI.h"
#include "vm/NativeObject.h"
namespace js {
class FreeOp;
class PluralRulesObject : public NativeObject
{
public:
static const Class class_;
static constexpr uint32_t INTERNALS_SLOT = 0;
static constexpr uint32_t UPLURAL_RULES_SLOT = 1;
static constexpr uint32_t SLOT_COUNT = 2;
static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
"INTERNALS_SLOT must match self-hosting define for internals object slot");
private:
static const ClassOps classOps_;
static void finalize(FreeOp* fop, JSObject* obj);
};
extern JSObject*
CreatePluralRulesPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
JS::Handle<GlobalObject*> global);
/**
* Returns a new PluralRules instance.
* Self-hosted code cannot cache this constructor (as it does for others in
* Utilities.js) because it is initialized after self-hosted code is compiled.
*
* Usage: pluralRules = intl_PluralRules(locales, options)
*/
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.
*
* A plural rule is a grammatical category that expresses count distinctions
* (such as "one", "two", "few" etc.).
*
* Usage: rule = intl_SelectPluralRule(pluralRules, x)
*/
extern MOZ_MUST_USE bool
intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an array of plural rules categories for a given
* locale and type.
*
* Usage: categories = intl_GetPluralCategories(locale, type)
*
* Example:
*
* intl_getPluralCategories('pl', 'cardinal'); // ['one', 'few', 'many', 'other']
*/
extern MOZ_MUST_USE bool
intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp);
} // namespace js
#endif /* builtin_intl_PluralRules_h */

View file

@ -0,0 +1,231 @@
/* 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/. */
/********** Intl.PluralRules **********/
/**
* PluralRules internal properties.
*
* Spec: ECMAScript 402 API, PluralRules, 1.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);
}
};
/**
* Compute an internal properties object from |lazyPluralRulesData|.
*/
function resolvePluralRulesInternals(lazyPluralRulesData) {
assert(IsObject(lazyPluralRulesData), "lazy data not an object?");
var internalProps = std_Object_create(null);
var requestedLocales = lazyPluralRulesData.requestedLocales;
var PluralRules = pluralRulesInternalProperties;
// Step 13.
const r = ResolveLocale(callFunction(PluralRules.availableLocales, PluralRules),
lazyPluralRulesData.requestedLocales,
lazyPluralRulesData.opt,
noRelevantExtensionKeys, undefined);
// Step 14.
internalProps.locale = r.locale;
internalProps.type = lazyPluralRulesData.type;
internalProps.pluralCategories = intl_GetPluralCategories(
internalProps.locale,
internalProps.type);
internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits;
internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits;
internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits;
if ("minimumSignificantDigits" in lazyPluralRulesData) {
assert("maximumSignificantDigits" in lazyPluralRulesData, "min/max sig digits mismatch");
internalProps.minimumSignificantDigits = lazyPluralRulesData.minimumSignificantDigits;
internalProps.maximumSignificantDigits = lazyPluralRulesData.maximumSignificantDigits;
}
return internalProps;
}
/**
* Returns an object containing the PluralRules internal properties of |obj|,
* or throws a TypeError if |obj| isn't PluralRules-initialized.
*/
function getPluralRulesInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "PluralRules", methodName);
assert(internals.type === "PluralRules", "bad type escaped getIntlObjectInternals");
var internalProps = maybeInternalProperties(internals);
if (internalProps)
return internalProps;
internalProps = resolvePluralRulesInternals(internals.lazyData);
setInternalProperties(internals, internalProps);
return internalProps;
}
/**
* Initializes an object as a PluralRules.
*
* This method is complicated a moderate bit by its implementing initialization
* as a *lazy* concept. Everything that must happen now, does -- but we defer
* all the work we can until the object is actually used as a PluralRules.
* This later work occurs in |resolvePluralRulesInternals|; steps not noted
* here occur there.
*
* Spec: ECMAScript 402 API, PluralRules, 1.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);
// Lazy PluralRules data has the following structure:
//
// {
// requestedLocales: List of locales,
// type: "cardinal" / "ordinal",
//
// opt: // opt object computer in InitializePluralRules
// {
// localeMatcher: "lookup" / "best fit",
// }
//
// minimumIntegerDigits: integer ∈ [1, 21],
// minimumFractionDigits: integer ∈ [0, 20],
// maximumFractionDigits: integer ∈ [0, 20],
//
// // optional
// minimumSignificantDigits: integer ∈ [1, 21],
// maximumSignificantDigits: integer ∈ [1, 21],
// }
//
// Note that lazy data is only installed as a final step of initialization,
// so every PluralRules lazy data object has *all* these properties, never a
// subset of them.
const lazyPluralRulesData = std_Object_create(null);
// Step 3.
let requestedLocales = CanonicalizeLocaleList(locales);
lazyPluralRulesData.requestedLocales = requestedLocales;
// Steps 4-5.
if (options === undefined)
options = {};
else
options = ToObject(options);
// Step 6.
const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal");
lazyPluralRulesData.type = type;
// Step 8.
let opt = new Record();
lazyPluralRulesData.opt = opt;
// Steps 9-10.
let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
// Step 11.
SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0);
// Step 12.
if (lazyPluralRulesData.maximumFractionDigits === undefined) {
lazyPluralRulesData.maximumFractionDigits =
std_Math_max(lazyPluralRulesData.minimumFractionDigits, 3);
}
setLazyData(internals, "PluralRules", lazyPluralRulesData)
}
/**
* Returns the subset of the given locale list for which this locale list has a
* 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.
*/
function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(pluralRulesInternalProperties.availableLocales,
pluralRulesInternalProperties);
// Step 2.
let requestedLocales = CanonicalizeLocaleList(locales);
// Step 3.
return SupportedLocales(availableLocales, requestedLocales, options);
}
/**
* Returns a String value representing the plural category matching
* 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.
*/
function Intl_PluralRules_select(value) {
// Step 1.
let pluralRules = this;
// Step 2.
let internals = getPluralRulesInternals(pluralRules, "select");
// Steps 3-4.
let n = ToNumber(value);
// Step 5.
return intl_SelectPluralRule(pluralRules, n);
}
/**
* Returns the resolved options for a PluralRules object.
*
* Spec: ECMAScript 402 API, PluralRules, 1.4.4.
*/
function Intl_PluralRules_resolvedOptions() {
var internals = getPluralRulesInternals(this, "resolvedOptions");
var result = {
locale: internals.locale,
type: internals.type,
pluralCategories: callFunction(std_Array_slice, internals.pluralCategories, 0),
minimumIntegerDigits: internals.minimumIntegerDigits,
minimumFractionDigits: internals.minimumFractionDigits,
maximumFractionDigits: internals.maximumFractionDigits,
};
var optionalProperties = [
"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]);
}
return result;
}

View file

@ -0,0 +1,321 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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.RelativeTimeFormat proposal. */
#include "builtin/intl/RelativeTimeFormat.h"
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "jscntxt.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/ScopedICUObject.h"
#include "vm/GlobalObject.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using mozilla::IsNegativeZero;
using mozilla::Range;
using mozilla::RangedPtr;
using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::StringsAreEqual;
/**************** RelativeTimeFormat *****************/
const ClassOps RelativeTimeFormatObject::classOps_ = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* enumerate */
nullptr, /* newEnumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
RelativeTimeFormatObject::finalize
};
const Class RelativeTimeFormatObject::class_ = {
js_Object_str,
JSCLASS_HAS_RESERVED_SLOTS(RelativeTimeFormatObject::SLOT_COUNT) |
JSCLASS_FOREGROUND_FINALIZE,
&RelativeTimeFormatObject::classOps_
};
#if JS_HAS_TOSOURCE
static bool
relativeTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().RelativeTimeFormat);
return true;
}
#endif
static const JSFunctionSpec relativeTimeFormat_static_methods[] = {
JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_RelativeTimeFormat_supportedLocalesOf", 1, 0),
JS_FS_END
};
static const JSFunctionSpec relativeTimeFormat_methods[] = {
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_RelativeTimeFormat_resolvedOptions", 0, 0),
JS_SELF_HOSTED_FN("format", "Intl_RelativeTimeFormat_format", 2, 0),
#if JS_HAS_TOSOURCE
JS_FN(js_toSource_str, relativeTimeFormat_toSource, 0, 0),
#endif
JS_FS_END
};
static const JSPropertySpec relativeTimeFormat_properties[] = {
JS_STRING_SYM_PS(toStringTag, "Intl.RelativeTimeFormat", JSPROP_READONLY),
JS_PS_END};
/**
* RelativeTimeFormat constructor.
* Spec: ECMAScript 402 API, RelativeTimeFormat, 1.1
*/
static bool
RelativeTimeFormat(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
if (!ThrowIfNotConstructing(cx, args, "Intl.RelativeTimeFormat"))
return false;
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
if (!proto) {
proto = GlobalObject::getOrCreateRelativeTimeFormatPrototype(cx, cx->global());
if (!proto)
return false;
}
RootedObject relativeTimeFormat(cx);
relativeTimeFormat = NewObjectWithGivenProto<RelativeTimeFormatObject>(cx, proto);
if (!relativeTimeFormat)
return false;
relativeTimeFormat->as<NativeObject>().setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue());
relativeTimeFormat->as<NativeObject>().setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr));
RootedValue locales(cx, args.get(0));
RootedValue options(cx, args.get(1));
// Step 3.
if (!intl::InitializeObject(cx, relativeTimeFormat, cx->names().InitializeRelativeTimeFormat, locales, options))
return false;
args.rval().setObject(*relativeTimeFormat);
return true;
}
void
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);
}
}
JSObject*
js::CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
{
RootedFunction ctor(cx);
ctor = global->createConstructor(cx, &RelativeTimeFormat, cx->names().RelativeTimeFormat, 0);
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &RelativeTimeFormatObject::class_));
if (!proto)
return nullptr;
proto->setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
if (!JS_DefineFunctions(cx, ctor, relativeTimeFormat_static_methods))
return nullptr;
if (!JS_DefineFunctions(cx, proto, relativeTimeFormat_methods))
return nullptr;
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;
}
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
{
/**
* Only strings with numeric components like `1 day ago`.
*/
Always,
/**
* Natural-language strings like `yesterday` when possible,
* otherwise strings with numeric components as in `7 months ago`.
*/
Auto,
};
bool
js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
RootedObject relativeTimeFormat(cx, &args[0].toObject());
RootedObject internals(cx, intl::GetInternalsObject(cx, relativeTimeFormat));
if (!internals)
return false;
RootedValue value(cx);
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
return false;
JSAutoByteString locale(cx, value.toString());
if (!locale)
return false;
if (!GetProperty(cx, internals, internals, cx->names().style, &value))
return false;
RootedLinearString style(cx, value.toString()->ensureLinear(cx));
if (!style)
return false;
double t = args[1].toNumber();
UDateRelativeDateTimeFormatterStyle relDateTimeStyle;
if (StringEqualsAscii(style, "short")) {
relDateTimeStyle = UDAT_STYLE_SHORT;
} else if (StringEqualsAscii(style, "narrow")) {
relDateTimeStyle = UDAT_STYLE_NARROW;
} else {
MOZ_ASSERT(StringEqualsAscii(style, "long"));
relDateTimeStyle = UDAT_STYLE_LONG;
}
URelativeDateTimeUnit relDateTimeUnit;
{
JSLinearString* unit = args[2].toString()->ensureLinear(cx);
if (!unit) {
return false;
}
if (StringEqualsAscii(unit, "second") || StringEqualsAscii(unit, "seconds")) {
relDateTimeUnit = UDAT_REL_UNIT_SECOND;
} else if (StringEqualsAscii(unit, "minute") || StringEqualsAscii(unit, "minutes")) {
relDateTimeUnit = UDAT_REL_UNIT_MINUTE;
} else if (StringEqualsAscii(unit, "hour") || StringEqualsAscii(unit, "hours")) {
relDateTimeUnit = UDAT_REL_UNIT_HOUR;
} else if (StringEqualsAscii(unit, "day") || StringEqualsAscii(unit, "days")) {
relDateTimeUnit = UDAT_REL_UNIT_DAY;
} else if (StringEqualsAscii(unit, "week") || StringEqualsAscii(unit, "weeks")) {
relDateTimeUnit = UDAT_REL_UNIT_WEEK;
} else if (StringEqualsAscii(unit, "month") || StringEqualsAscii(unit, "months")) {
relDateTimeUnit = UDAT_REL_UNIT_MONTH;
} else if (StringEqualsAscii(unit, "quarter") || StringEqualsAscii(unit, "quarters")) {
relDateTimeUnit = UDAT_REL_UNIT_QUARTER;
} else {
MOZ_ASSERT(StringEqualsAscii(unit, "year") || StringEqualsAscii(unit, "years"));
relDateTimeUnit = UDAT_REL_UNIT_YEAR;
}
}
if (!GetProperty(cx, internals, internals, cx->names().numeric, &value))
return false;
RootedLinearString numeric(cx, value.toString()->ensureLinear(cx));
if (!numeric)
return false;
RelativeTimeNumeric relDateTimeNumeric;
if (StringEqualsAscii(numeric, "auto")) {
relDateTimeNumeric = RelativeTimeNumeric::Auto;
} else {
MOZ_ASSERT(StringEqualsAscii(numeric, "always"));
relDateTimeNumeric = RelativeTimeNumeric::Always;
}
Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
return false;
UErrorCode status = U_ZERO_ERROR;
URelativeDateTimeFormatter* rtf =
ureldatefmt_open(IcuLocale(locale.ptr()), nullptr, relDateTimeStyle,
UDISPCTX_CAPITALIZATION_FOR_STANDALONE, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<URelativeDateTimeFormatter, ureldatefmt_close> closeRelativeTimeFormat(rtf);
JSString* str =
CallICU(cx, [rtf, t, relDateTimeUnit, relDateTimeNumeric](UChar* chars, int32_t size,
UErrorCode* status)
{
auto fmt = relDateTimeNumeric == RelativeTimeNumeric::Auto
? ureldatefmt_format
: ureldatefmt_formatNumeric;
return fmt(rtf, t, relDateTimeUnit, chars, size, status);
});
if (!str)
return false;
args.rval().setString(str);
return true;
}

View file

@ -0,0 +1,68 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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_RelativeTimeFormat_h
#define builtin_intl_RelativeTimeFormat_h
#include "mozilla/Attributes.h"
#include <stdint.h>
#include "builtin/SelfHostingDefines.h"
#include "js/Class.h"
#include "vm/NativeObject.h"
namespace js {
class FreeOp;
class RelativeTimeFormatObject : public NativeObject
{
public:
static const Class class_;
static constexpr uint32_t INTERNALS_SLOT = 0;
static constexpr uint32_t URELATIVE_TIME_FORMAT_SLOT = 1;
static constexpr uint32_t SLOT_COUNT = 2;
static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
"INTERNALS_SLOT must match self-hosting define for internals object slot");
private:
static const ClassOps classOps_;
static void finalize(FreeOp* fop, JSObject* obj);
};
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.
*
* t should be a number representing a number to be formatted.
* unit should be "second", "minute", "hour", "day", "week", "month", "quarter", or "year".
*
* Usage: formatted = intl_FormatRelativeTime(relativeTimeFormat, t, unit)
*/
extern MOZ_MUST_USE bool
intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp);
} // namespace js
#endif /* builtin_intl_RelativeTimeFormat_h */

View file

@ -0,0 +1,242 @@
/* 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/. */
/********** Intl.RelativeTimeFormat **********/
/**
* RelativeTimeFormat internal properties.
*
* Spec: ECMAScript 402 API, RelativeTimeFormat, 1.3.3.
*/
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: [],
};
function relativeTimeFormatLocaleData() {
// RelativeTimeFormat doesn't support any extension keys.
return {};
}
/**
* Compute an internal properties object from |lazyRelativeTimeFormatData|.
*/
function resolveRelativeTimeFormatInternals(lazyRelativeTimeFormatData) {
assert(IsObject(lazyRelativeTimeFormatData), "lazy data not an object?");
var internalProps = std_Object_create(null);
var RelativeTimeFormat = relativeTimeFormatInternalProperties;
// Steps 7-8.
const r = ResolveLocale(callFunction(RelativeTimeFormat.availableLocales, RelativeTimeFormat),
lazyRelativeTimeFormatData.requestedLocales,
lazyRelativeTimeFormatData.opt,
RelativeTimeFormat.relevantExtensionKeys,
RelativeTimeFormat.localeData);
// Step 9-10.
internalProps.locale = r.locale;
// Step 11.
assert(r.locale === r.dataLocale,
"resolved locale matches the resolved data-locale when no extension-keys are present");
// Step 13.
internalProps.style = lazyRelativeTimeFormatData.style;
// Step 15.
internalProps.numeric = lazyRelativeTimeFormatData.numeric;
// Steps 16-20 (Not relevant in our implementation).
return internalProps;
}
/**
* 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);
assert(internals.type === "RelativeTimeFormat", "bad type escaped getIntlObjectInternals");
var internalProps = maybeInternalProperties(internals);
if (internalProps)
return internalProps;
internalProps = resolveRelativeTimeFormatInternals(internals.lazyData);
setInternalProperties(internals, internalProps);
return internalProps;
}
/**
* Initializes an object as a RelativeTimeFormat.
*
* This method is complicated a moderate bit by its implementing initialization
* as a *lazy* concept. Everything that must happen now, does -- but we defer
* all the work we can until the object is actually used as a RelativeTimeFormat.
* This later work occurs in |resolveRelativeTimeFormatInternals|; steps not noted
* here occur there.
*
* 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);
// Lazy RelativeTimeFormat data has the following structure:
//
// {
// requestedLocales: List of locales,
// style: "long" / "short" / "narrow",
// numeric: "always" / "auto",
//
// opt: // opt object computed in InitializeRelativeTimeFormat
// {
// localeMatcher: "lookup" / "best fit",
// }
// }
//
// Note that lazy data is only installed as a final step of initialization,
// so every RelativeTimeFormat lazy data object has *all* these properties, never a
// subset of them.
const lazyRelativeTimeFormatData = std_Object_create(null);
// Step 1.
let requestedLocales = CanonicalizeLocaleList(locales);
lazyRelativeTimeFormatData.requestedLocales = requestedLocales;
// Steps 2-3.
if (options === undefined)
options = std_Object_create(null);
else
options = ToObject(options);
// Step 4.
let opt = new Record();
// Steps 5-6.
let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
lazyRelativeTimeFormatData.opt = opt;
// Steps 12-13.
const style = GetOption(options, "style", "string", ["long", "short", "narrow"], "long");
lazyRelativeTimeFormatData.style = style;
// Steps 14-15.
const numeric = GetOption(options, "numeric", "string", ["always", "auto"], "always");
lazyRelativeTimeFormatData.numeric = numeric;
setLazyData(internals, "RelativeTimeFormat", lazyRelativeTimeFormatData)
}
/**
* Returns the subset of the given locale list for which this locale list has a
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript 402 API, RelativeTimeFormat, 1.3.2.
*/
function Intl_RelativeTimeFormat_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(relativeTimeFormatInternalProperties.availableLocales,
relativeTimeFormatInternalProperties);
// Step 2.
let requestedLocales = CanonicalizeLocaleList(locales);
// Step 3.
return SupportedLocales(availableLocales, requestedLocales, options);
}
/**
* Returns a String value representing the written form of a relative date
* formatted according to the effective locale and the formatting options
* of this RelativeTimeFormat object.
*
* Spec: ECMAScript 402 API, RelativeTImeFormat, 1.4.3.
*/
function Intl_RelativeTimeFormat_format(value, unit) {
// Step 1.
let relativeTimeFormat = this;
// Step 2.
let internals = getRelativeTimeFormatInternals(relativeTimeFormat, "format");
// Step 3.
let t = ToNumber(value);
// Step 4.
let u = ToString(unit);
// PartitionRelativeTimePattern, step 4.
if (!Number_isFinite(t)) {
ThrowRangeError(JSMSG_DATE_NOT_FINITE, "RelativeTimeFormat");
}
// PartitionRelativeTimePattern, step 5.
switch (u) {
case "second":
case "seconds":
case "minute":
case "minutes":
case "hour":
case "hours":
case "day":
case "days":
case "week":
case "weeks":
case "month":
case "months":
case "quarter":
case "quarters":
case "year":
case "years":
break;
default:
ThrowRangeError(JSMSG_INVALID_OPTION_VALUE, "unit", u);
}
// Step 5.
return intl_FormatRelativeTime(relativeTimeFormat, t, u, internals.numeric);
}
/**
* Returns the resolved options for a PluralRules object.
*
* Spec: ECMAScript 402 API, RelativeTimeFormat, 1.4.4.
*/
function Intl_RelativeTimeFormat_resolvedOptions() {
var internals = getRelativeTimeFormatInternals(this, "resolvedOptions");
// Steps 4-5.
var result = {
locale: internals.locale,
style: internals.style,
numeric: internals.numeric,
};
// Step 6.
return result;
}

View file

@ -0,0 +1,44 @@
/* -*- 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/. */
#ifndef builtin_intl_ScopedICUObject_h
#define builtin_intl_ScopedICUObject_h
/*
* A simple RAII class to assure ICU objects are automatically deallocated at
* scope end. Unfortunately, ICU's C++ API is uniformly unstable, so we can't
* use its smart pointers for this.
*/
namespace js {
template <typename T, void (Delete)(T*)>
class ScopedICUObject
{
T* ptr_;
public:
explicit ScopedICUObject(T* ptr)
: ptr_(ptr)
{}
~ScopedICUObject() {
if (ptr_)
Delete(ptr_);
}
// In cases where an object should be deleted on abnormal exits,
// but returned to the caller if everything goes well, call forget()
// to transfer the object just before returning.
T* forget() {
T* tmp = ptr_;
ptr_ = nullptr;
return tmp;
}
};
} // namespace js
#endif /* builtin_intl_ScopedICUObject_h */

View file

@ -0,0 +1,300 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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/. */
/* Runtime-wide Intl data shared across compartments. */
#include "builtin/intl/SharedIntlData.h"
#include "mozilla/Assertions.h"
#include "mozilla/HashFunctions.h"
#include <stdint.h>
#include "jsatom.h"
#include "jsstr.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/intl/TimeZoneDataGenerated.h"
#include "js/Utility.h"
using js::HashNumber;
using js::intl::StringsAreEqual;
template<typename Char>
static constexpr Char
ToUpperASCII(Char c)
{
return ('a' <= c && c <= 'z')
? (c & ~0x20)
: c;
}
static_assert(ToUpperASCII('a') == 'A', "verifying 'a' uppercases correctly");
static_assert(ToUpperASCII('m') == 'M', "verifying 'm' uppercases correctly");
static_assert(ToUpperASCII('z') == 'Z', "verifying 'z' uppercases correctly");
static_assert(ToUpperASCII(u'a') == u'A', "verifying u'a' uppercases correctly");
static_assert(ToUpperASCII(u'k') == u'K', "verifying u'k' uppercases correctly");
static_assert(ToUpperASCII(u'z') == u'Z', "verifying u'z' uppercases correctly");
template<typename Char>
static HashNumber
HashStringIgnoreCaseASCII(const Char* s, size_t length)
{
uint32_t hash = 0;
for (size_t i = 0; i < length; i++)
hash = mozilla::AddToHash(hash, ToUpperASCII(s[i]));
return hash;
}
template<typename Char1, typename Char2>
static bool
EqualCharsIgnoreCaseASCII(const Char1* s1, const Char2* s2, size_t len)
{
for (const Char1* s1end = s1 + len; s1 < s1end; s1++, s2++) {
if (ToUpperASCII(*s1) != ToUpperASCII(*s2))
return false;
}
return true;
}
js::intl::SharedIntlData::TimeZoneHasher::Lookup::Lookup(JSFlatString* timeZone)
: isLatin1(timeZone->hasLatin1Chars()), length(timeZone->length())
{
if (isLatin1) {
latin1Chars = timeZone->latin1Chars(nogc);
hash = HashStringIgnoreCaseASCII(latin1Chars, length);
} else {
twoByteChars = timeZone->twoByteChars(nogc);
hash = HashStringIgnoreCaseASCII(twoByteChars, length);
}
}
bool
js::intl::SharedIntlData::TimeZoneHasher::match(TimeZoneName key, const Lookup& lookup)
{
if (key->length() != lookup.length)
return false;
// Compare time zone names ignoring ASCII case differences.
if (key->hasLatin1Chars()) {
const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
if (lookup.isLatin1)
return EqualCharsIgnoreCaseASCII(keyChars, lookup.latin1Chars, lookup.length);
return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
}
const char16_t* keyChars = key->twoByteChars(lookup.nogc);
if (lookup.isLatin1)
return EqualCharsIgnoreCaseASCII(lookup.latin1Chars, keyChars, lookup.length);
return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
}
static bool
IsLegacyICUTimeZone(const char* timeZone)
{
for (const auto& legacyTimeZone : js::timezone::legacyICUTimeZones) {
if (StringsAreEqual(timeZone, legacyTimeZone))
return true;
}
return false;
}
bool
js::intl::SharedIntlData::ensureTimeZones(JSContext* cx)
{
if (timeZoneDataInitialized)
return true;
// If initTimeZones() was called previously, but didn't complete due to
// OOM, clear all sets/maps and start from scratch.
if (availableTimeZones.initialized())
availableTimeZones.finish();
if (!availableTimeZones.init()) {
ReportOutOfMemory(cx);
return false;
}
UErrorCode status = U_ZERO_ERROR;
UEnumeration* values = ucal_openTimeZones(&status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UEnumeration, uenum_close> toClose(values);
RootedAtom timeZone(cx);
while (true) {
int32_t size;
const char* rawTimeZone = uenum_next(values, &size, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
if (rawTimeZone == nullptr)
break;
// Skip legacy ICU time zone names.
if (IsLegacyICUTimeZone(rawTimeZone))
continue;
MOZ_ASSERT(size >= 0);
timeZone = Atomize(cx, rawTimeZone, size_t(size));
if (!timeZone)
return false;
TimeZoneHasher::Lookup lookup(timeZone);
TimeZoneSet::AddPtr p = availableTimeZones.lookupForAdd(lookup);
// ICU shouldn't report any duplicate time zone names, but if it does,
// just ignore the duplicate name.
if (!p && !availableTimeZones.add(p, timeZone)) {
ReportOutOfMemory(cx);
return false;
}
}
if (ianaZonesTreatedAsLinksByICU.initialized())
ianaZonesTreatedAsLinksByICU.finish();
if (!ianaZonesTreatedAsLinksByICU.init()) {
ReportOutOfMemory(cx);
return false;
}
for (const char* rawTimeZone : timezone::ianaZonesTreatedAsLinksByICU) {
MOZ_ASSERT(rawTimeZone != nullptr);
timeZone = Atomize(cx, rawTimeZone, strlen(rawTimeZone));
if (!timeZone)
return false;
TimeZoneHasher::Lookup lookup(timeZone);
TimeZoneSet::AddPtr p = ianaZonesTreatedAsLinksByICU.lookupForAdd(lookup);
MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaZonesTreatedAsLinksByICU");
if (!ianaZonesTreatedAsLinksByICU.add(p, timeZone)) {
ReportOutOfMemory(cx);
return false;
}
}
if (ianaLinksCanonicalizedDifferentlyByICU.initialized())
ianaLinksCanonicalizedDifferentlyByICU.finish();
if (!ianaLinksCanonicalizedDifferentlyByICU.init()) {
ReportOutOfMemory(cx);
return false;
}
RootedAtom linkName(cx);
RootedAtom& target = timeZone;
for (const auto& linkAndTarget : timezone::ianaLinksCanonicalizedDifferentlyByICU) {
const char* rawLinkName = linkAndTarget.link;
const char* rawTarget = linkAndTarget.target;
MOZ_ASSERT(rawLinkName != nullptr);
linkName = Atomize(cx, rawLinkName, strlen(rawLinkName));
if (!linkName)
return false;
MOZ_ASSERT(rawTarget != nullptr);
target = Atomize(cx, rawTarget, strlen(rawTarget));
if (!target)
return false;
TimeZoneHasher::Lookup lookup(linkName);
TimeZoneMap::AddPtr p = ianaLinksCanonicalizedDifferentlyByICU.lookupForAdd(lookup);
MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaLinksCanonicalizedDifferentlyByICU");
if (!ianaLinksCanonicalizedDifferentlyByICU.add(p, linkName, target)) {
ReportOutOfMemory(cx);
return false;
}
}
MOZ_ASSERT(!timeZoneDataInitialized, "ensureTimeZones is neither reentrant nor thread-safe");
timeZoneDataInitialized = true;
return true;
}
bool
js::intl::SharedIntlData::validateTimeZoneName(JSContext* cx, HandleString timeZone,
MutableHandleString result)
{
if (!ensureTimeZones(cx))
return false;
Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
if (!timeZoneFlat)
return false;
TimeZoneHasher::Lookup lookup(timeZoneFlat);
if (TimeZoneSet::Ptr p = availableTimeZones.lookup(lookup))
result.set(*p);
return true;
}
bool
js::intl::SharedIntlData::tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, HandleString timeZone,
MutableHandleString result)
{
if (!ensureTimeZones(cx))
return false;
Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
if (!timeZoneFlat)
return false;
TimeZoneHasher::Lookup lookup(timeZoneFlat);
MOZ_ASSERT(availableTimeZones.has(lookup), "Invalid time zone name");
if (TimeZoneMap::Ptr p = ianaLinksCanonicalizedDifferentlyByICU.lookup(lookup)) {
// The effectively supported time zones aren't known at compile time,
// when
// 1. SpiderMonkey was compiled with "--with-system-icu".
// 2. ICU's dynamic time zone data loading feature was used.
// (ICU supports loading time zone files at runtime through the
// ICU_TIMEZONE_FILES_DIR environment variable.)
// Ensure ICU supports the new target zone before applying the update.
TimeZoneName targetTimeZone = p->value();
TimeZoneHasher::Lookup targetLookup(targetTimeZone);
if (availableTimeZones.has(targetLookup))
result.set(targetTimeZone);
} else if (TimeZoneSet::Ptr p = ianaZonesTreatedAsLinksByICU.lookup(lookup)) {
result.set(*p);
}
return true;
}
void
js::intl::SharedIntlData::destroyInstance()
{
availableTimeZones.finish();
ianaZonesTreatedAsLinksByICU.finish();
ianaLinksCanonicalizedDifferentlyByICU.finish();
}
void
js::intl::SharedIntlData::trace(JSTracer* trc)
{
// Atoms are always tenured.
if (!trc->runtime()->isHeapMinorCollecting()) {
availableTimeZones.trace(trc);
ianaZonesTreatedAsLinksByICU.trace(trc);
ianaLinksCanonicalizedDifferentlyByICU.trace(trc);
}
}
size_t
js::intl::SharedIntlData::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
{
return availableTimeZones.sizeOfExcludingThis(mallocSizeOf) +
ianaZonesTreatedAsLinksByICU.sizeOfExcludingThis(mallocSizeOf) +
ianaLinksCanonicalizedDifferentlyByICU.sizeOfExcludingThis(mallocSizeOf);
}

View file

@ -0,0 +1,164 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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_SharedIntlData_h
#define builtin_intl_SharedIntlData_h
#include "mozilla/MemoryReporting.h"
#include <stddef.h>
#include "jsalloc.h"
#include "js/CharacterEncoding.h"
#include "js/GCAPI.h"
#include "js/GCHashTable.h"
#include "js/RootingAPI.h"
#include "js/Utility.h"
#include "vm/String.h"
namespace js {
namespace intl {
/**
* Stores Intl data which can be shared across compartments (but not contexts).
*
* Used for data which is expensive when computed repeatedly or is not
* available through ICU.
*/
class SharedIntlData
{
/**
* Information tracking the set of the supported time zone names, derived
* from the IANA time zone database <https://www.iana.org/time-zones>.
*
* There are two kinds of IANA time zone names: Zone and Link (denoted as
* such in database source files). Zone names are the canonical, preferred
* name for a time zone, e.g. Asia/Kolkata. Link names simply refer to
* target Zone names for their meaning, e.g. Asia/Calcutta targets
* Asia/Kolkata. That a name is a Link doesn't *necessarily* reflect a
* sense of deprecation: some Link names also exist partly for convenience,
* e.g. UTC and GMT as Link names targeting the Zone name Etc/UTC.
*
* Two data sources determine the time zone names we support: those ICU
* supports and IANA's zone information.
*
* Unfortunately the names ICU and IANA support, and their Link
* relationships from name to target, aren't identical, so we can't simply
* implicitly trust ICU's name handling. We must perform various
* preprocessing of user-provided zone names and post-processing of
* ICU-provided zone names to implement ECMA-402's IANA-consistent behavior.
*
* Also see <https://ssl.icu-project.org/trac/ticket/12044> and
* <http://unicode.org/cldr/trac/ticket/9892>.
*/
using TimeZoneName = JSAtom*;
struct TimeZoneHasher
{
struct Lookup
{
union {
const JS::Latin1Char* latin1Chars;
const char16_t* twoByteChars;
};
bool isLatin1;
size_t length;
JS::AutoCheckCannotGC nogc;
HashNumber hash;
explicit Lookup(JSFlatString* timeZone);
};
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
static bool match(TimeZoneName key, const Lookup& lookup);
};
using TimeZoneSet = js::GCHashSet<TimeZoneName,
TimeZoneHasher,
js::SystemAllocPolicy>;
using TimeZoneMap = js::GCHashMap<TimeZoneName,
TimeZoneName,
TimeZoneHasher,
js::SystemAllocPolicy>;
/**
* As a threshold matter, available time zones are those time zones ICU
* supports, via ucal_openTimeZones. But ICU supports additional non-IANA
* time zones described in intl/icu/source/tools/tzcode/icuzones (listed in
* IntlTimeZoneData.cpp's |legacyICUTimeZones|) for its own backwards
* compatibility purposes. This set consists of ICU's supported time zones,
* minus all backwards-compatibility time zones.
*/
TimeZoneSet availableTimeZones;
/**
* IANA treats some time zone names as Zones, that ICU instead treats as
* Links. For example, IANA considers "America/Indiana/Indianapolis" to be
* a Zone and "America/Fort_Wayne" a Link that targets it, but ICU
* considers the former a Link that targets "America/Indianapolis" (which
* IANA treats as a Link).
*
* ECMA-402 requires that we respect IANA data, so if we're asked to
* canonicalize a time zone name in this set, we must *not* return ICU's
* canonicalization.
*/
TimeZoneSet ianaZonesTreatedAsLinksByICU;
/**
* IANA treats some time zone names as Links to one target, that ICU
* instead treats as either Zones, or Links to different targets. An
* example of the former is "Asia/Calcutta, which IANA assigns the target
* "Asia/Kolkata" but ICU considers its own Zone. An example of the latter
* is "America/Virgin", which IANA assigns the target
* "America/Port_of_Spain" but ICU assigns the target "America/St_Thomas".
*
* ECMA-402 requires that we respect IANA data, so if we're asked to
* canonicalize a time zone name that's a key in this map, we *must* return
* the corresponding value and *must not* return ICU's canonicalization.
*/
TimeZoneMap ianaLinksCanonicalizedDifferentlyByICU;
bool timeZoneDataInitialized = false;
/**
* Precomputes the available time zone names, because it's too expensive to
* call ucal_openTimeZones() repeatedly.
*/
bool ensureTimeZones(JSContext* cx);
public:
/**
* Returns the validated time zone name in |result|. If the input time zone
* isn't a valid IANA time zone name, |result| remains unchanged.
*/
bool validateTimeZoneName(JSContext* cx, JS::HandleString timeZone,
JS::MutableHandleString result);
/**
* Returns the canonical time zone name in |result|. If no canonical name
* was found, |result| remains unchanged.
*
* This method only handles time zones which are canonicalized differently
* by ICU when compared to IANA.
*/
bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
JS::MutableHandleString result);
void destroyInstance();
void trace(JSTracer* trc);
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
};
} // namespace intl
} // namespace js
#endif /* builtin_intl_SharedIntlData_h */

View file

@ -1,8 +1,8 @@
// Generated by make_intl_data.py. DO NOT EDIT.
// tzdata version = 2022e
#ifndef builtin_IntlTimeZoneData_h
#define builtin_IntlTimeZoneData_h
#ifndef builtin_intl_TimeZoneDataGenerated_h
#define builtin_intl_TimeZoneDataGenerated_h
namespace js {
namespace timezone {
@ -143,4 +143,4 @@ const char* const legacyICUTimeZones[] = {
} // namespace timezone
} // namespace js
#endif /* builtin_IntlTimeZoneData_h */
#endif /* builtin_intl_TimeZoneDataGenerated_h */

View file

@ -12,8 +12,8 @@
Target "langtags":
This script extracts information about mappings between deprecated and
current BCP 47 language tags from the IANA Language Subtag Registry and
converts it to JavaScript object definitions in IntlData.js. The definitions
are used in Intl.js.
converts it to JavaScript object definitions in
LangTagMappingsGenerated.js. The definitions are used in Intl.js.
The IANA Language Subtag Registry is imported from
https://www.iana.org/assignments/language-subtag-registry
@ -190,7 +190,7 @@ def writeLanguageTagData(intlData, fileDate, url, langTagMappings, langSubtagMap
"Mappings from extlang subtags to preferred values", fileDate, url)
def updateLangTags(args):
""" Update the IntlData.js file. """
""" Update the LangTagMappingsGenerated.js file. """
url = args.url
out = args.out
filename = args.file
@ -685,8 +685,8 @@ def processTimeZones(tzdataDir, icuDir, icuTzDir, version, ignoreBackzone, ignor
println(tzdataVersionComment.format(version))
println(u"")
println(u"#ifndef builtin_IntlTimeZoneData_h")
println(u"#define builtin_IntlTimeZoneData_h")
println(u"#ifndef builtin_intl_TimeZoneDataGenerated_h")
println(u"#define builtin_intl_TimeZoneDataGenerated_h")
println(u"")
println(u"namespace js {")
@ -727,7 +727,7 @@ def processTimeZones(tzdataDir, icuDir, icuTzDir, version, ignoreBackzone, ignor
println(u"} // namespace timezone")
println(u"} // namespace js")
println(u"")
println(u"#endif /* builtin_IntlTimeZoneData_h */")
println(u"#endif /* builtin_intl_TimeZoneDataGenerated_h */")
def updateBackzoneLinks(tzdataDir, links):
(backzoneZones, backzoneLinks) = readIANAFiles(tzdataDir, ["backzone"])
@ -878,16 +878,9 @@ def generateTzDataTests(tzdataDir, version, ignoreBackzone, testDir):
generateTzDataTestBackzone(tzdataDir, version, ignoreBackzone, testDir)
generateTzDataTestBackzoneLinks(tzdataDir, version, ignoreBackzone, testDir)
def updateTzdata(args):
def updateTzdata(topsrcdir, args):
""" Update the time zone cpp file. """
# This script must reside in js/src/builtin to work correctly.
(thisDir, thisFile) = os.path.split(os.path.abspath(sys.argv[0]))
thisDir = os.path.normpath(thisDir)
if "/".join(thisDir.split(os.sep)[-3:]) != "js/src/builtin":
raise RuntimeError("%s must reside in js/src/builtin" % sys.argv[0])
topsrcdir = "/".join(thisDir.split(os.sep)[:-3])
icuDir = os.path.join(topsrcdir, "intl/icu/source")
if not os.path.isdir(icuDir):
raise RuntimeError("not a directory: %s" % icuDir)
@ -947,6 +940,13 @@ def updateTzdata(args):
if __name__ == "__main__":
import argparse
# This script must reside in js/src/builtin/intl to work correctly.
(thisDir, thisFile) = os.path.split(os.path.abspath(sys.argv[0]))
dirPaths = os.path.normpath(thisDir).split(os.sep)
if "/".join(dirPaths[-4:]) != "js/src/builtin/intl":
raise RuntimeError("%s must reside in js/src/builtin/intl" % sys.argv[0])
topsrcdir = "/".join(dirPaths[:-4])
def EnsureHttps(v):
if not v.startswith("https:"):
raise argparse.ArgumentTypeError("URL protocol must be https: " % v)
@ -963,7 +963,7 @@ if __name__ == "__main__":
type=EnsureHttps,
help="Download url for language-subtag-registry.txt (default: %(default)s)")
parser_tags.add_argument("--out",
default="IntlData.js",
default="LangTagMappingsGenerated.js",
help="Output file (default: %(default)s)")
parser_tags.add_argument("file",
nargs="?",
@ -984,9 +984,9 @@ if __name__ == "__main__":
"accurate time zone canonicalization reflecting the actual time "
"zones as used by ICU.")
parser_tz.add_argument("--out",
default="IntlTimeZoneData.h",
default="TimeZoneDataGenerated.h",
help="Output file (default: %(default)s)")
parser_tz.set_defaults(func=updateTzdata)
parser_tz.set_defaults(func=partial(updateTzdata, topsrcdir))
args = parser.parse_args()
args.func(args)

View file

@ -43,7 +43,6 @@
#include "builtin/AtomicsObject.h"
#include "builtin/Eval.h"
#include "builtin/Intl.h"
#include "builtin/MapObject.h"
#include "builtin/Promise.h"
#include "builtin/RegExp.h"

View file

@ -30,7 +30,7 @@
#include "jstypes.h"
#include "jsutil.h"
#include "builtin/Intl.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/RegExp.h"
#include "jit/InlinableNatives.h"
#include "js/Conversions.h"

View file

@ -114,7 +114,14 @@ EXPORTS.js += [
UNIFIED_SOURCES += [
'builtin/AtomicsObject.cpp',
'builtin/Eval.cpp',
'builtin/Intl.cpp',
'builtin/intl/Collator.cpp',
'builtin/intl/CommonFunctions.cpp',
'builtin/intl/DateTimeFormat.cpp',
'builtin/intl/IntlObject.cpp',
'builtin/intl/NumberFormat.cpp',
'builtin/intl/PluralRules.cpp',
'builtin/intl/RelativeTimeFormat.cpp',
'builtin/intl/SharedIntlData.cpp',
'builtin/MapObject.cpp',
'builtin/ModuleObject.cpp',
'builtin/Object.cpp',
@ -661,8 +668,14 @@ selfhosted.inputs = [
'builtin/Error.js',
'builtin/Function.js',
'builtin/Generator.js',
'builtin/Intl.js',
'builtin/IntlData.js',
'builtin/intl/Collator.js',
'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',
'builtin/Iterator.js',
'builtin/Map.js',
'builtin/Module.js',

View file

@ -16,7 +16,6 @@
#include "builtin/AtomicsObject.h"
#include "builtin/Eval.h"
#include "builtin/Intl.h"
#include "builtin/MapObject.h"
#include "builtin/ModuleObject.h"
#include "builtin/Object.h"

View file

@ -25,7 +25,7 @@
# include "wasm/WasmSignalHandlers.h"
#endif
#include "builtin/AtomicsObject.h"
#include "builtin/Intl.h"
#include "builtin/intl/SharedIntlData.h"
#include "builtin/Promise.h"
#include "ds/FixedSizeHash.h"
#include "frontend/NameCollections.h"
@ -810,7 +810,7 @@ struct JSRuntime : public JS::shadow::Runtime,
const char* getDefaultLocale();
/* Shared Intl data for this runtime. */
js::SharedIntlData sharedIntlData;
js::intl::SharedIntlData sharedIntlData;
void traceSharedIntlData(JSTracer* trc);

View file

@ -22,7 +22,12 @@
#include "jswrapper.h"
#include "selfhosted.out.h"
#include "builtin/Intl.h"
#include "builtin/intl/Collator.h"
#include "builtin/intl/DateTimeFormat.h"
#include "builtin/intl/IntlObject.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/intl/PluralRules.h"
#include "builtin/intl/RelativeTimeFormat.h"
#include "builtin/MapObject.h"
#include "builtin/ModuleObject.h"
#include "builtin/Object.h"
@ -2443,7 +2448,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FOR_EACH_REFERENCE_TYPE_REPR(LOAD_AND_STORE_REFERENCE_FN_DECLS)
#undef LOAD_AND_STORE_REFERENCE_FN_DECLS
// See builtin/Intl.h for descriptions of the intl_* functions.
// See builtin/intl/*.h for descriptions of the intl_* functions.
JS_FN("intl_availableCalendars", intl_availableCalendars, 1,0),
JS_FN("intl_availableCollations", intl_availableCollations, 1,0),
JS_FN("intl_canonicalizeTimeZone", intl_canonicalizeTimeZone, 1,0),

View file

@ -1618,6 +1618,12 @@ CSSStyleSheet::DidDirty()
ClearRuleCascades();
}
void
CSSStyleSheet::AssertHasUniqueInner()
{
MOZ_ASSERT(mInner->mSheets.Length() == 1, "expected unique inner");
}
nsresult
CSSStyleSheet::RegisterNamespaceRule(css::Rule* aRule)
{

View file

@ -217,6 +217,7 @@ public:
void WillDirty();
void DidDirty();
void AssertHasUniqueInner();
private:
CSSStyleSheet(const CSSStyleSheet& aCopy,

View file

@ -1571,9 +1571,43 @@ StyleRule::GetSelectorText(nsAString& aSelectorText)
void
StyleRule::SetSelectorText(const nsAString& aSelectorText)
{
// XXX TBI - get a parser and re-parse the selectors,
// XXX then need to re-compute the cascade
// XXX and dirty sheet
CSSStyleSheet* sheet = GetStyleSheet();
nsIDocument* doc = GetDocument();
RefPtr<css::Loader> loader;
if (doc) {
loader = doc->CSSLoader();
}
// NOTE: Passing a null loader means that the parser is always in
// standards mode and never in quirks mode.
nsCSSParser css(loader, sheet);
// StyleRule lives inside of the Inner, it is unsafe to call WillDirty
// if sheet does not already have a unique Inner.
sheet->AssertHasUniqueInner();
sheet->WillDirty();
nsCSSSelectorList* selectorList = nullptr;
nsresult result = css.ParseSelectorString(
aSelectorText, sheet->GetSheetURI(), 0, &selectorList);
if (NS_FAILED(result)) {
// Ignore parsing errors and continue to use the previous value.
return;
}
// Replace selector.
delete mSelector;
mSelector = selectorList;
sheet->DidDirty();
if (doc) {
mozAutoDocUpdate updateBatch(doc, UPDATE_STYLE, true);
doc->StyleRuleChanged(sheet, this);
}
}
/* virtual */ size_t

View file

@ -186,6 +186,7 @@ public:
// WillDirty and then make no change and skip the DidDirty call.
inline void WillDirty();
inline void DidDirty();
inline void AssertHasUniqueInner();
private:
// Get a handle to the various stylesheet bits which live on the 'inner' for

View file

@ -710,15 +710,36 @@ class Clobber(MachCommandBase):
raise
if 'python' in what:
# TODO: Once we move to Python 3, we should probably use
# shutil.which to get the fully qualified path for these commands.
cmd = ''
if os.path.isdir(mozpath.join(self.topsrcdir, '.hg')):
cmd = ['hg', 'purge', '--all', '-I', 'glob:**.py[co]']
elif os.path.isdir(mozpath.join(self.topsrcdir, '.git')):
cmd = ['git', 'clean', '-f', '-x', '*.py[co]']
if not cmd:
ret = self.clobber_python_fallback()
else:
cmd = ['find', '.', '-type', 'f', '-name', '*.py[co]', '-delete']
ret = subprocess.call(cmd, cwd=self.topsrcdir)
# It is possible that git or hg is either not installed or
# excluded from PATH despite the existence of their data
# directories, so use a fallback instead of failing early.
try:
ret = subprocess.call(cmd, cwd=self.topsrcdir)
except OSError as e:
ret = self.clobber_python_fallback()
return ret
def clobber_python_fallback(self):
cmd = ['find', '.', '-type', 'f', '-name', '*.py[co]', '-delete']
# Execute the command through the shell if we're on Windows to ensure
# that our copy of `find` is run rather than the OS default.
# This is because on Windows, Popen (and by extension, subprocess.call)
# ignores PATH and looks only at the current working directory.
use_shell = sys.platform.startswith('win')
return subprocess.call(cmd, cwd=self.topsrcdir, shell=use_shell)
@CommandProvider
class Logs(MachCommandBase):
"""Provide commands to read mach logs."""

View file

@ -27,7 +27,7 @@
#include "sqlite3.h"
#ifdef SQLITE_OS_WIN
#ifdef XP_WIN
// "windows.h" was included and it can #define lots of things we care about...
#undef CompareString
#endif
@ -917,6 +917,12 @@ Service::Observe(nsISupports *, const char *aTopic, const char16_t *)
getConnections(connections);
for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
if (!connections[i]->isClosed()) {
#ifdef DEBUG
nsCString msg;
msg.AppendPrintf("Storage connection to '%s' was not closed.",
connections[i]->getFilename().get());
NS_ERROR(msg.get());
#endif
MOZ_CRASH();
}
}