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