mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-15 08:53:07 +09:00
Merge remote-tracking branch 'origin/tracking' into custom
This commit is contained in:
commit
fc9eb6a1b3
44 changed files with 8731 additions and 8225 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
530
js/src/builtin/intl/Collator.cpp
Normal file
530
js/src/builtin/intl/Collator.cpp
Normal 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;
|
||||
}
|
||||
94
js/src/builtin/intl/Collator.h
Normal file
94
js/src/builtin/intl/Collator.h
Normal 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 */
|
||||
355
js/src/builtin/intl/Collator.js
Normal file
355
js/src/builtin/intl/Collator.js
Normal 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;
|
||||
}
|
||||
|
||||
116
js/src/builtin/intl/CommonFunctions.cpp
Normal file
116
js/src/builtin/intl/CommonFunctions.cpp
Normal 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;
|
||||
}
|
||||
146
js/src/builtin/intl/CommonFunctions.h
Normal file
146
js/src/builtin/intl/CommonFunctions.h
Normal 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 */
|
||||
1245
js/src/builtin/intl/CommonFunctions.js
Normal file
1245
js/src/builtin/intl/CommonFunctions.js
Normal file
File diff suppressed because it is too large
Load diff
819
js/src/builtin/intl/DateTimeFormat.cpp
Normal file
819
js/src/builtin/intl/DateTimeFormat.cpp
Normal 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;
|
||||
}
|
||||
|
||||
143
js/src/builtin/intl/DateTimeFormat.h
Normal file
143
js/src/builtin/intl/DateTimeFormat.h
Normal 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 */
|
||||
898
js/src/builtin/intl/DateTimeFormat.js
Normal file
898
js/src/builtin/intl/DateTimeFormat.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
50
js/src/builtin/intl/ICUHeader.h
Normal file
50
js/src/builtin/intl/ICUHeader.h
Normal 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 */
|
||||
512
js/src/builtin/intl/IntlObject.cpp
Normal file
512
js/src/builtin/intl/IntlObject.cpp
Normal 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();
|
||||
}
|
||||
100
js/src/builtin/intl/IntlObject.h
Normal file
100
js/src/builtin/intl/IntlObject.h
Normal 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 */
|
||||
162
js/src/builtin/intl/IntlObject.js
Normal file
162
js/src/builtin/intl/IntlObject.js
Normal 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;
|
||||
|
||||
}
|
||||
904
js/src/builtin/intl/NumberFormat.cpp
Normal file
904
js/src/builtin/intl/NumberFormat.cpp
Normal 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;
|
||||
}
|
||||
|
||||
89
js/src/builtin/intl/NumberFormat.h
Normal file
89
js/src/builtin/intl/NumberFormat.h
Normal 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 */
|
||||
511
js/src/builtin/intl/NumberFormat.js
Normal file
511
js/src/builtin/intl/NumberFormat.js
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
473
js/src/builtin/intl/PluralRules.cpp
Normal file
473
js/src/builtin/intl/PluralRules.cpp
Normal 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;
|
||||
}
|
||||
91
js/src/builtin/intl/PluralRules.h
Normal file
91
js/src/builtin/intl/PluralRules.h
Normal 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 */
|
||||
231
js/src/builtin/intl/PluralRules.js
Normal file
231
js/src/builtin/intl/PluralRules.js
Normal 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;
|
||||
}
|
||||
|
||||
321
js/src/builtin/intl/RelativeTimeFormat.cpp
Normal file
321
js/src/builtin/intl/RelativeTimeFormat.cpp
Normal 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;
|
||||
}
|
||||
68
js/src/builtin/intl/RelativeTimeFormat.h
Normal file
68
js/src/builtin/intl/RelativeTimeFormat.h
Normal 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 */
|
||||
242
js/src/builtin/intl/RelativeTimeFormat.js
Normal file
242
js/src/builtin/intl/RelativeTimeFormat.js
Normal 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;
|
||||
}
|
||||
|
||||
44
js/src/builtin/intl/ScopedICUObject.h
Normal file
44
js/src/builtin/intl/ScopedICUObject.h
Normal 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 */
|
||||
300
js/src/builtin/intl/SharedIntlData.cpp
Normal file
300
js/src/builtin/intl/SharedIntlData.cpp
Normal 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);
|
||||
}
|
||||
164
js/src/builtin/intl/SharedIntlData.h
Normal file
164
js/src/builtin/intl/SharedIntlData.h
Normal 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 */
|
||||
|
|
@ -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 */
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ public:
|
|||
|
||||
void WillDirty();
|
||||
void DidDirty();
|
||||
void AssertHasUniqueInner();
|
||||
|
||||
private:
|
||||
CSSStyleSheet(const CSSStyleSheet& aCopy,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue