diff options
Diffstat (limited to 'js/src')
-rw-r--r-- | js/src/builtin/Intl.cpp | 306 | ||||
-rw-r--r-- | js/src/builtin/Intl.h | 25 | ||||
-rw-r--r-- | js/src/builtin/Intl.js | 269 | ||||
-rw-r--r-- | js/src/vm/CommonPropertyNames.h | 4 | ||||
-rw-r--r-- | js/src/vm/GlobalObject.h | 6 | ||||
-rw-r--r-- | js/src/vm/SelfHosting.cpp | 2 |
6 files changed, 603 insertions, 9 deletions
diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 5455b3a854..494a0c0473 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -11,6 +11,7 @@ #include "builtin/Intl.h" #include "mozilla/Casting.h" +#include "mozilla/FloatingPoint.h" #include "mozilla/PodOperations.h" #include "mozilla/Range.h" @@ -28,10 +29,12 @@ #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" #include "vm/DateTime.h" #include "vm/GlobalObject.h" @@ -3205,6 +3208,303 @@ js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp) return true; } +/**************** RelativeTimeFormat *****************/ + +static void relativeTimeFormat_finalize(FreeOp* fop, JSObject* obj); + +static const uint32_t URELATIVE_TIME_FORMAT_SLOT = 0; +static const uint32_t RELATIVE_TIME_FORMAT_SLOTS_COUNT = 1; + +static const ClassOps RelativeTimeFormatClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* enumerate */ + nullptr, /* newEnumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + relativeTimeFormat_finalize +}; + +static const Class RelativeTimeFormatClass = { + js_Object_str, + JSCLASS_HAS_RESERVED_SLOTS(RELATIVE_TIME_FORMAT_SLOTS_COUNT) | + JSCLASS_FOREGROUND_FINALIZE, + &RelativeTimeFormatClassOps +}; + +#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(cx, &RelativeTimeFormatClass, proto); + if (!relativeTimeFormat) + return false; + + relativeTimeFormat->as<NativeObject>().setReservedSlot(URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr)); + + RootedValue locales(cx, args.get(0)); + RootedValue options(cx, args.get(1)); + + // Step 3. + if (!IntlInitialize(cx, relativeTimeFormat, cx->names().InitializeRelativeTimeFormat, locales, options)) + return false; + + args.rval().setObject(*relativeTimeFormat); + return true; +} + +static void +relativeTimeFormat_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(URELATIVE_TIME_FORMAT_SLOT); + if (!slot.isUndefined()) { + if (URelativeDateTimeFormatter* rtf = static_cast<URelativeDateTimeFormatter*>(slot.toPrivate())) + ureldatefmt_close(rtf); + } +} + +static JSObject* +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, &RelativeTimeFormatClass)); + if (!proto) + return nullptr; + proto->setReservedSlot(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 (!CreateDefaultOptions(cx, &options)) + return nullptr; + + if (!IntlInitialize(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 (!intl_availableLocales(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, GetInternals(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)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + int32_t size; + + if (relDateTimeNumeric == RelativeTimeNumeric::Auto) { + size = ureldatefmt_format(rtf, t, relDateTimeUnit, Char16ToUChar(chars.begin()), + INITIAL_CHAR_BUFFER_SIZE, &status); + } else { + MOZ_ASSERT(relDateTimeNumeric == RelativeTimeNumeric::Always); + size = ureldatefmt_formatNumeric(rtf, t, relDateTimeUnit, Char16ToUChar(chars.begin()), + INITIAL_CHAR_BUFFER_SIZE, &status); + } + + ScopedICUObject<URelativeDateTimeFormatter, ureldatefmt_close> closeRelativeTimeFormat(rtf); + + JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size); + if (!str) + return false; + + args.rval().setString(str); + return true; +} + bool js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp) { @@ -3647,6 +3947,11 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> 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, @@ -3668,6 +3973,7 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global) 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". diff --git a/js/src/builtin/Intl.h b/js/src/builtin/Intl.h index fd1fc5da6a..330b8a4963 100644 --- a/js/src/builtin/Intl.h +++ b/js/src/builtin/Intl.h @@ -406,6 +406,31 @@ intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp); 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 diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js index ef0aa986a6..e7d1b58ed6 100644 --- a/js/src/builtin/Intl.js +++ b/js/src/builtin/Intl.js @@ -23,6 +23,7 @@ intl_FormatDateTime: false, intl_SelectPluralRule: false, intl_GetPluralCategories: false, + intl_FormatRelativeTime: false, intl_GetCalendarInfo: false, */ @@ -1275,7 +1276,8 @@ function setLazyData(internals, type, lazyData) { assert(internals.type === "partial", "can't set lazy data for anything but a newborn"); assert(type === "Collator" || type === "DateTimeFormat" || - type == "NumberFormat" || type === "PluralRules", + type === "NumberFormat" || type === "PluralRules" || + type === "RelativeTimeFormat", "bad type"); assert(IsObject(lazyData), "non-object lazy data"); @@ -1327,7 +1329,8 @@ function isInitializedIntlObject(obj) { assert(callFunction(std_Object_hasOwnProperty, internals, "type"), "missing type"); var type = internals.type; assert(type === "partial" || type === "Collator" || - type === "DateTimeFormat" || type === "NumberFormat" || type === "PluralRules", + type === "DateTimeFormat" || type === "NumberFormat" || + type === "PluralRules" || type === "RelativeTimeFormat", "unexpected type"); assert(callFunction(std_Object_hasOwnProperty, internals, "lazyData"), "missing lazyData"); assert(callFunction(std_Object_hasOwnProperty, internals, "internalProps"), "missing internalProps"); @@ -1381,14 +1384,24 @@ function getInternals(obj) var internalProps; var type = internals.type; - if (type === "Collator") - internalProps = resolveCollatorInternals(lazyData) - else if (type === "DateTimeFormat") - internalProps = resolveDateTimeFormatInternals(lazyData) - else if (type === "PluralRules") - internalProps = resolvePluralRulesInternals(lazyData) - else + + switch (type) { + case "Collator": + internalProps = resolveCollatorInternals(lazyData); + break; + case "DateTimeFormat": + internalProps = resolveDateTimeFormatInternals(lazyData); + break; + case "PluralRules": + internalProps = resolvePluralRulesInternals(lazyData); + break; + case "RelativeTimeFormat": + internalProps = resolveRelativeTimeFormatInternals(lazyData); + break; + default: // type === "NumberFormat" internalProps = resolveNumberFormatInternals(lazyData); + break; + } setInternalProperties(internals, internalProps); return internalProps; } @@ -3252,6 +3265,244 @@ function Intl_PluralRules_resolvedOptions() { } +/********** 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; +} + function Intl_getCanonicalLocales(locales) { let codes = CanonicalizeLocaleList(locales); let result = []; diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 445b8a6bb3..8ecd3a2083 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -182,6 +182,7 @@ macro(InitializeDateTimeFormat, InitializeDateTimeFormat, "InitializeDateTimeFormat") \ macro(InitializeNumberFormat, InitializeNumberFormat, "InitializeNumberFormat") \ macro(InitializePluralRules, InitializePluralRules, "InitializePluralRules") \ + macro(InitializeRelativeTimeFormat, InitializeRelativeTimeFormat, "InitializeRelativeTimeFormat") \ macro(innermost, innermost, "innermost") \ macro(inNursery, inNursery, "inNursery") \ macro(input, input, "input") \ @@ -297,6 +298,8 @@ macro(Reify, Reify, "Reify") \ macro(reject, reject, "reject") \ macro(rejected, rejected, "rejected") \ + macro(RelativeTimeFormat, RelativeTimeFormat, "RelativeTimeFormat") \ + macro(RelativeTimeFormatFormat, RelativeTimeFormatFormat, "Intl_RelativeTimeFormat_Format") \ macro(RequireObjectCoercible, RequireObjectCoercible, "RequireObjectCoercible") \ macro(resolve, resolve, "resolve") \ macro(resumeGenerator, resumeGenerator, "resumeGenerator") \ @@ -364,6 +367,7 @@ macro(unescape, unescape, "unescape") \ macro(uneval, uneval, "uneval") \ macro(unicode, unicode, "unicode") \ + macro(unit, unit, "unit") \ macro(uninitialized, uninitialized, "uninitialized") \ macro(unsized, unsized, "unsized") \ macro(unwatch, unwatch, "unwatch") \ diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index f9c0149f12..2e1b6ce87b 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -110,6 +110,7 @@ class GlobalObject : public NativeObject NUMBER_FORMAT_PROTO, DATE_TIME_FORMAT_PROTO, PLURAL_RULES_PROTO, + RELATIVE_TIME_FORMAT_PROTO, MODULE_PROTO, IMPORT_ENTRY_PROTO, EXPORT_ENTRY_PROTO, @@ -513,6 +514,11 @@ class GlobalObject : public NativeObject return getOrCreateObject(cx, global, PLURAL_RULES_PROTO, initIntlObject); } + static JSObject* + getOrCreateRelativeTimeFormatPrototype(JSContext* cx, Handle<GlobalObject*> global) { + return getOrCreateObject(cx, global, RELATIVE_TIME_FORMAT_PROTO, initIntlObject); + } + static bool ensureModulePrototypesCreated(JSContext *cx, Handle<GlobalObject*> global); JSObject* maybeGetModulePrototype() { diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 29c75c2064..058cc6b6d0 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -2520,6 +2520,8 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0), JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0), JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0), + JS_FN("intl_RelativeTimeFormat_availableLocales", intl_RelativeTimeFormat_availableLocales, 0,0), + JS_FN("intl_FormatRelativeTime", intl_FormatRelativeTime, 3,0), JS_INLINABLE_FN("IsRegExpObject", intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0, |