tor-browser

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

DurationFormat.cpp (55201B)


      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 /* Intl.DurationFormat implementation. */
      8 
      9 #include "builtin/intl/DurationFormat.h"
     10 
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/intl/DateTimeFormat.h"
     13 #include "mozilla/intl/ListFormat.h"
     14 #include "mozilla/intl/NumberFormat.h"
     15 #include "mozilla/Span.h"
     16 
     17 #include <array>
     18 #include <charconv>
     19 
     20 #include "jspubtd.h"
     21 #include "NamespaceImports.h"
     22 
     23 #include "builtin/intl/CommonFunctions.h"
     24 #include "builtin/intl/FormatBuffer.h"
     25 #include "builtin/intl/LanguageTag.h"
     26 #include "builtin/intl/ListFormat.h"
     27 #include "builtin/intl/LocaleNegotiation.h"
     28 #include "builtin/intl/NumberFormat.h"
     29 #include "builtin/temporal/Duration.h"
     30 #include "gc/AllocKind.h"
     31 #include "gc/GCContext.h"
     32 #include "js/CallArgs.h"
     33 #include "js/PropertyDescriptor.h"
     34 #include "js/PropertySpec.h"
     35 #include "js/RootingAPI.h"
     36 #include "js/TypeDecls.h"
     37 #include "js/Value.h"
     38 #include "vm/GlobalObject.h"
     39 #include "vm/JSContext.h"
     40 #include "vm/PlainObject.h"
     41 #include "vm/SelfHosting.h"
     42 #include "vm/WellKnownAtom.h"
     43 
     44 #include "vm/JSObject-inl.h"
     45 #include "vm/NativeObject-inl.h"
     46 
     47 using namespace js;
     48 using namespace js::intl;
     49 
     50 static constexpr auto durationUnits = std::array{
     51    temporal::TemporalUnit::Year,        temporal::TemporalUnit::Month,
     52    temporal::TemporalUnit::Week,        temporal::TemporalUnit::Day,
     53    temporal::TemporalUnit::Hour,        temporal::TemporalUnit::Minute,
     54    temporal::TemporalUnit::Second,      temporal::TemporalUnit::Millisecond,
     55    temporal::TemporalUnit::Microsecond, temporal::TemporalUnit::Nanosecond,
     56 };
     57 
     58 const JSClass DurationFormatObject::class_ = {
     59    "Intl.DurationFormat",
     60    JSCLASS_HAS_RESERVED_SLOTS(DurationFormatObject::SLOT_COUNT) |
     61        JSCLASS_HAS_CACHED_PROTO(JSProto_DurationFormat) |
     62        JSCLASS_FOREGROUND_FINALIZE,
     63    &DurationFormatObject::classOps_,
     64    &DurationFormatObject::classSpec_,
     65 };
     66 
     67 const JSClass& DurationFormatObject::protoClass_ = PlainObject::class_;
     68 
     69 static bool durationFormat_format(JSContext* cx, unsigned argc, Value* vp);
     70 static bool durationFormat_formatToParts(JSContext* cx, unsigned argc,
     71                                         Value* vp);
     72 static bool durationFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
     73                                              Value* vp);
     74 
     75 static bool durationFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
     76  CallArgs args = CallArgsFromVp(argc, vp);
     77  args.rval().setString(cx->names().DurationFormat);
     78  return true;
     79 }
     80 
     81 static const JSFunctionSpec durationFormat_static_methods[] = {
     82    JS_FN("supportedLocalesOf", durationFormat_supportedLocalesOf, 1, 0),
     83    JS_FS_END,
     84 };
     85 
     86 static const JSFunctionSpec durationFormat_methods[] = {
     87    JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DurationFormat_resolvedOptions",
     88                      0, 0),
     89    JS_FN("format", durationFormat_format, 1, 0),
     90    JS_FN("formatToParts", durationFormat_formatToParts, 1, 0),
     91    JS_FN("toSource", durationFormat_toSource, 0, 0),
     92    JS_FS_END,
     93 };
     94 
     95 static const JSPropertySpec durationFormat_properties[] = {
     96    JS_STRING_SYM_PS(toStringTag, "Intl.DurationFormat", JSPROP_READONLY),
     97    JS_PS_END,
     98 };
     99 
    100 static bool DurationFormat(JSContext* cx, unsigned argc, Value* vp);
    101 
    102 const JSClassOps DurationFormatObject::classOps_ = {
    103    nullptr,                         // addProperty
    104    nullptr,                         // delProperty
    105    nullptr,                         // enumerate
    106    nullptr,                         // newEnumerate
    107    nullptr,                         // resolve
    108    nullptr,                         // mayResolve
    109    DurationFormatObject::finalize,  // finalize
    110    nullptr,                         // call
    111    nullptr,                         // construct
    112    nullptr,                         // trace
    113 };
    114 
    115 const ClassSpec DurationFormatObject::classSpec_ = {
    116    GenericCreateConstructor<DurationFormat, 0, gc::AllocKind::FUNCTION>,
    117    GenericCreatePrototype<DurationFormatObject>,
    118    durationFormat_static_methods,
    119    nullptr,
    120    durationFormat_methods,
    121    durationFormat_properties,
    122    nullptr,
    123    ClassSpec::DontDefineConstructor,
    124 };
    125 
    126 void js::DurationFormatObject::finalize(JS::GCContext* gcx, JSObject* obj) {
    127  MOZ_ASSERT(gcx->onMainThread());
    128 
    129  auto* durationFormat = &obj->as<DurationFormatObject>();
    130 
    131  for (auto unit : durationUnits) {
    132    if (auto* nf = durationFormat->getNumberFormat(unit)) {
    133      RemoveICUCellMemory(gcx, obj, NumberFormatObject::EstimatedMemoryUse);
    134      delete nf;
    135    }
    136  }
    137 
    138  if (auto* lf = durationFormat->getListFormat()) {
    139    RemoveICUCellMemory(gcx, obj, ListFormatObject::EstimatedMemoryUse);
    140    delete lf;
    141  }
    142 
    143  if (auto* options = durationFormat->getOptions()) {
    144    gcx->delete_(obj, options, MemoryUse::IntlOptions);
    145  }
    146 }
    147 
    148 /**
    149 * Intl.DurationFormat ( [ locales [ , options ] ] )
    150 */
    151 static bool DurationFormat(JSContext* cx, unsigned argc, Value* vp) {
    152  CallArgs args = CallArgsFromVp(argc, vp);
    153 
    154  // Step 1.
    155  if (!ThrowIfNotConstructing(cx, args, "Intl.DurationFormat")) {
    156    return false;
    157  }
    158 
    159  // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
    160  RootedObject proto(cx);
    161  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_DurationFormat,
    162                                          &proto)) {
    163    return false;
    164  }
    165 
    166  Rooted<DurationFormatObject*> durationFormat(
    167      cx, NewObjectWithClassProto<DurationFormatObject>(cx, proto));
    168  if (!durationFormat) {
    169    return false;
    170  }
    171 
    172  HandleValue locales = args.get(0);
    173  HandleValue options = args.get(1);
    174 
    175  // Steps 3-28.
    176  if (!InitializeObject(cx, durationFormat,
    177                        cx->names().InitializeDurationFormat, locales,
    178                        options)) {
    179    return false;
    180  }
    181 
    182  args.rval().setObject(*durationFormat);
    183  return true;
    184 }
    185 
    186 /**
    187 * Returns the time separator string for the given locale and numbering system.
    188 */
    189 static JSString* GetTimeSeparator(
    190    JSContext* cx, Handle<DurationFormatObject*> durationFormat) {
    191  if (auto* separator = durationFormat->getTimeSeparator()) {
    192    return separator;
    193  }
    194 
    195  Rooted<JSObject*> internals(cx, GetInternalsObject(cx, durationFormat));
    196  if (!internals) {
    197    return nullptr;
    198  }
    199 
    200  Rooted<Value> value(cx);
    201 
    202  if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
    203    return nullptr;
    204  }
    205 
    206  UniqueChars locale = EncodeLocale(cx, value.toString());
    207  if (!locale) {
    208    return nullptr;
    209  }
    210 
    211  if (!GetProperty(cx, internals, internals, cx->names().numberingSystem,
    212                   &value)) {
    213    return nullptr;
    214  }
    215 
    216  UniqueChars numberingSystem = EncodeAscii(cx, value.toString());
    217  if (!numberingSystem) {
    218    return nullptr;
    219  }
    220 
    221  FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> separator(cx);
    222  auto result = mozilla::intl::DateTimeFormat::GetTimeSeparator(
    223      mozilla::MakeStringSpan(locale.get()),
    224      mozilla::MakeStringSpan(numberingSystem.get()), separator);
    225  if (result.isErr()) {
    226    ReportInternalError(cx, result.unwrapErr());
    227    return nullptr;
    228  }
    229 
    230  auto* string = separator.toString(cx);
    231  if (!string) {
    232    return nullptr;
    233  }
    234 
    235  durationFormat->setTimeSeparator(string);
    236  return string;
    237 }
    238 
    239 struct DurationValue {
    240  // The seconds part in a `temporal::TimeDuration` can't exceed
    241  // 9'007'199'254'740'991 and the nanoseconds part can't exceed 999'999'999.
    242  // This means the string representation needs at most 27 characters.
    243  static constexpr size_t MaximumDecimalStringLength =
    244      /* sign */ 1 +
    245      /* seconds part */ 16 +
    246      /* decimal dot */ 1 +
    247      /* nanoseconds part */ 9;
    248 
    249  // Next power of two after `MaximumDecimalStringLength`.
    250  static constexpr size_t DecimalStringCapacity = 32;
    251 
    252  double number = 0;
    253  char decimal[DecimalStringCapacity] = {};
    254 
    255  explicit DurationValue() = default;
    256  explicit DurationValue(double number) : number(number) {}
    257 
    258  bool isNegative() const {
    259    return mozilla::IsNegative(number) || decimal[0] == '-';
    260  }
    261 
    262  auto abs() const {
    263    // Return unchanged if not negative.
    264    if (!isNegative()) {
    265      return *this;
    266    }
    267 
    268    // Call |std::abs| for non-decimal values.
    269    if (!isDecimal()) {
    270      return DurationValue{std::abs(number)};
    271    }
    272 
    273    // Copy decimal strings without the leading '-' sign character.
    274    auto result = DurationValue{};
    275    std::copy(std::next(decimal), std::end(decimal), result.decimal);
    276    return result;
    277  }
    278 
    279  // |number| is active by default unless |decimal| is used.
    280  bool isDecimal() const { return decimal[0] != '\0'; }
    281 
    282  // Return true if this value represents either +0 or -0.
    283  bool isZero() const { return number == 0 && !isDecimal(); }
    284 
    285  operator std::string_view() const {
    286    MOZ_ASSERT(isDecimal());
    287    return {decimal};
    288  }
    289 };
    290 
    291 /**
    292 * Return the |unit| value from |duration|.
    293 */
    294 static auto ToDurationValue(const temporal::Duration& duration,
    295                            temporal::TemporalUnit unit) {
    296  using namespace temporal;
    297 
    298  switch (unit) {
    299    case TemporalUnit::Year:
    300      return DurationValue{duration.years};
    301    case TemporalUnit::Month:
    302      return DurationValue{duration.months};
    303    case TemporalUnit::Week:
    304      return DurationValue{duration.weeks};
    305    case TemporalUnit::Day:
    306      return DurationValue{duration.days};
    307    case TemporalUnit::Hour:
    308      return DurationValue{duration.hours};
    309    case TemporalUnit::Minute:
    310      return DurationValue{duration.minutes};
    311    case TemporalUnit::Second:
    312      return DurationValue{duration.seconds};
    313    case TemporalUnit::Millisecond:
    314      return DurationValue{duration.milliseconds};
    315    case TemporalUnit::Microsecond:
    316      return DurationValue{duration.microseconds};
    317    case TemporalUnit::Nanosecond:
    318      return DurationValue{duration.nanoseconds};
    319    case TemporalUnit::Unset:
    320    case TemporalUnit::Auto:
    321      break;
    322  }
    323  MOZ_CRASH("invalid temporal unit");
    324 }
    325 
    326 /**
    327 * Return the "display" property name for |unit|.
    328 */
    329 static PropertyName* DurationDisplayName(temporal::TemporalUnit unit,
    330                                         JSContext* cx) {
    331  using namespace temporal;
    332 
    333  switch (unit) {
    334    case TemporalUnit::Year:
    335      return cx->names().yearsDisplay;
    336    case TemporalUnit::Month:
    337      return cx->names().monthsDisplay;
    338    case TemporalUnit::Week:
    339      return cx->names().weeksDisplay;
    340    case TemporalUnit::Day:
    341      return cx->names().daysDisplay;
    342    case TemporalUnit::Hour:
    343      return cx->names().hoursDisplay;
    344    case TemporalUnit::Minute:
    345      return cx->names().minutesDisplay;
    346    case TemporalUnit::Second:
    347      return cx->names().secondsDisplay;
    348    case TemporalUnit::Millisecond:
    349      return cx->names().millisecondsDisplay;
    350    case TemporalUnit::Microsecond:
    351      return cx->names().microsecondsDisplay;
    352    case TemporalUnit::Nanosecond:
    353      return cx->names().nanosecondsDisplay;
    354    case TemporalUnit::Unset:
    355    case TemporalUnit::Auto:
    356      break;
    357  }
    358  MOZ_CRASH("invalid temporal unit");
    359 }
    360 
    361 /**
    362 * Convert |value|, which must be a string, to a |DurationDisplay|.
    363 */
    364 static bool ToDurationDisplay(JSContext* cx, const Value& value,
    365                              DurationDisplay* result) {
    366  MOZ_ASSERT(value.isString());
    367 
    368  auto* linear = value.toString()->ensureLinear(cx);
    369  if (!linear) {
    370    return false;
    371  }
    372 
    373  if (StringEqualsAscii(linear, "auto")) {
    374    *result = DurationDisplay::Auto;
    375  } else {
    376    MOZ_ASSERT(StringEqualsAscii(linear, "always"));
    377    *result = DurationDisplay::Always;
    378  }
    379  return true;
    380 }
    381 
    382 /**
    383 * Return the "style" property name for |unit|.
    384 */
    385 static PropertyName* DurationStyleName(temporal::TemporalUnit unit,
    386                                       JSContext* cx) {
    387  using namespace temporal;
    388 
    389  switch (unit) {
    390    case TemporalUnit::Year:
    391      return cx->names().yearsStyle;
    392    case TemporalUnit::Month:
    393      return cx->names().monthsStyle;
    394    case TemporalUnit::Week:
    395      return cx->names().weeksStyle;
    396    case TemporalUnit::Day:
    397      return cx->names().daysStyle;
    398    case TemporalUnit::Hour:
    399      return cx->names().hoursStyle;
    400    case TemporalUnit::Minute:
    401      return cx->names().minutesStyle;
    402    case TemporalUnit::Second:
    403      return cx->names().secondsStyle;
    404    case TemporalUnit::Millisecond:
    405      return cx->names().millisecondsStyle;
    406    case TemporalUnit::Microsecond:
    407      return cx->names().microsecondsStyle;
    408    case TemporalUnit::Nanosecond:
    409      return cx->names().nanosecondsStyle;
    410    case TemporalUnit::Unset:
    411    case TemporalUnit::Auto:
    412      break;
    413  }
    414  MOZ_CRASH("invalid temporal unit");
    415 }
    416 
    417 /**
    418 * Convert |value|, which must be a string, to a |DurationStyle|.
    419 */
    420 static bool ToDurationStyle(JSContext* cx, const Value& value,
    421                            DurationStyle* result) {
    422  MOZ_ASSERT(value.isString());
    423 
    424  auto* linear = value.toString()->ensureLinear(cx);
    425  if (!linear) {
    426    return false;
    427  }
    428 
    429  if (StringEqualsAscii(linear, "long")) {
    430    *result = DurationStyle::Long;
    431  } else if (StringEqualsAscii(linear, "short")) {
    432    *result = DurationStyle::Short;
    433  } else if (StringEqualsAscii(linear, "narrow")) {
    434    *result = DurationStyle::Narrow;
    435  } else if (StringEqualsAscii(linear, "numeric")) {
    436    *result = DurationStyle::Numeric;
    437  } else {
    438    MOZ_ASSERT(StringEqualsAscii(linear, "2-digit"));
    439    *result = DurationStyle::TwoDigit;
    440  }
    441  return true;
    442 }
    443 
    444 /**
    445 * Return the fractional digits setting from |durationFormat|.
    446 */
    447 static std::pair<uint32_t, uint32_t> GetFractionalDigits(
    448    const DurationFormatObject* durationFormat) {
    449  auto* options = durationFormat->getOptions();
    450  MOZ_ASSERT(options, "unexpected unresolved duration format options");
    451 
    452  int8_t digits = options->fractionalDigits;
    453  MOZ_ASSERT(digits <= 9);
    454 
    455  if (digits < 0) {
    456    return {0U, 9U};
    457  }
    458  return {uint32_t(digits), uint32_t(digits)};
    459 }
    460 
    461 static DurationUnitOptions GetUnitOptions(const DurationFormatOptions& options,
    462                                          temporal::TemporalUnit unit) {
    463  using namespace temporal;
    464 
    465  switch (unit) {
    466 #define GET_UNIT_OPTIONS(name) \
    467  DurationUnitOptions { options.name##Display, options.name##Style }
    468 
    469    case TemporalUnit::Year:
    470      return GET_UNIT_OPTIONS(years);
    471    case TemporalUnit::Month:
    472      return GET_UNIT_OPTIONS(months);
    473    case TemporalUnit::Week:
    474      return GET_UNIT_OPTIONS(weeks);
    475    case TemporalUnit::Day:
    476      return GET_UNIT_OPTIONS(days);
    477    case TemporalUnit::Hour:
    478      return GET_UNIT_OPTIONS(hours);
    479    case TemporalUnit::Minute:
    480      return GET_UNIT_OPTIONS(minutes);
    481    case TemporalUnit::Second:
    482      return GET_UNIT_OPTIONS(seconds);
    483    case TemporalUnit::Millisecond:
    484      return GET_UNIT_OPTIONS(milliseconds);
    485    case TemporalUnit::Microsecond:
    486      return GET_UNIT_OPTIONS(microseconds);
    487    case TemporalUnit::Nanosecond:
    488      return GET_UNIT_OPTIONS(nanoseconds);
    489    case TemporalUnit::Unset:
    490    case TemporalUnit::Auto:
    491      break;
    492 
    493 #undef GET_UNIT_OPTIONS
    494  }
    495  MOZ_CRASH("invalid duration unit");
    496 }
    497 
    498 static void SetUnitOptions(DurationFormatOptions& options,
    499                           temporal::TemporalUnit unit,
    500                           const DurationUnitOptions& unitOptions) {
    501  using namespace temporal;
    502 
    503  switch (unit) {
    504 #define SET_UNIT_OPTIONS(name)                    \
    505  do {                                            \
    506    options.name##Display = unitOptions.display_; \
    507    options.name##Style = unitOptions.style_;     \
    508  } while (0)
    509 
    510    case TemporalUnit::Year:
    511      SET_UNIT_OPTIONS(years);
    512      return;
    513    case TemporalUnit::Month:
    514      SET_UNIT_OPTIONS(months);
    515      return;
    516    case TemporalUnit::Week:
    517      SET_UNIT_OPTIONS(weeks);
    518      return;
    519    case TemporalUnit::Day:
    520      SET_UNIT_OPTIONS(days);
    521      return;
    522    case TemporalUnit::Hour:
    523      SET_UNIT_OPTIONS(hours);
    524      return;
    525    case TemporalUnit::Minute:
    526      SET_UNIT_OPTIONS(minutes);
    527      return;
    528    case TemporalUnit::Second:
    529      SET_UNIT_OPTIONS(seconds);
    530      return;
    531    case TemporalUnit::Millisecond:
    532      SET_UNIT_OPTIONS(milliseconds);
    533      return;
    534    case TemporalUnit::Microsecond:
    535      SET_UNIT_OPTIONS(microseconds);
    536      return;
    537    case TemporalUnit::Nanosecond:
    538      SET_UNIT_OPTIONS(nanoseconds);
    539      return;
    540    case TemporalUnit::Unset:
    541    case TemporalUnit::Auto:
    542      break;
    543 
    544 #undef SET_UNIT_OPTIONS
    545  }
    546  MOZ_CRASH("invalid duration unit");
    547 }
    548 
    549 static DurationFormatOptions* NewDurationFormatOptions(
    550    JSContext* cx, Handle<DurationFormatObject*> durationFormat) {
    551  Rooted<JSObject*> internals(cx, GetInternalsObject(cx, durationFormat));
    552  if (!internals) {
    553    return nullptr;
    554  }
    555 
    556  auto options = cx->make_unique<DurationFormatOptions>();
    557  if (!options) {
    558    return nullptr;
    559  }
    560 
    561  Rooted<Value> value(cx);
    562  for (temporal::TemporalUnit unit : durationUnits) {
    563    DurationDisplay display;
    564    if (!GetProperty(cx, internals, internals, DurationDisplayName(unit, cx),
    565                     &value)) {
    566      return nullptr;
    567    }
    568    if (!ToDurationDisplay(cx, value, &display)) {
    569      return nullptr;
    570    }
    571 
    572    DurationStyle style;
    573    if (!GetProperty(cx, internals, internals, DurationStyleName(unit, cx),
    574                     &value)) {
    575      return nullptr;
    576    }
    577    if (!ToDurationStyle(cx, value, &style)) {
    578      return nullptr;
    579    }
    580 
    581    SetUnitOptions(*options, unit,
    582                   DurationUnitOptions{static_cast<uint8_t>(display),
    583                                       static_cast<uint8_t>(style)});
    584  }
    585 
    586  if (!GetProperty(cx, internals, internals, cx->names().fractionalDigits,
    587                   &value)) {
    588    return nullptr;
    589  }
    590  if (value.isUndefined()) {
    591    options->fractionalDigits = -1;
    592  } else {
    593    options->fractionalDigits = value.toInt32();
    594  }
    595 
    596  return options.release();
    597 }
    598 
    599 static DurationFormatOptions* GetOrCreateDurationFormatOptions(
    600    JSContext* cx, Handle<DurationFormatObject*> durationFormat) {
    601  auto* options = durationFormat->getOptions();
    602  if (options) {
    603    return options;
    604  }
    605 
    606  options = NewDurationFormatOptions(cx, durationFormat);
    607  if (!options) {
    608    return nullptr;
    609  }
    610  durationFormat->setOptions(options);
    611 
    612  AddCellMemory(durationFormat, sizeof(DurationFormatOptions),
    613                MemoryUse::IntlOptions);
    614  return options;
    615 }
    616 
    617 /**
    618 * Return the locale for `mozilla::intl::NumberFormat` objects.
    619 */
    620 static UniqueChars NewDurationNumberFormatLocale(
    621    JSContext* cx, Handle<DurationFormatObject*> durationFormat) {
    622  // ICU expects numberingSystem as a Unicode locale extensions on locale.
    623 
    624  Rooted<JSObject*> internals(cx, GetInternalsObject(cx, durationFormat));
    625  if (!internals) {
    626    return nullptr;
    627  }
    628 
    629  JS::RootedVector<UnicodeExtensionKeyword> keywords(cx);
    630 
    631  Rooted<Value> value(cx);
    632  if (!GetProperty(cx, internals, internals, cx->names().numberingSystem,
    633                   &value)) {
    634    return nullptr;
    635  }
    636 
    637  {
    638    auto* numberingSystem = value.toString()->ensureLinear(cx);
    639    if (!numberingSystem) {
    640      return nullptr;
    641    }
    642 
    643    if (!keywords.emplaceBack("nu", numberingSystem)) {
    644      return nullptr;
    645    }
    646  }
    647 
    648  return FormatLocale(cx, internals, keywords);
    649 }
    650 
    651 /**
    652 * Create a `mozilla::intl::NumberFormat` instance based on |internals.locale|
    653 * and |options|.
    654 */
    655 static mozilla::intl::NumberFormat* NewDurationNumberFormat(
    656    JSContext* cx, Handle<DurationFormatObject*> durationFormat,
    657    const mozilla::intl::NumberFormatOptions& options) {
    658  auto locale = NewDurationNumberFormatLocale(cx, durationFormat);
    659  if (!locale) {
    660    return nullptr;
    661  }
    662 
    663  auto result = mozilla::intl::NumberFormat::TryCreate(locale.get(), options);
    664  if (result.isErr()) {
    665    ReportInternalError(cx, result.unwrapErr());
    666    return nullptr;
    667  }
    668  return result.unwrap().release();
    669 }
    670 
    671 /**
    672 * Return the singular name for |unit|.
    673 */
    674 static std::string_view UnitName(temporal::TemporalUnit unit) {
    675  using namespace temporal;
    676 
    677  switch (unit) {
    678    case TemporalUnit::Year:
    679      return "year";
    680    case TemporalUnit::Month:
    681      return "month";
    682    case TemporalUnit::Week:
    683      return "week";
    684    case TemporalUnit::Day:
    685      return "day";
    686    case TemporalUnit::Hour:
    687      return "hour";
    688    case TemporalUnit::Minute:
    689      return "minute";
    690    case TemporalUnit::Second:
    691      return "second";
    692    case TemporalUnit::Millisecond:
    693      return "millisecond";
    694    case TemporalUnit::Microsecond:
    695      return "microsecond";
    696    case TemporalUnit::Nanosecond:
    697      return "nanosecond";
    698    case TemporalUnit::Unset:
    699    case TemporalUnit::Auto:
    700      break;
    701  }
    702  MOZ_CRASH("invalid temporal unit");
    703 }
    704 
    705 /**
    706 * Return the singular name for |unit|.
    707 */
    708 static auto PartUnitName(temporal::TemporalUnit unit) {
    709  using namespace temporal;
    710 
    711  switch (unit) {
    712    case TemporalUnit::Year:
    713      return &JSAtomState::year;
    714    case TemporalUnit::Month:
    715      return &JSAtomState::month;
    716    case TemporalUnit::Week:
    717      return &JSAtomState::week;
    718    case TemporalUnit::Day:
    719      return &JSAtomState::day;
    720    case TemporalUnit::Hour:
    721      return &JSAtomState::hour;
    722    case TemporalUnit::Minute:
    723      return &JSAtomState::minute;
    724    case TemporalUnit::Second:
    725      return &JSAtomState::second;
    726    case TemporalUnit::Millisecond:
    727      return &JSAtomState::millisecond;
    728    case TemporalUnit::Microsecond:
    729      return &JSAtomState::microsecond;
    730    case TemporalUnit::Nanosecond:
    731      return &JSAtomState::nanosecond;
    732    case TemporalUnit::Unset:
    733    case TemporalUnit::Auto:
    734      break;
    735  }
    736  MOZ_CRASH("invalid temporal unit");
    737 }
    738 
    739 /**
    740 * Convert a duration-style to the corresponding NumberFormat unit-display.
    741 */
    742 static auto UnitDisplay(DurationStyle style) {
    743  using UnitDisplay = mozilla::intl::NumberFormatOptions::UnitDisplay;
    744 
    745  switch (style) {
    746    case DurationStyle::Long:
    747      return UnitDisplay::Long;
    748    case DurationStyle::Short:
    749      return UnitDisplay::Short;
    750    case DurationStyle::Narrow:
    751      return UnitDisplay::Narrow;
    752    case DurationStyle::Numeric:
    753    case DurationStyle::TwoDigit:
    754      // Both numeric styles are invalid inputs for this function.
    755      break;
    756  }
    757  MOZ_CRASH("invalid duration style");
    758 }
    759 
    760 /**
    761 * ComputeFractionalDigits ( durationFormat, duration )
    762 *
    763 * Return the fractional seconds from |duration| as an exact value. This is
    764 * either an integer Number value when the fractional part is zero, or a
    765 * decimal string when the fractional part is non-zero.
    766 */
    767 static auto ComputeFractionalDigits(const temporal::Duration& duration,
    768                                    temporal::TemporalUnit unit) {
    769  using namespace temporal;
    770 
    771  MOZ_ASSERT(IsValidDuration(duration));
    772  MOZ_ASSERT(TemporalUnit::Second <= unit && unit <= TemporalUnit::Microsecond);
    773 
    774  // Directly return the duration amount when no sub-seconds are present, i.e.
    775  // the fractional part is zero.
    776  TimeDuration timeDuration;
    777  int32_t exponent;
    778  switch (unit) {
    779    case TemporalUnit::Second: {
    780      if (duration.milliseconds == 0 && duration.microseconds == 0 &&
    781          duration.nanoseconds == 0) {
    782        return DurationValue{duration.seconds};
    783      }
    784      timeDuration = TimeDurationFromComponents({
    785          0,
    786          0,
    787          0,
    788          0,
    789          0,
    790          0,
    791          duration.seconds,
    792          duration.milliseconds,
    793          duration.microseconds,
    794          duration.nanoseconds,
    795      });
    796      exponent = 100'000'000;
    797      break;
    798    }
    799 
    800    case TemporalUnit::Millisecond: {
    801      if (duration.microseconds == 0 && duration.nanoseconds == 0) {
    802        return DurationValue{duration.milliseconds};
    803      }
    804      timeDuration = TimeDurationFromComponents({
    805          0,
    806          0,
    807          0,
    808          0,
    809          0,
    810          0,
    811          0,
    812          duration.milliseconds,
    813          duration.microseconds,
    814          duration.nanoseconds,
    815      });
    816      exponent = 100'000;
    817      break;
    818    }
    819 
    820    case TemporalUnit::Microsecond: {
    821      if (duration.nanoseconds == 0) {
    822        return DurationValue{duration.microseconds};
    823      }
    824      timeDuration = TimeDurationFromComponents({
    825          0,
    826          0,
    827          0,
    828          0,
    829          0,
    830          0,
    831          0,
    832          0,
    833          duration.microseconds,
    834          duration.nanoseconds,
    835      });
    836      exponent = 100;
    837      break;
    838    }
    839 
    840    default:
    841      MOZ_CRASH("bad temporal unit");
    842  }
    843 
    844  // Return the result as a decimal string when the fractional part is non-zero.
    845 
    846  DurationValue result{};
    847 
    848  char* chars = result.decimal;
    849 
    850  // Leading '-' sign when the duration is negative.
    851  if (timeDuration < TimeDuration{}) {
    852    *chars++ = '-';
    853    timeDuration = timeDuration.abs();
    854  }
    855 
    856  // Next the string representation of the seconds value.
    857  auto res =
    858      std::to_chars(chars, std::end(result.decimal), timeDuration.seconds);
    859  MOZ_ASSERT(res.ec == std::errc());
    860 
    861  // Set |chars| to one past the last character written by `std::to_chars`.
    862  chars = res.ptr;
    863 
    864  // Finish with string representation of the nanoseconds value, without any
    865  // trailing zeros.
    866  int32_t nanos = timeDuration.nanoseconds;
    867  for (int32_t k = 100'000'000; k != 0 && nanos != 0; k /= 10) {
    868    // Add decimal separator add the correct position based on |exponent|.
    869    if (k == exponent) {
    870      *chars++ = '.';
    871    }
    872 
    873    *chars++ = char('0' + (nanos / k));
    874    nanos %= k;
    875  }
    876 
    877  MOZ_ASSERT((chars - result.decimal) <=
    878                 ptrdiff_t(DurationValue::MaximumDecimalStringLength),
    879             "unexpected decimal string length");
    880 
    881  return result;
    882 }
    883 
    884 /**
    885 * FormatNumericHours ( durationFormat, hoursValue, signDisplayed )
    886 *
    887 * FormatNumericMinutes ( durationFormat, minutesValue, hoursDisplayed,
    888 * signDisplayed )
    889 *
    890 * FormatNumericSeconds ( durationFormat, secondsValue, minutesDisplayed,
    891 * signDisplayed )
    892 */
    893 static mozilla::intl::NumberFormat* NewNumericFormatter(
    894    JSContext* cx, Handle<DurationFormatObject*> durationFormat,
    895    temporal::TemporalUnit unit) {
    896  // FormatNumericHours, step 1. (Not applicable in our implementation.)
    897  // FormatNumericMinutes, steps 1-2. (Not applicable in our implementation.)
    898  // FormatNumericSeconds, steps 1-2. (Not applicable in our implementation.)
    899 
    900  // FormatNumericHours, step 2.
    901  // FormatNumericMinutes, step 3.
    902  // FormatNumericSeconds, step 3.
    903  auto* dfOptions = durationFormat->getOptions();
    904  MOZ_ASSERT(dfOptions, "unexpected unresolved duration format options");
    905 
    906  auto style = GetUnitOptions(*dfOptions, unit).style();
    907 
    908  // FormatNumericHours, step 3.
    909  // FormatNumericMinutes, step 4.
    910  // FormatNumericSeconds, step 4.
    911  MOZ_ASSERT(style == DurationStyle::Numeric ||
    912             style == DurationStyle::TwoDigit);
    913 
    914  // FormatNumericHours, step 4.
    915  // FormatNumericMinutes, step 5.
    916  // FormatNumericSeconds, step 5.
    917  mozilla::intl::NumberFormatOptions options{};
    918 
    919  // FormatNumericHours, steps 5-6. (Not applicable in our implementation.)
    920  // FormatNumericMinutes, steps 6-7. (Not applicable in our implementation.)
    921  // FormatNumericSeconds, steps 6-7. (Not applicable in our implementation.)
    922 
    923  // FormatNumericHours, step 7.
    924  // FormatNumericMinutes, step 8.
    925  // FormatNumericSeconds, step 8.
    926  if (style == DurationStyle::TwoDigit) {
    927    options.mMinIntegerDigits = mozilla::Some(2);
    928  }
    929 
    930  // FormatNumericHours, step 8. (Not applicable in our implementation.)
    931  // FormatNumericMinutes, step 9. (Not applicable in our implementation.)
    932  // FormatNumericSeconds, step 9. (Not applicable in our implementation.)
    933 
    934  // FormatNumericHours, step 9.
    935  // FormatNumericMinutes, step 10.
    936  // FormatNumericSeconds, step 10.
    937  options.mGrouping = mozilla::intl::NumberFormatOptions::Grouping::Never;
    938 
    939  // FormatNumericSeconds, steps 11-14.
    940  if (unit == temporal::TemporalUnit::Second) {
    941    // FormatNumericSeconds, step 11.
    942    auto fractionalDigits = GetFractionalDigits(durationFormat);
    943 
    944    // FormatNumericSeconds, steps 12-13.
    945    options.mFractionDigits = mozilla::Some(fractionalDigits);
    946 
    947    // FormatNumericSeconds, step 14.
    948    options.mRoundingMode =
    949        mozilla::intl::NumberFormatOptions::RoundingMode::Trunc;
    950  }
    951 
    952  // FormatNumericHours, step 10.
    953  // FormatNumericMinutes, step 11.
    954  // FormatNumericSeconds, step 15.
    955  return NewDurationNumberFormat(cx, durationFormat, options);
    956 }
    957 
    958 static mozilla::intl::NumberFormat* GetOrCreateNumericFormatter(
    959    JSContext* cx, Handle<DurationFormatObject*> durationFormat,
    960    temporal::TemporalUnit unit) {
    961  // Obtain a cached mozilla::intl::NumberFormat object.
    962  auto* nf = durationFormat->getNumberFormat(unit);
    963  if (nf) {
    964    return nf;
    965  }
    966 
    967  nf = NewNumericFormatter(cx, durationFormat, unit);
    968  if (!nf) {
    969    return nullptr;
    970  }
    971  durationFormat->setNumberFormat(unit, nf);
    972 
    973  AddICUCellMemory(durationFormat, NumberFormatObject::EstimatedMemoryUse);
    974  return nf;
    975 }
    976 
    977 /**
    978 * NextUnitFractional ( durationFormat, unit )
    979 */
    980 static bool NextUnitFractional(const DurationFormatObject* durationFormat,
    981                               temporal::TemporalUnit unit) {
    982  using namespace temporal;
    983 
    984  // Steps 1-3.
    985  if (TemporalUnit::Second <= unit && unit <= TemporalUnit::Microsecond) {
    986    auto* options = durationFormat->getOptions();
    987    MOZ_ASSERT(options, "unexpected unresolved duration format options");
    988 
    989    using TemporalUnitType = std::underlying_type_t<TemporalUnit>;
    990 
    991    auto nextUnit =
    992        static_cast<TemporalUnit>(static_cast<TemporalUnitType>(unit) + 1);
    993    auto nextStyle = GetUnitOptions(*options, nextUnit).style();
    994    return nextStyle == DurationStyle::Numeric;
    995  }
    996 
    997  // Step 4.
    998  return false;
    999 }
   1000 
   1001 /**
   1002 * PartitionDurationFormatPattern ( durationFormat, duration )
   1003 */
   1004 static mozilla::intl::NumberFormat* NewNumberFormat(
   1005    JSContext* cx, Handle<DurationFormatObject*> durationFormat,
   1006    temporal::TemporalUnit unit, DurationStyle style) {
   1007  // Step 4.h.i.
   1008  mozilla::intl::NumberFormatOptions options{};
   1009 
   1010  // Step 4.h.ii.
   1011  if (NextUnitFractional(durationFormat, unit)) {
   1012    // Steps 4.h.ii.2-4.
   1013    auto fractionalDigits = GetFractionalDigits(durationFormat);
   1014    options.mFractionDigits = mozilla::Some(fractionalDigits);
   1015 
   1016    // Step 4.h.ii.5.
   1017    options.mRoundingMode =
   1018        mozilla::intl::NumberFormatOptions::RoundingMode::Trunc;
   1019  }
   1020 
   1021  // Steps 4.h.iii.4-6.
   1022  options.mUnit = mozilla::Some(std::pair{UnitName(unit), UnitDisplay(style)});
   1023 
   1024  // Step 4.h.iii.7.
   1025  return NewDurationNumberFormat(cx, durationFormat, options);
   1026 }
   1027 
   1028 static mozilla::intl::NumberFormat* GetOrCreateNumberFormat(
   1029    JSContext* cx, Handle<DurationFormatObject*> durationFormat,
   1030    temporal::TemporalUnit unit, DurationStyle style) {
   1031  // Obtain a cached mozilla::intl::NumberFormat object.
   1032  auto* nf = durationFormat->getNumberFormat(unit);
   1033  if (nf) {
   1034    return nf;
   1035  }
   1036 
   1037  nf = NewNumberFormat(cx, durationFormat, unit, style);
   1038  if (!nf) {
   1039    return nullptr;
   1040  }
   1041  durationFormat->setNumberFormat(unit, nf);
   1042 
   1043  AddICUCellMemory(durationFormat, NumberFormatObject::EstimatedMemoryUse);
   1044  return nf;
   1045 }
   1046 
   1047 static JSLinearString* FormatDurationValueToString(
   1048    JSContext* cx, mozilla::intl::NumberFormat* nf,
   1049    const DurationValue& value) {
   1050  if (value.isDecimal()) {
   1051    return FormatNumber(cx, nf, std::string_view{value});
   1052  }
   1053  return FormatNumber(cx, nf, value.number);
   1054 }
   1055 
   1056 static ArrayObject* FormatDurationValueToParts(JSContext* cx,
   1057                                               mozilla::intl::NumberFormat* nf,
   1058                                               const DurationValue& value,
   1059                                               temporal::TemporalUnit unit) {
   1060  if (value.isDecimal()) {
   1061    return FormatNumberToParts(cx, nf, std::string_view{value},
   1062                               PartUnitName(unit));
   1063  }
   1064  return FormatNumberToParts(cx, nf, value.number, PartUnitName(unit));
   1065 }
   1066 
   1067 static bool FormatDurationValue(JSContext* cx, mozilla::intl::NumberFormat* nf,
   1068                                temporal::TemporalUnit unit,
   1069                                const DurationValue& value, bool formatToParts,
   1070                                MutableHandle<Value> result) {
   1071  if (!formatToParts) {
   1072    auto* str = FormatDurationValueToString(cx, nf, value);
   1073    if (!str) {
   1074      return false;
   1075    }
   1076    result.setString(str);
   1077  } else {
   1078    auto* parts = FormatDurationValueToParts(cx, nf, value, unit);
   1079    if (!parts) {
   1080      return false;
   1081    }
   1082    result.setObject(*parts);
   1083  }
   1084  return true;
   1085 }
   1086 
   1087 /**
   1088 * FormatNumericHours ( durationFormat, hoursValue, signDisplayed )
   1089 *
   1090 * FormatNumericMinutes ( durationFormat, minutesValue, hoursDisplayed,
   1091 * signDisplayed )
   1092 *
   1093 * FormatNumericSeconds ( durationFormat, secondsValue, minutesDisplayed,
   1094 * signDisplayed )
   1095 */
   1096 static bool FormatNumericHoursOrMinutesOrSeconds(
   1097    JSContext* cx, Handle<DurationFormatObject*> durationFormat,
   1098    temporal::TemporalUnit unit, const DurationValue& value, bool formatToParts,
   1099    MutableHandle<Value> result) {
   1100  MOZ_ASSERT(temporal::TemporalUnit::Hour <= unit &&
   1101             unit <= temporal::TemporalUnit::Second);
   1102 
   1103  // FormatNumericHours, steps 1-10.
   1104  // FormatNumericMinutes, steps 1-11.
   1105  // FormatNumericSeconds, steps 1-15.
   1106  auto* nf = GetOrCreateNumericFormatter(cx, durationFormat, unit);
   1107  if (!nf) {
   1108    return false;
   1109  }
   1110 
   1111  // FormatNumericHours, steps 11-13.
   1112  // FormatNumericMinutes, steps 12-14.
   1113  // FormatNumericSeconds, steps 16-18.
   1114  return FormatDurationValue(cx, nf, unit, value, formatToParts, result);
   1115 }
   1116 
   1117 static PlainObject* NewLiteralPart(JSContext* cx, JSString* value) {
   1118  Rooted<IdValueVector> properties(cx, cx);
   1119  if (!properties.emplaceBack(NameToId(cx->names().type),
   1120                              StringValue(cx->names().literal))) {
   1121    return nullptr;
   1122  }
   1123  if (!properties.emplaceBack(NameToId(cx->names().value),
   1124                              StringValue(value))) {
   1125    return nullptr;
   1126  }
   1127 
   1128  return NewPlainObjectWithUniqueNames(cx, properties);
   1129 }
   1130 
   1131 /**
   1132 * FormatNumericUnits ( durationFormat, duration, firstNumericUnit,
   1133 * signDisplayed )
   1134 */
   1135 static bool FormatNumericUnits(JSContext* cx,
   1136                               Handle<DurationFormatObject*> durationFormat,
   1137                               const temporal::Duration& duration,
   1138                               temporal::TemporalUnit firstNumericUnit,
   1139                               bool signDisplayed, bool formatToParts,
   1140                               MutableHandle<Value> result) {
   1141  using namespace temporal;
   1142 
   1143  auto* options = durationFormat->getOptions();
   1144  MOZ_ASSERT(options, "unexpected unresolved duration format options");
   1145 
   1146  Rooted<Value> formattedValue(cx);
   1147 
   1148  // Step 1.
   1149  MOZ_ASSERT(TemporalUnit::Hour <= firstNumericUnit &&
   1150             firstNumericUnit <= TemporalUnit::Second);
   1151 
   1152  // Step 2.
   1153  using FormattedNumericUnitsVector = JS::GCVector<Value, 3>;
   1154  Rooted<FormattedNumericUnitsVector> numericPartsList(cx, cx);
   1155  if (!numericPartsList.reserve(3)) {
   1156    return false;
   1157  }
   1158 
   1159  // Step 3.
   1160  auto hoursValue = DurationValue{duration.hours};
   1161 
   1162  // Step 4.
   1163  auto hoursDisplay = GetUnitOptions(*options, TemporalUnit::Hour).display();
   1164 
   1165  // Step 5.
   1166  auto minutesValue = DurationValue{duration.minutes};
   1167 
   1168  // Step 6.
   1169  auto minutesDisplay =
   1170      GetUnitOptions(*options, TemporalUnit::Minute).display();
   1171 
   1172  // Step 7-8.
   1173  auto secondsValue = ComputeFractionalDigits(duration, TemporalUnit::Second);
   1174 
   1175  // Step 9.
   1176  auto secondsDisplay =
   1177      GetUnitOptions(*options, TemporalUnit::Second).display();
   1178 
   1179  // Step 10.
   1180  bool hoursFormatted = false;
   1181 
   1182  // Step 11.
   1183  if (firstNumericUnit == TemporalUnit::Hour) {
   1184    // Step 11.a.
   1185    hoursFormatted =
   1186        !hoursValue.isZero() || hoursDisplay == DurationDisplay::Always;
   1187  }
   1188 
   1189  // Steps 12-13.
   1190  bool secondsFormatted =
   1191      !secondsValue.isZero() || secondsDisplay == DurationDisplay::Always;
   1192 
   1193  // Step 14.
   1194  bool minutesFormatted = false;
   1195 
   1196  // Step 15.
   1197  if (firstNumericUnit == TemporalUnit::Hour ||
   1198      firstNumericUnit == TemporalUnit::Minute) {
   1199    // Steps 15.a-b.
   1200    minutesFormatted = (hoursFormatted && secondsFormatted) ||
   1201                       !minutesValue.isZero() ||
   1202                       minutesDisplay == DurationDisplay::Always;
   1203  }
   1204 
   1205  // Return early when no units are displayed.
   1206  if (!hoursFormatted && !minutesFormatted && !secondsFormatted) {
   1207    return true;
   1208  }
   1209 
   1210  // Step 16.
   1211  if (hoursFormatted) {
   1212    // Step 16.a.
   1213    if (signDisplayed) {
   1214      if (hoursValue.isZero() && temporal::DurationSign(duration) < 0) {
   1215        hoursValue = DurationValue{-0.0};
   1216      }
   1217    } else {
   1218      // Use the absolute value to avoid changing number-format sign display.
   1219      hoursValue = hoursValue.abs();
   1220    }
   1221 
   1222    // Step 16.b.
   1223    if (!FormatNumericHoursOrMinutesOrSeconds(cx, durationFormat,
   1224                                              TemporalUnit::Hour, hoursValue,
   1225                                              formatToParts, &formattedValue)) {
   1226      return false;
   1227    }
   1228 
   1229    // Step 16.c.
   1230    numericPartsList.infallibleAppend(formattedValue);
   1231 
   1232    // Step 16.d.
   1233    signDisplayed = false;
   1234  }
   1235 
   1236  // Step 17.
   1237  if (minutesFormatted) {
   1238    // Step 17.a.
   1239    if (signDisplayed) {
   1240      if (minutesValue.isZero() && temporal::DurationSign(duration) < 0) {
   1241        minutesValue = DurationValue{-0.0};
   1242      }
   1243    } else {
   1244      // Use the absolute value to avoid changing number-format sign display.
   1245      minutesValue = minutesValue.abs();
   1246    }
   1247 
   1248    // Step 17.b.
   1249    if (!FormatNumericHoursOrMinutesOrSeconds(
   1250            cx, durationFormat, TemporalUnit::Minute, minutesValue,
   1251            formatToParts, &formattedValue)) {
   1252      return false;
   1253    }
   1254 
   1255    // Step 17.c.
   1256    numericPartsList.infallibleAppend(formattedValue);
   1257 
   1258    // Step 17.d.
   1259    signDisplayed = false;
   1260  }
   1261 
   1262  // Step 18.
   1263  if (secondsFormatted) {
   1264    // Step 18.a.
   1265    if (!signDisplayed) {
   1266      // Use the absolute value to avoid changing number-format sign display.
   1267      secondsValue = secondsValue.abs();
   1268    }
   1269    if (!FormatNumericHoursOrMinutesOrSeconds(
   1270            cx, durationFormat, TemporalUnit::Second, secondsValue,
   1271            formatToParts, &formattedValue)) {
   1272      return false;
   1273    }
   1274 
   1275    // Step 18.b.
   1276    numericPartsList.infallibleAppend(formattedValue);
   1277  }
   1278 
   1279  MOZ_ASSERT(numericPartsList.length() > 0);
   1280 
   1281  // Step 19.
   1282  if (numericPartsList.length() <= 1) {
   1283    result.set(numericPartsList[0]);
   1284    return true;
   1285  }
   1286 
   1287  Rooted<JSString*> timeSeparator(cx, GetTimeSeparator(cx, durationFormat));
   1288  if (!timeSeparator) {
   1289    return false;
   1290  }
   1291 
   1292  // Combine the individual parts into a single result.
   1293  if (!formatToParts) {
   1294    // Perform string concatenation when not formatting to parts.
   1295 
   1296    Rooted<JSString*> string(cx, numericPartsList[0].toString());
   1297    Rooted<JSString*> nextString(cx);
   1298    for (size_t i = 1; i < numericPartsList.length(); i++) {
   1299      // Add the time separator between all elements.
   1300      string = ConcatStrings<CanGC>(cx, string, timeSeparator);
   1301      if (!string) {
   1302        return false;
   1303      }
   1304 
   1305      // Concatenate the formatted parts.
   1306      nextString = numericPartsList[i].toString();
   1307      string = ConcatStrings<CanGC>(cx, string, nextString);
   1308      if (!string) {
   1309        return false;
   1310      }
   1311    }
   1312 
   1313    result.setString(string);
   1314  } else {
   1315    // Append all formatted parts into a new array when formatting to parts.
   1316 
   1317    // First compute the final length of the result array.
   1318    size_t length = 0;
   1319    for (size_t i = 0; i < numericPartsList.length(); i++) {
   1320      length += numericPartsList[i].toObject().as<ArrayObject>().length();
   1321    }
   1322 
   1323    // Account for the time separator parts.
   1324    length += numericPartsList.length() - 1;
   1325 
   1326    Rooted<ArrayObject*> array(cx, NewDenseFullyAllocatedArray(cx, length));
   1327    if (!array) {
   1328      return false;
   1329    }
   1330    array->ensureDenseInitializedLength(0, length);
   1331 
   1332    size_t index = 0;
   1333    for (size_t i = 0; i < numericPartsList.length(); i++) {
   1334      // Add the time separator between all elements.
   1335      if (i > 0) {
   1336        auto* timeSeparatorPart = NewLiteralPart(cx, timeSeparator);
   1337        if (!timeSeparatorPart) {
   1338          return false;
   1339        }
   1340        array->initDenseElement(index++, ObjectValue(*timeSeparatorPart));
   1341      }
   1342 
   1343      auto* part = &numericPartsList[i].toObject().as<ArrayObject>();
   1344      MOZ_ASSERT(IsPackedArray(part));
   1345 
   1346      // Append the formatted parts from |part|.
   1347      for (size_t j = 0; j < part->length(); j++) {
   1348        array->initDenseElement(index++, part->getDenseElement(j));
   1349      }
   1350    }
   1351    MOZ_ASSERT(index == length);
   1352 
   1353    result.setObject(*array);
   1354  }
   1355  return true;
   1356 }
   1357 
   1358 static mozilla::intl::ListFormat* NewDurationListFormat(
   1359    JSContext* cx, Handle<DurationFormatObject*> durationFormat) {
   1360  Rooted<JSObject*> internals(cx, GetInternalsObject(cx, durationFormat));
   1361  if (!internals) {
   1362    return nullptr;
   1363  }
   1364 
   1365  Rooted<Value> value(cx);
   1366  if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
   1367    return nullptr;
   1368  }
   1369 
   1370  UniqueChars locale = EncodeLocale(cx, value.toString());
   1371  if (!locale) {
   1372    return nullptr;
   1373  }
   1374 
   1375  mozilla::intl::ListFormat::Options options;
   1376  options.mType = mozilla::intl::ListFormat::Type::Unit;
   1377 
   1378  if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
   1379    return nullptr;
   1380  }
   1381  {
   1382    auto* linear = value.toString()->ensureLinear(cx);
   1383    if (!linear) {
   1384      return nullptr;
   1385    }
   1386 
   1387    using ListFormatStyle = mozilla::intl::ListFormat::Style;
   1388    if (StringEqualsLiteral(linear, "long")) {
   1389      options.mStyle = ListFormatStyle::Long;
   1390    } else if (StringEqualsLiteral(linear, "short")) {
   1391      options.mStyle = ListFormatStyle::Short;
   1392    } else if (StringEqualsLiteral(linear, "narrow")) {
   1393      options.mStyle = ListFormatStyle::Narrow;
   1394    } else {
   1395      MOZ_ASSERT(StringEqualsLiteral(linear, "digital"));
   1396      options.mStyle = ListFormatStyle::Short;
   1397    }
   1398  }
   1399 
   1400  auto result = mozilla::intl::ListFormat::TryCreate(
   1401      mozilla::MakeStringSpan(locale.get()), options);
   1402  if (result.isErr()) {
   1403    ReportInternalError(cx, result.unwrapErr());
   1404    return nullptr;
   1405  }
   1406  return result.unwrap().release();
   1407 }
   1408 
   1409 static mozilla::intl::ListFormat* GetOrCreateListFormat(
   1410    JSContext* cx, Handle<DurationFormatObject*> durationFormat) {
   1411  // Obtain a cached mozilla::intl::ListFormat object.
   1412  auto* lf = durationFormat->getListFormat();
   1413  if (lf) {
   1414    return lf;
   1415  }
   1416 
   1417  lf = NewDurationListFormat(cx, durationFormat);
   1418  if (!lf) {
   1419    return nullptr;
   1420  }
   1421  durationFormat->setListFormat(lf);
   1422 
   1423  AddICUCellMemory(durationFormat, ListFormatObject::EstimatedMemoryUse);
   1424  return lf;
   1425 }
   1426 
   1427 // Stack space must be large enough to hold all ten duration values.
   1428 static constexpr size_t FormattedDurationValueVectorCapacity = 10;
   1429 
   1430 using FormattedDurationValueVector =
   1431    JS::GCVector<JS::Value, FormattedDurationValueVectorCapacity>;
   1432 
   1433 /**
   1434 * ListFormatParts ( durationFormat, partitionedPartsList )
   1435 */
   1436 static bool ListFormatParts(
   1437    JSContext* cx, Handle<DurationFormatObject*> durationFormat,
   1438    Handle<FormattedDurationValueVector> partitionedPartsList,
   1439    bool formatToParts, MutableHandle<Value> result) {
   1440  // Steps 1-6.
   1441  auto* lf = GetOrCreateListFormat(cx, durationFormat);
   1442  if (!lf) {
   1443    return false;
   1444  }
   1445 
   1446  // <https://unicode.org/reports/tr35/tr35-general.html#ListPatterns> requires
   1447  // that the list patterns are sorted, for example "{1} and {0}" isn't a valid
   1448  // pattern, because "{1}" appears before "{0}". This requirement also means
   1449  // all entries appear in order in the formatted result.
   1450 
   1451  // Step 7.
   1452  Vector<UniqueTwoByteChars, mozilla::intl::DEFAULT_LIST_LENGTH> strings(cx);
   1453  mozilla::intl::ListFormat::StringList stringList{};
   1454 
   1455  // Step 8.
   1456  Rooted<JSString*> string(cx);
   1457  Rooted<JSString*> nextString(cx);
   1458  Rooted<ArrayObject*> parts(cx);
   1459  Rooted<NativeObject*> part(cx);
   1460  Rooted<Value> value(cx);
   1461  for (size_t i = 0; i < partitionedPartsList.length(); i++) {
   1462    if (!formatToParts) {
   1463      string = partitionedPartsList[i].toString();
   1464    } else {
   1465      parts = &partitionedPartsList[i].toObject().as<ArrayObject>();
   1466      MOZ_ASSERT(IsPackedArray(parts));
   1467 
   1468      // Combine the individual number-formatted parts into a single string.
   1469      string = cx->emptyString();
   1470      for (size_t j = 0; j < parts->length(); j++) {
   1471        part = &parts->getDenseElement(j).toObject().as<NativeObject>();
   1472        MOZ_ASSERT(part->containsPure(cx->names().type) &&
   1473                       part->containsPure(cx->names().value),
   1474                   "part is a number-formatted element");
   1475 
   1476        if (!GetProperty(cx, part, part, cx->names().value, &value)) {
   1477          return false;
   1478        }
   1479        MOZ_ASSERT(value.isString());
   1480 
   1481        nextString = value.toString();
   1482        string = ConcatStrings<CanGC>(cx, string, nextString);
   1483        if (!string) {
   1484          return false;
   1485        }
   1486      }
   1487    }
   1488 
   1489    auto* linear = string->ensureLinear(cx);
   1490    if (!linear) {
   1491      return false;
   1492    }
   1493 
   1494    size_t linearLength = linear->length();
   1495 
   1496    auto chars = cx->make_pod_array<char16_t>(linearLength);
   1497    if (!chars) {
   1498      return false;
   1499    }
   1500    CopyChars(chars.get(), *linear);
   1501 
   1502    if (!strings.append(std::move(chars))) {
   1503      return false;
   1504    }
   1505 
   1506    if (!stringList.emplaceBack(strings[i].get(), linearLength)) {
   1507      return false;
   1508    }
   1509  }
   1510 
   1511  FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
   1512  mozilla::intl::ListFormat::PartVector partVector{};
   1513 
   1514  // Step 9.
   1515  auto formatResult = formatToParts
   1516                          ? lf->FormatToParts(stringList, buffer, partVector)
   1517                          : lf->Format(stringList, buffer);
   1518  if (formatResult.isErr()) {
   1519    ReportInternalError(cx, formatResult.unwrapErr());
   1520    return false;
   1521  }
   1522 
   1523  Rooted<JSLinearString*> overallResult(cx, buffer.toString(cx));
   1524  if (!overallResult) {
   1525    return false;
   1526  }
   1527 
   1528  // Directly return the string result when not formatting to parts.
   1529  if (!formatToParts) {
   1530    result.setString(overallResult);
   1531    return true;
   1532  }
   1533 
   1534  // Step 10.
   1535  size_t partitionedPartsIndex = 0;
   1536 
   1537  // Step 11. (Not applicable in our implementation.)
   1538 
   1539  // Compute the final length of the result array.
   1540  size_t flattenedLength = 0;
   1541  for (size_t i = 0; i < partitionedPartsList.length(); i++) {
   1542    auto* parts = &partitionedPartsList[i].toObject().as<ArrayObject>();
   1543    flattenedLength += parts->length();
   1544  }
   1545  for (const auto& part : partVector) {
   1546    if (part.first == mozilla::intl::ListFormat::PartType::Literal) {
   1547      flattenedLength += 1;
   1548    }
   1549  }
   1550 
   1551  // Step 12.
   1552  Rooted<ArrayObject*> flattenedPartsList(
   1553      cx, NewDenseFullyAllocatedArray(cx, flattenedLength));
   1554  if (!flattenedPartsList) {
   1555    return false;
   1556  }
   1557  flattenedPartsList->ensureDenseInitializedLength(0, flattenedLength);
   1558 
   1559  // Step 13.
   1560  size_t flattenedPartsIndex = 0;
   1561  size_t partBeginIndex = 0;
   1562  for (const auto& part : partVector) {
   1563    // Steps 13.a-b.
   1564    if (part.first == mozilla::intl::ListFormat::PartType::Element) {
   1565      // Step 13.a.i.
   1566      MOZ_ASSERT(partitionedPartsIndex < partitionedPartsList.length(),
   1567                 "partitionedPartsIndex is an index into result");
   1568 
   1569      // Step 13.a.ii.
   1570      auto* parts = &partitionedPartsList[partitionedPartsIndex]
   1571                         .toObject()
   1572                         .as<ArrayObject>();
   1573      MOZ_ASSERT(IsPackedArray(parts));
   1574 
   1575      // Step 13.a.iii.
   1576      //
   1577      // Replace the "element" parts with the number-formatted result.
   1578      for (size_t i = 0; i < parts->length(); i++) {
   1579        flattenedPartsList->initDenseElement(flattenedPartsIndex++,
   1580                                             parts->getDenseElement(i));
   1581      }
   1582 
   1583      // Step 13.a.iv.
   1584      partitionedPartsIndex += 1;
   1585    } else {
   1586      // Step 13.b.i.
   1587      //
   1588      // Append "literal" parts as-is.
   1589      MOZ_ASSERT(part.first == mozilla::intl::ListFormat::PartType::Literal);
   1590 
   1591      // Step 13.b.ii.
   1592      MOZ_ASSERT(part.second >= partBeginIndex);
   1593      auto* partStr = NewDependentString(cx, overallResult, partBeginIndex,
   1594                                         part.second - partBeginIndex);
   1595      if (!partStr) {
   1596        return false;
   1597      }
   1598 
   1599      auto* literalPart = NewLiteralPart(cx, partStr);
   1600      if (!literalPart) {
   1601        return false;
   1602      }
   1603 
   1604      flattenedPartsList->initDenseElement(flattenedPartsIndex++,
   1605                                           ObjectValue(*literalPart));
   1606    }
   1607 
   1608    partBeginIndex = part.second;
   1609  }
   1610 
   1611  MOZ_ASSERT(partitionedPartsIndex == partitionedPartsList.length(),
   1612             "all number-formatted parts handled");
   1613  MOZ_ASSERT(flattenedPartsIndex == flattenedLength,
   1614             "flattened array length miscomputed");
   1615 
   1616  // Step 14.
   1617  result.setObject(*flattenedPartsList);
   1618  return true;
   1619 }
   1620 
   1621 /**
   1622 * PartitionDurationFormatPattern ( durationFormat, duration )
   1623 */
   1624 static bool PartitionDurationFormatPattern(
   1625    JSContext* cx, Handle<DurationFormatObject*> durationFormat,
   1626    Handle<Value> durationLike, bool formatToParts,
   1627    MutableHandle<Value> result) {
   1628  using namespace temporal;
   1629 
   1630  Duration duration;
   1631  if (!ToTemporalDuration(cx, durationLike, &duration)) {
   1632    return false;
   1633  }
   1634 
   1635  // Normalize -0 to +0 by adding zero.
   1636  duration.years += +0.0;
   1637  duration.months += +0.0;
   1638  duration.weeks += +0.0;
   1639  duration.days += +0.0;
   1640  duration.hours += +0.0;
   1641  duration.minutes += +0.0;
   1642  duration.seconds += +0.0;
   1643  duration.milliseconds += +0.0;
   1644  duration.microseconds += +0.0;
   1645  duration.nanoseconds += +0.0;
   1646 
   1647  static_assert(durationUnits.size() == FormattedDurationValueVectorCapacity,
   1648                "inline stack capacity large enough for all duration units");
   1649 
   1650  auto* options = GetOrCreateDurationFormatOptions(cx, durationFormat);
   1651  if (!options) {
   1652    return false;
   1653  }
   1654 
   1655  Rooted<Value> formattedValue(cx);
   1656 
   1657  // Step 1.
   1658  Rooted<FormattedDurationValueVector> formattedValues(cx, cx);
   1659  if (!formattedValues.reserve(FormattedDurationValueVectorCapacity)) {
   1660    return false;
   1661  }
   1662 
   1663  // Step 2.
   1664  bool signDisplayed = true;
   1665 
   1666  // Step 3.
   1667  bool numericUnitFound = false;
   1668 
   1669  // Step 4.
   1670  for (auto unit : durationUnits) {
   1671    if (numericUnitFound) {
   1672      break;
   1673    }
   1674 
   1675    // Step 4.a. (Moved below)
   1676 
   1677    // Step 4.b.
   1678    auto unitOptions = GetUnitOptions(*options, unit);
   1679 
   1680    // Step 4.c.
   1681    auto style = unitOptions.style();
   1682 
   1683    // Step 4.d.
   1684    auto display = unitOptions.display();
   1685 
   1686    // Steps 4.e-f. (Not applicable in our implementation.)
   1687 
   1688    // Steps 4.g-h.
   1689    if (style == DurationStyle::Numeric || style == DurationStyle::TwoDigit) {
   1690      // Step 4.g.i.
   1691      if (!FormatNumericUnits(cx, durationFormat, duration, unit, signDisplayed,
   1692                              formatToParts, &formattedValue)) {
   1693        return false;
   1694      }
   1695 
   1696      // Step 4.g.ii.
   1697      if (!formattedValue.isUndefined()) {
   1698        formattedValues.infallibleAppend(formattedValue);
   1699      }
   1700 
   1701      // Step 4.g.iii.
   1702      numericUnitFound = true;
   1703    } else {
   1704      // Step 4.a.
   1705      auto value = ToDurationValue(duration, unit);
   1706 
   1707      // Step 4.h.i. (Performed in NewNumberFormat)
   1708 
   1709      // Step 4.h.ii.
   1710      if (NextUnitFractional(durationFormat, unit)) {
   1711        // Step 4.h.ii.1.
   1712        value = ComputeFractionalDigits(duration, unit);
   1713 
   1714        // Steps 4.h.ii.2-5. (Performed in NewNumberFormat)
   1715 
   1716        // Step 4.h.ii.6.
   1717        numericUnitFound = true;
   1718      }
   1719 
   1720      // Step 4.h.iii. (Condition inverted to reduce indentation.)
   1721      if (display == DurationDisplay::Auto && value.isZero()) {
   1722        continue;
   1723      }
   1724 
   1725      // Steps 4.h.iii.2-3.
   1726      if (signDisplayed) {
   1727        // Step 4.h.iii.2.a.
   1728        signDisplayed = false;
   1729 
   1730        // Step 4.h.iii.2.b.
   1731        if (value.isZero() && temporal::DurationSign(duration) < 0) {
   1732          value = DurationValue{-0.0};
   1733        }
   1734      } else {
   1735        // Use the absolute value to avoid changing number-format sign display.
   1736        value = value.abs();
   1737      }
   1738 
   1739      // Steps 4.h.iii.1, 4.h.iii.4-7.
   1740      auto* nf = GetOrCreateNumberFormat(cx, durationFormat, unit, style);
   1741      if (!nf) {
   1742        return false;
   1743      }
   1744 
   1745      // Steps 4.h.iii.8-10.
   1746      if (!FormatDurationValue(cx, nf, unit, value, formatToParts,
   1747                               &formattedValue)) {
   1748        return false;
   1749      }
   1750 
   1751      // Step 4.h.iii.11.
   1752      formattedValues.infallibleAppend(formattedValue);
   1753    }
   1754  }
   1755 
   1756  // Step 5.
   1757  return ListFormatParts(cx, durationFormat, formattedValues, formatToParts,
   1758                         result);
   1759 }
   1760 
   1761 static bool IsDurationFormat(HandleValue v) {
   1762  return v.isObject() && v.toObject().is<DurationFormatObject>();
   1763 }
   1764 
   1765 /**
   1766 * Intl.DurationFormat.prototype.format ( durationLike )
   1767 */
   1768 static bool durationFormat_format(JSContext* cx, const JS::CallArgs& args) {
   1769  Rooted<DurationFormatObject*> durationFormat(
   1770      cx, &args.thisv().toObject().as<DurationFormatObject>());
   1771  return PartitionDurationFormatPattern(
   1772      cx, durationFormat, args.get(0), /* formatToParts= */ false, args.rval());
   1773 }
   1774 
   1775 /**
   1776 * Intl.DurationFormat.prototype.format ( durationLike )
   1777 */
   1778 static bool durationFormat_format(JSContext* cx, unsigned argc, Value* vp) {
   1779  CallArgs args = CallArgsFromVp(argc, vp);
   1780  return CallNonGenericMethod<IsDurationFormat, durationFormat_format>(cx,
   1781                                                                       args);
   1782 }
   1783 
   1784 /**
   1785 * Intl.DurationFormat.prototype.formatToParts ( durationLike )
   1786 */
   1787 static bool durationFormat_formatToParts(JSContext* cx,
   1788                                         const JS::CallArgs& args) {
   1789  Rooted<DurationFormatObject*> durationFormat(
   1790      cx, &args.thisv().toObject().as<DurationFormatObject>());
   1791  return PartitionDurationFormatPattern(cx, durationFormat, args.get(0),
   1792                                        /* formatToParts= */ true, args.rval());
   1793 }
   1794 
   1795 /**
   1796 * Intl.DurationFormat.prototype.formatToParts ( durationLike )
   1797 */
   1798 static bool durationFormat_formatToParts(JSContext* cx, unsigned argc,
   1799                                         Value* vp) {
   1800  CallArgs args = CallArgsFromVp(argc, vp);
   1801  return CallNonGenericMethod<IsDurationFormat, durationFormat_formatToParts>(
   1802      cx, args);
   1803 }
   1804 
   1805 /**
   1806 * Intl.DurationFormat.supportedLocalesOf ( locales [ , options ] )
   1807 */
   1808 static bool durationFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
   1809                                              Value* vp) {
   1810  CallArgs args = CallArgsFromVp(argc, vp);
   1811 
   1812  // Steps 1-3.
   1813  auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::DurationFormat,
   1814                                   args.get(0), args.get(1));
   1815  if (!array) {
   1816    return false;
   1817  }
   1818  args.rval().setObject(*array);
   1819  return true;
   1820 }
   1821 
   1822 bool js::TemporalDurationToLocaleString(JSContext* cx,
   1823                                        const JS::CallArgs& args) {
   1824  MOZ_ASSERT(args.thisv().isObject());
   1825  MOZ_ASSERT(args.thisv().toObject().is<temporal::DurationObject>());
   1826 
   1827  Rooted<DurationFormatObject*> durationFormat(
   1828      cx, NewBuiltinClassInstance<DurationFormatObject>(cx));
   1829  if (!durationFormat) {
   1830    return false;
   1831  }
   1832 
   1833  if (!intl::InitializeObject(cx, durationFormat,
   1834                              cx->names().InitializeDurationFormat, args.get(0),
   1835                              args.get(1))) {
   1836    return false;
   1837  }
   1838 
   1839  return PartitionDurationFormatPattern(cx, durationFormat, args.thisv(),
   1840                                        /* formatToParts= */ false,
   1841                                        args.rval());
   1842 }