tor-browser

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

Collator.cpp (15551B)


      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.Collator implementation. */
      8 
      9 #include "builtin/intl/Collator.h"
     10 
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/intl/Collator.h"
     13 #include "mozilla/intl/Locale.h"
     14 #include "mozilla/Span.h"
     15 
     16 #include "builtin/Array.h"
     17 #include "builtin/intl/CommonFunctions.h"
     18 #include "builtin/intl/FormatBuffer.h"
     19 #include "builtin/intl/LanguageTag.h"
     20 #include "builtin/intl/LocaleNegotiation.h"
     21 #include "builtin/intl/SharedIntlData.h"
     22 #include "gc/GCContext.h"
     23 #include "js/PropertySpec.h"
     24 #include "js/StableStringChars.h"
     25 #include "js/TypeDecls.h"
     26 #include "vm/GlobalObject.h"
     27 #include "vm/JSContext.h"
     28 #include "vm/PlainObject.h"  // js::PlainObject
     29 #include "vm/Runtime.h"
     30 #include "vm/StringType.h"
     31 
     32 #include "vm/GeckoProfiler-inl.h"
     33 #include "vm/JSObject-inl.h"
     34 
     35 using namespace js;
     36 using namespace js::intl;
     37 
     38 using JS::AutoStableStringChars;
     39 
     40 using js::intl::ReportInternalError;
     41 using js::intl::SharedIntlData;
     42 
     43 const JSClassOps CollatorObject::classOps_ = {
     44    nullptr,                   // addProperty
     45    nullptr,                   // delProperty
     46    nullptr,                   // enumerate
     47    nullptr,                   // newEnumerate
     48    nullptr,                   // resolve
     49    nullptr,                   // mayResolve
     50    CollatorObject::finalize,  // finalize
     51    nullptr,                   // call
     52    nullptr,                   // construct
     53    nullptr,                   // trace
     54 };
     55 
     56 const JSClass CollatorObject::class_ = {
     57    "Intl.Collator",
     58    JSCLASS_HAS_RESERVED_SLOTS(CollatorObject::SLOT_COUNT) |
     59        JSCLASS_HAS_CACHED_PROTO(JSProto_Collator) |
     60        JSCLASS_FOREGROUND_FINALIZE,
     61    &CollatorObject::classOps_,
     62    &CollatorObject::classSpec_,
     63 };
     64 
     65 const JSClass& CollatorObject::protoClass_ = PlainObject::class_;
     66 
     67 static bool collator_supportedLocalesOf(JSContext* cx, unsigned argc,
     68                                        Value* vp);
     69 
     70 static bool collator_toSource(JSContext* cx, unsigned argc, Value* vp) {
     71  CallArgs args = CallArgsFromVp(argc, vp);
     72  args.rval().setString(cx->names().Collator);
     73  return true;
     74 }
     75 
     76 static const JSFunctionSpec collator_static_methods[] = {
     77    JS_FN("supportedLocalesOf", collator_supportedLocalesOf, 1, 0),
     78    JS_FS_END,
     79 };
     80 
     81 static const JSFunctionSpec collator_methods[] = {
     82    JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Collator_resolvedOptions", 0, 0),
     83    JS_FN("toSource", collator_toSource, 0, 0),
     84    JS_FS_END,
     85 };
     86 
     87 static const JSPropertySpec collator_properties[] = {
     88    JS_SELF_HOSTED_GET("compare", "$Intl_Collator_compare_get", 0),
     89    JS_STRING_SYM_PS(toStringTag, "Intl.Collator", JSPROP_READONLY),
     90    JS_PS_END,
     91 };
     92 
     93 static bool Collator(JSContext* cx, unsigned argc, Value* vp);
     94 
     95 const ClassSpec CollatorObject::classSpec_ = {
     96    GenericCreateConstructor<Collator, 0, gc::AllocKind::FUNCTION>,
     97    GenericCreatePrototype<CollatorObject>,
     98    collator_static_methods,
     99    nullptr,
    100    collator_methods,
    101    collator_properties,
    102    nullptr,
    103    ClassSpec::DontDefineConstructor,
    104 };
    105 
    106 /**
    107 * 10.1.2 Intl.Collator([ locales [, options]])
    108 *
    109 * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
    110 */
    111 static bool Collator(JSContext* cx, unsigned argc, Value* vp) {
    112  AutoJSConstructorProfilerEntry pseudoFrame(cx, "Intl.Collator");
    113  CallArgs args = CallArgsFromVp(argc, vp);
    114 
    115  // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
    116 
    117  // Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
    118  RootedObject proto(cx);
    119  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Collator, &proto)) {
    120    return false;
    121  }
    122 
    123  Rooted<CollatorObject*> collator(
    124      cx, NewObjectWithClassProto<CollatorObject>(cx, proto));
    125  if (!collator) {
    126    return false;
    127  }
    128 
    129  HandleValue locales = args.get(0);
    130  HandleValue options = args.get(1);
    131 
    132  // Step 6.
    133  if (!intl::InitializeObject(cx, collator, cx->names().InitializeCollator,
    134                              locales, options)) {
    135    return false;
    136  }
    137 
    138  args.rval().setObject(*collator);
    139  return true;
    140 }
    141 
    142 CollatorObject* js::intl::CreateCollator(JSContext* cx, Handle<Value> locales,
    143                                         Handle<Value> options) {
    144  Rooted<CollatorObject*> collator(cx,
    145                                   NewBuiltinClassInstance<CollatorObject>(cx));
    146  if (!collator) {
    147    return nullptr;
    148  }
    149 
    150  if (!InitializeObject(cx, collator, cx->names().InitializeCollator, locales,
    151                        options)) {
    152    return nullptr;
    153  }
    154 
    155  return collator;
    156 }
    157 
    158 CollatorObject* js::intl::GetOrCreateCollator(JSContext* cx,
    159                                              Handle<Value> locales,
    160                                              Handle<Value> options) {
    161  // Try to use a cached instance when |locales| is either undefined or a
    162  // string, and |options| is undefined.
    163  if ((locales.isUndefined() || locales.isString()) && options.isUndefined()) {
    164    Rooted<JSLinearString*> locale(cx);
    165    if (locales.isString()) {
    166      locale = locales.toString()->ensureLinear(cx);
    167      if (!locale) {
    168        return nullptr;
    169      }
    170    }
    171    return cx->global()->globalIntlData().getOrCreateCollator(cx, locale);
    172  }
    173 
    174  // Create a new Intl.Collator instance.
    175  return CreateCollator(cx, locales, options);
    176 }
    177 
    178 void js::CollatorObject::finalize(JS::GCContext* gcx, JSObject* obj) {
    179  MOZ_ASSERT(gcx->onMainThread());
    180 
    181  if (mozilla::intl::Collator* coll = obj->as<CollatorObject>().getCollator()) {
    182    intl::RemoveICUCellMemory(gcx, obj, CollatorObject::EstimatedMemoryUse);
    183    delete coll;
    184  }
    185 }
    186 
    187 bool js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp) {
    188  CallArgs args = CallArgsFromVp(argc, vp);
    189  MOZ_ASSERT(args.length() == 1);
    190  MOZ_ASSERT(args[0].isString());
    191 
    192  UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
    193  if (!locale) {
    194    return false;
    195  }
    196  auto keywords =
    197      mozilla::intl::Collator::GetBcp47KeywordValuesForLocale(locale.get());
    198  if (keywords.isErr()) {
    199    ReportInternalError(cx, keywords.unwrapErr());
    200    return false;
    201  }
    202 
    203  RootedObject collations(cx, NewDenseEmptyArray(cx));
    204  if (!collations) {
    205    return false;
    206  }
    207 
    208  // The first element of the collations array must be |null| per
    209  // ES2017 Intl, 10.2.3 Internal Slots.
    210  if (!NewbornArrayPush(cx, collations, NullValue())) {
    211    return false;
    212  }
    213 
    214  for (auto result : keywords.unwrap()) {
    215    if (result.isErr()) {
    216      ReportInternalError(cx);
    217      return false;
    218    }
    219    mozilla::Span<const char> collation = result.unwrap();
    220 
    221    // Per ECMA-402, 10.2.3, we don't include standard and search:
    222    // "The values 'standard' and 'search' must not be used as elements in
    223    // any [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co
    224    // array."
    225    static constexpr auto standard = mozilla::MakeStringSpan("standard");
    226    static constexpr auto search = mozilla::MakeStringSpan("search");
    227    if (collation == standard || collation == search) {
    228      continue;
    229    }
    230 
    231    JSString* jscollation = NewStringCopy<CanGC>(cx, collation);
    232    if (!jscollation) {
    233      return false;
    234    }
    235    if (!NewbornArrayPush(cx, collations, StringValue(jscollation))) {
    236      return false;
    237    }
    238  }
    239 
    240  args.rval().setObject(*collations);
    241  return true;
    242 }
    243 
    244 /**
    245 * Returns a new mozilla::intl::Collator with the locale and collation options
    246 * of the given Collator.
    247 */
    248 static mozilla::intl::Collator* NewIntlCollator(
    249    JSContext* cx, Handle<CollatorObject*> collator) {
    250  RootedValue value(cx);
    251  RootedObject internals(cx, intl::GetInternalsObject(cx, collator));
    252  if (!internals) {
    253    return nullptr;
    254  }
    255 
    256  using mozilla::intl::Collator;
    257 
    258  Collator::Options options{};
    259 
    260  if (!GetProperty(cx, internals, internals, cx->names().usage, &value)) {
    261    return nullptr;
    262  }
    263 
    264  enum class Usage { Search, Sort };
    265 
    266  Usage usage;
    267  {
    268    JSLinearString* str = value.toString()->ensureLinear(cx);
    269    if (!str) {
    270      return nullptr;
    271    }
    272 
    273    if (StringEqualsLiteral(str, "search")) {
    274      usage = Usage::Search;
    275    } else {
    276      MOZ_ASSERT(StringEqualsLiteral(str, "sort"));
    277      usage = Usage::Sort;
    278    }
    279  }
    280 
    281  JS::RootedVector<intl::UnicodeExtensionKeyword> keywords(cx);
    282 
    283  // ICU expects collation as Unicode locale extensions on locale.
    284  if (usage == Usage::Search) {
    285    if (!keywords.emplaceBack("co", cx->names().search)) {
    286      return nullptr;
    287    }
    288 
    289    // Search collations can't select a different collation, so the collation
    290    // property is guaranteed to be "default".
    291 #ifdef DEBUG
    292    if (!GetProperty(cx, internals, internals, cx->names().collation, &value)) {
    293      return nullptr;
    294    }
    295 
    296    JSLinearString* collation = value.toString()->ensureLinear(cx);
    297    if (!collation) {
    298      return nullptr;
    299    }
    300 
    301    MOZ_ASSERT(StringEqualsLiteral(collation, "default"));
    302 #endif
    303  } else {
    304    if (!GetProperty(cx, internals, internals, cx->names().collation, &value)) {
    305      return nullptr;
    306    }
    307 
    308    JSLinearString* collation = value.toString()->ensureLinear(cx);
    309    if (!collation) {
    310      return nullptr;
    311    }
    312 
    313    // Set collation as a Unicode locale extension when it was specified.
    314    if (!StringEqualsLiteral(collation, "default")) {
    315      if (!keywords.emplaceBack("co", collation)) {
    316        return nullptr;
    317      }
    318    }
    319  }
    320 
    321  UniqueChars locale = intl::FormatLocale(cx, internals, keywords);
    322  if (!locale) {
    323    return nullptr;
    324  }
    325 
    326  if (!GetProperty(cx, internals, internals, cx->names().sensitivity, &value)) {
    327    return nullptr;
    328  }
    329 
    330  {
    331    JSLinearString* sensitivity = value.toString()->ensureLinear(cx);
    332    if (!sensitivity) {
    333      return nullptr;
    334    }
    335    if (StringEqualsLiteral(sensitivity, "base")) {
    336      options.sensitivity = Collator::Sensitivity::Base;
    337    } else if (StringEqualsLiteral(sensitivity, "accent")) {
    338      options.sensitivity = Collator::Sensitivity::Accent;
    339    } else if (StringEqualsLiteral(sensitivity, "case")) {
    340      options.sensitivity = Collator::Sensitivity::Case;
    341    } else {
    342      MOZ_ASSERT(StringEqualsLiteral(sensitivity, "variant"));
    343      options.sensitivity = Collator::Sensitivity::Variant;
    344    }
    345  }
    346 
    347  if (!GetProperty(cx, internals, internals, cx->names().ignorePunctuation,
    348                   &value)) {
    349    return nullptr;
    350  }
    351  options.ignorePunctuation = value.toBoolean();
    352 
    353  if (!GetProperty(cx, internals, internals, cx->names().numeric, &value)) {
    354    return nullptr;
    355  }
    356  if (!value.isUndefined()) {
    357    options.numeric = value.toBoolean();
    358  }
    359 
    360  if (!GetProperty(cx, internals, internals, cx->names().caseFirst, &value)) {
    361    return nullptr;
    362  }
    363  if (!value.isUndefined()) {
    364    JSLinearString* caseFirst = value.toString()->ensureLinear(cx);
    365    if (!caseFirst) {
    366      return nullptr;
    367    }
    368    if (StringEqualsLiteral(caseFirst, "upper")) {
    369      options.caseFirst = Collator::CaseFirst::Upper;
    370    } else if (StringEqualsLiteral(caseFirst, "lower")) {
    371      options.caseFirst = Collator::CaseFirst::Lower;
    372    } else {
    373      MOZ_ASSERT(StringEqualsLiteral(caseFirst, "false"));
    374      options.caseFirst = Collator::CaseFirst::False;
    375    }
    376  }
    377 
    378  auto collResult = Collator::TryCreate(locale.get());
    379  if (collResult.isErr()) {
    380    ReportInternalError(cx, collResult.unwrapErr());
    381    return nullptr;
    382  }
    383  auto coll = collResult.unwrap();
    384 
    385  auto optResult = coll->SetOptions(options);
    386  if (optResult.isErr()) {
    387    ReportInternalError(cx, optResult.unwrapErr());
    388    return nullptr;
    389  }
    390 
    391  return coll.release();
    392 }
    393 
    394 static mozilla::intl::Collator* GetOrCreateCollator(
    395    JSContext* cx, Handle<CollatorObject*> collator) {
    396  // Obtain a cached mozilla::intl::Collator object.
    397  mozilla::intl::Collator* coll = collator->getCollator();
    398  if (coll) {
    399    return coll;
    400  }
    401 
    402  coll = NewIntlCollator(cx, collator);
    403  if (!coll) {
    404    return nullptr;
    405  }
    406  collator->setCollator(coll);
    407 
    408  intl::AddICUCellMemory(collator, CollatorObject::EstimatedMemoryUse);
    409  return coll;
    410 }
    411 
    412 static bool intl_CompareStrings(JSContext* cx, mozilla::intl::Collator* coll,
    413                                JSString* str1, JSString* str2,
    414                                MutableHandleValue result) {
    415  MOZ_ASSERT(str1);
    416  MOZ_ASSERT(str2);
    417 
    418  if (str1 == str2) {
    419    result.setInt32(0);
    420    return true;
    421  }
    422 
    423  AutoStableStringChars stableChars1(cx);
    424  if (!stableChars1.initTwoByte(cx, str1)) {
    425    return false;
    426  }
    427 
    428  AutoStableStringChars stableChars2(cx);
    429  if (!stableChars2.initTwoByte(cx, str2)) {
    430    return false;
    431  }
    432 
    433  mozilla::Range<const char16_t> chars1 = stableChars1.twoByteRange();
    434  mozilla::Range<const char16_t> chars2 = stableChars2.twoByteRange();
    435 
    436  result.setInt32(coll->CompareStrings(chars1, chars2));
    437  return true;
    438 }
    439 
    440 bool js::intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp) {
    441  CallArgs args = CallArgsFromVp(argc, vp);
    442  MOZ_ASSERT(args.length() == 3);
    443  MOZ_ASSERT(args[0].isObject());
    444  MOZ_ASSERT(args[1].isString());
    445  MOZ_ASSERT(args[2].isString());
    446 
    447  Rooted<CollatorObject*> collator(cx,
    448                                   &args[0].toObject().as<CollatorObject>());
    449 
    450  mozilla::intl::Collator* coll = GetOrCreateCollator(cx, collator);
    451  if (!coll) {
    452    return false;
    453  }
    454 
    455  // Use the UCollator to actually compare the strings.
    456  JSString* str1 = args[1].toString();
    457  JSString* str2 = args[2].toString();
    458  return intl_CompareStrings(cx, coll, str1, str2, args.rval());
    459 }
    460 
    461 bool js::intl::CompareStrings(JSContext* cx, Handle<CollatorObject*> collator,
    462                              Handle<JSString*> str1, Handle<JSString*> str2,
    463                              MutableHandle<Value> result) {
    464  mozilla::intl::Collator* coll = GetOrCreateCollator(cx, collator);
    465  if (!coll) {
    466    return false;
    467  }
    468  return intl_CompareStrings(cx, coll, str1, str2, result);
    469 }
    470 
    471 bool js::intl_isUpperCaseFirst(JSContext* cx, unsigned argc, Value* vp) {
    472  CallArgs args = CallArgsFromVp(argc, vp);
    473  MOZ_ASSERT(args.length() == 1);
    474  MOZ_ASSERT(args[0].isString());
    475 
    476  SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
    477 
    478  Rooted<JSLinearString*> locale(cx, args[0].toString()->ensureLinear(cx));
    479  if (!locale) {
    480    return false;
    481  }
    482 
    483  bool isUpperFirst;
    484  if (!sharedIntlData.isUpperCaseFirst(cx, locale, &isUpperFirst)) {
    485    return false;
    486  }
    487 
    488  args.rval().setBoolean(isUpperFirst);
    489  return true;
    490 }
    491 
    492 bool js::intl_isIgnorePunctuation(JSContext* cx, unsigned argc, Value* vp) {
    493  CallArgs args = CallArgsFromVp(argc, vp);
    494  MOZ_ASSERT(args.length() == 1);
    495  MOZ_ASSERT(args[0].isString());
    496 
    497  SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
    498 
    499  Rooted<JSLinearString*> locale(cx, args[0].toString()->ensureLinear(cx));
    500  if (!locale) {
    501    return false;
    502  }
    503 
    504  bool isIgnorePunctuation;
    505  if (!sharedIntlData.isIgnorePunctuation(cx, locale, &isIgnorePunctuation)) {
    506    return false;
    507  }
    508 
    509  args.rval().setBoolean(isIgnorePunctuation);
    510  return true;
    511 }
    512 
    513 /**
    514 * Intl.Collator.supportedLocalesOf ( locales [ , options ] )
    515 */
    516 static bool collator_supportedLocalesOf(JSContext* cx, unsigned argc,
    517                                        Value* vp) {
    518  CallArgs args = CallArgsFromVp(argc, vp);
    519 
    520  // Steps 1-3.
    521  auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::Collator,
    522                                   args.get(0), args.get(1));
    523  if (!array) {
    524    return false;
    525  }
    526  args.rval().setObject(*array);
    527  return true;
    528 }