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