tor-browser

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

ListFormat.cpp (11750B)


      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 #include "builtin/intl/ListFormat.h"
      8 
      9 #include "mozilla/Assertions.h"
     10 #include "mozilla/intl/ListFormat.h"
     11 
     12 #include <stddef.h>
     13 
     14 #include "builtin/Array.h"
     15 #include "builtin/intl/CommonFunctions.h"
     16 #include "builtin/intl/FormatBuffer.h"
     17 #include "builtin/intl/LocaleNegotiation.h"
     18 #include "gc/GCContext.h"
     19 #include "js/Utility.h"
     20 #include "js/Vector.h"
     21 #include "vm/JSContext.h"
     22 #include "vm/PlainObject.h"  // js::PlainObject
     23 #include "vm/StringType.h"
     24 
     25 #include "vm/JSObject-inl.h"
     26 #include "vm/NativeObject-inl.h"
     27 #include "vm/ObjectOperations-inl.h"
     28 
     29 using namespace js;
     30 using namespace js::intl;
     31 
     32 const JSClassOps ListFormatObject::classOps_ = {
     33    nullptr,                     // addProperty
     34    nullptr,                     // delProperty
     35    nullptr,                     // enumerate
     36    nullptr,                     // newEnumerate
     37    nullptr,                     // resolve
     38    nullptr,                     // mayResolve
     39    ListFormatObject::finalize,  // finalize
     40    nullptr,                     // call
     41    nullptr,                     // construct
     42    nullptr,                     // trace
     43 };
     44 const JSClass ListFormatObject::class_ = {
     45    "Intl.ListFormat",
     46    JSCLASS_HAS_RESERVED_SLOTS(ListFormatObject::SLOT_COUNT) |
     47        JSCLASS_HAS_CACHED_PROTO(JSProto_ListFormat) |
     48        JSCLASS_FOREGROUND_FINALIZE,
     49    &ListFormatObject::classOps_,
     50    &ListFormatObject::classSpec_,
     51 };
     52 
     53 const JSClass& ListFormatObject::protoClass_ = PlainObject::class_;
     54 
     55 static bool listFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
     56                                          Value* vp);
     57 
     58 static bool listFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
     59  CallArgs args = CallArgsFromVp(argc, vp);
     60  args.rval().setString(cx->names().ListFormat);
     61  return true;
     62 }
     63 
     64 static const JSFunctionSpec listFormat_static_methods[] = {
     65    JS_FN("supportedLocalesOf", listFormat_supportedLocalesOf, 1, 0),
     66    JS_FS_END,
     67 };
     68 
     69 static const JSFunctionSpec listFormat_methods[] = {
     70    JS_SELF_HOSTED_FN("resolvedOptions", "Intl_ListFormat_resolvedOptions", 0,
     71                      0),
     72    JS_SELF_HOSTED_FN("format", "Intl_ListFormat_format", 1, 0),
     73    JS_SELF_HOSTED_FN("formatToParts", "Intl_ListFormat_formatToParts", 1, 0),
     74    JS_FN("toSource", listFormat_toSource, 0, 0),
     75    JS_FS_END,
     76 };
     77 
     78 static const JSPropertySpec listFormat_properties[] = {
     79    JS_STRING_SYM_PS(toStringTag, "Intl.ListFormat", JSPROP_READONLY),
     80    JS_PS_END,
     81 };
     82 
     83 static bool ListFormat(JSContext* cx, unsigned argc, Value* vp);
     84 
     85 const ClassSpec ListFormatObject::classSpec_ = {
     86    GenericCreateConstructor<ListFormat, 0, gc::AllocKind::FUNCTION>,
     87    GenericCreatePrototype<ListFormatObject>,
     88    listFormat_static_methods,
     89    nullptr,
     90    listFormat_methods,
     91    listFormat_properties,
     92    nullptr,
     93    ClassSpec::DontDefineConstructor,
     94 };
     95 
     96 /**
     97 * Intl.ListFormat([ locales [, options]])
     98 */
     99 static bool ListFormat(JSContext* cx, unsigned argc, Value* vp) {
    100  CallArgs args = CallArgsFromVp(argc, vp);
    101 
    102  // Step 1.
    103  if (!ThrowIfNotConstructing(cx, args, "Intl.ListFormat")) {
    104    return false;
    105  }
    106 
    107  // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
    108  RootedObject proto(cx);
    109  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ListFormat,
    110                                          &proto)) {
    111    return false;
    112  }
    113 
    114  Rooted<ListFormatObject*> listFormat(
    115      cx, NewObjectWithClassProto<ListFormatObject>(cx, proto));
    116  if (!listFormat) {
    117    return false;
    118  }
    119 
    120  HandleValue locales = args.get(0);
    121  HandleValue options = args.get(1);
    122 
    123  // Step 3.
    124  if (!intl::InitializeObject(cx, listFormat, cx->names().InitializeListFormat,
    125                              locales, options)) {
    126    return false;
    127  }
    128 
    129  args.rval().setObject(*listFormat);
    130  return true;
    131 }
    132 
    133 void js::ListFormatObject::finalize(JS::GCContext* gcx, JSObject* obj) {
    134  MOZ_ASSERT(gcx->onMainThread());
    135 
    136  mozilla::intl::ListFormat* lf =
    137      obj->as<ListFormatObject>().getListFormatSlot();
    138  if (lf) {
    139    intl::RemoveICUCellMemory(gcx, obj, ListFormatObject::EstimatedMemoryUse);
    140    delete lf;
    141  }
    142 }
    143 
    144 /**
    145 * Returns a new ListFormat with the locale and list formatting options
    146 * of the given ListFormat.
    147 */
    148 static mozilla::intl::ListFormat* NewListFormat(
    149    JSContext* cx, Handle<ListFormatObject*> listFormat) {
    150  RootedObject internals(cx, intl::GetInternalsObject(cx, listFormat));
    151  if (!internals) {
    152    return nullptr;
    153  }
    154 
    155  RootedValue value(cx);
    156 
    157  if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
    158    return nullptr;
    159  }
    160  UniqueChars locale = intl::EncodeLocale(cx, value.toString());
    161  if (!locale) {
    162    return nullptr;
    163  }
    164 
    165  mozilla::intl::ListFormat::Options options;
    166 
    167  using ListFormatType = mozilla::intl::ListFormat::Type;
    168  if (!GetProperty(cx, internals, internals, cx->names().type, &value)) {
    169    return nullptr;
    170  }
    171  {
    172    JSLinearString* strType = value.toString()->ensureLinear(cx);
    173    if (!strType) {
    174      return nullptr;
    175    }
    176 
    177    if (StringEqualsLiteral(strType, "conjunction")) {
    178      options.mType = ListFormatType::Conjunction;
    179    } else if (StringEqualsLiteral(strType, "disjunction")) {
    180      options.mType = ListFormatType::Disjunction;
    181    } else {
    182      MOZ_ASSERT(StringEqualsLiteral(strType, "unit"));
    183      options.mType = ListFormatType::Unit;
    184    }
    185  }
    186 
    187  using ListFormatStyle = mozilla::intl::ListFormat::Style;
    188  if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
    189    return nullptr;
    190  }
    191  {
    192    JSLinearString* strStyle = value.toString()->ensureLinear(cx);
    193    if (!strStyle) {
    194      return nullptr;
    195    }
    196 
    197    if (StringEqualsLiteral(strStyle, "long")) {
    198      options.mStyle = ListFormatStyle::Long;
    199    } else if (StringEqualsLiteral(strStyle, "short")) {
    200      options.mStyle = ListFormatStyle::Short;
    201    } else {
    202      MOZ_ASSERT(StringEqualsLiteral(strStyle, "narrow"));
    203      options.mStyle = ListFormatStyle::Narrow;
    204    }
    205  }
    206 
    207  auto result = mozilla::intl::ListFormat::TryCreate(
    208      mozilla::MakeStringSpan(locale.get()), options);
    209 
    210  if (result.isOk()) {
    211    return result.unwrap().release();
    212  }
    213 
    214  js::intl::ReportInternalError(cx, result.unwrapErr());
    215  return nullptr;
    216 }
    217 
    218 static mozilla::intl::ListFormat* GetOrCreateListFormat(
    219    JSContext* cx, Handle<ListFormatObject*> listFormat) {
    220  // Obtain a cached mozilla::intl::ListFormat object.
    221  mozilla::intl::ListFormat* lf = listFormat->getListFormatSlot();
    222  if (lf) {
    223    return lf;
    224  }
    225 
    226  lf = NewListFormat(cx, listFormat);
    227  if (!lf) {
    228    return nullptr;
    229  }
    230  listFormat->setListFormatSlot(lf);
    231 
    232  intl::AddICUCellMemory(listFormat, ListFormatObject::EstimatedMemoryUse);
    233  return lf;
    234 }
    235 
    236 /**
    237 * FormatList ( listFormat, list )
    238 */
    239 static bool FormatList(JSContext* cx, mozilla::intl::ListFormat* lf,
    240                       const mozilla::intl::ListFormat::StringList& list,
    241                       MutableHandleValue result) {
    242  intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> formatBuffer(cx);
    243  auto formatResult = lf->Format(list, formatBuffer);
    244  if (formatResult.isErr()) {
    245    js::intl::ReportInternalError(cx, formatResult.unwrapErr());
    246    return false;
    247  }
    248 
    249  JSString* str = formatBuffer.toString(cx);
    250  if (!str) {
    251    return false;
    252  }
    253  result.setString(str);
    254  return true;
    255 }
    256 
    257 /**
    258 * FormatListToParts ( listFormat, list )
    259 */
    260 static bool FormatListToParts(JSContext* cx, mozilla::intl::ListFormat* lf,
    261                              const mozilla::intl::ListFormat::StringList& list,
    262                              MutableHandleValue result) {
    263  intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
    264  mozilla::intl::ListFormat::PartVector parts;
    265  auto formatResult = lf->FormatToParts(list, buffer, parts);
    266  if (formatResult.isErr()) {
    267    intl::ReportInternalError(cx, formatResult.unwrapErr());
    268    return false;
    269  }
    270 
    271  RootedString overallResult(cx, buffer.toString(cx));
    272  if (!overallResult) {
    273    return false;
    274  }
    275 
    276  Rooted<ArrayObject*> partsArray(
    277      cx, NewDenseFullyAllocatedArray(cx, parts.length()));
    278  if (!partsArray) {
    279    return false;
    280  }
    281  partsArray->ensureDenseInitializedLength(0, parts.length());
    282 
    283  RootedObject singlePart(cx);
    284  RootedValue val(cx);
    285 
    286  size_t index = 0;
    287  size_t beginIndex = 0;
    288  for (const mozilla::intl::ListFormat::Part& part : parts) {
    289    singlePart = NewPlainObject(cx);
    290    if (!singlePart) {
    291      return false;
    292    }
    293 
    294    if (part.first == mozilla::intl::ListFormat::PartType::Element) {
    295      val = StringValue(cx->names().element);
    296    } else {
    297      val = StringValue(cx->names().literal);
    298    }
    299 
    300    if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) {
    301      return false;
    302    }
    303 
    304    // There could be an empty string so the endIndex coule be equal to
    305    // beginIndex.
    306    MOZ_ASSERT(part.second >= beginIndex);
    307    JSLinearString* partStr = NewDependentString(cx, overallResult, beginIndex,
    308                                                 part.second - beginIndex);
    309    if (!partStr) {
    310      return false;
    311    }
    312    val = StringValue(partStr);
    313    if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) {
    314      return false;
    315    }
    316 
    317    beginIndex = part.second;
    318    partsArray->initDenseElement(index++, ObjectValue(*singlePart));
    319  }
    320 
    321  MOZ_ASSERT(index == parts.length());
    322  MOZ_ASSERT(beginIndex == buffer.length());
    323  result.setObject(*partsArray);
    324 
    325  return true;
    326 }
    327 
    328 bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) {
    329  CallArgs args = CallArgsFromVp(argc, vp);
    330  MOZ_ASSERT(args.length() == 3);
    331 
    332  Rooted<ListFormatObject*> listFormat(
    333      cx, &args[0].toObject().as<ListFormatObject>());
    334 
    335  bool formatToParts = args[2].toBoolean();
    336 
    337  mozilla::intl::ListFormat* lf = GetOrCreateListFormat(cx, listFormat);
    338  if (!lf) {
    339    return false;
    340  }
    341 
    342  // Collect all strings and their lengths.
    343  //
    344  // 'strings' takes the ownership of those strings, and 'list' will be passed
    345  // to mozilla::intl::ListFormat as a Span.
    346  Vector<UniqueTwoByteChars, mozilla::intl::DEFAULT_LIST_LENGTH> strings(cx);
    347  mozilla::intl::ListFormat::StringList list;
    348 
    349  Rooted<ArrayObject*> listObj(cx, &args[1].toObject().as<ArrayObject>());
    350  RootedValue value(cx);
    351  uint32_t listLen = listObj->length();
    352  for (uint32_t i = 0; i < listLen; i++) {
    353    if (!GetElement(cx, listObj, listObj, i, &value)) {
    354      return false;
    355    }
    356 
    357    JSLinearString* linear = value.toString()->ensureLinear(cx);
    358    if (!linear) {
    359      return false;
    360    }
    361 
    362    size_t linearLength = linear->length();
    363 
    364    UniqueTwoByteChars chars = cx->make_pod_array<char16_t>(linearLength);
    365    if (!chars) {
    366      return false;
    367    }
    368    CopyChars(chars.get(), *linear);
    369 
    370    if (!strings.append(std::move(chars))) {
    371      return false;
    372    }
    373 
    374    if (!list.emplaceBack(strings[i].get(), linearLength)) {
    375      return false;
    376    }
    377  }
    378 
    379  if (formatToParts) {
    380    return FormatListToParts(cx, lf, list, args.rval());
    381  }
    382  return FormatList(cx, lf, list, args.rval());
    383 }
    384 
    385 /**
    386 * Intl.ListFormat.supportedLocalesOf ( locales [ , options ] )
    387 */
    388 static bool listFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
    389                                          Value* vp) {
    390  CallArgs args = CallArgsFromVp(argc, vp);
    391 
    392  // Steps 1-3.
    393  auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::ListFormat,
    394                                   args.get(0), args.get(1));
    395  if (!array) {
    396    return false;
    397  }
    398  args.rval().setObject(*array);
    399  return true;
    400 }