tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RelativeTimeFormat.cpp (12994B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /* Implementation of the Intl.RelativeTimeFormat proposal. */
      8 
      9 #include "builtin/intl/RelativeTimeFormat.h"
     10 
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/intl/RelativeTimeFormat.h"
     13 
     14 #include "builtin/intl/CommonFunctions.h"
     15 #include "builtin/intl/FormatBuffer.h"
     16 #include "builtin/intl/LanguageTag.h"
     17 #include "builtin/intl/LocaleNegotiation.h"
     18 #include "gc/GCContext.h"
     19 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     20 #include "js/Printer.h"
     21 #include "js/PropertySpec.h"
     22 #include "vm/GlobalObject.h"
     23 #include "vm/JSContext.h"
     24 #include "vm/PlainObject.h"  // js::PlainObject
     25 #include "vm/StringType.h"
     26 
     27 #include "vm/NativeObject-inl.h"
     28 
     29 using namespace js;
     30 using namespace js::intl;
     31 
     32 /**************** RelativeTimeFormat *****************/
     33 
     34 const JSClassOps RelativeTimeFormatObject::classOps_ = {
     35    nullptr,                             // addProperty
     36    nullptr,                             // delProperty
     37    nullptr,                             // enumerate
     38    nullptr,                             // newEnumerate
     39    nullptr,                             // resolve
     40    nullptr,                             // mayResolve
     41    RelativeTimeFormatObject::finalize,  // finalize
     42    nullptr,                             // call
     43    nullptr,                             // construct
     44    nullptr,                             // trace
     45 };
     46 
     47 const JSClass RelativeTimeFormatObject::class_ = {
     48    "Intl.RelativeTimeFormat",
     49    JSCLASS_HAS_RESERVED_SLOTS(RelativeTimeFormatObject::SLOT_COUNT) |
     50        JSCLASS_HAS_CACHED_PROTO(JSProto_RelativeTimeFormat) |
     51        JSCLASS_FOREGROUND_FINALIZE,
     52    &RelativeTimeFormatObject::classOps_,
     53    &RelativeTimeFormatObject::classSpec_,
     54 };
     55 
     56 const JSClass& RelativeTimeFormatObject::protoClass_ = PlainObject::class_;
     57 
     58 static bool relativeTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
     59                                                  Value* vp);
     60 
     61 static bool relativeTimeFormat_toSource(JSContext* cx, unsigned argc,
     62                                        Value* vp) {
     63  CallArgs args = CallArgsFromVp(argc, vp);
     64  args.rval().setString(cx->names().RelativeTimeFormat);
     65  return true;
     66 }
     67 
     68 static const JSFunctionSpec relativeTimeFormat_static_methods[] = {
     69    JS_FN("supportedLocalesOf", relativeTimeFormat_supportedLocalesOf, 1, 0),
     70    JS_FS_END,
     71 };
     72 
     73 static const JSFunctionSpec relativeTimeFormat_methods[] = {
     74    JS_SELF_HOSTED_FN("resolvedOptions",
     75                      "Intl_RelativeTimeFormat_resolvedOptions", 0, 0),
     76    JS_SELF_HOSTED_FN("format", "Intl_RelativeTimeFormat_format", 2, 0),
     77    JS_SELF_HOSTED_FN("formatToParts", "Intl_RelativeTimeFormat_formatToParts",
     78                      2, 0),
     79    JS_FN("toSource", relativeTimeFormat_toSource, 0, 0),
     80    JS_FS_END,
     81 };
     82 
     83 static const JSPropertySpec relativeTimeFormat_properties[] = {
     84    JS_STRING_SYM_PS(toStringTag, "Intl.RelativeTimeFormat", JSPROP_READONLY),
     85    JS_PS_END,
     86 };
     87 
     88 static bool RelativeTimeFormat(JSContext* cx, unsigned argc, Value* vp);
     89 
     90 const ClassSpec RelativeTimeFormatObject::classSpec_ = {
     91    GenericCreateConstructor<RelativeTimeFormat, 0, gc::AllocKind::FUNCTION>,
     92    GenericCreatePrototype<RelativeTimeFormatObject>,
     93    relativeTimeFormat_static_methods,
     94    nullptr,
     95    relativeTimeFormat_methods,
     96    relativeTimeFormat_properties,
     97    nullptr,
     98    ClassSpec::DontDefineConstructor,
     99 };
    100 
    101 /**
    102 * RelativeTimeFormat constructor.
    103 * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.1
    104 */
    105 static bool RelativeTimeFormat(JSContext* cx, unsigned argc, Value* vp) {
    106  CallArgs args = CallArgsFromVp(argc, vp);
    107 
    108  // Step 1.
    109  if (!ThrowIfNotConstructing(cx, args, "Intl.RelativeTimeFormat")) {
    110    return false;
    111  }
    112 
    113  // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
    114  RootedObject proto(cx);
    115  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_RelativeTimeFormat,
    116                                          &proto)) {
    117    return false;
    118  }
    119 
    120  Rooted<RelativeTimeFormatObject*> relativeTimeFormat(cx);
    121  relativeTimeFormat =
    122      NewObjectWithClassProto<RelativeTimeFormatObject>(cx, proto);
    123  if (!relativeTimeFormat) {
    124    return false;
    125  }
    126 
    127  HandleValue locales = args.get(0);
    128  HandleValue options = args.get(1);
    129 
    130  // Step 3.
    131  if (!intl::InitializeObject(cx, relativeTimeFormat,
    132                              cx->names().InitializeRelativeTimeFormat, locales,
    133                              options)) {
    134    return false;
    135  }
    136 
    137  args.rval().setObject(*relativeTimeFormat);
    138  return true;
    139 }
    140 
    141 void js::RelativeTimeFormatObject::finalize(JS::GCContext* gcx, JSObject* obj) {
    142  MOZ_ASSERT(gcx->onMainThread());
    143 
    144  if (mozilla::intl::RelativeTimeFormat* rtf =
    145          obj->as<RelativeTimeFormatObject>().getRelativeTimeFormatter()) {
    146    intl::RemoveICUCellMemory(gcx, obj,
    147                              RelativeTimeFormatObject::EstimatedMemoryUse);
    148 
    149    // This was allocated using `new` in mozilla::intl::RelativeTimeFormat,
    150    // so we delete here.
    151    delete rtf;
    152  }
    153 }
    154 
    155 /**
    156 * Returns a new URelativeDateTimeFormatter with the locale and options of the
    157 * given RelativeTimeFormatObject.
    158 */
    159 static mozilla::intl::RelativeTimeFormat* NewRelativeTimeFormatter(
    160    JSContext* cx, Handle<RelativeTimeFormatObject*> relativeTimeFormat) {
    161  RootedValue value(cx);
    162  RootedObject internals(cx, intl::GetInternalsObject(cx, relativeTimeFormat));
    163  if (!internals) {
    164    return nullptr;
    165  }
    166 
    167  // ICU expects numberingSystem as a Unicode locale extensions on locale.
    168 
    169  JS::RootedVector<intl::UnicodeExtensionKeyword> keywords(cx);
    170 
    171  if (!GetProperty(cx, internals, internals, cx->names().numberingSystem,
    172                   &value)) {
    173    return nullptr;
    174  }
    175 
    176  {
    177    JSLinearString* numberingSystem = value.toString()->ensureLinear(cx);
    178    if (!numberingSystem) {
    179      return nullptr;
    180    }
    181 
    182    if (!keywords.emplaceBack("nu", numberingSystem)) {
    183      return nullptr;
    184    }
    185  }
    186 
    187  UniqueChars locale = intl::FormatLocale(cx, internals, keywords);
    188  if (!locale) {
    189    return nullptr;
    190  }
    191 
    192  if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
    193    return nullptr;
    194  }
    195 
    196  using RelativeTimeFormatOptions = mozilla::intl::RelativeTimeFormatOptions;
    197  RelativeTimeFormatOptions options;
    198  {
    199    JSLinearString* style = value.toString()->ensureLinear(cx);
    200    if (!style) {
    201      return nullptr;
    202    }
    203 
    204    if (StringEqualsLiteral(style, "short")) {
    205      options.style = RelativeTimeFormatOptions::Style::Short;
    206    } else if (StringEqualsLiteral(style, "narrow")) {
    207      options.style = RelativeTimeFormatOptions::Style::Narrow;
    208    } else {
    209      MOZ_ASSERT(StringEqualsLiteral(style, "long"));
    210      options.style = RelativeTimeFormatOptions::Style::Long;
    211    }
    212  }
    213 
    214  if (!GetProperty(cx, internals, internals, cx->names().numeric, &value)) {
    215    return nullptr;
    216  }
    217 
    218  {
    219    JSLinearString* numeric = value.toString()->ensureLinear(cx);
    220    if (!numeric) {
    221      return nullptr;
    222    }
    223 
    224    if (StringEqualsLiteral(numeric, "auto")) {
    225      options.numeric = RelativeTimeFormatOptions::Numeric::Auto;
    226    } else {
    227      MOZ_ASSERT(StringEqualsLiteral(numeric, "always"));
    228      options.numeric = RelativeTimeFormatOptions::Numeric::Always;
    229    }
    230  }
    231 
    232  using RelativeTimeFormat = mozilla::intl::RelativeTimeFormat;
    233  mozilla::Result<mozilla::UniquePtr<RelativeTimeFormat>,
    234                  mozilla::intl::ICUError>
    235      result = RelativeTimeFormat::TryCreate(locale.get(), options);
    236 
    237  if (result.isOk()) {
    238    return result.unwrap().release();
    239  }
    240 
    241  intl::ReportInternalError(cx, result.unwrapErr());
    242  return nullptr;
    243 }
    244 
    245 static mozilla::intl::RelativeTimeFormat* GetOrCreateRelativeTimeFormat(
    246    JSContext* cx, Handle<RelativeTimeFormatObject*> relativeTimeFormat) {
    247  // Obtain a cached RelativeDateTimeFormatter object.
    248  mozilla::intl::RelativeTimeFormat* rtf =
    249      relativeTimeFormat->getRelativeTimeFormatter();
    250  if (rtf) {
    251    return rtf;
    252  }
    253 
    254  rtf = NewRelativeTimeFormatter(cx, relativeTimeFormat);
    255  if (!rtf) {
    256    return nullptr;
    257  }
    258  relativeTimeFormat->setRelativeTimeFormatter(rtf);
    259 
    260  intl::AddICUCellMemory(relativeTimeFormat,
    261                         RelativeTimeFormatObject::EstimatedMemoryUse);
    262  return rtf;
    263 }
    264 
    265 bool js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp) {
    266  CallArgs args = CallArgsFromVp(argc, vp);
    267  MOZ_ASSERT(args.length() == 4);
    268  MOZ_ASSERT(args[0].isObject());
    269  MOZ_ASSERT(args[1].isNumber());
    270  MOZ_ASSERT(args[2].isString());
    271  MOZ_ASSERT(args[3].isBoolean());
    272 
    273  Rooted<RelativeTimeFormatObject*> relativeTimeFormat(cx);
    274  relativeTimeFormat = &args[0].toObject().as<RelativeTimeFormatObject>();
    275 
    276  bool formatToParts = args[3].toBoolean();
    277 
    278  // PartitionRelativeTimePattern, step 4.
    279  double t = args[1].toNumber();
    280  if (!std::isfinite(t)) {
    281    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    282                              JSMSG_DATE_NOT_FINITE, "RelativeTimeFormat",
    283                              formatToParts ? "formatToParts" : "format");
    284    return false;
    285  }
    286 
    287  mozilla::intl::RelativeTimeFormat* rtf =
    288      GetOrCreateRelativeTimeFormat(cx, relativeTimeFormat);
    289  if (!rtf) {
    290    return false;
    291  }
    292 
    293  intl::RelativeTimeFormatUnit jsUnitType;
    294  using FormatUnit = mozilla::intl::RelativeTimeFormat::FormatUnit;
    295  FormatUnit relTimeUnit;
    296  {
    297    JSLinearString* unit = args[2].toString()->ensureLinear(cx);
    298    if (!unit) {
    299      return false;
    300    }
    301 
    302    // PartitionRelativeTimePattern, step 5.
    303    if (StringEqualsLiteral(unit, "second") ||
    304        StringEqualsLiteral(unit, "seconds")) {
    305      jsUnitType = &JSAtomState::second;
    306      relTimeUnit = FormatUnit::Second;
    307    } else if (StringEqualsLiteral(unit, "minute") ||
    308               StringEqualsLiteral(unit, "minutes")) {
    309      jsUnitType = &JSAtomState::minute;
    310      relTimeUnit = FormatUnit::Minute;
    311    } else if (StringEqualsLiteral(unit, "hour") ||
    312               StringEqualsLiteral(unit, "hours")) {
    313      jsUnitType = &JSAtomState::hour;
    314      relTimeUnit = FormatUnit::Hour;
    315    } else if (StringEqualsLiteral(unit, "day") ||
    316               StringEqualsLiteral(unit, "days")) {
    317      jsUnitType = &JSAtomState::day;
    318      relTimeUnit = FormatUnit::Day;
    319    } else if (StringEqualsLiteral(unit, "week") ||
    320               StringEqualsLiteral(unit, "weeks")) {
    321      jsUnitType = &JSAtomState::week;
    322      relTimeUnit = FormatUnit::Week;
    323    } else if (StringEqualsLiteral(unit, "month") ||
    324               StringEqualsLiteral(unit, "months")) {
    325      jsUnitType = &JSAtomState::month;
    326      relTimeUnit = FormatUnit::Month;
    327    } else if (StringEqualsLiteral(unit, "quarter") ||
    328               StringEqualsLiteral(unit, "quarters")) {
    329      jsUnitType = &JSAtomState::quarter;
    330      relTimeUnit = FormatUnit::Quarter;
    331    } else if (StringEqualsLiteral(unit, "year") ||
    332               StringEqualsLiteral(unit, "years")) {
    333      jsUnitType = &JSAtomState::year;
    334      relTimeUnit = FormatUnit::Year;
    335    } else {
    336      if (auto unitChars = QuoteString(cx, unit, '"')) {
    337        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    338                                  JSMSG_INVALID_OPTION_VALUE, "unit",
    339                                  unitChars.get());
    340      }
    341      return false;
    342    }
    343  }
    344 
    345  using ICUError = mozilla::intl::ICUError;
    346  if (formatToParts) {
    347    mozilla::intl::NumberPartVector parts;
    348    mozilla::Result<mozilla::Span<const char16_t>, ICUError> result =
    349        rtf->formatToParts(t, relTimeUnit, parts);
    350 
    351    if (result.isErr()) {
    352      intl::ReportInternalError(cx, result.unwrapErr());
    353      return false;
    354    }
    355 
    356    RootedString str(cx, NewStringCopy<CanGC>(cx, result.unwrap()));
    357    if (!str) {
    358      return false;
    359    }
    360 
    361    return js::intl::FormattedRelativeTimeToParts(cx, str, parts, jsUnitType,
    362                                                  args.rval());
    363  }
    364 
    365  js::intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
    366  mozilla::Result<Ok, ICUError> result = rtf->format(t, relTimeUnit, buffer);
    367 
    368  if (result.isErr()) {
    369    intl::ReportInternalError(cx, result.unwrapErr());
    370    return false;
    371  }
    372 
    373  JSString* str = buffer.toString(cx);
    374  if (!str) {
    375    return false;
    376  }
    377 
    378  args.rval().setString(str);
    379  return true;
    380 }
    381 
    382 /**
    383 * Intl.RelativeTimeFormat.supportedLocalesOf ( locales [ , options ] )
    384 */
    385 static bool relativeTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
    386                                                  Value* vp) {
    387  CallArgs args = CallArgsFromVp(argc, vp);
    388 
    389  // Steps 1-3.
    390  auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::RelativeTimeFormat,
    391                                   args.get(0), args.get(1));
    392  if (!array) {
    393    return false;
    394  }
    395  args.rval().setObject(*array);
    396  return true;
    397 }