tor-browser

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

DisplayNames.h (32300B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 #ifndef intl_components_DisplayNames_h_
      5 #define intl_components_DisplayNames_h_
      6 
      7 #include <string_view>
      8 #include "unicode/udat.h"
      9 #include "unicode/udatpg.h"
     10 #include "unicode/uldnames.h"
     11 #include "unicode/uloc.h"
     12 #include "unicode/ucurr.h"
     13 #include "mozilla/intl/Calendar.h"
     14 #include "mozilla/intl/DateTimePatternGenerator.h"
     15 #include "mozilla/intl/ICU4CGlue.h"
     16 #include "mozilla/intl/Locale.h"
     17 #include "mozilla/Buffer.h"
     18 #include "mozilla/Casting.h"
     19 #include "mozilla/PodOperations.h"
     20 #include "mozilla/Result.h"
     21 #include "mozilla/Span.h"
     22 #include "mozilla/TextUtils.h"
     23 #include "mozilla/UniquePtr.h"
     24 
     25 namespace mozilla::intl {
     26 /**
     27 * Provide more granular errors for DisplayNames rather than use the generic
     28 * ICUError type. This helps with providing more actionable feedback for
     29 * errors with input validation.
     30 *
     31 * This type can't be nested in the DisplayNames class because it needs the
     32 * UnusedZero and HasFreeLSB definitions.
     33 */
     34 enum class DisplayNamesError {
     35  // Since we claim UnusedZero<DisplayNamesError>::value and
     36  // HasFreeLSB<Error>::value == true below, we must only use positive,
     37  // even enum values.
     38  InternalError = 2,
     39  OutOfMemory = 4,
     40  InvalidOption = 6,
     41  DuplicateVariantSubtag = 8,
     42  InvalidLanguageTag = 10,
     43 };
     44 }  // namespace mozilla::intl
     45 
     46 namespace mozilla::detail {
     47 // Ensure the efficient packing of the error types into the result. See
     48 // ICUError.h and the ICUError comments for more information.
     49 template <>
     50 struct UnusedZero<intl::DisplayNamesError>
     51    : UnusedZeroEnum<intl::DisplayNamesError> {};
     52 
     53 template <>
     54 struct HasFreeLSB<intl::DisplayNamesError> {
     55  static constexpr bool value = true;
     56 };
     57 }  // namespace mozilla::detail
     58 
     59 namespace mozilla::intl {
     60 
     61 // NOTE: The UTF-35 canonical "code" value for months and quarters are 1-based
     62 // integers, so some of the following enums are 1-based for consistency with
     63 // that. For simplicity, we make all of the following enums 1-based, but use
     64 // `EnumToIndex` (see below) to convert to zero based if indexing into internal
     65 // (non-ICU) tables.
     66 
     67 /**
     68 * Month choices for display names.
     69 */
     70 enum class Month : uint8_t {
     71  January = 1,
     72  February,
     73  March,
     74  April,
     75  May,
     76  June,
     77  July,
     78  August,
     79  September,
     80  October,
     81  November,
     82  December,
     83  // Some calendar systems feature a 13th month.
     84  // https://en.wikipedia.org/wiki/Undecimber
     85  Undecimber
     86 };
     87 
     88 /**
     89 * Quarter choices for display names.
     90 */
     91 enum class Quarter : uint8_t {
     92  Q1 = 1,
     93  Q2,
     94  Q3,
     95  Q4,
     96 };
     97 
     98 /**
     99 * Day period choices for display names.
    100 */
    101 enum class DayPeriod : uint8_t {
    102  AM = 1,
    103  PM,
    104 };
    105 
    106 /**
    107 * DateTimeField choices for display names.
    108 */
    109 enum class DateTimeField : uint8_t {
    110  Era = 1,
    111  Year,
    112  Quarter,
    113  Month,
    114  WeekOfYear,
    115  Weekday,
    116  Day,
    117  DayPeriod,
    118  Hour,
    119  Minute,
    120  Second,
    121  TimeZoneName,
    122 };
    123 
    124 /**
    125 * DisplayNames provide a way to get the localized names of various types of
    126 * information such as the names of the day of the week, months, currency etc.
    127 *
    128 * This class backs SpiderMonkeys implementation of Intl.DisplayNames
    129 * https://tc39.es/ecma402/#intl-displaynames-objects
    130 */
    131 class DisplayNames final {
    132 public:
    133  /**
    134   * The style of the display name, specified by the amount of space available
    135   * for displaying the text.
    136   */
    137  enum class Style {
    138    Narrow,
    139    Short,
    140    Long,
    141    // Note: Abbreviated is not part of ECMA-402, but it is available for
    142    // internal Mozilla usage.
    143    Abbreviated,
    144  };
    145 
    146  /**
    147   * Use either standard or dialect names for the "Language" type.
    148   */
    149  enum class LanguageDisplay {
    150    Standard,
    151    Dialect,
    152  };
    153 
    154  /**
    155   * Determines the fallback behavior if no match is found.
    156   */
    157  enum class Fallback {
    158    // The buffer will contain an empty string.
    159    None,
    160    // The buffer will contain the code, but typically in a canonicalized form.
    161    Code
    162  };
    163 
    164  /**
    165   * These options correlate to the ECMA-402 DisplayNames options. The defaults
    166   * values must match the default initialized values of ECMA-402. The type
    167   * option is omitted as the C++ API relies on directly calling the
    168   * DisplayNames::Get* methods.
    169   *
    170   * https://tc39.es/ecma402/#intl-displaynames-objects
    171   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames
    172   */
    173  struct Options {
    174    Style style = Style::Long;
    175    LanguageDisplay languageDisplay = LanguageDisplay::Standard;
    176  };
    177 
    178  DisplayNames(ULocaleDisplayNames* aDisplayNames, Span<const char> aLocale,
    179               Options aOptions)
    180      : mOptions(aOptions), mULocaleDisplayNames(aDisplayNames) {
    181    MOZ_ASSERT(aDisplayNames);
    182 
    183    // Copy the span and ensure null termination.
    184    mLocale = Buffer<char>(aLocale.size() + 1);
    185    PodCopy(mLocale.begin(), aLocale.data(), aLocale.size());
    186    mLocale[aLocale.size()] = '\0';
    187  }
    188 
    189  /**
    190   * Initialize a new DisplayNames for the provided locale and using the
    191   * provided options.
    192   *
    193   * https://tc39.es/ecma402/#sec-Intl.DisplayNames
    194   */
    195  static Result<UniquePtr<DisplayNames>, ICUError> TryCreate(
    196      const char* aLocale, Options aOptions);
    197 
    198  // Not copyable or movable
    199  DisplayNames(const DisplayNames&) = delete;
    200  DisplayNames& operator=(const DisplayNames&) = delete;
    201 
    202  ~DisplayNames();
    203 
    204  /**
    205   * Easily convert to a more specific DisplayNames error.
    206   */
    207  DisplayNamesError ToError(ICUError aError) const;
    208 
    209  /**
    210   * Easily convert to a more specific DisplayNames error.
    211   */
    212  DisplayNamesError ToError(Locale::CanonicalizationError aError) const;
    213 
    214 private:
    215  /**
    216   * A helper function to handle the fallback behavior, where if there is a
    217   * fallback the buffer is filled with the "code", often in canonicalized form.
    218   */
    219  template <typename B, typename Fn>
    220  static Result<Ok, DisplayNamesError> HandleFallback(B& aBuffer,
    221                                                      Fallback aFallback,
    222                                                      Fn aGetFallbackSpan) {
    223    if (aBuffer.length() == 0 &&
    224        aFallback == mozilla::intl::DisplayNames::Fallback::Code) {
    225      if (!FillBuffer(aGetFallbackSpan(), aBuffer)) {
    226        return Err(DisplayNamesError::OutOfMemory);
    227      }
    228    }
    229    return Ok();
    230  }
    231 
    232  /**
    233   * This is a specialized form of the FillBufferWithICUCall for DisplayNames.
    234   * Different APIs report that no display name is found with different
    235   * statuses. This method signals no display name was found by setting the
    236   * buffer to 0.
    237   *
    238   * The display name APIs such as `uldn_scriptDisplayName`,
    239   * `uloc_getDisplayScript`, and `uldn_regionDisplayName` report
    240   * U_ILLEGAL_ARGUMENT_ERROR when no display name was found. In order to
    241   * accomodate fallbacking, return an empty string in this case.
    242   */
    243  template <typename B, typename F>
    244  static ICUResult FillBufferWithICUDisplayNames(
    245      B& aBuffer, UErrorCode aNoDisplayNameStatus, F aCallback) {
    246    return FillBufferWithICUCall(
    247        aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
    248          int32_t res = aCallback(target, length, status);
    249 
    250          if (*status == aNoDisplayNameStatus) {
    251            *status = U_ZERO_ERROR;
    252            res = 0;
    253          }
    254          return res;
    255        });
    256  }
    257 
    258  /**
    259   * An internal helper to compute the list of display names for various
    260   * DateTime options.
    261   */
    262  Result<Ok, DisplayNamesError> ComputeDateTimeDisplayNames(
    263      UDateFormatSymbolType symbolType, mozilla::Span<const int32_t> indices,
    264      Span<const char> aCalendar);
    265 
    266  // The following are the stack-allocated sizes for various strings using the
    267  // mozilla::Vector. The numbers should be large enough to fit the common
    268  // cases, and when the strings are too large they will fall back to heap
    269  // allocations.
    270 
    271  // Fit BCP 47 locales such as "en-US", "zh-Hant". Locales can get quite long,
    272  // but 32 should fit most smaller locales without a lot of extensions.
    273  static constexpr size_t LocaleVecLength = 32;
    274  // Fit calendar names such as "gregory", "buddhist", "islamic-civil".
    275  // "islamic-umalqura" is 16 bytes + 1 for null termination, so round up to 32.
    276  static constexpr size_t CalendarVecLength = 32;
    277 
    278  /**
    279   * Given an ASCII alpha, convert it to upper case.
    280   */
    281  static inline char16_t AsciiAlphaToUpperCase(char16_t aCh) {
    282    MOZ_ASSERT(IsAsciiAlpha(aCh));
    283    return AsciiToUpperCase(aCh);
    284  };
    285 
    286  /**
    287   * Attempt to use enums to safely index into an array.
    288   *
    289   * Note: The enums we support here are all defined starting from 1.
    290   */
    291  template <typename T>
    292  inline int32_t EnumToIndex(size_t aSize, T aEnum) {
    293    size_t index = static_cast<size_t>(aEnum) - 1;
    294    MOZ_RELEASE_ASSERT(index < aSize,
    295                       "Enum indexing mismatch for display names.");
    296    return index;
    297  }
    298 
    299  /**
    300   * Convert the month to a numeric code as a string.
    301   */
    302  static Span<const char> ToCodeString(Month aMonth);
    303 
    304 public:
    305  /**
    306   * Get the localized name of a language. Part of ECMA-402.
    307   *
    308   * Accepts:
    309   *  languageCode ["-" scriptCode] ["-" regionCode ] *("-" variant )
    310   *  Where the language code is:
    311   *    1. A two letters ISO 639-1 language code
    312   *         https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
    313   *    2. A three letters ISO 639-2 language code
    314   *         https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
    315   *
    316   * Examples:
    317   *  "es-ES" => "European Spanish" (en-US), "español de España" (es-ES)
    318   *  "zh-Hant" => "Traditional Chinese" (en-US), "chino tradicional" (es-ES)
    319   */
    320  template <typename B>
    321  Result<Ok, DisplayNamesError> GetLanguage(
    322      B& aBuffer, Span<const char> aLanguage,
    323      Fallback aFallback = Fallback::None) const {
    324    static_assert(std::is_same<typename B::CharType, char16_t>::value);
    325    mozilla::intl::Locale tag;
    326    if (LocaleParser::TryParseBaseName(aLanguage, tag).isErr()) {
    327      return Err(DisplayNamesError::InvalidOption);
    328    }
    329 
    330    {
    331      // ICU always canonicalizes the input locale, but since we know that ICU's
    332      // canonicalization is incomplete, we need to perform our own
    333      // canonicalization to ensure consistent result.
    334      auto result = tag.CanonicalizeBaseName();
    335      if (result.isErr()) {
    336        return Err(ToError(result.unwrapErr()));
    337      }
    338    }
    339 
    340    Vector<char, DisplayNames::LocaleVecLength> tagVec;
    341    {
    342      VectorToBufferAdaptor tagBuffer(tagVec);
    343      auto result = tag.ToString(tagBuffer);
    344      if (result.isErr()) {
    345        return Err(ToError(result.unwrapErr()));
    346      }
    347      if (!tagVec.append('\0')) {
    348        // The tag should be null terminated.
    349        return Err(DisplayNamesError::OutOfMemory);
    350      }
    351    }
    352 
    353    auto result = FillBufferWithICUDisplayNames(
    354        aBuffer, U_ILLEGAL_ARGUMENT_ERROR,
    355        [&](UChar* target, int32_t length, UErrorCode* status) {
    356          return uldn_localeDisplayName(mULocaleDisplayNames.GetConst(),
    357                                        tagVec.begin(), target, length, status);
    358        });
    359    if (result.isErr()) {
    360      return Err(ToError(result.unwrapErr()));
    361    }
    362 
    363    return HandleFallback(aBuffer, aFallback, [&] {
    364      // Remove the null terminator.
    365      return Span(tagVec.begin(), tagVec.length() - 1);
    366    });
    367  };
    368 
    369  /**
    370   * Get the localized name of a region. Part of ECMA-402.
    371   *
    372   * Accepts:
    373   *  1. an ISO-3166 two letters:
    374   *      https://www.iso.org/iso-3166-country-codes.html
    375   *  2. region code, or a three digits UN M49 Geographic Regions.
    376   *      https://unstats.un.org/unsd/methodology/m49/
    377   *
    378   * Examples
    379   *  "US"  => "United States" (en-US), "Estados Unidos", (es-ES)
    380   *  "158" => "Taiwan" (en-US), "Taiwán", (es-ES)
    381   */
    382  template <typename B>
    383  Result<Ok, DisplayNamesError> GetRegion(
    384      B& aBuffer, Span<const char> aCode,
    385      Fallback aFallback = Fallback::None) const {
    386    static_assert(std::is_same<typename B::CharType, char16_t>::value);
    387 
    388    if (!IsStructurallyValidRegionTag(aCode)) {
    389      return Err(DisplayNamesError::InvalidOption);
    390    }
    391    mozilla::intl::RegionSubtag region{aCode};
    392 
    393    mozilla::intl::Locale tag;
    394    tag.SetLanguage("und");
    395    tag.SetRegion(region);
    396 
    397    {
    398      // ICU always canonicalizes the input locale, but since we know that ICU's
    399      // canonicalization is incomplete, we need to perform our own
    400      // canonicalization to ensure consistent result.
    401      auto result = tag.CanonicalizeBaseName();
    402      if (result.isErr()) {
    403        return Err(ToError(result.unwrapErr()));
    404      }
    405    }
    406 
    407    MOZ_ASSERT(tag.Region().Present());
    408 
    409    // Note: ICU requires the region subtag to be in canonical case.
    410    const mozilla::intl::RegionSubtag& canonicalRegion = tag.Region();
    411 
    412    char regionChars[mozilla::intl::LanguageTagLimits::RegionLength + 1] = {};
    413    std::copy_n(canonicalRegion.Span().data(), canonicalRegion.Length(),
    414                regionChars);
    415 
    416    auto result = FillBufferWithICUDisplayNames(
    417        aBuffer, U_ILLEGAL_ARGUMENT_ERROR,
    418        [&](UChar* chars, uint32_t size, UErrorCode* status) {
    419          return uldn_regionDisplayName(
    420              mULocaleDisplayNames.GetConst(), regionChars, chars,
    421              AssertedCast<int32_t, uint32_t>(size), status);
    422        });
    423 
    424    if (result.isErr()) {
    425      return Err(ToError(result.unwrapErr()));
    426    }
    427 
    428    return HandleFallback(aBuffer, aFallback, [&] {
    429      region.ToUpperCase();
    430      return region.Span();
    431    });
    432  }
    433 
    434  /**
    435   * Get the localized name of a currency. Part of ECMA-402.
    436   *
    437   * Accepts:
    438   *   A 3-letter ISO 4217 currency code.
    439   *   https://en.wikipedia.org/wiki/ISO_4217
    440   *
    441   * Examples:
    442   *   "EUR" => "Euro" (en-US), "euro" (es_ES), "欧元", (zh)
    443   *   "JPY" => "Japanese Yen" (en-US), "yen" (es_ES), "日元", (zh)
    444   */
    445  template <typename B>
    446  Result<Ok, DisplayNamesError> GetCurrency(
    447      B& aBuffer, Span<const char> aCurrency,
    448      Fallback aFallback = Fallback::None) const {
    449    static_assert(std::is_same<typename B::CharType, char16_t>::value);
    450    if (aCurrency.size() != 3) {
    451      return Err(DisplayNamesError::InvalidOption);
    452    }
    453 
    454    if (!mozilla::IsAsciiAlpha(aCurrency[0]) ||
    455        !mozilla::IsAsciiAlpha(aCurrency[1]) ||
    456        !mozilla::IsAsciiAlpha(aCurrency[2])) {
    457      return Err(DisplayNamesError::InvalidOption);
    458    }
    459 
    460    // Normally this type of operation wouldn't be safe, but ASCII characters
    461    // all take 1 byte in UTF-8 encoding, and can be zero padded to be valid
    462    // UTF-16. Currency codes are all three ASCII letters.
    463    // Normalize to upper case so we can easily detect the fallback case.
    464    char16_t currency[] = {AsciiAlphaToUpperCase(aCurrency[0]),
    465                           AsciiAlphaToUpperCase(aCurrency[1]),
    466                           AsciiAlphaToUpperCase(aCurrency[2]), u'\0'};
    467 
    468    UCurrNameStyle style;
    469    switch (mOptions.style) {
    470      case Style::Long:
    471        style = UCURR_LONG_NAME;
    472        break;
    473      case Style::Abbreviated:
    474      case Style::Short:
    475        style = UCURR_SYMBOL_NAME;
    476        break;
    477      case Style::Narrow:
    478        style = UCURR_NARROW_SYMBOL_NAME;
    479        break;
    480    }
    481 
    482    int32_t length = 0;
    483    UErrorCode status = U_ZERO_ERROR;
    484    const char16_t* name = ucurr_getName(currency, IcuLocale(mLocale), style,
    485                                         nullptr, &length, &status);
    486    if (U_FAILURE(status)) {
    487      return Err(DisplayNamesError::InternalError);
    488    }
    489 
    490    // No localized currency name was found when the error code is
    491    // U_USING_DEFAULT_WARNING and the returned string is equal to the (upper
    492    // case transformed) currency code. When `aFallback` is `Fallback::Code`,
    493    // we don't have to perform any additional work, because ICU already
    494    // returned the currency code in its normalized, upper case form.
    495    if (aFallback == DisplayNames::Fallback::None &&
    496        status == U_USING_DEFAULT_WARNING && length == 3 &&
    497        std::u16string_view{name, 3} == std::u16string_view{currency, 3}) {
    498      if (aBuffer.length() != 0) {
    499        // Ensure an empty string is in the buffer when there is no fallback.
    500        aBuffer.written(0);
    501      }
    502      return Ok();
    503    }
    504 
    505    if (!FillBuffer(Span(name, length), aBuffer)) {
    506      return Err(DisplayNamesError::OutOfMemory);
    507    }
    508 
    509    return Ok();
    510  }
    511 
    512  /**
    513   * Get the localized name of a script. Part of ECMA-402.
    514   *
    515   * Accepts:
    516   *   ECMA-402 expects the ISO-15924 four letters script code.
    517   *   https://unicode.org/iso15924/iso15924-codes.html
    518   *   e.g. "Latn"
    519   *
    520   * Examples:
    521   *   "Cher" => "Cherokee" (en-US), "cherokee" (es-ES)
    522   *   "Latn" => "Latin" (en-US), "latino" (es-ES)
    523   */
    524  template <typename B>
    525  Result<Ok, DisplayNamesError> GetScript(
    526      B& aBuffer, Span<const char> aScript,
    527      Fallback aFallback = Fallback::None) const {
    528    static_assert(std::is_same<typename B::CharType, char16_t>::value);
    529 
    530    if (!IsStructurallyValidScriptTag(aScript)) {
    531      return Err(DisplayNamesError::InvalidOption);
    532    }
    533    mozilla::intl::ScriptSubtag script{aScript};
    534 
    535    mozilla::intl::Locale tag;
    536    tag.SetLanguage("und");
    537    tag.SetScript(script);
    538 
    539    {
    540      // ICU always canonicalizes the input locale, but since we know that ICU's
    541      // canonicalization is incomplete, we need to perform our own
    542      // canonicalization to ensure consistent result.
    543      auto result = tag.CanonicalizeBaseName();
    544      if (result.isErr()) {
    545        return Err(ToError(result.unwrapErr()));
    546      }
    547    }
    548 
    549    MOZ_ASSERT(tag.Script().Present());
    550    mozilla::Vector<char, DisplayNames::LocaleVecLength> tagString;
    551    VectorToBufferAdaptor buffer(tagString);
    552 
    553    switch (mOptions.style) {
    554      case Style::Long: {
    555        // |uldn_scriptDisplayName| doesn't use the stand-alone form for script
    556        // subtags, so we're using |uloc_getDisplayScript| instead. (This only
    557        // applies to the long form.)
    558        //
    559        // ICU bug: https://unicode-org.atlassian.net/browse/ICU-9301
    560 
    561        // |uloc_getDisplayScript| expects a full locale identifier as its
    562        // input.
    563        if (auto result = tag.ToString(buffer); result.isErr()) {
    564          return Err(ToError(result.unwrapErr()));
    565        }
    566 
    567        // Null terminate the tag string.
    568        if (!tagString.append('\0')) {
    569          return Err(DisplayNamesError::OutOfMemory);
    570        }
    571 
    572        auto result = FillBufferWithICUDisplayNames(
    573            aBuffer, U_USING_DEFAULT_WARNING,
    574            [&](UChar* target, int32_t length, UErrorCode* status) {
    575              return uloc_getDisplayScript(tagString.begin(),
    576                                           IcuLocale(mLocale), target, length,
    577                                           status);
    578            });
    579 
    580        if (result.isErr()) {
    581          return Err(ToError(result.unwrapErr()));
    582        }
    583        break;
    584      }
    585      case Style::Abbreviated:
    586      case Style::Short:
    587      case Style::Narrow: {
    588        // Note: ICU requires the script subtag to be in canonical case.
    589        const mozilla::intl::ScriptSubtag& canonicalScript = tag.Script();
    590 
    591        char scriptChars[mozilla::intl::LanguageTagLimits::ScriptLength + 1] =
    592            {};
    593        MOZ_ASSERT(canonicalScript.Length() <=
    594                   mozilla::intl::LanguageTagLimits::ScriptLength + 1);
    595        std::copy_n(canonicalScript.Span().data(), canonicalScript.Length(),
    596                    scriptChars);
    597 
    598        auto result = FillBufferWithICUDisplayNames(
    599            aBuffer, U_ILLEGAL_ARGUMENT_ERROR,
    600            [&](UChar* target, int32_t length, UErrorCode* status) {
    601              return uldn_scriptDisplayName(mULocaleDisplayNames.GetConst(),
    602                                            scriptChars, target, length,
    603                                            status);
    604            });
    605 
    606        if (result.isErr()) {
    607          return Err(ToError(result.unwrapErr()));
    608        }
    609        break;
    610      }
    611    }
    612 
    613    return HandleFallback(aBuffer, aFallback, [&] {
    614      script.ToTitleCase();
    615      return script.Span();
    616    });
    617  };
    618 
    619  /**
    620   * Get the localized name of a calendar.
    621   * Part of Intl.DisplayNames V2. https://tc39.es/intl-displaynames-v2/
    622   * Accepts:
    623   *   Unicode calendar key:
    624   *   https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#unicode_calendar_keys
    625   */
    626  template <typename B>
    627  Result<Ok, DisplayNamesError> GetCalendar(
    628      B& aBuffer, Span<const char> aCalendar,
    629      Fallback aFallback = Fallback::None) const {
    630    if (aCalendar.empty() || !IsAscii(aCalendar)) {
    631      return Err(DisplayNamesError::InvalidOption);
    632    }
    633 
    634    if (LocaleParser::CanParseUnicodeExtensionType(aCalendar).isErr()) {
    635      return Err(DisplayNamesError::InvalidOption);
    636    }
    637 
    638    // Convert into canonical case before searching for replacements.
    639    Vector<char, DisplayNames::CalendarVecLength> lowerCaseCalendar;
    640    for (size_t i = 0; i < aCalendar.size(); i++) {
    641      if (!lowerCaseCalendar.append(AsciiToLowerCase(aCalendar[i]))) {
    642        return Err(DisplayNamesError::OutOfMemory);
    643      }
    644    }
    645    if (!lowerCaseCalendar.append('\0')) {
    646      return Err(DisplayNamesError::OutOfMemory);
    647    }
    648 
    649    Span<const char> canonicalCalendar = mozilla::Span(
    650        lowerCaseCalendar.begin(), lowerCaseCalendar.length() - 1);
    651 
    652    // Search if there's a replacement for the Unicode calendar keyword.
    653    {
    654      Span<const char> key = mozilla::MakeStringSpan("ca");
    655      Span<const char> type = canonicalCalendar;
    656      if (const char* replacement =
    657              mozilla::intl::Locale::ReplaceUnicodeExtensionType(key, type)) {
    658        canonicalCalendar = MakeStringSpan(replacement);
    659      }
    660    }
    661 
    662    // The input calendar name is user-controlled, so be extra cautious before
    663    // passing arbitrarily large strings to ICU.
    664    static constexpr size_t maximumCalendarLength = 100;
    665 
    666    if (canonicalCalendar.size() <= maximumCalendarLength) {
    667      // |uldn_keyValueDisplayName| expects old-style keyword values.
    668      if (const char* legacyCalendar =
    669              uloc_toLegacyType("calendar", canonicalCalendar.Elements())) {
    670        auto result = FillBufferWithICUDisplayNames(
    671            aBuffer, U_ILLEGAL_ARGUMENT_ERROR,
    672            [&](UChar* chars, uint32_t size, UErrorCode* status) {
    673              // |uldn_keyValueDisplayName| expects old-style keyword values.
    674              return uldn_keyValueDisplayName(mULocaleDisplayNames.GetConst(),
    675                                              "calendar", legacyCalendar, chars,
    676                                              size, status);
    677            });
    678        if (result.isErr()) {
    679          return Err(ToError(result.unwrapErr()));
    680        }
    681      } else {
    682        aBuffer.written(0);
    683      }
    684    } else {
    685      aBuffer.written(0);
    686    }
    687 
    688    return HandleFallback(aBuffer, aFallback,
    689                          [&] { return canonicalCalendar; });
    690  }
    691 
    692  /**
    693   * Get the localized name of a weekday. This is a MozExtension, and not
    694   * currently part of ECMA-402.
    695   */
    696  template <typename B>
    697  Result<Ok, DisplayNamesError> GetWeekday(
    698      B& aBuffer, Weekday aWeekday, Span<const char> aCalendar,
    699      Fallback aFallback = Fallback::None) {
    700    // SpiderMonkey static casts the enum, so ensure it is correctly in range.
    701    MOZ_ASSERT(aWeekday >= Weekday::Monday && aWeekday <= Weekday::Sunday);
    702 
    703    UDateFormatSymbolType symbolType;
    704    switch (mOptions.style) {
    705      case DisplayNames::Style::Long:
    706        symbolType = UDAT_STANDALONE_WEEKDAYS;
    707        break;
    708 
    709      case DisplayNames::Style::Abbreviated:
    710        // ICU "short" is CLDR "abbreviated" format.
    711        symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS;
    712        break;
    713 
    714      case DisplayNames::Style::Short:
    715        // ICU "shorter" is CLDR "short" format.
    716        symbolType = UDAT_STANDALONE_SHORTER_WEEKDAYS;
    717        break;
    718 
    719      case DisplayNames::Style::Narrow:
    720        symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
    721        break;
    722    }
    723 
    724    static constexpr int32_t indices[] = {
    725        UCAL_MONDAY, UCAL_TUESDAY,  UCAL_WEDNESDAY, UCAL_THURSDAY,
    726        UCAL_FRIDAY, UCAL_SATURDAY, UCAL_SUNDAY};
    727 
    728    if (auto result = ComputeDateTimeDisplayNames(
    729            symbolType, mozilla::Span(indices), aCalendar);
    730        result.isErr()) {
    731      return result.propagateErr();
    732    }
    733    MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices));
    734 
    735    auto& name =
    736        mDateTimeDisplayNames[EnumToIndex(std::size(indices), aWeekday)];
    737    if (!FillBuffer(name.AsSpan(), aBuffer)) {
    738      return Err(DisplayNamesError::OutOfMemory);
    739    }
    740 
    741    // There is no need to fallback, as invalid options are
    742    // DisplayNamesError::InvalidOption.
    743    return Ok();
    744  }
    745 
    746  /**
    747   * Get the localized name of a month. This is a MozExtension, and not
    748   * currently part of ECMA-402.
    749   */
    750  template <typename B>
    751  Result<Ok, DisplayNamesError> GetMonth(B& aBuffer, Month aMonth,
    752                                         Span<const char> aCalendar,
    753                                         Fallback aFallback = Fallback::None) {
    754    // SpiderMonkey static casts the enum, so ensure it is correctly in range.
    755    MOZ_ASSERT(aMonth >= Month::January && aMonth <= Month::Undecimber);
    756 
    757    UDateFormatSymbolType symbolType;
    758    switch (mOptions.style) {
    759      case DisplayNames::Style::Long:
    760        symbolType = UDAT_STANDALONE_MONTHS;
    761        break;
    762 
    763      case DisplayNames::Style::Abbreviated:
    764      case DisplayNames::Style::Short:
    765        symbolType = UDAT_STANDALONE_SHORT_MONTHS;
    766        break;
    767 
    768      case DisplayNames::Style::Narrow:
    769        symbolType = UDAT_STANDALONE_NARROW_MONTHS;
    770        break;
    771    }
    772 
    773    static constexpr int32_t indices[] = {
    774        UCAL_JANUARY,   UCAL_FEBRUARY, UCAL_MARCH,    UCAL_APRIL,
    775        UCAL_MAY,       UCAL_JUNE,     UCAL_JULY,     UCAL_AUGUST,
    776        UCAL_SEPTEMBER, UCAL_OCTOBER,  UCAL_NOVEMBER, UCAL_DECEMBER,
    777        UCAL_UNDECIMBER};
    778 
    779    if (auto result = ComputeDateTimeDisplayNames(
    780            symbolType, mozilla::Span(indices), aCalendar);
    781        result.isErr()) {
    782      return result.propagateErr();
    783    }
    784    MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices));
    785    auto& name = mDateTimeDisplayNames[EnumToIndex(std::size(indices), aMonth)];
    786    if (!FillBuffer(Span(name.AsSpan()), aBuffer)) {
    787      return Err(DisplayNamesError::OutOfMemory);
    788    }
    789 
    790    return HandleFallback(aBuffer, aFallback,
    791                          [&] { return ToCodeString(aMonth); });
    792  }
    793 
    794  /**
    795   * Get the localized name of a quarter. This is a MozExtension, and not
    796   * currently part of ECMA-402.
    797   */
    798  template <typename B>
    799  Result<Ok, DisplayNamesError> GetQuarter(
    800      B& aBuffer, Quarter aQuarter, Span<const char> aCalendar,
    801      Fallback aFallback = Fallback::None) {
    802    // SpiderMonkey static casts the enum, so ensure it is correctly in range.
    803    MOZ_ASSERT(aQuarter >= Quarter::Q1 && aQuarter <= Quarter::Q4);
    804 
    805    UDateFormatSymbolType symbolType;
    806    switch (mOptions.style) {
    807      case DisplayNames::Style::Long:
    808        symbolType = UDAT_STANDALONE_QUARTERS;
    809        break;
    810 
    811      case DisplayNames::Style::Abbreviated:
    812      case DisplayNames::Style::Short:
    813        symbolType = UDAT_STANDALONE_SHORT_QUARTERS;
    814        break;
    815 
    816      case DisplayNames::Style::Narrow:
    817        symbolType = UDAT_STANDALONE_NARROW_QUARTERS;
    818        break;
    819    }
    820 
    821    // ICU doesn't provide an enum for quarters.
    822    static constexpr int32_t indices[] = {0, 1, 2, 3};
    823 
    824    if (auto result = ComputeDateTimeDisplayNames(
    825            symbolType, mozilla::Span(indices), aCalendar);
    826        result.isErr()) {
    827      return result.propagateErr();
    828    }
    829    MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices));
    830 
    831    auto& name =
    832        mDateTimeDisplayNames[EnumToIndex(std::size(indices), aQuarter)];
    833    if (!FillBuffer(Span(name.AsSpan()), aBuffer)) {
    834      return Err(DisplayNamesError::OutOfMemory);
    835    }
    836 
    837    // There is no need to fallback, as invalid options are
    838    // DisplayNamesError::InvalidOption.
    839    return Ok();
    840  }
    841 
    842  /**
    843   * Get the localized name of a day period. This is a MozExtension, and not
    844   * currently part of ECMA-402.
    845   */
    846  template <typename B>
    847  Result<Ok, DisplayNamesError> GetDayPeriod(
    848      B& aBuffer, DayPeriod aDayPeriod, Span<const char> aCalendar,
    849      Fallback aFallback = Fallback::None) {
    850    UDateFormatSymbolType symbolType;
    851    switch (mOptions.style) {
    852      case DisplayNames::Style::Long:
    853 #ifndef U_HIDE_DRAFT_API
    854        symbolType = UDAT_AM_PMS_WIDE;
    855 #else
    856        symbolType = UDAT_AM_PMS;
    857 #endif
    858        break;
    859 
    860      case DisplayNames::Style::Abbreviated:
    861      case DisplayNames::Style::Short:
    862        symbolType = UDAT_AM_PMS;
    863        break;
    864 
    865      case DisplayNames::Style::Narrow:
    866 #ifndef U_HIDE_DRAFT_API
    867        symbolType = UDAT_AM_PMS_NARROW;
    868 #else
    869        symbolType = UDAT_AM_PMS;
    870 #endif
    871        break;
    872    }
    873 
    874    static constexpr int32_t indices[] = {UCAL_AM, UCAL_PM};
    875 
    876    if (auto result = ComputeDateTimeDisplayNames(
    877            symbolType, mozilla::Span(indices), aCalendar);
    878        result.isErr()) {
    879      return result.propagateErr();
    880    }
    881    MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices));
    882 
    883    auto& name =
    884        mDateTimeDisplayNames[EnumToIndex(std::size(indices), aDayPeriod)];
    885    if (!FillBuffer(name.AsSpan(), aBuffer)) {
    886      return Err(DisplayNamesError::OutOfMemory);
    887    }
    888 
    889    // There is no need to fallback, as invalid options are
    890    // DisplayNamesError::InvalidOption.
    891    return Ok();
    892  }
    893 
    894  /**
    895   * Get the localized name of a date time field.
    896   * Part of Intl.DisplayNames V2. https://tc39.es/intl-displaynames-v2/
    897   * Accepts:
    898   *    "era", "year", "quarter", "month", "weekOfYear", "weekday", "day",
    899   *    "dayPeriod", "hour", "minute", "second", "timeZoneName"
    900   * Examples:
    901   *   "weekday" => "day of the week"
    902   *   "dayPeriod" => "AM/PM"
    903   */
    904  template <typename B>
    905  Result<Ok, DisplayNamesError> GetDateTimeField(
    906      B& aBuffer, DateTimeField aField,
    907      DateTimePatternGenerator& aDateTimePatternGen,
    908      Fallback aFallback = Fallback::None) {
    909    UDateTimePatternField field;
    910    switch (aField) {
    911      case DateTimeField::Era:
    912        field = UDATPG_ERA_FIELD;
    913        break;
    914      case DateTimeField::Year:
    915        field = UDATPG_YEAR_FIELD;
    916        break;
    917      case DateTimeField::Quarter:
    918        field = UDATPG_QUARTER_FIELD;
    919        break;
    920      case DateTimeField::Month:
    921        field = UDATPG_MONTH_FIELD;
    922        break;
    923      case DateTimeField::WeekOfYear:
    924        field = UDATPG_WEEK_OF_YEAR_FIELD;
    925        break;
    926      case DateTimeField::Weekday:
    927        field = UDATPG_WEEKDAY_FIELD;
    928        break;
    929      case DateTimeField::Day:
    930        field = UDATPG_DAY_FIELD;
    931        break;
    932      case DateTimeField::DayPeriod:
    933        field = UDATPG_DAYPERIOD_FIELD;
    934        break;
    935      case DateTimeField::Hour:
    936        field = UDATPG_HOUR_FIELD;
    937        break;
    938      case DateTimeField::Minute:
    939        field = UDATPG_MINUTE_FIELD;
    940        break;
    941      case DateTimeField::Second:
    942        field = UDATPG_SECOND_FIELD;
    943        break;
    944      case DateTimeField::TimeZoneName:
    945        field = UDATPG_ZONE_FIELD;
    946        break;
    947    }
    948 
    949    UDateTimePGDisplayWidth width;
    950    switch (mOptions.style) {
    951      case DisplayNames::Style::Long:
    952        width = UDATPG_WIDE;
    953        break;
    954      case DisplayNames::Style::Abbreviated:
    955      case DisplayNames::Style::Short:
    956        width = UDATPG_ABBREVIATED;
    957        break;
    958      case DisplayNames::Style::Narrow:
    959        width = UDATPG_NARROW;
    960        break;
    961    }
    962 
    963    auto result = FillBufferWithICUCall(
    964        aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
    965          return udatpg_getFieldDisplayName(
    966              aDateTimePatternGen.GetUDateTimePatternGenerator(), field, width,
    967              target, length, status);
    968        });
    969 
    970    if (result.isErr()) {
    971      return Err(ToError(result.unwrapErr()));
    972    }
    973    // There is no need to fallback, as invalid options are
    974    // DisplayNamesError::InvalidOption.
    975    return Ok();
    976  }
    977 
    978  Options mOptions;
    979  Buffer<char> mLocale;
    980  Vector<Buffer<char16_t>> mDateTimeDisplayNames;
    981  ICUPointer<ULocaleDisplayNames> mULocaleDisplayNames =
    982      ICUPointer<ULocaleDisplayNames>(nullptr);
    983 };
    984 
    985 }  // namespace mozilla::intl
    986 
    987 #endif