From c7dab6c035a9135dcaaf63fa0b002b95b38ef8eb Mon Sep 17 00:00:00 2001 From: Martok Date: Thu, 16 Feb 2023 21:26:50 +0100 Subject: [PATCH] Issue #2046 - Move Intl.RelativeTimeFormat functionality into builtin/intl/RelativeTimeFormat.* --- js/src/builtin/Intl.cpp | 298 +------------------ js/src/builtin/Intl.h | 25 -- js/src/builtin/intl/RelativeTimeFormat.cpp | 321 +++++++++++++++++++++ js/src/builtin/intl/RelativeTimeFormat.h | 68 +++++ js/src/moz.build | 1 + js/src/vm/SelfHosting.cpp | 1 + 6 files changed, 393 insertions(+), 321 deletions(-) create mode 100644 js/src/builtin/intl/RelativeTimeFormat.cpp create mode 100644 js/src/builtin/intl/RelativeTimeFormat.h diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 70539fadf5..be601f3592 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -28,6 +28,7 @@ #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 "ds/Sort.h" #include "vm/DateTime.h" @@ -43,9 +44,7 @@ using namespace js; -using mozilla::AssertedCast; using mozilla::IsFinite; -using mozilla::IsNegativeZero; using js::intl::CallICU; using js::intl::GetAvailableLocales; @@ -53,298 +52,7 @@ using js::intl::IcuLocale; using js::intl::INITIAL_CHAR_BUFFER_SIZE; using js::intl::StringsAreEqual; -/**************** 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().setReservedSlot(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; -} - -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().getReservedSlot(URELATIVE_TIME_FORMAT_SLOT); - if (!slot.isUndefined()) { - if (URelativeDateTimeFormatter* rtf = static_cast(slot.toPrivate())) - ureldatefmt_close(rtf); - } -} - -static JSObject* -CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle 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 (!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 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 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; -} +/******************** Intl ********************/ bool js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp) @@ -714,8 +422,6 @@ js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp) return true; } -/******************** Intl ********************/ - const Class js::IntlClass = { js_Object_str, JSCLASS_HAS_CACHED_PROTO(JSProto_Intl) diff --git a/js/src/builtin/Intl.h b/js/src/builtin/Intl.h index dca971a7be..938ab4de95 100644 --- a/js/src/builtin/Intl.h +++ b/js/src/builtin/Intl.h @@ -35,31 +35,6 @@ InitIntlClass(JSContext* cx, HandleObject obj); * The following functions are for use by self-hosted code. */ -/******************** 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/RelativeTimeFormat.cpp b/js/src/builtin/intl/RelativeTimeFormat.cpp new file mode 100644 index 0000000000..101d1eb935 --- /dev/null +++ b/js/src/builtin/intl/RelativeTimeFormat.cpp @@ -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(cx, proto); + if (!relativeTimeFormat) + return false; + + relativeTimeFormat->as().setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue()); + relativeTimeFormat->as().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().getReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT); + if (!slot.isUndefined()) { + if (URelativeDateTimeFormatter* rtf = static_cast(slot.toPrivate())) + ureldatefmt_close(rtf); + } +} + +JSObject* +js::CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle 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 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 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; +} diff --git a/js/src/builtin/intl/RelativeTimeFormat.h b/js/src/builtin/intl/RelativeTimeFormat.h new file mode 100644 index 0000000000..70185c182b --- /dev/null +++ b/js/src/builtin/intl/RelativeTimeFormat.h @@ -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 + +#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 Intl, + JS::Handle 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 */ \ No newline at end of file diff --git a/js/src/moz.build b/js/src/moz.build index 23fcd7bdf6..333c82cd40 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -120,6 +120,7 @@ UNIFIED_SOURCES += [ 'builtin/intl/DateTimeFormat.cpp', 'builtin/intl/NumberFormat.cpp', 'builtin/intl/PluralRules.cpp', + 'builtin/intl/RelativeTimeFormat.cpp', 'builtin/intl/SharedIntlData.cpp', 'builtin/MapObject.cpp', 'builtin/ModuleObject.cpp', diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 3248f8d47a..817450a8d8 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -27,6 +27,7 @@ #include "builtin/intl/DateTimeFormat.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"