From d0f152b2e062b45d14ee14b8dc8be2b9d85ba6d5 Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Fri, 19 Jan 2018 20:26:54 -0800 Subject: [PATCH] Bug 1431957 - Move Intl.RelativeTimeFormat functionality into builtin/intl/RelativeTimeFormat.*. r=anba --HG-- rename : js/src/builtin/Intl.cpp => js/src/builtin/intl/RelativeTimeFormat.cpp extra : rebase_source : 36750b0baaf6b5cf1b85dbe5e7f3f1ca27ba7320 --- js/src/builtin/Intl.cpp | 306 ------------------- js/src/builtin/Intl.h | 43 --- js/src/builtin/intl/RelativeTimeFormat.cpp | 338 +++++++++++++++++++++ js/src/builtin/intl/RelativeTimeFormat.h | 69 +++++ js/src/jit/MCallOptimize.cpp | 1 + js/src/moz.build | 1 + js/src/vm/SelfHosting.cpp | 1 + 7 files changed, 410 insertions(+), 349 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 57ce570dc5f3..52d1e67732e2 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -53,8 +53,6 @@ using namespace js; -using mozilla::AssertedCast; -using mozilla::IsNegativeZero; using mozilla::Range; using mozilla::RangedPtr; @@ -65,310 +63,6 @@ using js::intl::IcuLocale; using js::intl::INITIAL_CHAR_BUFFER_SIZE; using js::intl::StringsAreEqual; -/**************** RelativeTimeFormat *****************/ - -const ClassOps RelativeTimeFormatObject::classOps_ = { - nullptr, /* addProperty */ - nullptr, /* delProperty */ - 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 -}; - -/** - * 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 (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) - return false; - - if (!proto) { - proto = GlobalObject::getOrCreateRelativeTimeFormatPrototype(cx, cx->global()); - if (!proto) - return false; - } - - Rooted relativeTimeFormat(cx); - relativeTimeFormat = NewObjectWithGivenProto(cx, proto); - if (!relativeTimeFormat) - return false; - - relativeTimeFormat->setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue()); - relativeTimeFormat->setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr)); - - HandleValue locales = args.get(0); - HandleValue options = args.get(1); - - // Step 3. - if (!intl::InitializeObject(cx, relativeTimeFormat, cx->names().InitializeRelativeTimeFormat, - locales, options)) - { - return false; - } - - args.rval().setObject(*relativeTimeFormat); - return true; -} - -void -RelativeTimeFormatObject::finalize(FreeOp* fop, JSObject* obj) -{ - MOZ_ASSERT(fop->onActiveCooperatingThread()); - - const Value& slot = - obj->as().getReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT); - 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; - - RootedObject proto(cx, GlobalObject::createBlankPrototype(cx, global)); - if (!proto) - return 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; - - RootedValue ctorValue(cx, ObjectValue(*ctor)); - if (!DefineDataProperty(cx, Intl, cx->names().RelativeTimeFormat, ctorValue, 0)) - return nullptr; - - return proto; -} - -/* static */ bool -js::GlobalObject::addRelativeTimeFormatConstructor(JSContext* cx, HandleObject intl) -{ - Handle global = cx->global(); - - { - const HeapSlot& slot = global->getReservedSlotRef(RELATIVE_TIME_FORMAT_PROTO); - if (!slot.isUndefined()) { - MOZ_ASSERT(slot.isObject()); - JS_ReportErrorASCII(cx, - "the RelativeTimeFormat constructor can't be added " - "multiple times in the same global"); - return false; - } - } - - JSObject* relativeTimeFormatProto = CreateRelativeTimeFormatPrototype(cx, intl, global); - if (!relativeTimeFormatProto) - return false; - - global->setReservedSlot(RELATIVE_TIME_FORMAT_PROTO, ObjectValue(*relativeTimeFormatProto)); - return true; -} - -bool -js::AddRelativeTimeFormatConstructor(JSContext* cx, JS::Handle intl) -{ - return GlobalObject::addRelativeTimeFormatConstructor(cx, intl); -} - - -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 RelativeTimeType -{ - /** - * Only strings with numeric components like `1 day ago`. - */ - Numeric, - /** - * Natural-language strings like `yesterday` when possible, - * otherwise strings with numeric components as in `7 months ago`. - */ - Text, -}; - -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()); - - double t = args[1].toNumber(); - - 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; - - UDateRelativeDateTimeFormatterStyle relDateTimeStyle; - { - JSLinearString* style = value.toString()->ensureLinear(cx); - if (!style) - return false; - - 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; - } - } - - if (!GetProperty(cx, internals, internals, cx->names().type, &value)) - return false; - - RelativeTimeType relDateTimeType; - { - JSLinearString* type = value.toString()->ensureLinear(cx); - if (!type) - return false; - - if (StringEqualsAscii(type, "text")) { - relDateTimeType = RelativeTimeType::Text; - } else { - MOZ_ASSERT(StringEqualsAscii(type, "numeric")); - relDateTimeType = RelativeTimeType::Numeric; - } - } - - URelativeDateTimeUnit relDateTimeUnit; - { - JSLinearString* unit = args[2].toString()->ensureLinear(cx); - if (!unit) - return false; - - if (StringEqualsAscii(unit, "second")) { - relDateTimeUnit = UDAT_REL_UNIT_SECOND; - } else if (StringEqualsAscii(unit, "minute")) { - relDateTimeUnit = UDAT_REL_UNIT_MINUTE; - } else if (StringEqualsAscii(unit, "hour")) { - relDateTimeUnit = UDAT_REL_UNIT_HOUR; - } else if (StringEqualsAscii(unit, "day")) { - relDateTimeUnit = UDAT_REL_UNIT_DAY; - } else if (StringEqualsAscii(unit, "week")) { - relDateTimeUnit = UDAT_REL_UNIT_WEEK; - } else if (StringEqualsAscii(unit, "month")) { - relDateTimeUnit = UDAT_REL_UNIT_MONTH; - } else if (StringEqualsAscii(unit, "quarter")) { - relDateTimeUnit = UDAT_REL_UNIT_QUARTER; - } else { - MOZ_ASSERT(StringEqualsAscii(unit, "year")); - relDateTimeUnit = UDAT_REL_UNIT_YEAR; - } - } - - // ICU doesn't handle -0 well: work around this by converting it to +0. - // See: http://bugs.icu-project.org/trac/ticket/12936 - if (IsNegativeZero(t)) - t = +0.0; - - 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, relDateTimeType](UChar* chars, int32_t size, - UErrorCode* status) - { - auto fmt = relDateTimeType == RelativeTimeType::Text - ? ureldatefmt_format - : ureldatefmt_formatNumeric; - return fmt(rtf, t, relDateTimeUnit, chars, size, status); - }); - if (!str) - return false; - - args.rval().setString(str); - return true; -} - - /******************** String ********************/ static const char* diff --git a/js/src/builtin/Intl.h b/js/src/builtin/Intl.h index b2f5465f04f4..df1f8e98442f 100644 --- a/js/src/builtin/Intl.h +++ b/js/src/builtin/Intl.h @@ -41,49 +41,6 @@ InitIntlClass(JSContext* cx, HandleObject obj); */ -/******************** RelativeTimeFormat ********************/ - -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); -}; - -/** - * 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); - /******************** Intl ********************/ /** diff --git a/js/src/builtin/intl/RelativeTimeFormat.cpp b/js/src/builtin/intl/RelativeTimeFormat.cpp new file mode 100644 index 000000000000..b8e240d90f66 --- /dev/null +++ b/js/src/builtin/intl/RelativeTimeFormat.cpp @@ -0,0 +1,338 @@ +/* -*- 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/ICUStubs.h" +#include "builtin/intl/ScopedICUObject.h" +#include "gc/FreeOp.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::DateTimeFormatOptions; +using js::intl::GetAvailableLocales; +using js::intl::IcuLocale; +using js::intl::StringsAreEqual; + +/**************** RelativeTimeFormat *****************/ + +const ClassOps RelativeTimeFormatObject::classOps_ = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + 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 +}; + +/** + * 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 (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) + return false; + + if (!proto) { + proto = GlobalObject::getOrCreateRelativeTimeFormatPrototype(cx, cx->global()); + if (!proto) + return false; + } + + Rooted relativeTimeFormat(cx); + relativeTimeFormat = NewObjectWithGivenProto(cx, proto); + if (!relativeTimeFormat) + return false; + + relativeTimeFormat->setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue()); + relativeTimeFormat->setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr)); + + HandleValue locales = args.get(0); + HandleValue options = 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->onActiveCooperatingThread()); + + const Value& slot = + obj->as().getReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT); + 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; + + RootedObject proto(cx, GlobalObject::createBlankPrototype(cx, global)); + if (!proto) + return 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; + + RootedValue ctorValue(cx, ObjectValue(*ctor)); + if (!DefineDataProperty(cx, Intl, cx->names().RelativeTimeFormat, ctorValue, 0)) + return nullptr; + + return proto; +} + +/* static */ bool +js::GlobalObject::addRelativeTimeFormatConstructor(JSContext* cx, HandleObject intl) +{ + Handle global = cx->global(); + + { + const HeapSlot& slot = global->getReservedSlotRef(RELATIVE_TIME_FORMAT_PROTO); + if (!slot.isUndefined()) { + MOZ_ASSERT(slot.isObject()); + JS_ReportErrorASCII(cx, + "the RelativeTimeFormat constructor can't be added " + "multiple times in the same global"); + return false; + } + } + + JSObject* relativeTimeFormatProto = CreateRelativeTimeFormatPrototype(cx, intl, global); + if (!relativeTimeFormatProto) + return false; + + global->setReservedSlot(RELATIVE_TIME_FORMAT_PROTO, ObjectValue(*relativeTimeFormatProto)); + return true; +} + +bool +js::AddRelativeTimeFormatConstructor(JSContext* cx, JS::Handle intl) +{ + return GlobalObject::addRelativeTimeFormatConstructor(cx, intl); +} + + +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 RelativeTimeType +{ + /** + * Only strings with numeric components like `1 day ago`. + */ + Numeric, + /** + * Natural-language strings like `yesterday` when possible, + * otherwise strings with numeric components as in `7 months ago`. + */ + Text, +}; + +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()); + + double t = args[1].toNumber(); + + 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; + + UDateRelativeDateTimeFormatterStyle relDateTimeStyle; + { + JSLinearString* style = value.toString()->ensureLinear(cx); + if (!style) + return false; + + 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; + } + } + + if (!GetProperty(cx, internals, internals, cx->names().type, &value)) + return false; + + RelativeTimeType relDateTimeType; + { + JSLinearString* type = value.toString()->ensureLinear(cx); + if (!type) + return false; + + if (StringEqualsAscii(type, "text")) { + relDateTimeType = RelativeTimeType::Text; + } else { + MOZ_ASSERT(StringEqualsAscii(type, "numeric")); + relDateTimeType = RelativeTimeType::Numeric; + } + } + + URelativeDateTimeUnit relDateTimeUnit; + { + JSLinearString* unit = args[2].toString()->ensureLinear(cx); + if (!unit) + return false; + + if (StringEqualsAscii(unit, "second")) { + relDateTimeUnit = UDAT_REL_UNIT_SECOND; + } else if (StringEqualsAscii(unit, "minute")) { + relDateTimeUnit = UDAT_REL_UNIT_MINUTE; + } else if (StringEqualsAscii(unit, "hour")) { + relDateTimeUnit = UDAT_REL_UNIT_HOUR; + } else if (StringEqualsAscii(unit, "day")) { + relDateTimeUnit = UDAT_REL_UNIT_DAY; + } else if (StringEqualsAscii(unit, "week")) { + relDateTimeUnit = UDAT_REL_UNIT_WEEK; + } else if (StringEqualsAscii(unit, "month")) { + relDateTimeUnit = UDAT_REL_UNIT_MONTH; + } else if (StringEqualsAscii(unit, "quarter")) { + relDateTimeUnit = UDAT_REL_UNIT_QUARTER; + } else { + MOZ_ASSERT(StringEqualsAscii(unit, "year")); + relDateTimeUnit = UDAT_REL_UNIT_YEAR; + } + } + + // ICU doesn't handle -0 well: work around this by converting it to +0. + // See: http://bugs.icu-project.org/trac/ticket/12936 + if (IsNegativeZero(t)) + t = +0.0; + + 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, relDateTimeType](UChar* chars, int32_t size, + UErrorCode* status) + { + auto fmt = relDateTimeType == RelativeTimeType::Text + ? 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 000000000000..e4891ddb89fb --- /dev/null +++ b/js/src/builtin/intl/RelativeTimeFormat.h @@ -0,0 +1,69 @@ +/* -*- 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, JS::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, JS::Value* vp); + +} // namespace js + +#endif /* builtin_intl_RelativeTimeFormat_h */ diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 8b76eb3cc97e..bc23d0bf23f3 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -16,6 +16,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/SIMD.h" #include "builtin/TestingFunctions.h" diff --git a/js/src/moz.build b/js/src/moz.build index a374fbe4bae1..eb457511da48 100755 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -165,6 +165,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 7b8c593e6a9d..422e71264038 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -28,6 +28,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"