tor-browser

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

DisplayNames.cpp (19225B)


      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.DisplayNames implementation. */
      8 
      9 #include "builtin/intl/DisplayNames.h"
     10 
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/intl/DisplayNames.h"
     13 #include "mozilla/PodOperations.h"
     14 #include "mozilla/Span.h"
     15 
     16 #include "jsnum.h"
     17 #include "jspubtd.h"
     18 
     19 #include "builtin/intl/CommonFunctions.h"
     20 #include "builtin/intl/FormatBuffer.h"
     21 #include "builtin/intl/LocaleNegotiation.h"
     22 #include "gc/AllocKind.h"
     23 #include "gc/GCContext.h"
     24 #include "js/CallArgs.h"
     25 #include "js/Class.h"
     26 #include "js/experimental/Intl.h"     // JS::AddMozDisplayNamesConstructor
     27 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     28 #include "js/Printer.h"
     29 #include "js/PropertyAndElement.h"  // JS_DefineFunctions, JS_DefineProperties
     30 #include "js/PropertyDescriptor.h"
     31 #include "js/PropertySpec.h"
     32 #include "js/RootingAPI.h"
     33 #include "js/TypeDecls.h"
     34 #include "js/Utility.h"
     35 #include "vm/GlobalObject.h"
     36 #include "vm/JSContext.h"
     37 #include "vm/JSObject.h"
     38 #include "vm/Runtime.h"
     39 #include "vm/SelfHosting.h"
     40 #include "vm/Stack.h"
     41 #include "vm/StringType.h"
     42 
     43 #include "vm/JSObject-inl.h"
     44 #include "vm/NativeObject-inl.h"
     45 
     46 using namespace js;
     47 using namespace js::intl;
     48 
     49 const JSClassOps DisplayNamesObject::classOps_ = {
     50    nullptr, /* addProperty */
     51    nullptr, /* delProperty */
     52    nullptr, /* enumerate */
     53    nullptr, /* newEnumerate */
     54    nullptr, /* resolve */
     55    nullptr, /* mayResolve */
     56    DisplayNamesObject::finalize,
     57 };
     58 
     59 const JSClass DisplayNamesObject::class_ = {
     60    "Intl.DisplayNames",
     61    JSCLASS_HAS_RESERVED_SLOTS(DisplayNamesObject::SLOT_COUNT) |
     62        JSCLASS_HAS_CACHED_PROTO(JSProto_DisplayNames) |
     63        JSCLASS_FOREGROUND_FINALIZE,
     64    &DisplayNamesObject::classOps_,
     65    &DisplayNamesObject::classSpec_,
     66 };
     67 
     68 const JSClass& DisplayNamesObject::protoClass_ = PlainObject::class_;
     69 
     70 static bool displayNames_supportedLocalesOf(JSContext* cx, unsigned argc,
     71                                            Value* vp);
     72 
     73 static bool displayNames_toSource(JSContext* cx, unsigned argc, Value* vp) {
     74  CallArgs args = CallArgsFromVp(argc, vp);
     75  args.rval().setString(cx->names().DisplayNames);
     76  return true;
     77 }
     78 
     79 static const JSFunctionSpec displayNames_static_methods[] = {
     80    JS_FN("supportedLocalesOf", displayNames_supportedLocalesOf, 1, 0),
     81    JS_FS_END,
     82 };
     83 
     84 static const JSFunctionSpec displayNames_methods[] = {
     85    JS_SELF_HOSTED_FN("of", "Intl_DisplayNames_of", 1, 0),
     86    JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DisplayNames_resolvedOptions", 0,
     87                      0),
     88    JS_FN("toSource", displayNames_toSource, 0, 0),
     89    JS_FS_END,
     90 };
     91 
     92 static const JSPropertySpec displayNames_properties[] = {
     93    JS_STRING_SYM_PS(toStringTag, "Intl.DisplayNames", JSPROP_READONLY),
     94    JS_PS_END,
     95 };
     96 
     97 static bool DisplayNames(JSContext* cx, unsigned argc, Value* vp);
     98 
     99 const ClassSpec DisplayNamesObject::classSpec_ = {
    100    GenericCreateConstructor<DisplayNames, 2, gc::AllocKind::FUNCTION>,
    101    GenericCreatePrototype<DisplayNamesObject>,
    102    displayNames_static_methods,
    103    nullptr,
    104    displayNames_methods,
    105    displayNames_properties,
    106    nullptr,
    107    ClassSpec::DontDefineConstructor,
    108 };
    109 
    110 enum class DisplayNamesOptions {
    111  Standard,
    112 
    113  // Calendar display names are no longer available with the current spec
    114  // proposal text, but may be re-enabled in the future. For our internal use
    115  // we still need to have them present, so use a feature guard for now.
    116  EnableMozExtensions,
    117 };
    118 
    119 /**
    120 * Initialize a new Intl.DisplayNames object using the named self-hosted
    121 * function.
    122 */
    123 static bool InitializeDisplayNamesObject(JSContext* cx, HandleObject obj,
    124                                         Handle<PropertyName*> initializer,
    125                                         HandleValue locales,
    126                                         HandleValue options,
    127                                         DisplayNamesOptions dnoptions) {
    128  FixedInvokeArgs<4> args(cx);
    129 
    130  args[0].setObject(*obj);
    131  args[1].set(locales);
    132  args[2].set(options);
    133  args[3].setBoolean(dnoptions == DisplayNamesOptions::EnableMozExtensions);
    134 
    135  RootedValue ignored(cx);
    136  if (!CallSelfHostedFunction(cx, initializer, NullHandleValue, args,
    137                              &ignored)) {
    138    return false;
    139  }
    140 
    141  MOZ_ASSERT(ignored.isUndefined(),
    142             "Unexpected return value from non-legacy Intl object initializer");
    143  return true;
    144 }
    145 
    146 /**
    147 * Intl.DisplayNames ([ locales [ , options ]])
    148 */
    149 static bool DisplayNames(JSContext* cx, const CallArgs& args,
    150                         DisplayNamesOptions dnoptions) {
    151  // Step 1.
    152  if (!ThrowIfNotConstructing(cx, args, "Intl.DisplayNames")) {
    153    return false;
    154  }
    155 
    156  // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
    157  RootedObject proto(cx);
    158  if (dnoptions == DisplayNamesOptions::Standard) {
    159    if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_DisplayNames,
    160                                            &proto)) {
    161      return false;
    162    }
    163  } else {
    164    RootedObject newTarget(cx, &args.newTarget().toObject());
    165    if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Null, &proto)) {
    166      return false;
    167    }
    168  }
    169 
    170  Rooted<DisplayNamesObject*> displayNames(cx);
    171  displayNames = NewObjectWithClassProto<DisplayNamesObject>(cx, proto);
    172  if (!displayNames) {
    173    return false;
    174  }
    175 
    176  HandleValue locales = args.get(0);
    177  HandleValue options = args.get(1);
    178 
    179  // Steps 3-26.
    180  if (!InitializeDisplayNamesObject(cx, displayNames,
    181                                    cx->names().InitializeDisplayNames, locales,
    182                                    options, dnoptions)) {
    183    return false;
    184  }
    185 
    186  // Step 27.
    187  args.rval().setObject(*displayNames);
    188  return true;
    189 }
    190 
    191 static bool DisplayNames(JSContext* cx, unsigned argc, Value* vp) {
    192  CallArgs args = CallArgsFromVp(argc, vp);
    193  return DisplayNames(cx, args, DisplayNamesOptions::Standard);
    194 }
    195 
    196 static bool MozDisplayNames(JSContext* cx, unsigned argc, Value* vp) {
    197  CallArgs args = CallArgsFromVp(argc, vp);
    198  return DisplayNames(cx, args, DisplayNamesOptions::EnableMozExtensions);
    199 }
    200 
    201 void js::DisplayNamesObject::finalize(JS::GCContext* gcx, JSObject* obj) {
    202  MOZ_ASSERT(gcx->onMainThread());
    203 
    204  if (mozilla::intl::DisplayNames* displayNames =
    205          obj->as<DisplayNamesObject>().getDisplayNames()) {
    206    intl::RemoveICUCellMemory(gcx, obj, DisplayNamesObject::EstimatedMemoryUse);
    207    delete displayNames;
    208  }
    209 }
    210 
    211 bool JS::AddMozDisplayNamesConstructor(JSContext* cx, HandleObject intl) {
    212  RootedObject ctor(cx, GlobalObject::createConstructor(
    213                            cx, MozDisplayNames, cx->names().DisplayNames, 2));
    214  if (!ctor) {
    215    return false;
    216  }
    217 
    218  RootedObject proto(
    219      cx, GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global()));
    220  if (!proto) {
    221    return false;
    222  }
    223 
    224  if (!LinkConstructorAndPrototype(cx, ctor, proto)) {
    225    return false;
    226  }
    227 
    228  if (!JS_DefineFunctions(cx, ctor, displayNames_static_methods)) {
    229    return false;
    230  }
    231 
    232  if (!JS_DefineFunctions(cx, proto, displayNames_methods)) {
    233    return false;
    234  }
    235 
    236  if (!JS_DefineProperties(cx, proto, displayNames_properties)) {
    237    return false;
    238  }
    239 
    240  RootedValue ctorValue(cx, ObjectValue(*ctor));
    241  return DefineDataProperty(cx, intl, cx->names().DisplayNames, ctorValue, 0);
    242 }
    243 
    244 static mozilla::intl::DisplayNames* NewDisplayNames(
    245    JSContext* cx, const char* locale,
    246    mozilla::intl::DisplayNames::Options& options) {
    247  auto result = mozilla::intl::DisplayNames::TryCreate(locale, options);
    248  if (result.isErr()) {
    249    intl::ReportInternalError(cx, result.unwrapErr());
    250    return nullptr;
    251  }
    252  return result.unwrap().release();
    253 }
    254 
    255 static mozilla::intl::DisplayNames* GetOrCreateDisplayNames(
    256    JSContext* cx, Handle<DisplayNamesObject*> displayNames, const char* locale,
    257    mozilla::intl::DisplayNames::Options& options) {
    258  // Obtain a cached mozilla::intl::DisplayNames object.
    259  mozilla::intl::DisplayNames* dn = displayNames->getDisplayNames();
    260  if (!dn) {
    261    dn = NewDisplayNames(cx, locale, options);
    262    if (!dn) {
    263      return nullptr;
    264    }
    265    displayNames->setDisplayNames(dn);
    266 
    267    intl::AddICUCellMemory(displayNames,
    268                           DisplayNamesObject::EstimatedMemoryUse);
    269  }
    270  return dn;
    271 }
    272 
    273 static void ReportInvalidOptionError(JSContext* cx, HandleString type,
    274                                     HandleString option) {
    275  if (UniqueChars optionStr = QuoteString(cx, option, '"')) {
    276    if (UniqueChars typeStr = QuoteString(cx, type)) {
    277      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    278                                JSMSG_INVALID_OPTION_VALUE, typeStr.get(),
    279                                optionStr.get());
    280    }
    281  }
    282 }
    283 
    284 static void ReportInvalidOptionError(JSContext* cx, const char* type,
    285                                     HandleString option) {
    286  if (UniqueChars str = QuoteString(cx, option, '"')) {
    287    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    288                              JSMSG_INVALID_OPTION_VALUE, type, str.get());
    289  }
    290 }
    291 
    292 static void ReportInvalidOptionError(JSContext* cx, const char* type,
    293                                     double option) {
    294  ToCStringBuf cbuf;
    295  const char* str = NumberToCString(&cbuf, option);
    296  MOZ_ASSERT(str);
    297  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    298                            JSMSG_INVALID_DIGITS_VALUE, str);
    299 }
    300 
    301 /**
    302 * intl_ComputeDisplayName(displayNames, locale, calendar, style,
    303 *                         languageDisplay, fallback, type, code)
    304 */
    305 bool js::intl_ComputeDisplayName(JSContext* cx, unsigned argc, Value* vp) {
    306  CallArgs args = CallArgsFromVp(argc, vp);
    307  MOZ_ASSERT(args.length() == 8);
    308 
    309  Rooted<DisplayNamesObject*> displayNames(
    310      cx, &args[0].toObject().as<DisplayNamesObject>());
    311 
    312  UniqueChars locale = intl::EncodeLocale(cx, args[1].toString());
    313  if (!locale) {
    314    return false;
    315  }
    316 
    317  Rooted<JSLinearString*> calendar(cx, args[2].toString()->ensureLinear(cx));
    318  if (!calendar) {
    319    return false;
    320  }
    321 
    322  Rooted<JSLinearString*> code(cx, args[7].toString()->ensureLinear(cx));
    323  if (!code) {
    324    return false;
    325  }
    326 
    327  mozilla::intl::DisplayNames::Style style;
    328  {
    329    JSLinearString* styleStr = args[3].toString()->ensureLinear(cx);
    330    if (!styleStr) {
    331      return false;
    332    }
    333 
    334    if (StringEqualsLiteral(styleStr, "long")) {
    335      style = mozilla::intl::DisplayNames::Style::Long;
    336    } else if (StringEqualsLiteral(styleStr, "short")) {
    337      style = mozilla::intl::DisplayNames::Style::Short;
    338    } else if (StringEqualsLiteral(styleStr, "narrow")) {
    339      style = mozilla::intl::DisplayNames::Style::Narrow;
    340    } else {
    341      MOZ_ASSERT(StringEqualsLiteral(styleStr, "abbreviated"));
    342      style = mozilla::intl::DisplayNames::Style::Abbreviated;
    343    }
    344  }
    345 
    346  mozilla::intl::DisplayNames::LanguageDisplay languageDisplay;
    347  {
    348    JSLinearString* language = args[4].toString()->ensureLinear(cx);
    349    if (!language) {
    350      return false;
    351    }
    352 
    353    if (StringEqualsLiteral(language, "dialect")) {
    354      languageDisplay = mozilla::intl::DisplayNames::LanguageDisplay::Dialect;
    355    } else {
    356      MOZ_ASSERT(language->empty() ||
    357                 StringEqualsLiteral(language, "standard"));
    358      languageDisplay = mozilla::intl::DisplayNames::LanguageDisplay::Standard;
    359    }
    360  }
    361 
    362  mozilla::intl::DisplayNames::Fallback fallback;
    363  {
    364    JSLinearString* fallbackStr = args[5].toString()->ensureLinear(cx);
    365    if (!fallbackStr) {
    366      return false;
    367    }
    368 
    369    if (StringEqualsLiteral(fallbackStr, "none")) {
    370      fallback = mozilla::intl::DisplayNames::Fallback::None;
    371    } else {
    372      MOZ_ASSERT(StringEqualsLiteral(fallbackStr, "code"));
    373      fallback = mozilla::intl::DisplayNames::Fallback::Code;
    374    }
    375  }
    376 
    377  Rooted<JSLinearString*> type(cx, args[6].toString()->ensureLinear(cx));
    378  if (!type) {
    379    return false;
    380  }
    381 
    382  mozilla::intl::DisplayNames::Options options{
    383      style,
    384      languageDisplay,
    385  };
    386 
    387  // If a calendar exists, set it as an option.
    388  JS::UniqueChars calendarChars = nullptr;
    389  if (!calendar->empty()) {
    390    calendarChars = JS_EncodeStringToUTF8(cx, calendar);
    391    if (!calendarChars) {
    392      return false;
    393    }
    394  }
    395 
    396  mozilla::intl::DisplayNames* dn =
    397      GetOrCreateDisplayNames(cx, displayNames, locale.get(), options);
    398  if (!dn) {
    399    return false;
    400  }
    401 
    402  // The "code" is usually a small ASCII string, so try to avoid an allocation
    403  // by copying it to the stack. Unfortunately we can't pass a string span of
    404  // the JSString directly to the unified DisplayNames API, as the
    405  // intl::FormatBuffer will be written to. This writing can trigger a GC and
    406  // invalidate the span, creating a nogc rooting hazard.
    407  JS::UniqueChars utf8 = nullptr;
    408  unsigned char ascii[32];
    409  mozilla::Span<const char> codeSpan = nullptr;
    410  if (code->length() < 32 && code->hasLatin1Chars() && StringIsAscii(code)) {
    411    JS::AutoCheckCannotGC nogc;
    412    mozilla::PodCopy(ascii, code->latin1Chars(nogc), code->length());
    413    codeSpan =
    414        mozilla::Span(reinterpret_cast<const char*>(ascii), code->length());
    415  } else {
    416    utf8 = JS_EncodeStringToUTF8(cx, code);
    417    if (!utf8) {
    418      return false;
    419    }
    420    codeSpan = mozilla::MakeStringSpan(utf8.get());
    421  }
    422 
    423  intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
    424  mozilla::Result<mozilla::Ok, mozilla::intl::DisplayNamesError> result =
    425      mozilla::Ok{};
    426 
    427  if (StringEqualsLiteral(type, "language")) {
    428    result = dn->GetLanguage(buffer, codeSpan, fallback);
    429  } else if (StringEqualsLiteral(type, "script")) {
    430    result = dn->GetScript(buffer, codeSpan, fallback);
    431  } else if (StringEqualsLiteral(type, "region")) {
    432    result = dn->GetRegion(buffer, codeSpan, fallback);
    433  } else if (StringEqualsLiteral(type, "currency")) {
    434    result = dn->GetCurrency(buffer, codeSpan, fallback);
    435  } else if (StringEqualsLiteral(type, "calendar")) {
    436    result = dn->GetCalendar(buffer, codeSpan, fallback);
    437  } else if (StringEqualsLiteral(type, "weekday")) {
    438    double d = LinearStringToNumber(code);
    439    if (!IsInteger(d) || d < 1 || d > 7) {
    440      ReportInvalidOptionError(cx, "weekday", d);
    441      return false;
    442    }
    443    result =
    444        dn->GetWeekday(buffer, static_cast<mozilla::intl::Weekday>(d),
    445                       mozilla::MakeStringSpan(calendarChars.get()), fallback);
    446  } else if (StringEqualsLiteral(type, "month")) {
    447    double d = LinearStringToNumber(code);
    448    if (!IsInteger(d) || d < 1 || d > 13) {
    449      ReportInvalidOptionError(cx, "month", d);
    450      return false;
    451    }
    452 
    453    result =
    454        dn->GetMonth(buffer, static_cast<mozilla::intl::Month>(d),
    455                     mozilla::MakeStringSpan(calendarChars.get()), fallback);
    456 
    457  } else if (StringEqualsLiteral(type, "quarter")) {
    458    double d = LinearStringToNumber(code);
    459 
    460    // Inlined implementation of `IsValidQuarterCode ( quarter )`.
    461    if (!IsInteger(d) || d < 1 || d > 4) {
    462      ReportInvalidOptionError(cx, "quarter", d);
    463      return false;
    464    }
    465 
    466    result =
    467        dn->GetQuarter(buffer, static_cast<mozilla::intl::Quarter>(d),
    468                       mozilla::MakeStringSpan(calendarChars.get()), fallback);
    469 
    470  } else if (StringEqualsLiteral(type, "dayPeriod")) {
    471    mozilla::intl::DayPeriod dayPeriod;
    472    if (StringEqualsLiteral(code, "am")) {
    473      dayPeriod = mozilla::intl::DayPeriod::AM;
    474    } else if (StringEqualsLiteral(code, "pm")) {
    475      dayPeriod = mozilla::intl::DayPeriod::PM;
    476    } else {
    477      ReportInvalidOptionError(cx, "dayPeriod", code);
    478      return false;
    479    }
    480    result = dn->GetDayPeriod(buffer, dayPeriod,
    481                              mozilla::MakeStringSpan(calendarChars.get()),
    482                              fallback);
    483 
    484  } else {
    485    MOZ_ASSERT(StringEqualsLiteral(type, "dateTimeField"));
    486    mozilla::intl::DateTimeField field;
    487    if (StringEqualsLiteral(code, "era")) {
    488      field = mozilla::intl::DateTimeField::Era;
    489    } else if (StringEqualsLiteral(code, "year")) {
    490      field = mozilla::intl::DateTimeField::Year;
    491    } else if (StringEqualsLiteral(code, "quarter")) {
    492      field = mozilla::intl::DateTimeField::Quarter;
    493    } else if (StringEqualsLiteral(code, "month")) {
    494      field = mozilla::intl::DateTimeField::Month;
    495    } else if (StringEqualsLiteral(code, "weekOfYear")) {
    496      field = mozilla::intl::DateTimeField::WeekOfYear;
    497    } else if (StringEqualsLiteral(code, "weekday")) {
    498      field = mozilla::intl::DateTimeField::Weekday;
    499    } else if (StringEqualsLiteral(code, "day")) {
    500      field = mozilla::intl::DateTimeField::Day;
    501    } else if (StringEqualsLiteral(code, "dayPeriod")) {
    502      field = mozilla::intl::DateTimeField::DayPeriod;
    503    } else if (StringEqualsLiteral(code, "hour")) {
    504      field = mozilla::intl::DateTimeField::Hour;
    505    } else if (StringEqualsLiteral(code, "minute")) {
    506      field = mozilla::intl::DateTimeField::Minute;
    507    } else if (StringEqualsLiteral(code, "second")) {
    508      field = mozilla::intl::DateTimeField::Second;
    509    } else if (StringEqualsLiteral(code, "timeZoneName")) {
    510      field = mozilla::intl::DateTimeField::TimeZoneName;
    511    } else {
    512      ReportInvalidOptionError(cx, "dateTimeField", code);
    513      return false;
    514    }
    515 
    516    intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
    517    mozilla::intl::DateTimePatternGenerator* dtpgen =
    518        sharedIntlData.getDateTimePatternGenerator(cx, locale.get());
    519    if (!dtpgen) {
    520      return false;
    521    }
    522 
    523    result = dn->GetDateTimeField(buffer, field, *dtpgen, fallback);
    524  }
    525 
    526  if (result.isErr()) {
    527    switch (result.unwrapErr()) {
    528      case mozilla::intl::DisplayNamesError::InternalError:
    529        intl::ReportInternalError(cx);
    530        break;
    531      case mozilla::intl::DisplayNamesError::OutOfMemory:
    532        ReportOutOfMemory(cx);
    533        break;
    534      case mozilla::intl::DisplayNamesError::InvalidOption:
    535        ReportInvalidOptionError(cx, type, code);
    536        break;
    537      case mozilla::intl::DisplayNamesError::DuplicateVariantSubtag:
    538        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    539                                  JSMSG_DUPLICATE_VARIANT_SUBTAG);
    540        break;
    541      case mozilla::intl::DisplayNamesError::InvalidLanguageTag:
    542        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    543                                  JSMSG_INVALID_LANGUAGE_TAG);
    544        break;
    545    }
    546    return false;
    547  }
    548 
    549  JSString* str = buffer.toString(cx);
    550  if (!str) {
    551    return false;
    552  }
    553 
    554  if (str->empty()) {
    555    args.rval().setUndefined();
    556  } else {
    557    args.rval().setString(str);
    558  }
    559 
    560  return true;
    561 }
    562 
    563 /**
    564 * Intl.DisplayNames.supportedLocalesOf ( locales [ , options ] )
    565 */
    566 static bool displayNames_supportedLocalesOf(JSContext* cx, unsigned argc,
    567                                            Value* vp) {
    568  CallArgs args = CallArgsFromVp(argc, vp);
    569 
    570  // Steps 1-3.
    571  auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::DisplayNames,
    572                                   args.get(0), args.get(1));
    573  if (!array) {
    574    return false;
    575  }
    576  args.rval().setObject(*array);
    577  return true;
    578 }