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/ArrayUtils.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Casting.h"
14 #include "mozilla/FloatingPoint.h"
15 #include "mozilla/UniquePtr.h"
16 
17 #include <algorithm>
18 #include <cstring>
19 #include <iterator>
20 #include <stddef.h>
21 #include <stdint.h>
22 #include <string>
23 #include <type_traits>
24 
25 #include "builtin/Array.h"
26 #include "builtin/intl/CommonFunctions.h"
27 #include "builtin/intl/LanguageTag.h"
28 #include "builtin/intl/MeasureUnitGenerated.h"
29 #include "builtin/intl/RelativeTimeFormat.h"
30 #include "builtin/intl/ScopedICUObject.h"
31 #include "ds/Sort.h"
32 #include "gc/FreeOp.h"
33 #include "js/CharacterEncoding.h"
34 #include "js/PropertySpec.h"
35 #include "js/RootingAPI.h"
36 #include "js/TypeDecls.h"
37 #include "js/Vector.h"
38 #include "unicode/udata.h"
39 #include "unicode/ufieldpositer.h"
40 #include "unicode/uformattedvalue.h"
41 #include "unicode/unum.h"
42 #include "unicode/unumberformatter.h"
43 #include "unicode/unumsys.h"
44 #include "unicode/ures.h"
45 #include "unicode/utypes.h"
46 #include "vm/BigIntType.h"
47 #include "vm/GlobalObject.h"
48 #include "vm/JSContext.h"
49 #include "vm/PlainObject.h"  // js::PlainObject
50 #include "vm/SelfHosting.h"
51 #include "vm/Stack.h"
52 #include "vm/StringType.h"
53 
54 #include "vm/JSObject-inl.h"
55 
56 using namespace js;
57 
58 using mozilla::AssertedCast;
59 using mozilla::IsFinite;
60 using mozilla::IsNaN;
61 using mozilla::IsNegative;
62 using mozilla::SpecificNaN;
63 
64 using js::intl::CallICU;
65 using js::intl::DateTimeFormatOptions;
66 using js::intl::FieldType;
67 using js::intl::IcuLocale;
68 
69 const JSClassOps NumberFormatObject::classOps_ = {
70     nullptr,                       // addProperty
71     nullptr,                       // delProperty
72     nullptr,                       // enumerate
73     nullptr,                       // newEnumerate
74     nullptr,                       // resolve
75     nullptr,                       // mayResolve
76     NumberFormatObject::finalize,  // finalize
77     nullptr,                       // call
78     nullptr,                       // hasInstance
79     nullptr,                       // construct
80     nullptr,                       // trace
81 };
82 
83 const JSClass NumberFormatObject::class_ = {
84     js_Object_str,
85     JSCLASS_HAS_RESERVED_SLOTS(NumberFormatObject::SLOT_COUNT) |
86         JSCLASS_HAS_CACHED_PROTO(JSProto_NumberFormat) |
87         JSCLASS_FOREGROUND_FINALIZE,
88     &NumberFormatObject::classOps_, &NumberFormatObject::classSpec_};
89 
90 const JSClass& NumberFormatObject::protoClass_ = PlainObject::class_;
91 
numberFormat_toSource(JSContext * cx,unsigned argc,Value * vp)92 static bool numberFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
93   CallArgs args = CallArgsFromVp(argc, vp);
94   args.rval().setString(cx->names().NumberFormat);
95   return true;
96 }
97 
98 static const JSFunctionSpec numberFormat_static_methods[] = {
99     JS_SELF_HOSTED_FN("supportedLocalesOf",
100                       "Intl_NumberFormat_supportedLocalesOf", 1, 0),
101     JS_FS_END};
102 
103 static const JSFunctionSpec numberFormat_methods[] = {
104     JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0,
105                       0),
106     JS_SELF_HOSTED_FN("formatToParts", "Intl_NumberFormat_formatToParts", 1, 0),
107     JS_FN(js_toSource_str, numberFormat_toSource, 0, 0), JS_FS_END};
108 
109 static const JSPropertySpec numberFormat_properties[] = {
110     JS_SELF_HOSTED_GET("format", "$Intl_NumberFormat_format_get", 0),
111     JS_STRING_SYM_PS(toStringTag, "Object", JSPROP_READONLY), JS_PS_END};
112 
113 static bool NumberFormat(JSContext* cx, unsigned argc, Value* vp);
114 
115 const ClassSpec NumberFormatObject::classSpec_ = {
116     GenericCreateConstructor<NumberFormat, 0, gc::AllocKind::FUNCTION>,
117     GenericCreatePrototype<NumberFormatObject>,
118     numberFormat_static_methods,
119     nullptr,
120     numberFormat_methods,
121     numberFormat_properties,
122     nullptr,
123     ClassSpec::DontDefineConstructor};
124 
125 /**
126  * 11.2.1 Intl.NumberFormat([ locales [, options]])
127  *
128  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
129  */
NumberFormat(JSContext * cx,const CallArgs & args,bool construct)130 static bool NumberFormat(JSContext* cx, const CallArgs& args, bool construct) {
131   // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
132 
133   // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
134   RootedObject proto(cx);
135   if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_NumberFormat,
136                                           &proto)) {
137     return false;
138   }
139 
140   Rooted<NumberFormatObject*> numberFormat(cx);
141   numberFormat = NewObjectWithClassProto<NumberFormatObject>(cx, proto);
142   if (!numberFormat) {
143     return false;
144   }
145 
146   RootedValue thisValue(cx,
147                         construct ? ObjectValue(*numberFormat) : args.thisv());
148   HandleValue locales = args.get(0);
149   HandleValue options = args.get(1);
150 
151   // Step 3.
152   return intl::LegacyInitializeObject(
153       cx, numberFormat, cx->names().InitializeNumberFormat, thisValue, locales,
154       options, DateTimeFormatOptions::Standard, args.rval());
155 }
156 
NumberFormat(JSContext * cx,unsigned argc,Value * vp)157 static bool NumberFormat(JSContext* cx, unsigned argc, Value* vp) {
158   CallArgs args = CallArgsFromVp(argc, vp);
159   return NumberFormat(cx, args, args.isConstructing());
160 }
161 
intl_NumberFormat(JSContext * cx,unsigned argc,Value * vp)162 bool js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp) {
163   CallArgs args = CallArgsFromVp(argc, vp);
164   MOZ_ASSERT(args.length() == 2);
165   MOZ_ASSERT(!args.isConstructing());
166   // intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it
167   // cannot be used with "new", but it still has to be treated as a
168   // constructor.
169   return NumberFormat(cx, args, true);
170 }
171 
finalize(JSFreeOp * fop,JSObject * obj)172 void js::NumberFormatObject::finalize(JSFreeOp* fop, JSObject* obj) {
173   MOZ_ASSERT(fop->onMainThread());
174 
175   auto* numberFormat = &obj->as<NumberFormatObject>();
176   UNumberFormatter* nf = numberFormat->getNumberFormatter();
177   UFormattedNumber* formatted = numberFormat->getFormattedNumber();
178 
179   if (nf) {
180     intl::RemoveICUCellMemory(fop, obj, NumberFormatObject::EstimatedMemoryUse);
181 
182     unumf_close(nf);
183   }
184   if (formatted) {
185     // UFormattedNumber memory tracked as part of UNumberFormatter.
186 
187     unumf_closeResult(formatted);
188   }
189 }
190 
intl_numberingSystem(JSContext * cx,unsigned argc,Value * vp)191 bool js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp) {
192   CallArgs args = CallArgsFromVp(argc, vp);
193   MOZ_ASSERT(args.length() == 1);
194   MOZ_ASSERT(args[0].isString());
195 
196   UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
197   if (!locale) {
198     return false;
199   }
200 
201   UErrorCode status = U_ZERO_ERROR;
202   UNumberingSystem* numbers = unumsys_open(IcuLocale(locale.get()), &status);
203   if (U_FAILURE(status)) {
204     intl::ReportInternalError(cx);
205     return false;
206   }
207 
208   ScopedICUObject<UNumberingSystem, unumsys_close> toClose(numbers);
209 
210   const char* name = unumsys_getName(numbers);
211   if (!name) {
212     intl::ReportInternalError(cx);
213     return false;
214   }
215 
216   JSString* jsname = NewStringCopyZ<CanGC>(cx, name);
217   if (!jsname) {
218     return false;
219   }
220 
221   args.rval().setString(jsname);
222   return true;
223 }
224 
225 #if DEBUG || MOZ_SYSTEM_ICU
226 class UResourceBundleDeleter {
227  public:
operator ()(UResourceBundle * aPtr)228   void operator()(UResourceBundle* aPtr) { ures_close(aPtr); }
229 };
230 
231 using UniqueUResourceBundle =
232     mozilla::UniquePtr<UResourceBundle, UResourceBundleDeleter>;
233 
intl_availableMeasurementUnits(JSContext * cx,unsigned argc,Value * vp)234 bool js::intl_availableMeasurementUnits(JSContext* cx, unsigned argc,
235                                         Value* vp) {
236   CallArgs args = CallArgsFromVp(argc, vp);
237   MOZ_ASSERT(args.length() == 0);
238 
239   RootedObject measurementUnits(
240       cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
241   if (!measurementUnits) {
242     return false;
243   }
244 
245   // Lookup the available measurement units in the resource boundle of the root
246   // locale.
247 
248   static const char packageName[] =
249       U_ICUDATA_NAME U_TREE_SEPARATOR_STRING "unit";
250   static const char rootLocale[] = "";
251 
252   UErrorCode status = U_ZERO_ERROR;
253   UResourceBundle* rawRes = ures_open(packageName, rootLocale, &status);
254   if (U_FAILURE(status)) {
255     intl::ReportInternalError(cx);
256     return false;
257   }
258   UniqueUResourceBundle res(rawRes);
259 
260   UResourceBundle* rawUnits =
261       ures_getByKey(res.get(), "units", nullptr, &status);
262   if (U_FAILURE(status)) {
263     intl::ReportInternalError(cx);
264     return false;
265   }
266   UniqueUResourceBundle units(rawUnits);
267 
268   RootedAtom unitAtom(cx);
269 
270   int32_t unitsSize = ures_getSize(units.get());
271   for (int32_t i = 0; i < unitsSize; i++) {
272     UResourceBundle* rawType =
273         ures_getByIndex(units.get(), i, nullptr, &status);
274     if (U_FAILURE(status)) {
275       intl::ReportInternalError(cx);
276       return false;
277     }
278     UniqueUResourceBundle type(rawType);
279 
280     int32_t typeSize = ures_getSize(type.get());
281     for (int32_t j = 0; j < typeSize; j++) {
282       UResourceBundle* rawSubtype =
283           ures_getByIndex(type.get(), j, nullptr, &status);
284       if (U_FAILURE(status)) {
285         intl::ReportInternalError(cx);
286         return false;
287       }
288       UniqueUResourceBundle subtype(rawSubtype);
289 
290       const char* unitIdentifier = ures_getKey(subtype.get());
291 
292       unitAtom = Atomize(cx, unitIdentifier, strlen(unitIdentifier));
293       if (!unitAtom) {
294         return false;
295       }
296       if (!DefineDataProperty(cx, measurementUnits, unitAtom->asPropertyName(),
297                               TrueHandleValue)) {
298         return false;
299       }
300     }
301   }
302 
303   args.rval().setObject(*measurementUnits);
304   return true;
305 }
306 #endif
307 
currency(JSLinearString * currency)308 bool js::intl::NumberFormatterSkeleton::currency(JSLinearString* currency) {
309   MOZ_ASSERT(currency->length() == 3,
310              "IsWellFormedCurrencyCode permits only length-3 strings");
311 
312   char16_t currencyChars[] = {currency->latin1OrTwoByteChar(0),
313                               currency->latin1OrTwoByteChar(1),
314                               currency->latin1OrTwoByteChar(2), '\0'};
315   return append(u"currency/") && append(currencyChars) && append(' ');
316 }
317 
currencyDisplay(CurrencyDisplay display)318 bool js::intl::NumberFormatterSkeleton::currencyDisplay(
319     CurrencyDisplay display) {
320   switch (display) {
321     case CurrencyDisplay::Code:
322       return appendToken(u"unit-width-iso-code");
323     case CurrencyDisplay::Name:
324       return appendToken(u"unit-width-full-name");
325     case CurrencyDisplay::Symbol:
326       // Default, no additional tokens needed.
327       return true;
328     case CurrencyDisplay::NarrowSymbol:
329       return appendToken(u"unit-width-narrow");
330   }
331   MOZ_CRASH("unexpected currency display type");
332 }
333 
FindSimpleMeasureUnit(const char * name)334 static const MeasureUnit& FindSimpleMeasureUnit(const char* name) {
335   auto measureUnit = std::lower_bound(
336       std::begin(simpleMeasureUnits), std::end(simpleMeasureUnits), name,
337       [](const auto& measureUnit, const char* name) {
338         return strcmp(measureUnit.name, name) < 0;
339       });
340   MOZ_ASSERT(measureUnit != std::end(simpleMeasureUnits),
341              "unexpected unit identifier: unit not found");
342   MOZ_ASSERT(strcmp(measureUnit->name, name) == 0,
343              "unexpected unit identifier: wrong unit found");
344   return *measureUnit;
345 }
346 
MaxUnitLength()347 static constexpr size_t MaxUnitLength() {
348   // Enable by default when libstdc++ 7 is the minimal version expected
349 #if _GLIBCXX_RELEASE >= 7
350   size_t length = 0;
351   for (const auto& unit : simpleMeasureUnits) {
352     length = std::max(length, std::char_traits<char>::length(unit.name));
353   }
354   return length * 2 + std::char_traits<char>::length("-per-");
355 #else
356   return mozilla::ArrayLength("mile-scandinavian-per-mile-scandinavian") - 1;
357 #endif
358 }
359 
unit(JSLinearString * unit)360 bool js::intl::NumberFormatterSkeleton::unit(JSLinearString* unit) {
361   MOZ_RELEASE_ASSERT(unit->length() <= MaxUnitLength());
362 
363   char unitChars[MaxUnitLength() + 1] = {};
364   CopyChars(reinterpret_cast<Latin1Char*>(unitChars), *unit);
365 
366   auto appendUnit = [this](const MeasureUnit& unit) {
367     return append(unit.type, strlen(unit.type)) && append('-') &&
368            append(unit.name, strlen(unit.name));
369   };
370 
371   // |unit| can be a compound unit identifier, separated by "-per-".
372 
373   static constexpr char separator[] = "-per-";
374   if (char* p = strstr(unitChars, separator)) {
375     // Split into two strings.
376     p[0] = '\0';
377 
378     auto& numerator = FindSimpleMeasureUnit(unitChars);
379     if (!append(u"measure-unit/") || !appendUnit(numerator) || !append(' ')) {
380       return false;
381     }
382 
383     auto& denominator = FindSimpleMeasureUnit(p + strlen(separator));
384     if (!append(u"per-measure-unit/") || !appendUnit(denominator) ||
385         !append(' ')) {
386       return false;
387     }
388   } else {
389     auto& simple = FindSimpleMeasureUnit(unitChars);
390     if (!append(u"measure-unit/") || !appendUnit(simple) || !append(' ')) {
391       return false;
392     }
393   }
394   return true;
395 }
396 
unitDisplay(UnitDisplay display)397 bool js::intl::NumberFormatterSkeleton::unitDisplay(UnitDisplay display) {
398   switch (display) {
399     case UnitDisplay::Short:
400       return appendToken(u"unit-width-short");
401     case UnitDisplay::Narrow:
402       return appendToken(u"unit-width-narrow");
403     case UnitDisplay::Long:
404       return appendToken(u"unit-width-full-name");
405   }
406   MOZ_CRASH("unexpected unit display type");
407 }
408 
percent()409 bool js::intl::NumberFormatterSkeleton::percent() {
410   return appendToken(u"percent scale/100");
411 }
412 
fractionDigits(uint32_t min,uint32_t max)413 bool js::intl::NumberFormatterSkeleton::fractionDigits(uint32_t min,
414                                                        uint32_t max) {
415   // Note: |min| can be zero here.
416   MOZ_ASSERT(min <= max);
417   return append('.') && appendN('0', min) && appendN('#', max - min) &&
418          append(' ');
419 }
420 
integerWidth(uint32_t min)421 bool js::intl::NumberFormatterSkeleton::integerWidth(uint32_t min) {
422   MOZ_ASSERT(min > 0);
423   return append(u"integer-width/+") && appendN('0', min) && append(' ');
424 }
425 
significantDigits(uint32_t min,uint32_t max)426 bool js::intl::NumberFormatterSkeleton::significantDigits(uint32_t min,
427                                                           uint32_t max) {
428   MOZ_ASSERT(min > 0);
429   MOZ_ASSERT(min <= max);
430   return appendN('@', min) && appendN('#', max - min) && append(' ');
431 }
432 
useGrouping(bool on)433 bool js::intl::NumberFormatterSkeleton::useGrouping(bool on) {
434   return on || appendToken(u"group-off");
435 }
436 
notation(Notation style)437 bool js::intl::NumberFormatterSkeleton::notation(Notation style) {
438   switch (style) {
439     case Notation::Standard:
440       // Default, no additional tokens needed.
441       return true;
442     case Notation::Scientific:
443       return appendToken(u"scientific");
444     case Notation::Engineering:
445       return appendToken(u"engineering");
446     case Notation::CompactShort:
447       return appendToken(u"compact-short");
448     case Notation::CompactLong:
449       return appendToken(u"compact-long");
450   }
451   MOZ_CRASH("unexpected notation style");
452 }
453 
signDisplay(SignDisplay display)454 bool js::intl::NumberFormatterSkeleton::signDisplay(SignDisplay display) {
455   switch (display) {
456     case SignDisplay::Auto:
457       // Default, no additional tokens needed.
458       return true;
459     case SignDisplay::Always:
460       return appendToken(u"sign-always");
461     case SignDisplay::Never:
462       return appendToken(u"sign-never");
463     case SignDisplay::ExceptZero:
464       return appendToken(u"sign-except-zero");
465     case SignDisplay::Accounting:
466       return appendToken(u"sign-accounting");
467     case SignDisplay::AccountingAlways:
468       return appendToken(u"sign-accounting-always");
469     case SignDisplay::AccountingExceptZero:
470       return appendToken(u"sign-accounting-except-zero");
471   }
472   MOZ_CRASH("unexpected sign display type");
473 }
474 
roundingModeHalfUp()475 bool js::intl::NumberFormatterSkeleton::roundingModeHalfUp() {
476   return appendToken(u"rounding-mode-half-up");
477 }
478 
toFormatter(JSContext * cx,const char * locale)479 UNumberFormatter* js::intl::NumberFormatterSkeleton::toFormatter(
480     JSContext* cx, const char* locale) {
481   UErrorCode status = U_ZERO_ERROR;
482   UNumberFormatter* nf = unumf_openForSkeletonAndLocale(
483       vector_.begin(), vector_.length(), locale, &status);
484   if (U_FAILURE(status)) {
485     intl::ReportInternalError(cx);
486     return nullptr;
487   }
488   return nf;
489 }
490 
491 /**
492  * Returns a new UNumberFormatter with the locale and number formatting options
493  * of the given NumberFormat.
494  */
NewUNumberFormatter(JSContext * cx,Handle<NumberFormatObject * > numberFormat)495 static UNumberFormatter* NewUNumberFormatter(
496     JSContext* cx, Handle<NumberFormatObject*> numberFormat) {
497   RootedValue value(cx);
498 
499   RootedObject internals(cx, intl::GetInternalsObject(cx, numberFormat));
500   if (!internals) {
501     return nullptr;
502   }
503 
504   if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
505     return nullptr;
506   }
507 
508   // ICU expects numberingSystem as a Unicode locale extensions on locale.
509 
510   intl::LanguageTag tag(cx);
511   {
512     JSLinearString* locale = value.toString()->ensureLinear(cx);
513     if (!locale) {
514       return nullptr;
515     }
516 
517     if (!intl::LanguageTagParser::parse(cx, locale, tag)) {
518       return nullptr;
519     }
520   }
521 
522   JS::RootedVector<intl::UnicodeExtensionKeyword> keywords(cx);
523 
524   if (!GetProperty(cx, internals, internals, cx->names().numberingSystem,
525                    &value)) {
526     return nullptr;
527   }
528 
529   {
530     JSLinearString* numberingSystem = value.toString()->ensureLinear(cx);
531     if (!numberingSystem) {
532       return nullptr;
533     }
534 
535     if (!keywords.emplaceBack("nu", numberingSystem)) {
536       return nullptr;
537     }
538   }
539 
540   // |ApplyUnicodeExtensionToTag| applies the new keywords to the front of
541   // the Unicode extension subtag. We're then relying on ICU to follow RFC
542   // 6067, which states that any trailing keywords using the same key
543   // should be ignored.
544   if (!intl::ApplyUnicodeExtensionToTag(cx, tag, keywords)) {
545     return nullptr;
546   }
547 
548   UniqueChars locale = tag.toStringZ(cx);
549   if (!locale) {
550     return nullptr;
551   }
552 
553   intl::NumberFormatterSkeleton skeleton(cx);
554 
555   if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
556     return nullptr;
557   }
558 
559   bool accountingSign = false;
560   {
561     JSLinearString* style = value.toString()->ensureLinear(cx);
562     if (!style) {
563       return nullptr;
564     }
565 
566     if (StringEqualsLiteral(style, "currency")) {
567       if (!GetProperty(cx, internals, internals, cx->names().currency,
568                        &value)) {
569         return nullptr;
570       }
571       JSLinearString* currency = value.toString()->ensureLinear(cx);
572       if (!currency) {
573         return nullptr;
574       }
575 
576       if (!skeleton.currency(currency)) {
577         return nullptr;
578       }
579 
580       if (!GetProperty(cx, internals, internals, cx->names().currencyDisplay,
581                        &value)) {
582         return nullptr;
583       }
584       JSLinearString* currencyDisplay = value.toString()->ensureLinear(cx);
585       if (!currencyDisplay) {
586         return nullptr;
587       }
588 
589       using CurrencyDisplay = intl::NumberFormatterSkeleton::CurrencyDisplay;
590 
591       CurrencyDisplay display;
592       if (StringEqualsLiteral(currencyDisplay, "code")) {
593         display = CurrencyDisplay::Code;
594       } else if (StringEqualsLiteral(currencyDisplay, "symbol")) {
595         display = CurrencyDisplay::Symbol;
596       } else if (StringEqualsLiteral(currencyDisplay, "narrowSymbol")) {
597         display = CurrencyDisplay::NarrowSymbol;
598       } else {
599         MOZ_ASSERT(StringEqualsLiteral(currencyDisplay, "name"));
600         display = CurrencyDisplay::Name;
601       }
602 
603       if (!skeleton.currencyDisplay(display)) {
604         return nullptr;
605       }
606 
607       if (!GetProperty(cx, internals, internals, cx->names().currencySign,
608                        &value)) {
609         return nullptr;
610       }
611       JSLinearString* currencySign = value.toString()->ensureLinear(cx);
612       if (!currencySign) {
613         return nullptr;
614       }
615 
616       if (StringEqualsLiteral(currencySign, "accounting")) {
617         accountingSign = true;
618       } else {
619         MOZ_ASSERT(StringEqualsLiteral(currencySign, "standard"));
620       }
621     } else if (StringEqualsLiteral(style, "percent")) {
622       if (!skeleton.percent()) {
623         return nullptr;
624       }
625     } else if (StringEqualsLiteral(style, "unit")) {
626       if (!GetProperty(cx, internals, internals, cx->names().unit, &value)) {
627         return nullptr;
628       }
629       JSLinearString* unit = value.toString()->ensureLinear(cx);
630       if (!unit) {
631         return nullptr;
632       }
633 
634       if (!skeleton.unit(unit)) {
635         return nullptr;
636       }
637 
638       if (!GetProperty(cx, internals, internals, cx->names().unitDisplay,
639                        &value)) {
640         return nullptr;
641       }
642       JSLinearString* unitDisplay = value.toString()->ensureLinear(cx);
643       if (!unitDisplay) {
644         return nullptr;
645       }
646 
647       using UnitDisplay = intl::NumberFormatterSkeleton::UnitDisplay;
648 
649       UnitDisplay display;
650       if (StringEqualsLiteral(unitDisplay, "short")) {
651         display = UnitDisplay::Short;
652       } else if (StringEqualsLiteral(unitDisplay, "narrow")) {
653         display = UnitDisplay::Narrow;
654       } else {
655         MOZ_ASSERT(StringEqualsLiteral(unitDisplay, "long"));
656         display = UnitDisplay::Long;
657       }
658 
659       if (!skeleton.unitDisplay(display)) {
660         return nullptr;
661       }
662     } else {
663       MOZ_ASSERT(StringEqualsLiteral(style, "decimal"));
664     }
665   }
666 
667   bool hasMinimumSignificantDigits;
668   if (!HasProperty(cx, internals, cx->names().minimumSignificantDigits,
669                    &hasMinimumSignificantDigits)) {
670     return nullptr;
671   }
672 
673   if (hasMinimumSignificantDigits) {
674     if (!GetProperty(cx, internals, internals,
675                      cx->names().minimumSignificantDigits, &value)) {
676       return nullptr;
677     }
678     uint32_t minimumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
679 
680     if (!GetProperty(cx, internals, internals,
681                      cx->names().maximumSignificantDigits, &value)) {
682       return nullptr;
683     }
684     uint32_t maximumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
685 
686     if (!skeleton.significantDigits(minimumSignificantDigits,
687                                     maximumSignificantDigits)) {
688       return nullptr;
689     }
690   }
691 
692   bool hasMinimumFractionDigits;
693   if (!HasProperty(cx, internals, cx->names().minimumFractionDigits,
694                    &hasMinimumFractionDigits)) {
695     return nullptr;
696   }
697 
698   if (hasMinimumFractionDigits) {
699     if (!GetProperty(cx, internals, internals,
700                      cx->names().minimumFractionDigits, &value)) {
701       return nullptr;
702     }
703     uint32_t minimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
704 
705     if (!GetProperty(cx, internals, internals,
706                      cx->names().maximumFractionDigits, &value)) {
707       return nullptr;
708     }
709     uint32_t maximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
710 
711     if (!skeleton.fractionDigits(minimumFractionDigits,
712                                  maximumFractionDigits)) {
713       return nullptr;
714     }
715   }
716 
717   if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
718                    &value)) {
719     return nullptr;
720   }
721   uint32_t minimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
722 
723   if (!skeleton.integerWidth(minimumIntegerDigits)) {
724     return nullptr;
725   }
726 
727   if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value)) {
728     return nullptr;
729   }
730   if (!skeleton.useGrouping(value.toBoolean())) {
731     return nullptr;
732   }
733 
734   if (!GetProperty(cx, internals, internals, cx->names().notation, &value)) {
735     return nullptr;
736   }
737 
738   {
739     JSLinearString* notation = value.toString()->ensureLinear(cx);
740     if (!notation) {
741       return nullptr;
742     }
743 
744     using Notation = intl::NumberFormatterSkeleton::Notation;
745 
746     Notation style;
747     if (StringEqualsLiteral(notation, "standard")) {
748       style = Notation::Standard;
749     } else if (StringEqualsLiteral(notation, "scientific")) {
750       style = Notation::Scientific;
751     } else if (StringEqualsLiteral(notation, "engineering")) {
752       style = Notation::Engineering;
753     } else {
754       MOZ_ASSERT(StringEqualsLiteral(notation, "compact"));
755 
756       if (!GetProperty(cx, internals, internals, cx->names().compactDisplay,
757                        &value)) {
758         return nullptr;
759       }
760 
761       JSLinearString* compactDisplay = value.toString()->ensureLinear(cx);
762       if (!compactDisplay) {
763         return nullptr;
764       }
765 
766       if (StringEqualsLiteral(compactDisplay, "short")) {
767         style = Notation::CompactShort;
768       } else {
769         MOZ_ASSERT(StringEqualsLiteral(compactDisplay, "long"));
770         style = Notation::CompactLong;
771       }
772     }
773 
774     if (!skeleton.notation(style)) {
775       return nullptr;
776     }
777   }
778 
779   if (!GetProperty(cx, internals, internals, cx->names().signDisplay, &value)) {
780     return nullptr;
781   }
782 
783   {
784     JSLinearString* signDisplay = value.toString()->ensureLinear(cx);
785     if (!signDisplay) {
786       return nullptr;
787     }
788 
789     using SignDisplay = intl::NumberFormatterSkeleton::SignDisplay;
790 
791     SignDisplay display;
792     if (StringEqualsLiteral(signDisplay, "auto")) {
793       if (accountingSign) {
794         display = SignDisplay::Accounting;
795       } else {
796         display = SignDisplay::Auto;
797       }
798     } else if (StringEqualsLiteral(signDisplay, "never")) {
799       display = SignDisplay::Never;
800     } else if (StringEqualsLiteral(signDisplay, "always")) {
801       if (accountingSign) {
802         display = SignDisplay::AccountingAlways;
803       } else {
804         display = SignDisplay::Always;
805       }
806     } else {
807       MOZ_ASSERT(StringEqualsLiteral(signDisplay, "exceptZero"));
808       if (accountingSign) {
809         display = SignDisplay::AccountingExceptZero;
810       } else {
811         display = SignDisplay::ExceptZero;
812       }
813     }
814 
815     if (!skeleton.signDisplay(display)) {
816       return nullptr;
817     }
818   }
819 
820   if (!skeleton.roundingModeHalfUp()) {
821     return nullptr;
822   }
823 
824   return skeleton.toFormatter(cx, locale.get());
825 }
826 
NewUFormattedNumber(JSContext * cx)827 static UFormattedNumber* NewUFormattedNumber(JSContext* cx) {
828   UErrorCode status = U_ZERO_ERROR;
829   UFormattedNumber* formatted = unumf_openResult(&status);
830   if (U_FAILURE(status)) {
831     intl::ReportInternalError(cx);
832     return nullptr;
833   }
834   return formatted;
835 }
836 
PartitionNumberPattern(JSContext * cx,const UNumberFormatter * nf,UFormattedNumber * formatted,HandleValue x)837 static const UFormattedValue* PartitionNumberPattern(
838     JSContext* cx, const UNumberFormatter* nf, UFormattedNumber* formatted,
839     HandleValue x) {
840   UErrorCode status = U_ZERO_ERROR;
841   if (x.isNumber()) {
842     double num = x.toNumber();
843 
844     // ICU incorrectly formats NaN values with the sign bit set, as if they
845     // were negative.  Replace all NaNs with a single pattern with sign bit
846     // unset ("positive", that is) until ICU is fixed.
847     if (MOZ_UNLIKELY(IsNaN(num))) {
848       num = SpecificNaN<double>(0, 1);
849     }
850 
851     unumf_formatDouble(nf, num, formatted, &status);
852   } else {
853     RootedBigInt bi(cx, x.toBigInt());
854 
855     int64_t num;
856     if (BigInt::isInt64(bi, &num)) {
857       unumf_formatInt(nf, num, formatted, &status);
858     } else {
859       JSLinearString* str = BigInt::toString<CanGC>(cx, bi, 10);
860       if (!str) {
861         return nullptr;
862       }
863       MOZ_ASSERT(str->hasLatin1Chars());
864 
865       // Tell the analysis the |unumf_formatDecimal| function can't GC.
866       JS::AutoSuppressGCAnalysis nogc;
867 
868       const char* chars = reinterpret_cast<const char*>(str->latin1Chars(nogc));
869       unumf_formatDecimal(nf, chars, str->length(), formatted, &status);
870     }
871   }
872   if (U_FAILURE(status)) {
873     intl::ReportInternalError(cx);
874     return nullptr;
875   }
876 
877   const UFormattedValue* formattedValue =
878       unumf_resultAsValue(formatted, &status);
879   if (U_FAILURE(status)) {
880     intl::ReportInternalError(cx);
881     return nullptr;
882   }
883   return formattedValue;
884 }
885 
FormattedNumberToString(JSContext * cx,const UFormattedValue * formattedValue)886 static JSString* FormattedNumberToString(
887     JSContext* cx, const UFormattedValue* formattedValue) {
888   UErrorCode status = U_ZERO_ERROR;
889   int32_t strLength;
890   const char16_t* str = ufmtval_getString(formattedValue, &strLength, &status);
891   if (U_FAILURE(status)) {
892     intl::ReportInternalError(cx);
893     return nullptr;
894   }
895 
896   return NewStringCopyN<CanGC>(cx, str, AssertedCast<uint32_t>(strLength));
897 }
898 
FormatNumeric(JSContext * cx,const UNumberFormatter * nf,UFormattedNumber * formatted,HandleValue x,MutableHandleValue result)899 static bool FormatNumeric(JSContext* cx, const UNumberFormatter* nf,
900                           UFormattedNumber* formatted, HandleValue x,
901                           MutableHandleValue result) {
902   const UFormattedValue* formattedValue =
903       PartitionNumberPattern(cx, nf, formatted, x);
904   if (!formattedValue) {
905     return false;
906   }
907 
908   JSString* str = FormattedNumberToString(cx, formattedValue);
909   if (!str) {
910     return false;
911   }
912 
913   result.setString(str);
914   return true;
915 }
916 
917 enum class FormattingType { ForUnit, NotForUnit };
918 
GetFieldTypeForNumberField(UNumberFormatFields fieldName,HandleValue x,FormattingType formattingType)919 static FieldType GetFieldTypeForNumberField(UNumberFormatFields fieldName,
920                                             HandleValue x,
921                                             FormattingType formattingType) {
922   // See intl/icu/source/i18n/unicode/unum.h for a detailed field list.  This
923   // list is deliberately exhaustive: cases might have to be added/removed if
924   // this code is compiled with a different ICU with more UNumberFormatFields
925   // enum initializers.  Please guard such cases with appropriate ICU
926   // version-testing #ifdefs, should cross-version divergence occur.
927   switch (fieldName) {
928     case UNUM_INTEGER_FIELD:
929       if (x.isNumber()) {
930         double d = x.toNumber();
931         if (IsNaN(d)) {
932           return &JSAtomState::nan;
933         }
934         if (!IsFinite(d)) {
935           return &JSAtomState::infinity;
936         }
937       }
938       return &JSAtomState::integer;
939 
940     case UNUM_GROUPING_SEPARATOR_FIELD:
941       return &JSAtomState::group;
942 
943     case UNUM_DECIMAL_SEPARATOR_FIELD:
944       return &JSAtomState::decimal;
945 
946     case UNUM_FRACTION_FIELD:
947       return &JSAtomState::fraction;
948 
949     case UNUM_SIGN_FIELD: {
950       // We coerce all NaNs to one with the sign bit unset, so all NaNs are
951       // positive in our implementation.
952       bool isNegative = x.isNumber()
953                             ? !IsNaN(x.toNumber()) && IsNegative(x.toNumber())
954                             : x.toBigInt()->isNegative();
955       return isNegative ? &JSAtomState::minusSign : &JSAtomState::plusSign;
956     }
957 
958     case UNUM_PERCENT_FIELD:
959       // Percent fields are returned as "unit" elements when the number
960       // formatter's style is "unit".
961       if (formattingType == FormattingType::ForUnit) {
962         return &JSAtomState::unit;
963       }
964       return &JSAtomState::percentSign;
965 
966     case UNUM_CURRENCY_FIELD:
967       return &JSAtomState::currency;
968 
969     case UNUM_PERMILL_FIELD:
970       MOZ_ASSERT_UNREACHABLE(
971           "unexpected permill field found, even though "
972           "we don't use any user-defined patterns that "
973           "would require a permill field");
974       break;
975 
976     case UNUM_EXPONENT_SYMBOL_FIELD:
977       return &JSAtomState::exponentSeparator;
978 
979     case UNUM_EXPONENT_SIGN_FIELD:
980       return &JSAtomState::exponentMinusSign;
981 
982     case UNUM_EXPONENT_FIELD:
983       return &JSAtomState::exponentInteger;
984 
985     case UNUM_MEASURE_UNIT_FIELD:
986       return &JSAtomState::unit;
987 
988     case UNUM_COMPACT_FIELD:
989       return &JSAtomState::compact;
990 
991 #ifndef U_HIDE_DEPRECATED_API
992     case UNUM_FIELD_COUNT:
993       MOZ_ASSERT_UNREACHABLE(
994           "format field sentinel value returned by iterator!");
995       break;
996 #endif
997   }
998 
999   MOZ_ASSERT_UNREACHABLE(
1000       "unenumerated, undocumented format field returned by iterator");
1001   return nullptr;
1002 }
1003 
1004 struct Field {
1005   uint32_t begin;
1006   uint32_t end;
1007   FieldType type;
1008 
1009   // Needed for vector-resizing scratch space.
1010   Field() = default;
1011 
FieldField1012   Field(uint32_t begin, uint32_t end, FieldType type)
1013       : begin(begin), end(end), type(type) {}
1014 };
1015 
1016 class NumberFormatFields {
1017   using FieldsVector = Vector<Field, 16>;
1018 
1019   FieldsVector fields_;
1020 
1021  public:
NumberFormatFields(JSContext * cx)1022   explicit NumberFormatFields(JSContext* cx) : fields_(cx) {}
1023 
1024   MOZ_MUST_USE bool append(FieldType type, int32_t begin, int32_t end);
1025 
1026   MOZ_MUST_USE ArrayObject* toArray(JSContext* cx,
1027                                     JS::HandleString overallResult,
1028                                     FieldType unitType);
1029 };
1030 
append(FieldType type,int32_t begin,int32_t end)1031 bool NumberFormatFields::append(FieldType type, int32_t begin, int32_t end) {
1032   MOZ_ASSERT(begin >= 0);
1033   MOZ_ASSERT(end >= 0);
1034   MOZ_ASSERT(begin < end, "erm, aren't fields always non-empty?");
1035 
1036   return fields_.emplaceBack(uint32_t(begin), uint32_t(end), type);
1037 }
1038 
toArray(JSContext * cx,HandleString overallResult,FieldType unitType)1039 ArrayObject* NumberFormatFields::toArray(JSContext* cx,
1040                                          HandleString overallResult,
1041                                          FieldType unitType) {
1042   // Merge sort the fields vector.  Expand the vector to have scratch space for
1043   // performing the sort.
1044   size_t fieldsLen = fields_.length();
1045   if (!fields_.growByUninitialized(fieldsLen)) {
1046     return nullptr;
1047   }
1048 
1049   MOZ_ALWAYS_TRUE(MergeSort(
1050       fields_.begin(), fieldsLen, fields_.begin() + fieldsLen,
1051       [](const Field& left, const Field& right, bool* lessOrEqual) {
1052         // Sort first by begin index, then to place
1053         // enclosing fields before nested fields.
1054         *lessOrEqual = left.begin < right.begin ||
1055                        (left.begin == right.begin && left.end > right.end);
1056         return true;
1057       }));
1058 
1059   // Delete the elements in the scratch space.
1060   fields_.shrinkBy(fieldsLen);
1061 
1062   // Then iterate over the sorted field list to generate a sequence of parts
1063   // (what ECMA-402 actually exposes).  A part is a maximal character sequence
1064   // entirely within no field or a single most-nested field.
1065   //
1066   // Diagrams may be helpful to illustrate how fields map to parts.  Consider
1067   // formatting -19,766,580,028,249.41, the US national surplus (negative
1068   // because it's actually a debt) on October 18, 2016.
1069   //
1070   //    var options =
1071   //      { style: "currency", currency: "USD", currencyDisplay: "name" };
1072   //    var usdFormatter = new Intl.NumberFormat("en-US", options);
1073   //    usdFormatter.format(-19766580028249.41);
1074   //
1075   // The formatted result is "-19,766,580,028,249.41 US dollars".  ICU
1076   // identifies these fields in the string:
1077   //
1078   //     UNUM_GROUPING_SEPARATOR_FIELD
1079   //                   |
1080   //   UNUM_SIGN_FIELD |  UNUM_DECIMAL_SEPARATOR_FIELD
1081   //    |   __________/|   |
1082   //    |  /   |   |   |   |
1083   //   "-19,766,580,028,249.41 US dollars"
1084   //     \________________/ |/ \_______/
1085   //             |          |      |
1086   //    UNUM_INTEGER_FIELD  |  UNUM_CURRENCY_FIELD
1087   //                        |
1088   //               UNUM_FRACTION_FIELD
1089   //
1090   // These fields map to parts as follows:
1091   //
1092   //         integer     decimal
1093   //       _____|________  |
1094   //      /  /| |\  |\  |\ |  literal
1095   //     /| / | | \ | \ | \|  |
1096   //   "-19,766,580,028,249.41 US dollars"
1097   //    |  \___|___|___/    |/ \________/
1098   //    |        |          |       |
1099   //    |      group        |   currency
1100   //    |                   |
1101   //   minusSign        fraction
1102   //
1103   // The sign is a part.  Each comma is a part, splitting the integer field
1104   // into parts for trillions/billions/&c. digits.  The decimal point is a
1105   // part.  Cents are a part.  The space between cents and currency is a part
1106   // (outside any field).  Last, the currency field is a part.
1107   //
1108   // Because parts fully partition the formatted string, we only track the
1109   // end of each part -- the beginning is implicitly the last part's end.
1110   struct Part {
1111     uint32_t end;
1112     FieldType type;
1113   };
1114 
1115   class PartGenerator {
1116     // The fields in order from start to end, then least to most nested.
1117     const FieldsVector& fields;
1118 
1119     // Index of the current field, in |fields|, being considered to
1120     // determine part boundaries.  |lastEnd <= fields[index].begin| is an
1121     // invariant.
1122     size_t index;
1123 
1124     // The end index of the last part produced, always less than or equal
1125     // to |limit|, strictly increasing.
1126     uint32_t lastEnd;
1127 
1128     // The length of the overall formatted string.
1129     const uint32_t limit;
1130 
1131     Vector<size_t, 4> enclosingFields;
1132 
1133     void popEnclosingFieldsEndingAt(uint32_t end) {
1134       MOZ_ASSERT_IF(enclosingFields.length() > 0,
1135                     fields[enclosingFields.back()].end >= end);
1136 
1137       while (enclosingFields.length() > 0 &&
1138              fields[enclosingFields.back()].end == end) {
1139         enclosingFields.popBack();
1140       }
1141     }
1142 
1143     bool nextPartInternal(Part* part) {
1144       size_t len = fields.length();
1145       MOZ_ASSERT(index <= len);
1146 
1147       // If we're out of fields, all that remains are part(s) consisting
1148       // of trailing portions of enclosing fields, and maybe a final
1149       // literal part.
1150       if (index == len) {
1151         if (enclosingFields.length() > 0) {
1152           const auto& enclosing = fields[enclosingFields.popCopy()];
1153           part->end = enclosing.end;
1154           part->type = enclosing.type;
1155 
1156           // If additional enclosing fields end where this part ends,
1157           // pop them as well.
1158           popEnclosingFieldsEndingAt(part->end);
1159         } else {
1160           part->end = limit;
1161           part->type = &JSAtomState::literal;
1162         }
1163 
1164         return true;
1165       }
1166 
1167       // Otherwise we still have a field to process.
1168       const Field* current = &fields[index];
1169       MOZ_ASSERT(lastEnd <= current->begin);
1170       MOZ_ASSERT(current->begin < current->end);
1171 
1172       // But first, deal with inter-field space.
1173       if (lastEnd < current->begin) {
1174         if (enclosingFields.length() > 0) {
1175           // Space between fields, within an enclosing field, is part
1176           // of that enclosing field, until the start of the current
1177           // field or the end of the enclosing field, whichever is
1178           // earlier.
1179           const auto& enclosing = fields[enclosingFields.back()];
1180           part->end = std::min(enclosing.end, current->begin);
1181           part->type = enclosing.type;
1182           popEnclosingFieldsEndingAt(part->end);
1183         } else {
1184           // If there's no enclosing field, the space is a literal.
1185           part->end = current->begin;
1186           part->type = &JSAtomState::literal;
1187         }
1188 
1189         return true;
1190       }
1191 
1192       // Otherwise, the part spans a prefix of the current field.  Find
1193       // the most-nested field containing that prefix.
1194       const Field* next;
1195       do {
1196         current = &fields[index];
1197 
1198         // If the current field is last, the part extends to its end.
1199         if (++index == len) {
1200           part->end = current->end;
1201           part->type = current->type;
1202           return true;
1203         }
1204 
1205         next = &fields[index];
1206         MOZ_ASSERT(current->begin <= next->begin);
1207         MOZ_ASSERT(current->begin < next->end);
1208 
1209         // If the next field nests within the current field, push an
1210         // enclosing field.  (If there are no nested fields, don't
1211         // bother pushing a field that'd be immediately popped.)
1212         if (current->end > next->begin) {
1213           if (!enclosingFields.append(index - 1)) {
1214             return false;
1215           }
1216         }
1217 
1218         // Do so until the next field begins after this one.
1219       } while (current->begin == next->begin);
1220 
1221       part->type = current->type;
1222 
1223       if (current->end <= next->begin) {
1224         // The next field begins after the current field ends.  Therefore
1225         // the current part ends at the end of the current field.
1226         part->end = current->end;
1227         popEnclosingFieldsEndingAt(part->end);
1228       } else {
1229         // The current field encloses the next one.  The current part
1230         // ends where the next field/part will start.
1231         part->end = next->begin;
1232       }
1233 
1234       return true;
1235     }
1236 
1237    public:
1238     PartGenerator(JSContext* cx, const FieldsVector& vec, uint32_t limit)
1239         : fields(vec),
1240           index(0),
1241           lastEnd(0),
1242           limit(limit),
1243           enclosingFields(cx) {}
1244 
1245     bool nextPart(bool* hasPart, Part* part) {
1246       // There are no parts left if we've partitioned the entire string.
1247       if (lastEnd == limit) {
1248         MOZ_ASSERT(enclosingFields.length() == 0);
1249         *hasPart = false;
1250         return true;
1251       }
1252 
1253       if (!nextPartInternal(part)) {
1254         return false;
1255       }
1256 
1257       *hasPart = true;
1258       lastEnd = part->end;
1259       return true;
1260     }
1261   };
1262 
1263   // Finally, generate the result array.
1264   size_t lastEndIndex = 0;
1265   RootedObject singlePart(cx);
1266   RootedValue propVal(cx);
1267 
1268   RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
1269   if (!partsArray) {
1270     return nullptr;
1271   }
1272 
1273   PartGenerator gen(cx, fields_, overallResult->length());
1274   do {
1275     bool hasPart;
1276     Part part;
1277     if (!gen.nextPart(&hasPart, &part)) {
1278       return nullptr;
1279     }
1280 
1281     if (!hasPart) {
1282       break;
1283     }
1284 
1285     FieldType type = part.type;
1286     size_t endIndex = part.end;
1287 
1288     MOZ_ASSERT(lastEndIndex < endIndex);
1289 
1290     singlePart = NewBuiltinClassInstance<PlainObject>(cx);
1291     if (!singlePart) {
1292       return nullptr;
1293     }
1294 
1295     propVal.setString(cx->names().*type);
1296     if (!DefineDataProperty(cx, singlePart, cx->names().type, propVal)) {
1297       return nullptr;
1298     }
1299 
1300     JSLinearString* partSubstr = NewDependentString(
1301         cx, overallResult, lastEndIndex, endIndex - lastEndIndex);
1302     if (!partSubstr) {
1303       return nullptr;
1304     }
1305 
1306     propVal.setString(partSubstr);
1307     if (!DefineDataProperty(cx, singlePart, cx->names().value, propVal)) {
1308       return nullptr;
1309     }
1310 
1311     if (unitType != nullptr && type != &JSAtomState::literal) {
1312       propVal.setString(cx->names().*unitType);
1313       if (!DefineDataProperty(cx, singlePart, cx->names().unit, propVal)) {
1314         return nullptr;
1315       }
1316     }
1317 
1318     if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) {
1319       return nullptr;
1320     }
1321 
1322     lastEndIndex = endIndex;
1323   } while (true);
1324 
1325   MOZ_ASSERT(lastEndIndex == overallResult->length(),
1326              "result array must partition the entire string");
1327 
1328   return partsArray;
1329 }
1330 
FormattedNumberToParts(JSContext * cx,const UFormattedValue * formattedValue,HandleValue number,FieldType relativeTimeUnit,FormattingType formattingType,MutableHandleValue result)1331 static bool FormattedNumberToParts(JSContext* cx,
1332                                    const UFormattedValue* formattedValue,
1333                                    HandleValue number,
1334                                    FieldType relativeTimeUnit,
1335                                    FormattingType formattingType,
1336                                    MutableHandleValue result) {
1337   MOZ_ASSERT(number.isNumeric());
1338 
1339   RootedString overallResult(cx, FormattedNumberToString(cx, formattedValue));
1340   if (!overallResult) {
1341     return false;
1342   }
1343 
1344   UErrorCode status = U_ZERO_ERROR;
1345   UConstrainedFieldPosition* fpos = ucfpos_open(&status);
1346   if (U_FAILURE(status)) {
1347     intl::ReportInternalError(cx);
1348     return false;
1349   }
1350   ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
1351 
1352   // We're only interested in UFIELD_CATEGORY_NUMBER fields.
1353   ucfpos_constrainCategory(fpos, UFIELD_CATEGORY_NUMBER, &status);
1354   if (U_FAILURE(status)) {
1355     intl::ReportInternalError(cx);
1356     return false;
1357   }
1358 
1359   // Vacuum up fields in the overall formatted string.
1360 
1361   NumberFormatFields fields(cx);
1362 
1363   while (true) {
1364     bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status);
1365     if (U_FAILURE(status)) {
1366       intl::ReportInternalError(cx);
1367       return false;
1368     }
1369     if (!hasMore) {
1370       break;
1371     }
1372 
1373     int32_t field = ucfpos_getField(fpos, &status);
1374     if (U_FAILURE(status)) {
1375       intl::ReportInternalError(cx);
1376       return false;
1377     }
1378 
1379     int32_t beginIndex, endIndex;
1380     ucfpos_getIndexes(fpos, &beginIndex, &endIndex, &status);
1381     if (U_FAILURE(status)) {
1382       intl::ReportInternalError(cx);
1383       return false;
1384     }
1385 
1386     FieldType type = GetFieldTypeForNumberField(UNumberFormatFields(field),
1387                                                 number, formattingType);
1388 
1389     if (!fields.append(type, beginIndex, endIndex)) {
1390       return false;
1391     }
1392   }
1393 
1394   ArrayObject* array = fields.toArray(cx, overallResult, relativeTimeUnit);
1395   if (!array) {
1396     return false;
1397   }
1398 
1399   result.setObject(*array);
1400   return true;
1401 }
1402 
FormattedRelativeTimeToParts(JSContext * cx,const UFormattedValue * formattedValue,double timeValue,FieldType relativeTimeUnit,MutableHandleValue result)1403 bool js::intl::FormattedRelativeTimeToParts(
1404     JSContext* cx, const UFormattedValue* formattedValue, double timeValue,
1405     FieldType relativeTimeUnit, MutableHandleValue result) {
1406   Value tval = DoubleValue(timeValue);
1407   return FormattedNumberToParts(
1408       cx, formattedValue, HandleValue::fromMarkedLocation(&tval),
1409       relativeTimeUnit, FormattingType::NotForUnit, result);
1410 }
1411 
FormatNumericToParts(JSContext * cx,const UNumberFormatter * nf,UFormattedNumber * formatted,HandleValue x,FormattingType formattingType,MutableHandleValue result)1412 static bool FormatNumericToParts(JSContext* cx, const UNumberFormatter* nf,
1413                                  UFormattedNumber* formatted, HandleValue x,
1414                                  FormattingType formattingType,
1415                                  MutableHandleValue result) {
1416   const UFormattedValue* formattedValue =
1417       PartitionNumberPattern(cx, nf, formatted, x);
1418   if (!formattedValue) {
1419     return false;
1420   }
1421 
1422   return FormattedNumberToParts(cx, formattedValue, x, nullptr, formattingType,
1423                                 result);
1424 }
1425 
intl_FormatNumber(JSContext * cx,unsigned argc,Value * vp)1426 bool js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp) {
1427   CallArgs args = CallArgsFromVp(argc, vp);
1428   MOZ_ASSERT(args.length() == 4);
1429   MOZ_ASSERT(args[0].isObject());
1430   MOZ_ASSERT(args[1].isNumeric());
1431   MOZ_ASSERT(args[2].isBoolean());
1432   MOZ_ASSERT(args[3].isBoolean());
1433 
1434   Rooted<NumberFormatObject*> numberFormat(
1435       cx, &args[0].toObject().as<NumberFormatObject>());
1436 
1437   // Obtain a cached UNumberFormatter object.
1438   UNumberFormatter* nf = numberFormat->getNumberFormatter();
1439   if (!nf) {
1440     nf = NewUNumberFormatter(cx, numberFormat);
1441     if (!nf) {
1442       return false;
1443     }
1444     numberFormat->setNumberFormatter(nf);
1445 
1446     intl::AddICUCellMemory(numberFormat,
1447                            NumberFormatObject::EstimatedMemoryUse);
1448   }
1449 
1450   // Obtain a cached UFormattedNumber object.
1451   UFormattedNumber* formatted = numberFormat->getFormattedNumber();
1452   if (!formatted) {
1453     formatted = NewUFormattedNumber(cx);
1454     if (!formatted) {
1455       return false;
1456     }
1457     numberFormat->setFormattedNumber(formatted);
1458 
1459     // UFormattedNumber memory tracked as part of UNumberFormatter.
1460   }
1461 
1462   // Use the UNumberFormatter to actually format the number.
1463   if (args[2].toBoolean()) {
1464     FormattingType formattingType = args[3].toBoolean()
1465                                         ? FormattingType::ForUnit
1466                                         : FormattingType::NotForUnit;
1467     return FormatNumericToParts(cx, nf, formatted, args[1], formattingType,
1468                                 args.rval());
1469   }
1470 
1471   return FormatNumeric(cx, nf, formatted, args[1], args.rval());
1472 }
1473