tor-browser

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

NumberFormat.cpp (44568B)


      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.NumberFormat implementation. */
      8 
      9 #include "builtin/intl/NumberFormat.h"
     10 
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/Casting.h"
     13 #include "mozilla/FloatingPoint.h"
     14 #include "mozilla/intl/Locale.h"
     15 #include "mozilla/intl/MeasureUnit.h"
     16 #include "mozilla/intl/MeasureUnitGenerated.h"
     17 #include "mozilla/intl/NumberFormat.h"
     18 #include "mozilla/intl/NumberingSystem.h"
     19 #include "mozilla/intl/NumberRangeFormat.h"
     20 #include "mozilla/Span.h"
     21 #include "mozilla/TextUtils.h"
     22 #include "mozilla/UniquePtr.h"
     23 
     24 #include <algorithm>
     25 #include <stddef.h>
     26 #include <stdint.h>
     27 #include <string>
     28 #include <string_view>
     29 #include <type_traits>
     30 
     31 #include "builtin/Array.h"
     32 #include "builtin/intl/CommonFunctions.h"
     33 #include "builtin/intl/FormatBuffer.h"
     34 #include "builtin/intl/LanguageTag.h"
     35 #include "builtin/intl/LocaleNegotiation.h"
     36 #include "builtin/intl/RelativeTimeFormat.h"
     37 #include "gc/GCContext.h"
     38 #include "js/CharacterEncoding.h"
     39 #include "js/PropertySpec.h"
     40 #include "js/RootingAPI.h"
     41 #include "js/TypeDecls.h"
     42 #include "util/Text.h"
     43 #include "vm/BigIntType.h"
     44 #include "vm/GlobalObject.h"
     45 #include "vm/JSContext.h"
     46 #include "vm/PlainObject.h"  // js::PlainObject
     47 #include "vm/StringType.h"
     48 
     49 #include "vm/GeckoProfiler-inl.h"
     50 #include "vm/JSObject-inl.h"
     51 #include "vm/NativeObject-inl.h"
     52 
     53 using namespace js;
     54 using namespace js::intl;
     55 
     56 using mozilla::AssertedCast;
     57 
     58 using js::intl::DateTimeFormatOptions;
     59 
     60 const JSClassOps NumberFormatObject::classOps_ = {
     61    nullptr,                       // addProperty
     62    nullptr,                       // delProperty
     63    nullptr,                       // enumerate
     64    nullptr,                       // newEnumerate
     65    nullptr,                       // resolve
     66    nullptr,                       // mayResolve
     67    NumberFormatObject::finalize,  // finalize
     68    nullptr,                       // call
     69    nullptr,                       // construct
     70    nullptr,                       // trace
     71 };
     72 
     73 const JSClass NumberFormatObject::class_ = {
     74    "Intl.NumberFormat",
     75    JSCLASS_HAS_RESERVED_SLOTS(NumberFormatObject::SLOT_COUNT) |
     76        JSCLASS_HAS_CACHED_PROTO(JSProto_NumberFormat) |
     77        JSCLASS_FOREGROUND_FINALIZE,
     78    &NumberFormatObject::classOps_,
     79    &NumberFormatObject::classSpec_,
     80 };
     81 
     82 const JSClass& NumberFormatObject::protoClass_ = PlainObject::class_;
     83 
     84 static bool numberFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
     85                                            Value* vp);
     86 
     87 static bool numberFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
     88  CallArgs args = CallArgsFromVp(argc, vp);
     89  args.rval().setString(cx->names().NumberFormat);
     90  return true;
     91 }
     92 
     93 static const JSFunctionSpec numberFormat_static_methods[] = {
     94    JS_FN("supportedLocalesOf", numberFormat_supportedLocalesOf, 1, 0),
     95    JS_FS_END,
     96 };
     97 
     98 static const JSFunctionSpec numberFormat_methods[] = {
     99    JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0,
    100                      0),
    101    JS_SELF_HOSTED_FN("formatToParts", "Intl_NumberFormat_formatToParts", 1, 0),
    102    JS_SELF_HOSTED_FN("formatRange", "Intl_NumberFormat_formatRange", 2, 0),
    103    JS_SELF_HOSTED_FN("formatRangeToParts",
    104                      "Intl_NumberFormat_formatRangeToParts", 2, 0),
    105    JS_FN("toSource", numberFormat_toSource, 0, 0),
    106    JS_FS_END,
    107 };
    108 
    109 static const JSPropertySpec numberFormat_properties[] = {
    110    JS_SELF_HOSTED_GET("format", "$Intl_NumberFormat_format_get", 0),
    111    JS_STRING_SYM_PS(toStringTag, "Intl.NumberFormat", JSPROP_READONLY),
    112    JS_PS_END,
    113 };
    114 
    115 static bool NumberFormat(JSContext* cx, unsigned argc, Value* vp);
    116 
    117 const ClassSpec NumberFormatObject::classSpec_ = {
    118    GenericCreateConstructor<NumberFormat, 0, gc::AllocKind::FUNCTION>,
    119    GenericCreatePrototype<NumberFormatObject>,
    120    numberFormat_static_methods,
    121    nullptr,
    122    numberFormat_methods,
    123    numberFormat_properties,
    124    nullptr,
    125    ClassSpec::DontDefineConstructor,
    126 };
    127 
    128 /**
    129 * 15.1.1 Intl.NumberFormat ( [ locales [ , options ] ] )
    130 *
    131 * ES2025 Intl draft rev 5ea95f8a98d660e94c177d6f5e88c6d2962123b1
    132 */
    133 static bool NumberFormat(JSContext* cx, const CallArgs& args, bool construct) {
    134  AutoJSConstructorProfilerEntry pseudoFrame(cx, "Intl.NumberFormat");
    135 
    136  // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
    137 
    138  // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
    139  RootedObject proto(cx);
    140  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_NumberFormat,
    141                                          &proto)) {
    142    return false;
    143  }
    144 
    145  Rooted<NumberFormatObject*> numberFormat(cx);
    146  numberFormat = NewObjectWithClassProto<NumberFormatObject>(cx, proto);
    147  if (!numberFormat) {
    148    return false;
    149  }
    150 
    151  RootedValue thisValue(cx,
    152                        construct ? ObjectValue(*numberFormat) : args.thisv());
    153  HandleValue locales = args.get(0);
    154  HandleValue options = args.get(1);
    155 
    156  // Steps 3-33.
    157  return intl::InitializeNumberFormatObject(cx, numberFormat, thisValue,
    158                                            locales, options, args.rval());
    159 }
    160 
    161 static bool NumberFormat(JSContext* cx, unsigned argc, Value* vp) {
    162  CallArgs args = CallArgsFromVp(argc, vp);
    163  return NumberFormat(cx, args, args.isConstructing());
    164 }
    165 
    166 bool js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp) {
    167  CallArgs args = CallArgsFromVp(argc, vp);
    168  MOZ_ASSERT(args.length() == 2);
    169  MOZ_ASSERT(!args.isConstructing());
    170  // intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it
    171  // cannot be used with "new", but it still has to be treated as a
    172  // constructor.
    173  return NumberFormat(cx, args, true);
    174 }
    175 
    176 NumberFormatObject* js::intl::CreateNumberFormat(JSContext* cx,
    177                                                 Handle<Value> locales,
    178                                                 Handle<Value> options) {
    179  Rooted<NumberFormatObject*> numberFormat(
    180      cx, NewBuiltinClassInstance<NumberFormatObject>(cx));
    181  if (!numberFormat) {
    182    return nullptr;
    183  }
    184 
    185  Rooted<Value> thisValue(cx, ObjectValue(*numberFormat));
    186  Rooted<Value> ignored(cx);
    187  if (!InitializeNumberFormatObject(cx, numberFormat, thisValue, locales,
    188                                    options, &ignored)) {
    189    return nullptr;
    190  }
    191  MOZ_ASSERT(&ignored.toObject() == numberFormat);
    192 
    193  return numberFormat;
    194 }
    195 
    196 NumberFormatObject* js::intl::GetOrCreateNumberFormat(JSContext* cx,
    197                                                      Handle<Value> locales,
    198                                                      Handle<Value> options) {
    199  // Try to use a cached instance when |locales| is either undefined or a
    200  // string, and |options| is undefined.
    201  if ((locales.isUndefined() || locales.isString()) && options.isUndefined()) {
    202    Rooted<JSLinearString*> locale(cx);
    203    if (locales.isString()) {
    204      locale = locales.toString()->ensureLinear(cx);
    205      if (!locale) {
    206        return nullptr;
    207      }
    208    }
    209    return cx->global()->globalIntlData().getOrCreateNumberFormat(cx, locale);
    210  }
    211 
    212  // Create a new Intl.NumberFormat instance.
    213  return CreateNumberFormat(cx, locales, options);
    214 }
    215 
    216 void js::NumberFormatObject::finalize(JS::GCContext* gcx, JSObject* obj) {
    217  MOZ_ASSERT(gcx->onMainThread());
    218 
    219  auto* numberFormat = &obj->as<NumberFormatObject>();
    220  mozilla::intl::NumberFormat* nf = numberFormat->getNumberFormatter();
    221  mozilla::intl::NumberRangeFormat* nrf =
    222      numberFormat->getNumberRangeFormatter();
    223 
    224  if (nf) {
    225    intl::RemoveICUCellMemory(gcx, obj, NumberFormatObject::EstimatedMemoryUse);
    226    // This was allocated using `new` in mozilla::intl::NumberFormat, so we
    227    // delete here.
    228    delete nf;
    229  }
    230 
    231  if (nrf) {
    232    intl::RemoveICUCellMemory(gcx, obj, EstimatedRangeFormatterMemoryUse);
    233    // This was allocated using `new` in mozilla::intl::NumberRangeFormat, so we
    234    // delete here.
    235    delete nrf;
    236  }
    237 }
    238 
    239 bool js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp) {
    240  CallArgs args = CallArgsFromVp(argc, vp);
    241  MOZ_ASSERT(args.length() == 1);
    242  MOZ_ASSERT(args[0].isString());
    243 
    244  UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
    245  if (!locale) {
    246    return false;
    247  }
    248 
    249  auto numberingSystem =
    250      mozilla::intl::NumberingSystem::TryCreate(locale.get());
    251  if (numberingSystem.isErr()) {
    252    intl::ReportInternalError(cx, numberingSystem.unwrapErr());
    253    return false;
    254  }
    255 
    256  auto name = numberingSystem.inspect()->GetName();
    257  if (name.isErr()) {
    258    intl::ReportInternalError(cx, name.unwrapErr());
    259    return false;
    260  }
    261 
    262  JSString* jsname = NewStringCopy<CanGC>(cx, name.unwrap());
    263  if (!jsname) {
    264    return false;
    265  }
    266 
    267  args.rval().setString(jsname);
    268  return true;
    269 }
    270 
    271 #if DEBUG || MOZ_SYSTEM_ICU
    272 bool js::intl_availableMeasurementUnits(JSContext* cx, unsigned argc,
    273                                        Value* vp) {
    274  CallArgs args = CallArgsFromVp(argc, vp);
    275  MOZ_ASSERT(args.length() == 0);
    276 
    277  RootedObject measurementUnits(cx, NewPlainObjectWithProto(cx, nullptr));
    278  if (!measurementUnits) {
    279    return false;
    280  }
    281 
    282  auto units = mozilla::intl::MeasureUnit::GetAvailable();
    283  if (units.isErr()) {
    284    intl::ReportInternalError(cx, units.unwrapErr());
    285    return false;
    286  }
    287 
    288  Rooted<JSAtom*> unitAtom(cx);
    289  for (auto unit : units.unwrap()) {
    290    if (unit.isErr()) {
    291      intl::ReportInternalError(cx);
    292      return false;
    293    }
    294    auto unitIdentifier = unit.unwrap();
    295 
    296    unitAtom = Atomize(cx, unitIdentifier.data(), unitIdentifier.size());
    297    if (!unitAtom) {
    298      return false;
    299    }
    300 
    301    if (!DefineDataProperty(cx, measurementUnits, unitAtom->asPropertyName(),
    302                            TrueHandleValue)) {
    303      return false;
    304    }
    305  }
    306 
    307  args.rval().setObject(*measurementUnits);
    308  return true;
    309 }
    310 #endif
    311 
    312 static constexpr size_t MaxUnitLength() {
    313  size_t length = 0;
    314  for (const auto& unit : mozilla::intl::simpleMeasureUnits) {
    315    length = std::max(length, std::char_traits<char>::length(unit.name));
    316  }
    317  return length * 2 + std::char_traits<char>::length("-per-");
    318 }
    319 
    320 static UniqueChars NumberFormatLocale(JSContext* cx, HandleObject internals) {
    321  // ICU expects numberingSystem as a Unicode locale extensions on locale.
    322 
    323  JS::RootedVector<intl::UnicodeExtensionKeyword> keywords(cx);
    324 
    325  RootedValue value(cx);
    326  if (!GetProperty(cx, internals, internals, cx->names().numberingSystem,
    327                   &value)) {
    328    return nullptr;
    329  }
    330 
    331  {
    332    JSLinearString* numberingSystem = value.toString()->ensureLinear(cx);
    333    if (!numberingSystem) {
    334      return nullptr;
    335    }
    336 
    337    if (!keywords.emplaceBack("nu", numberingSystem)) {
    338      return nullptr;
    339    }
    340  }
    341 
    342  return intl::FormatLocale(cx, internals, keywords);
    343 }
    344 
    345 struct NumberFormatOptions : public mozilla::intl::NumberRangeFormatOptions {
    346  static_assert(std::is_base_of_v<mozilla::intl::NumberFormatOptions,
    347                                  mozilla::intl::NumberRangeFormatOptions>);
    348 
    349  char currencyChars[3] = {};
    350  char unitChars[MaxUnitLength()] = {};
    351 };
    352 
    353 static bool FillNumberFormatOptions(JSContext* cx, HandleObject internals,
    354                                    NumberFormatOptions& options) {
    355  RootedValue value(cx);
    356  if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
    357    return false;
    358  }
    359 
    360  bool accountingSign = false;
    361  {
    362    JSLinearString* style = value.toString()->ensureLinear(cx);
    363    if (!style) {
    364      return false;
    365    }
    366 
    367    if (StringEqualsLiteral(style, "currency")) {
    368      if (!GetProperty(cx, internals, internals, cx->names().currency,
    369                       &value)) {
    370        return false;
    371      }
    372      JSLinearString* currency = value.toString()->ensureLinear(cx);
    373      if (!currency) {
    374        return false;
    375      }
    376 
    377      MOZ_RELEASE_ASSERT(
    378          currency->length() == 3,
    379          "IsWellFormedCurrencyCode permits only length-3 strings");
    380      MOZ_ASSERT(StringIsAscii(currency),
    381                 "IsWellFormedCurrencyCode permits only ASCII strings");
    382      CopyChars(reinterpret_cast<Latin1Char*>(options.currencyChars),
    383                *currency);
    384 
    385      if (!GetProperty(cx, internals, internals, cx->names().currencyDisplay,
    386                       &value)) {
    387        return false;
    388      }
    389      JSLinearString* currencyDisplay = value.toString()->ensureLinear(cx);
    390      if (!currencyDisplay) {
    391        return false;
    392      }
    393 
    394      using CurrencyDisplay =
    395          mozilla::intl::NumberFormatOptions::CurrencyDisplay;
    396 
    397      CurrencyDisplay display;
    398      if (StringEqualsLiteral(currencyDisplay, "code")) {
    399        display = CurrencyDisplay::Code;
    400      } else if (StringEqualsLiteral(currencyDisplay, "symbol")) {
    401        display = CurrencyDisplay::Symbol;
    402      } else if (StringEqualsLiteral(currencyDisplay, "narrowSymbol")) {
    403        display = CurrencyDisplay::NarrowSymbol;
    404      } else {
    405        MOZ_ASSERT(StringEqualsLiteral(currencyDisplay, "name"));
    406        display = CurrencyDisplay::Name;
    407      }
    408 
    409      if (!GetProperty(cx, internals, internals, cx->names().currencySign,
    410                       &value)) {
    411        return false;
    412      }
    413      JSLinearString* currencySign = value.toString()->ensureLinear(cx);
    414      if (!currencySign) {
    415        return false;
    416      }
    417 
    418      if (StringEqualsLiteral(currencySign, "accounting")) {
    419        accountingSign = true;
    420      } else {
    421        MOZ_ASSERT(StringEqualsLiteral(currencySign, "standard"));
    422      }
    423 
    424      options.mCurrency = mozilla::Some(
    425          std::make_pair(std::string_view(options.currencyChars, 3), display));
    426    } else if (StringEqualsLiteral(style, "percent")) {
    427      options.mPercent = true;
    428    } else if (StringEqualsLiteral(style, "unit")) {
    429      if (!GetProperty(cx, internals, internals, cx->names().unit, &value)) {
    430        return false;
    431      }
    432      JSLinearString* unit = value.toString()->ensureLinear(cx);
    433      if (!unit) {
    434        return false;
    435      }
    436 
    437      size_t unit_str_length = unit->length();
    438 
    439      MOZ_ASSERT(StringIsAscii(unit));
    440      MOZ_RELEASE_ASSERT(unit_str_length <= MaxUnitLength());
    441      CopyChars(reinterpret_cast<Latin1Char*>(options.unitChars), *unit);
    442 
    443      if (!GetProperty(cx, internals, internals, cx->names().unitDisplay,
    444                       &value)) {
    445        return false;
    446      }
    447      JSLinearString* unitDisplay = value.toString()->ensureLinear(cx);
    448      if (!unitDisplay) {
    449        return false;
    450      }
    451 
    452      using UnitDisplay = mozilla::intl::NumberFormatOptions::UnitDisplay;
    453 
    454      UnitDisplay display;
    455      if (StringEqualsLiteral(unitDisplay, "short")) {
    456        display = UnitDisplay::Short;
    457      } else if (StringEqualsLiteral(unitDisplay, "narrow")) {
    458        display = UnitDisplay::Narrow;
    459      } else {
    460        MOZ_ASSERT(StringEqualsLiteral(unitDisplay, "long"));
    461        display = UnitDisplay::Long;
    462      }
    463 
    464      options.mUnit = mozilla::Some(std::make_pair(
    465          std::string_view(options.unitChars, unit_str_length), display));
    466    } else {
    467      MOZ_ASSERT(StringEqualsLiteral(style, "decimal"));
    468    }
    469  }
    470 
    471  bool hasMinimumSignificantDigits;
    472  if (!HasProperty(cx, internals, cx->names().minimumSignificantDigits,
    473                   &hasMinimumSignificantDigits)) {
    474    return false;
    475  }
    476 
    477  if (hasMinimumSignificantDigits) {
    478    if (!GetProperty(cx, internals, internals,
    479                     cx->names().minimumSignificantDigits, &value)) {
    480      return false;
    481    }
    482    uint32_t minimumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
    483 
    484    if (!GetProperty(cx, internals, internals,
    485                     cx->names().maximumSignificantDigits, &value)) {
    486      return false;
    487    }
    488    uint32_t maximumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
    489 
    490    options.mSignificantDigits = mozilla::Some(
    491        std::make_pair(minimumSignificantDigits, maximumSignificantDigits));
    492  }
    493 
    494  bool hasMinimumFractionDigits;
    495  if (!HasProperty(cx, internals, cx->names().minimumFractionDigits,
    496                   &hasMinimumFractionDigits)) {
    497    return false;
    498  }
    499 
    500  if (hasMinimumFractionDigits) {
    501    if (!GetProperty(cx, internals, internals,
    502                     cx->names().minimumFractionDigits, &value)) {
    503      return false;
    504    }
    505    uint32_t minimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
    506 
    507    if (!GetProperty(cx, internals, internals,
    508                     cx->names().maximumFractionDigits, &value)) {
    509      return false;
    510    }
    511    uint32_t maximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
    512 
    513    options.mFractionDigits = mozilla::Some(
    514        std::make_pair(minimumFractionDigits, maximumFractionDigits));
    515  }
    516 
    517  if (!GetProperty(cx, internals, internals, cx->names().roundingPriority,
    518                   &value)) {
    519    return false;
    520  }
    521 
    522  {
    523    JSLinearString* roundingPriority = value.toString()->ensureLinear(cx);
    524    if (!roundingPriority) {
    525      return false;
    526    }
    527 
    528    using RoundingPriority =
    529        mozilla::intl::NumberFormatOptions::RoundingPriority;
    530 
    531    RoundingPriority priority;
    532    if (StringEqualsLiteral(roundingPriority, "auto")) {
    533      priority = RoundingPriority::Auto;
    534    } else if (StringEqualsLiteral(roundingPriority, "morePrecision")) {
    535      priority = RoundingPriority::MorePrecision;
    536    } else {
    537      MOZ_ASSERT(StringEqualsLiteral(roundingPriority, "lessPrecision"));
    538      priority = RoundingPriority::LessPrecision;
    539    }
    540 
    541    options.mRoundingPriority = priority;
    542  }
    543 
    544  if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
    545                   &value)) {
    546    return false;
    547  }
    548  options.mMinIntegerDigits =
    549      mozilla::Some(AssertedCast<uint32_t>(value.toInt32()));
    550 
    551  if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value)) {
    552    return false;
    553  }
    554 
    555  if (value.isString()) {
    556    JSLinearString* useGrouping = value.toString()->ensureLinear(cx);
    557    if (!useGrouping) {
    558      return false;
    559    }
    560 
    561    using Grouping = mozilla::intl::NumberFormatOptions::Grouping;
    562 
    563    Grouping grouping;
    564    if (StringEqualsLiteral(useGrouping, "auto")) {
    565      grouping = Grouping::Auto;
    566    } else if (StringEqualsLiteral(useGrouping, "always")) {
    567      grouping = Grouping::Always;
    568    } else {
    569      MOZ_ASSERT(StringEqualsLiteral(useGrouping, "min2"));
    570      grouping = Grouping::Min2;
    571    }
    572 
    573    options.mGrouping = grouping;
    574  } else {
    575    MOZ_ASSERT(value.isBoolean());
    576    MOZ_ASSERT(value.toBoolean() == false);
    577 
    578    using Grouping = mozilla::intl::NumberFormatOptions::Grouping;
    579 
    580    options.mGrouping = Grouping::Never;
    581  }
    582 
    583  if (!GetProperty(cx, internals, internals, cx->names().notation, &value)) {
    584    return false;
    585  }
    586 
    587  {
    588    JSLinearString* notation = value.toString()->ensureLinear(cx);
    589    if (!notation) {
    590      return false;
    591    }
    592 
    593    using Notation = mozilla::intl::NumberFormatOptions::Notation;
    594 
    595    Notation style;
    596    if (StringEqualsLiteral(notation, "standard")) {
    597      style = Notation::Standard;
    598    } else if (StringEqualsLiteral(notation, "scientific")) {
    599      style = Notation::Scientific;
    600    } else if (StringEqualsLiteral(notation, "engineering")) {
    601      style = Notation::Engineering;
    602    } else {
    603      MOZ_ASSERT(StringEqualsLiteral(notation, "compact"));
    604 
    605      if (!GetProperty(cx, internals, internals, cx->names().compactDisplay,
    606                       &value)) {
    607        return false;
    608      }
    609 
    610      JSLinearString* compactDisplay = value.toString()->ensureLinear(cx);
    611      if (!compactDisplay) {
    612        return false;
    613      }
    614 
    615      if (StringEqualsLiteral(compactDisplay, "short")) {
    616        style = Notation::CompactShort;
    617      } else {
    618        MOZ_ASSERT(StringEqualsLiteral(compactDisplay, "long"));
    619        style = Notation::CompactLong;
    620      }
    621    }
    622 
    623    options.mNotation = style;
    624  }
    625 
    626  if (!GetProperty(cx, internals, internals, cx->names().signDisplay, &value)) {
    627    return false;
    628  }
    629 
    630  {
    631    JSLinearString* signDisplay = value.toString()->ensureLinear(cx);
    632    if (!signDisplay) {
    633      return false;
    634    }
    635 
    636    using SignDisplay = mozilla::intl::NumberFormatOptions::SignDisplay;
    637 
    638    SignDisplay display;
    639    if (StringEqualsLiteral(signDisplay, "auto")) {
    640      if (accountingSign) {
    641        display = SignDisplay::Accounting;
    642      } else {
    643        display = SignDisplay::Auto;
    644      }
    645    } else if (StringEqualsLiteral(signDisplay, "never")) {
    646      display = SignDisplay::Never;
    647    } else if (StringEqualsLiteral(signDisplay, "always")) {
    648      if (accountingSign) {
    649        display = SignDisplay::AccountingAlways;
    650      } else {
    651        display = SignDisplay::Always;
    652      }
    653    } else if (StringEqualsLiteral(signDisplay, "exceptZero")) {
    654      if (accountingSign) {
    655        display = SignDisplay::AccountingExceptZero;
    656      } else {
    657        display = SignDisplay::ExceptZero;
    658      }
    659    } else {
    660      MOZ_ASSERT(StringEqualsLiteral(signDisplay, "negative"));
    661      if (accountingSign) {
    662        display = SignDisplay::AccountingNegative;
    663      } else {
    664        display = SignDisplay::Negative;
    665      }
    666    }
    667 
    668    options.mSignDisplay = display;
    669  }
    670 
    671  if (!GetProperty(cx, internals, internals, cx->names().roundingIncrement,
    672                   &value)) {
    673    return false;
    674  }
    675  options.mRoundingIncrement = AssertedCast<uint32_t>(value.toInt32());
    676 
    677  if (!GetProperty(cx, internals, internals, cx->names().roundingMode,
    678                   &value)) {
    679    return false;
    680  }
    681 
    682  {
    683    JSLinearString* roundingMode = value.toString()->ensureLinear(cx);
    684    if (!roundingMode) {
    685      return false;
    686    }
    687 
    688    using RoundingMode = mozilla::intl::NumberFormatOptions::RoundingMode;
    689 
    690    RoundingMode rounding;
    691    if (StringEqualsLiteral(roundingMode, "halfExpand")) {
    692      // "halfExpand" is the default mode, so we handle it first.
    693      rounding = RoundingMode::HalfExpand;
    694    } else if (StringEqualsLiteral(roundingMode, "ceil")) {
    695      rounding = RoundingMode::Ceil;
    696    } else if (StringEqualsLiteral(roundingMode, "floor")) {
    697      rounding = RoundingMode::Floor;
    698    } else if (StringEqualsLiteral(roundingMode, "expand")) {
    699      rounding = RoundingMode::Expand;
    700    } else if (StringEqualsLiteral(roundingMode, "trunc")) {
    701      rounding = RoundingMode::Trunc;
    702    } else if (StringEqualsLiteral(roundingMode, "halfCeil")) {
    703      rounding = RoundingMode::HalfCeil;
    704    } else if (StringEqualsLiteral(roundingMode, "halfFloor")) {
    705      rounding = RoundingMode::HalfFloor;
    706    } else if (StringEqualsLiteral(roundingMode, "halfTrunc")) {
    707      rounding = RoundingMode::HalfTrunc;
    708    } else {
    709      MOZ_ASSERT(StringEqualsLiteral(roundingMode, "halfEven"));
    710      rounding = RoundingMode::HalfEven;
    711    }
    712 
    713    options.mRoundingMode = rounding;
    714  }
    715 
    716  if (!GetProperty(cx, internals, internals, cx->names().trailingZeroDisplay,
    717                   &value)) {
    718    return false;
    719  }
    720 
    721  {
    722    JSLinearString* trailingZeroDisplay = value.toString()->ensureLinear(cx);
    723    if (!trailingZeroDisplay) {
    724      return false;
    725    }
    726 
    727    if (StringEqualsLiteral(trailingZeroDisplay, "auto")) {
    728      options.mStripTrailingZero = false;
    729    } else {
    730      MOZ_ASSERT(StringEqualsLiteral(trailingZeroDisplay, "stripIfInteger"));
    731      options.mStripTrailingZero = true;
    732    }
    733  }
    734 
    735  return true;
    736 }
    737 
    738 /**
    739 * Returns a new mozilla::intl::Number[Range]Format with the locale and number
    740 * formatting options of the given NumberFormat, or a nullptr if
    741 * initialization failed.
    742 */
    743 template <class Formatter>
    744 static Formatter* NewNumberFormat(JSContext* cx,
    745                                  Handle<NumberFormatObject*> numberFormat) {
    746  RootedObject internals(cx, intl::GetInternalsObject(cx, numberFormat));
    747  if (!internals) {
    748    return nullptr;
    749  }
    750 
    751  UniqueChars locale = NumberFormatLocale(cx, internals);
    752  if (!locale) {
    753    return nullptr;
    754  }
    755 
    756  NumberFormatOptions options;
    757  if (!FillNumberFormatOptions(cx, internals, options)) {
    758    return nullptr;
    759  }
    760 
    761  options.mRangeCollapse = NumberFormatOptions::RangeCollapse::Auto;
    762  options.mRangeIdentityFallback =
    763      NumberFormatOptions::RangeIdentityFallback::Approximately;
    764 
    765  mozilla::Result<mozilla::UniquePtr<Formatter>, mozilla::intl::ICUError>
    766      result = Formatter::TryCreate(locale.get(), options);
    767 
    768  if (result.isOk()) {
    769    return result.unwrap().release();
    770  }
    771 
    772  intl::ReportInternalError(cx, result.unwrapErr());
    773  return nullptr;
    774 }
    775 
    776 static mozilla::intl::NumberFormat* GetOrCreateNumberFormat(
    777    JSContext* cx, Handle<NumberFormatObject*> numberFormat) {
    778  // Obtain a cached mozilla::intl::NumberFormat object.
    779  mozilla::intl::NumberFormat* nf = numberFormat->getNumberFormatter();
    780  if (nf) {
    781    return nf;
    782  }
    783 
    784  nf = NewNumberFormat<mozilla::intl::NumberFormat>(cx, numberFormat);
    785  if (!nf) {
    786    return nullptr;
    787  }
    788  numberFormat->setNumberFormatter(nf);
    789 
    790  intl::AddICUCellMemory(numberFormat, NumberFormatObject::EstimatedMemoryUse);
    791  return nf;
    792 }
    793 
    794 static mozilla::intl::NumberRangeFormat* GetOrCreateNumberRangeFormat(
    795    JSContext* cx, Handle<NumberFormatObject*> numberFormat) {
    796  // Obtain a cached mozilla::intl::NumberRangeFormat object.
    797  mozilla::intl::NumberRangeFormat* nrf =
    798      numberFormat->getNumberRangeFormatter();
    799  if (nrf) {
    800    return nrf;
    801  }
    802 
    803  nrf = NewNumberFormat<mozilla::intl::NumberRangeFormat>(cx, numberFormat);
    804  if (!nrf) {
    805    return nullptr;
    806  }
    807  numberFormat->setNumberRangeFormatter(nrf);
    808 
    809  intl::AddICUCellMemory(numberFormat,
    810                         NumberFormatObject::EstimatedRangeFormatterMemoryUse);
    811  return nrf;
    812 }
    813 
    814 using FieldType = js::ImmutableTenuredPtr<PropertyName*> JSAtomState::*;
    815 
    816 static FieldType GetFieldTypeForNumberPartType(
    817    mozilla::intl::NumberPartType type) {
    818  switch (type) {
    819    case mozilla::intl::NumberPartType::ApproximatelySign:
    820      return &JSAtomState::approximatelySign;
    821    case mozilla::intl::NumberPartType::Compact:
    822      return &JSAtomState::compact;
    823    case mozilla::intl::NumberPartType::Currency:
    824      return &JSAtomState::currency;
    825    case mozilla::intl::NumberPartType::Decimal:
    826      return &JSAtomState::decimal;
    827    case mozilla::intl::NumberPartType::ExponentInteger:
    828      return &JSAtomState::exponentInteger;
    829    case mozilla::intl::NumberPartType::ExponentMinusSign:
    830      return &JSAtomState::exponentMinusSign;
    831    case mozilla::intl::NumberPartType::ExponentSeparator:
    832      return &JSAtomState::exponentSeparator;
    833    case mozilla::intl::NumberPartType::Fraction:
    834      return &JSAtomState::fraction;
    835    case mozilla::intl::NumberPartType::Group:
    836      return &JSAtomState::group;
    837    case mozilla::intl::NumberPartType::Infinity:
    838      return &JSAtomState::infinity;
    839    case mozilla::intl::NumberPartType::Integer:
    840      return &JSAtomState::integer;
    841    case mozilla::intl::NumberPartType::Literal:
    842      return &JSAtomState::literal;
    843    case mozilla::intl::NumberPartType::MinusSign:
    844      return &JSAtomState::minusSign;
    845    case mozilla::intl::NumberPartType::Nan:
    846      return &JSAtomState::nan;
    847    case mozilla::intl::NumberPartType::Percent:
    848      return &JSAtomState::percentSign;
    849    case mozilla::intl::NumberPartType::PlusSign:
    850      return &JSAtomState::plusSign;
    851    case mozilla::intl::NumberPartType::Unit:
    852      return &JSAtomState::unit;
    853  }
    854 
    855  MOZ_ASSERT_UNREACHABLE(
    856      "unenumerated, undocumented format field returned by iterator");
    857  return nullptr;
    858 }
    859 
    860 static FieldType GetFieldTypeForNumberPartSource(
    861    mozilla::intl::NumberPartSource source) {
    862  switch (source) {
    863    case mozilla::intl::NumberPartSource::Shared:
    864      return &JSAtomState::shared;
    865    case mozilla::intl::NumberPartSource::Start:
    866      return &JSAtomState::startRange;
    867    case mozilla::intl::NumberPartSource::End:
    868      return &JSAtomState::endRange;
    869  }
    870 
    871  MOZ_CRASH("unexpected number part source");
    872 }
    873 
    874 enum class DisplayNumberPartSource : bool { No, Yes };
    875 enum class DisplayLiteralUnit : bool { No, Yes };
    876 
    877 static ArrayObject* FormattedNumberToParts(
    878    JSContext* cx, HandleString str,
    879    const mozilla::intl::NumberPartVector& parts,
    880    DisplayNumberPartSource displaySource,
    881    DisplayLiteralUnit displayLiteralUnit, FieldType unitType) {
    882  size_t lastEndIndex = 0;
    883 
    884  RootedObject singlePart(cx);
    885  RootedValue propVal(cx);
    886 
    887  Rooted<ArrayObject*> partsArray(
    888      cx, NewDenseFullyAllocatedArray(cx, parts.length()));
    889  if (!partsArray) {
    890    return nullptr;
    891  }
    892  partsArray->ensureDenseInitializedLength(0, parts.length());
    893 
    894  size_t index = 0;
    895  for (const auto& part : parts) {
    896    FieldType type = GetFieldTypeForNumberPartType(part.type);
    897    size_t endIndex = part.endIndex;
    898 
    899    MOZ_ASSERT(lastEndIndex < endIndex);
    900 
    901    singlePart = NewPlainObject(cx);
    902    if (!singlePart) {
    903      return nullptr;
    904    }
    905 
    906    propVal.setString(cx->names().*type);
    907    if (!DefineDataProperty(cx, singlePart, cx->names().type, propVal)) {
    908      return nullptr;
    909    }
    910 
    911    JSLinearString* partSubstr =
    912        NewDependentString(cx, str, lastEndIndex, endIndex - lastEndIndex);
    913    if (!partSubstr) {
    914      return nullptr;
    915    }
    916 
    917    propVal.setString(partSubstr);
    918    if (!DefineDataProperty(cx, singlePart, cx->names().value, propVal)) {
    919      return nullptr;
    920    }
    921 
    922    if (displaySource == DisplayNumberPartSource::Yes) {
    923      FieldType source = GetFieldTypeForNumberPartSource(part.source);
    924 
    925      propVal.setString(cx->names().*source);
    926      if (!DefineDataProperty(cx, singlePart, cx->names().source, propVal)) {
    927        return nullptr;
    928      }
    929    }
    930 
    931    if (unitType != nullptr &&
    932        (type != &JSAtomState::literal ||
    933         displayLiteralUnit == DisplayLiteralUnit::Yes)) {
    934      propVal.setString(cx->names().*unitType);
    935      if (!DefineDataProperty(cx, singlePart, cx->names().unit, propVal)) {
    936        return nullptr;
    937      }
    938    }
    939 
    940    partsArray->initDenseElement(index++, ObjectValue(*singlePart));
    941 
    942    lastEndIndex = endIndex;
    943  }
    944 
    945  MOZ_ASSERT(index == parts.length());
    946  MOZ_ASSERT(lastEndIndex == str->length(),
    947             "result array must partition the entire string");
    948 
    949  return partsArray;
    950 }
    951 
    952 bool js::intl::FormattedRelativeTimeToParts(
    953    JSContext* cx, HandleString str,
    954    const mozilla::intl::NumberPartVector& parts,
    955    RelativeTimeFormatUnit relativeTimeUnit, MutableHandleValue result) {
    956  auto* array =
    957      FormattedNumberToParts(cx, str, parts, DisplayNumberPartSource::No,
    958                             DisplayLiteralUnit::No, relativeTimeUnit);
    959  if (!array) {
    960    return false;
    961  }
    962 
    963  result.setObject(*array);
    964  return true;
    965 }
    966 
    967 // Return true if the string starts with "0[bBoOxX]", possibly skipping over
    968 // leading whitespace.
    969 template <typename CharT>
    970 static bool IsNonDecimalNumber(mozilla::Range<const CharT> chars) {
    971  const CharT* end = chars.begin().get() + chars.length();
    972  const CharT* start = SkipSpace(chars.begin().get(), end);
    973 
    974  if (end - start >= 2 && start[0] == '0') {
    975    CharT ch = start[1];
    976    return ch == 'b' || ch == 'B' || ch == 'o' || ch == 'O' || ch == 'x' ||
    977           ch == 'X';
    978  }
    979  return false;
    980 }
    981 
    982 static bool IsNonDecimalNumber(const JSLinearString* str) {
    983  JS::AutoCheckCannotGC nogc;
    984  return str->hasLatin1Chars() ? IsNonDecimalNumber(str->latin1Range(nogc))
    985                               : IsNonDecimalNumber(str->twoByteRange(nogc));
    986 }
    987 
    988 /**
    989 * 15.5.16 ToIntlMathematicalValue ( value )
    990 *
    991 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
    992 */
    993 static bool ToIntlMathematicalValue(JSContext* cx, MutableHandleValue value) {
    994  // Step 1.
    995  if (!ToPrimitive(cx, JSTYPE_NUMBER, value)) {
    996    return false;
    997  }
    998 
    999  // Step 2.
   1000  if (value.isBigInt()) {
   1001    return true;
   1002  }
   1003 
   1004  // Step 4.
   1005  if (!value.isString()) {
   1006    // Step 4.a. (Steps 4.b-10 not applicable in our implementation.)
   1007    return ToNumber(cx, value);
   1008  }
   1009 
   1010  // Step 3.
   1011  JSLinearString* str = value.toString()->ensureLinear(cx);
   1012  if (!str) {
   1013    return false;
   1014  }
   1015 
   1016  // Steps 5-6, 8, and 9.a.
   1017  double number = LinearStringToNumber(str);
   1018 
   1019  // Step 7.
   1020  if (std::isnan(number)) {
   1021    // Set to NaN if the input can't be parsed as a number.
   1022    value.setNaN();
   1023    return true;
   1024  }
   1025 
   1026  // Step 9.
   1027  if (number == 0.0 || std::isinf(number)) {
   1028    // Step 9.a. (Reordered)
   1029 
   1030    // Steps 9.b-e.
   1031    value.setDouble(number);
   1032    return true;
   1033  }
   1034 
   1035  // Step 10.
   1036  if (IsNonDecimalNumber(str)) {
   1037    // ICU doesn't accept non-decimal numbers, so we have to convert the input
   1038    // into a base-10 string.
   1039 
   1040    MOZ_ASSERT(!mozilla::IsNegative(number),
   1041               "non-decimal numbers can't be negative");
   1042 
   1043    if (number < DOUBLE_INTEGRAL_PRECISION_LIMIT) {
   1044      // Fast-path if we can guarantee there was no loss of precision.
   1045      value.setDouble(number);
   1046    } else {
   1047      // For the slow-path convert the string into a BigInt.
   1048 
   1049      // StringToBigInt can't fail (other than OOM) when StringToNumber already
   1050      // succeeded.
   1051      RootedString rooted(cx, str);
   1052      BigInt* bi;
   1053      JS_TRY_VAR_OR_RETURN_FALSE(cx, bi, StringToBigInt(cx, rooted));
   1054      MOZ_ASSERT(bi);
   1055 
   1056      value.setBigInt(bi);
   1057    }
   1058  }
   1059  return true;
   1060 }
   1061 
   1062 // Return the number part of the input by removing leading and trailing
   1063 // whitespace.
   1064 template <typename CharT>
   1065 static mozilla::Span<const CharT> NumberPart(const CharT* chars,
   1066                                             size_t length) {
   1067  const CharT* start = chars;
   1068  const CharT* end = chars + length;
   1069 
   1070  start = SkipSpace(start, end);
   1071 
   1072  // |SkipSpace| only supports forward iteration, so inline the backwards
   1073  // iteration here.
   1074  MOZ_ASSERT(start <= end);
   1075  while (end > start && unicode::IsSpace(end[-1])) {
   1076    end--;
   1077  }
   1078 
   1079  // The number part is a non-empty, ASCII-only substring.
   1080  MOZ_ASSERT(start < end);
   1081  MOZ_ASSERT(mozilla::IsAscii(mozilla::Span(start, end)));
   1082 
   1083  return {start, end};
   1084 }
   1085 
   1086 static bool NumberPart(JSContext* cx, JSLinearString* str,
   1087                       const JS::AutoCheckCannotGC& nogc,
   1088                       JS::UniqueChars& latin1, std::string_view& result) {
   1089  if (str->hasLatin1Chars()) {
   1090    auto span = NumberPart(
   1091        reinterpret_cast<const char*>(str->latin1Chars(nogc)), str->length());
   1092 
   1093    result = {span.data(), span.size()};
   1094    return true;
   1095  }
   1096 
   1097  auto span = NumberPart(str->twoByteChars(nogc), str->length());
   1098 
   1099  latin1.reset(JS::LossyTwoByteCharsToNewLatin1CharsZ(cx, span).c_str());
   1100  if (!latin1) {
   1101    return false;
   1102  }
   1103 
   1104  result = {latin1.get(), span.size()};
   1105  return true;
   1106 }
   1107 
   1108 static JSLinearString* FormattedResultToString(
   1109    JSContext* cx,
   1110    mozilla::Result<std::u16string_view, mozilla::intl::ICUError>& result) {
   1111  if (result.isErr()) {
   1112    intl::ReportInternalError(cx, result.unwrapErr());
   1113    return nullptr;
   1114  }
   1115  return NewStringCopy<CanGC>(cx, result.unwrap());
   1116 }
   1117 
   1118 bool js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp) {
   1119  CallArgs args = CallArgsFromVp(argc, vp);
   1120  MOZ_ASSERT(args.length() == 3);
   1121  MOZ_ASSERT(args[0].isObject());
   1122  MOZ_ASSERT(args[2].isBoolean());
   1123 
   1124  Rooted<NumberFormatObject*> numberFormat(
   1125      cx, &args[0].toObject().as<NumberFormatObject>());
   1126 
   1127  RootedValue value(cx, args[1]);
   1128  if (!ToIntlMathematicalValue(cx, &value)) {
   1129    return false;
   1130  }
   1131 
   1132  mozilla::intl::NumberFormat* nf = GetOrCreateNumberFormat(cx, numberFormat);
   1133  if (!nf) {
   1134    return false;
   1135  }
   1136 
   1137  // Actually format the number
   1138  using ICUError = mozilla::intl::ICUError;
   1139 
   1140  bool formatToParts = args[2].toBoolean();
   1141  mozilla::Result<std::u16string_view, ICUError> result =
   1142      mozilla::Err(ICUError::InternalError);
   1143  mozilla::intl::NumberPartVector parts;
   1144  if (value.isNumber()) {
   1145    double num = value.toNumber();
   1146    if (formatToParts) {
   1147      result = nf->formatToParts(num, parts);
   1148    } else {
   1149      result = nf->format(num);
   1150    }
   1151  } else if (value.isBigInt()) {
   1152    RootedBigInt bi(cx, value.toBigInt());
   1153 
   1154    int64_t num;
   1155    if (BigInt::isInt64(bi, &num)) {
   1156      if (formatToParts) {
   1157        result = nf->formatToParts(num, parts);
   1158      } else {
   1159        result = nf->format(num);
   1160      }
   1161    } else {
   1162      JSLinearString* str = BigInt::toString<CanGC>(cx, bi, 10);
   1163      if (!str) {
   1164        return false;
   1165      }
   1166      MOZ_RELEASE_ASSERT(str->hasLatin1Chars());
   1167 
   1168      JS::AutoCheckCannotGC nogc;
   1169 
   1170      const char* chars = reinterpret_cast<const char*>(str->latin1Chars(nogc));
   1171      if (formatToParts) {
   1172        result =
   1173            nf->formatToParts(std::string_view(chars, str->length()), parts);
   1174      } else {
   1175        result = nf->format(std::string_view(chars, str->length()));
   1176      }
   1177    }
   1178  } else {
   1179    JSLinearString* str = value.toString()->ensureLinear(cx);
   1180    if (!str) {
   1181      return false;
   1182    }
   1183 
   1184    JS::AutoCheckCannotGC nogc;
   1185 
   1186    // Two-byte strings have to be copied into a separate |char| buffer.
   1187    JS::UniqueChars latin1;
   1188 
   1189    std::string_view sv;
   1190    if (!NumberPart(cx, str, nogc, latin1, sv)) {
   1191      return false;
   1192    }
   1193 
   1194    if (formatToParts) {
   1195      result = nf->formatToParts(sv, parts);
   1196    } else {
   1197      result = nf->format(sv);
   1198    }
   1199  }
   1200 
   1201  RootedString str(cx, FormattedResultToString(cx, result));
   1202  if (!str) {
   1203    return false;
   1204  }
   1205 
   1206  if (formatToParts) {
   1207    auto* array =
   1208        FormattedNumberToParts(cx, str, parts, DisplayNumberPartSource::No,
   1209                               DisplayLiteralUnit::No, nullptr);
   1210    if (!array) {
   1211      return false;
   1212    }
   1213 
   1214    args.rval().setObject(*array);
   1215    return true;
   1216  }
   1217 
   1218  args.rval().setString(str);
   1219  return true;
   1220 }
   1221 
   1222 JSString* js::intl::FormatNumber(JSContext* cx,
   1223                                 Handle<NumberFormatObject*> numberFormat,
   1224                                 double x) {
   1225  mozilla::intl::NumberFormat* nf = GetOrCreateNumberFormat(cx, numberFormat);
   1226  if (!nf) {
   1227    return nullptr;
   1228  }
   1229 
   1230  auto result = nf->format(x);
   1231  return FormattedResultToString(cx, result);
   1232 }
   1233 
   1234 JSString* js::intl::FormatBigInt(JSContext* cx,
   1235                                 Handle<NumberFormatObject*> numberFormat,
   1236                                 Handle<BigInt*> x) {
   1237  mozilla::intl::NumberFormat* nf = GetOrCreateNumberFormat(cx, numberFormat);
   1238  if (!nf) {
   1239    return nullptr;
   1240  }
   1241 
   1242  int64_t num;
   1243  if (BigInt::isInt64(x, &num)) {
   1244    auto result = nf->format(num);
   1245    return FormattedResultToString(cx, result);
   1246  }
   1247 
   1248  JSLinearString* str = BigInt::toString<CanGC>(cx, x, 10);
   1249  if (!str) {
   1250    return nullptr;
   1251  }
   1252  MOZ_RELEASE_ASSERT(str->hasLatin1Chars());
   1253 
   1254  mozilla::Result<std::u16string_view, mozilla::intl::ICUError> result{
   1255      std::u16string_view{}};
   1256  {
   1257    JS::AutoCheckCannotGC nogc;
   1258 
   1259    const char* chars = reinterpret_cast<const char*>(str->latin1Chars(nogc));
   1260    result = nf->format(std::string_view(chars, str->length()));
   1261  }
   1262  return FormattedResultToString(cx, result);
   1263 }
   1264 
   1265 static JSLinearString* ToLinearString(JSContext* cx, HandleValue val) {
   1266  // Special case to preserve negative zero.
   1267  if (val.isDouble() && mozilla::IsNegativeZero(val.toDouble())) {
   1268    constexpr std::string_view negativeZero = "-0";
   1269    return NewStringCopy<CanGC>(cx, negativeZero);
   1270  }
   1271 
   1272  JSString* str = ToString(cx, val);
   1273  return str ? str->ensureLinear(cx) : nullptr;
   1274 };
   1275 
   1276 bool js::intl_FormatNumberRange(JSContext* cx, unsigned argc, Value* vp) {
   1277  CallArgs args = CallArgsFromVp(argc, vp);
   1278  MOZ_ASSERT(args.length() == 4);
   1279  MOZ_ASSERT(args[0].isObject());
   1280  MOZ_ASSERT(!args[1].isUndefined());
   1281  MOZ_ASSERT(!args[2].isUndefined());
   1282  MOZ_ASSERT(args[3].isBoolean());
   1283 
   1284  Rooted<NumberFormatObject*> numberFormat(
   1285      cx, &args[0].toObject().as<NumberFormatObject>());
   1286  bool formatToParts = args[3].toBoolean();
   1287 
   1288  RootedValue start(cx, args[1]);
   1289  if (!ToIntlMathematicalValue(cx, &start)) {
   1290    return false;
   1291  }
   1292 
   1293  RootedValue end(cx, args[2]);
   1294  if (!ToIntlMathematicalValue(cx, &end)) {
   1295    return false;
   1296  }
   1297 
   1298  // PartitionNumberRangePattern, step 1.
   1299  if (start.isDouble() && std::isnan(start.toDouble())) {
   1300    JS_ReportErrorNumberASCII(
   1301        cx, GetErrorMessage, nullptr, JSMSG_NAN_NUMBER_RANGE, "start",
   1302        "NumberFormat", formatToParts ? "formatRangeToParts" : "formatRange");
   1303    return false;
   1304  }
   1305  if (end.isDouble() && std::isnan(end.toDouble())) {
   1306    JS_ReportErrorNumberASCII(
   1307        cx, GetErrorMessage, nullptr, JSMSG_NAN_NUMBER_RANGE, "end",
   1308        "NumberFormat", formatToParts ? "formatRangeToParts" : "formatRange");
   1309    return false;
   1310  }
   1311 
   1312  using NumberRangeFormat = mozilla::intl::NumberRangeFormat;
   1313  NumberRangeFormat* nf = GetOrCreateNumberRangeFormat(cx, numberFormat);
   1314  if (!nf) {
   1315    return false;
   1316  }
   1317 
   1318  auto valueRepresentableAsDouble = [](const Value& val, double* num) {
   1319    if (val.isNumber()) {
   1320      *num = val.toNumber();
   1321      return true;
   1322    }
   1323    if (val.isBigInt()) {
   1324      int64_t i64;
   1325      if (BigInt::isInt64(val.toBigInt(), &i64) &&
   1326          i64 < int64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT) &&
   1327          i64 > -int64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) {
   1328        *num = double(i64);
   1329        return true;
   1330      }
   1331    }
   1332    return false;
   1333  };
   1334 
   1335  // Actually format the number range.
   1336  using ICUError = mozilla::intl::ICUError;
   1337 
   1338  mozilla::Result<std::u16string_view, ICUError> result =
   1339      mozilla::Err(ICUError::InternalError);
   1340  mozilla::intl::NumberPartVector parts;
   1341 
   1342  double numStart, numEnd;
   1343  if (valueRepresentableAsDouble(start, &numStart) &&
   1344      valueRepresentableAsDouble(end, &numEnd)) {
   1345    if (formatToParts) {
   1346      result = nf->formatToParts(numStart, numEnd, parts);
   1347    } else {
   1348      result = nf->format(numStart, numEnd);
   1349    }
   1350  } else {
   1351    Rooted<JSLinearString*> strStart(cx, ToLinearString(cx, start));
   1352    if (!strStart) {
   1353      return false;
   1354    }
   1355 
   1356    Rooted<JSLinearString*> strEnd(cx, ToLinearString(cx, end));
   1357    if (!strEnd) {
   1358      return false;
   1359    }
   1360 
   1361    JS::AutoCheckCannotGC nogc;
   1362 
   1363    // Two-byte strings have to be copied into a separate |char| buffer.
   1364    JS::UniqueChars latin1Start;
   1365    JS::UniqueChars latin1End;
   1366 
   1367    std::string_view svStart;
   1368    if (!NumberPart(cx, strStart, nogc, latin1Start, svStart)) {
   1369      return false;
   1370    }
   1371 
   1372    std::string_view svEnd;
   1373    if (!NumberPart(cx, strEnd, nogc, latin1End, svEnd)) {
   1374      return false;
   1375    }
   1376 
   1377    if (formatToParts) {
   1378      result = nf->formatToParts(svStart, svEnd, parts);
   1379    } else {
   1380      result = nf->format(svStart, svEnd);
   1381    }
   1382  }
   1383 
   1384  if (result.isErr()) {
   1385    intl::ReportInternalError(cx, result.unwrapErr());
   1386    return false;
   1387  }
   1388 
   1389  RootedString str(cx, NewStringCopy<CanGC>(cx, result.unwrap()));
   1390  if (!str) {
   1391    return false;
   1392  }
   1393 
   1394  if (formatToParts) {
   1395    auto* array =
   1396        FormattedNumberToParts(cx, str, parts, DisplayNumberPartSource::Yes,
   1397                               DisplayLiteralUnit::No, nullptr);
   1398    if (!array) {
   1399      return false;
   1400    }
   1401 
   1402    args.rval().setObject(*array);
   1403    return true;
   1404  }
   1405 
   1406  args.rval().setString(str);
   1407  return true;
   1408 }
   1409 
   1410 JSLinearString* js::intl::FormatNumber(
   1411    JSContext* cx, mozilla::intl::NumberFormat* numberFormat, double x) {
   1412  auto result = numberFormat->format(x);
   1413  return FormattedResultToString(cx, result);
   1414 }
   1415 
   1416 JSLinearString* js::intl::FormatNumber(
   1417    JSContext* cx, mozilla::intl::NumberFormat* numberFormat,
   1418    std::string_view x) {
   1419  auto result = numberFormat->format(x);
   1420  return FormattedResultToString(cx, result);
   1421 }
   1422 
   1423 ArrayObject* js::intl::FormatNumberToParts(
   1424    JSContext* cx, mozilla::intl::NumberFormat* numberFormat, double x,
   1425    NumberFormatUnit unit) {
   1426  mozilla::intl::NumberPartVector parts;
   1427  auto result = numberFormat->formatToParts(x, parts);
   1428  Rooted<JSLinearString*> str(cx, FormattedResultToString(cx, result));
   1429  if (!str) {
   1430    return nullptr;
   1431  }
   1432  return FormattedNumberToParts(cx, str, parts, DisplayNumberPartSource::No,
   1433                                DisplayLiteralUnit::Yes, unit);
   1434 }
   1435 
   1436 ArrayObject* js::intl::FormatNumberToParts(
   1437    JSContext* cx, mozilla::intl::NumberFormat* numberFormat,
   1438    std::string_view x, NumberFormatUnit unit) {
   1439  mozilla::intl::NumberPartVector parts;
   1440  auto result = numberFormat->formatToParts(x, parts);
   1441  Rooted<JSLinearString*> str(cx, FormattedResultToString(cx, result));
   1442  if (!str) {
   1443    return nullptr;
   1444  }
   1445  return FormattedNumberToParts(cx, str, parts, DisplayNumberPartSource::No,
   1446                                DisplayLiteralUnit::Yes, unit);
   1447 }
   1448 
   1449 /**
   1450 * Intl.NumberFormat.supportedLocalesOf ( locales [ , options ] )
   1451 */
   1452 static bool numberFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
   1453                                            Value* vp) {
   1454  CallArgs args = CallArgsFromVp(argc, vp);
   1455 
   1456  // Steps 1-3.
   1457  auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::NumberFormat,
   1458                                   args.get(0), args.get(1));
   1459  if (!array) {
   1460    return false;
   1461  }
   1462  args.rval().setObject(*array);
   1463  return true;
   1464 }