• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef V8_INTL_SUPPORT
6 #error Internationalization is expected to be enabled.
7 #endif  // V8_INTL_SUPPORT
8 
9 #include "src/objects/js-collator.h"
10 
11 #include "src/execution/isolate.h"
12 #include "src/objects/js-collator-inl.h"
13 #include "src/objects/js-locale.h"
14 #include "src/objects/objects-inl.h"
15 #include "unicode/coll.h"
16 #include "unicode/locid.h"
17 #include "unicode/strenum.h"
18 #include "unicode/ucol.h"
19 #include "unicode/udata.h"
20 #include "unicode/uloc.h"
21 #include "unicode/utypes.h"
22 
23 namespace v8 {
24 namespace internal {
25 
26 namespace {
27 
28 enum class Usage {
29   SORT,
30   SEARCH,
31 };
32 
33 enum class Sensitivity {
34   kBase,
35   kAccent,
36   kCase,
37   kVariant,
38   kUndefined,
39 };
40 
41 // enum for "caseFirst" option.
42 enum class CaseFirst { kUndefined, kUpper, kLower, kFalse };
43 
GetCaseFirst(Isolate * isolate,Handle<JSReceiver> options,const char * method)44 Maybe<CaseFirst> GetCaseFirst(Isolate* isolate, Handle<JSReceiver> options,
45                               const char* method) {
46   return Intl::GetStringOption<CaseFirst>(
47       isolate, options, "caseFirst", method, {"upper", "lower", "false"},
48       {CaseFirst::kUpper, CaseFirst::kLower, CaseFirst::kFalse},
49       CaseFirst::kUndefined);
50 }
51 
52 // TODO(gsathya): Consider internalizing the value strings.
CreateDataPropertyForOptions(Isolate * isolate,Handle<JSObject> options,Handle<String> key,const char * value)53 void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
54                                   Handle<String> key, const char* value) {
55   DCHECK_NOT_NULL(value);
56   Handle<String> value_str =
57       isolate->factory()->NewStringFromAsciiChecked(value);
58 
59   // This is a brand new JSObject that shouldn't already have the same
60   // key so this shouldn't fail.
61   Maybe<bool> maybe = JSReceiver::CreateDataProperty(
62       isolate, options, key, value_str, Just(kDontThrow));
63   DCHECK(maybe.FromJust());
64   USE(maybe);
65 }
66 
CreateDataPropertyForOptions(Isolate * isolate,Handle<JSObject> options,Handle<String> key,bool value)67 void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
68                                   Handle<String> key, bool value) {
69   Handle<Object> value_obj = isolate->factory()->ToBoolean(value);
70 
71   // This is a brand new JSObject that shouldn't already have the same
72   // key so this shouldn't fail.
73   Maybe<bool> maybe = JSReceiver::CreateDataProperty(
74       isolate, options, key, value_obj, Just(kDontThrow));
75   DCHECK(maybe.FromJust());
76   USE(maybe);
77 }
78 
79 }  // anonymous namespace
80 
81 // static
ResolvedOptions(Isolate * isolate,Handle<JSCollator> collator)82 Handle<JSObject> JSCollator::ResolvedOptions(Isolate* isolate,
83                                              Handle<JSCollator> collator) {
84   Handle<JSObject> options =
85       isolate->factory()->NewJSObject(isolate->object_function());
86 
87   icu::Collator* icu_collator = collator->icu_collator().raw();
88   DCHECK_NOT_NULL(icu_collator);
89 
90   UErrorCode status = U_ZERO_ERROR;
91   bool numeric =
92       icu_collator->getAttribute(UCOL_NUMERIC_COLLATION, status) == UCOL_ON;
93   DCHECK(U_SUCCESS(status));
94 
95   const char* case_first = nullptr;
96   status = U_ZERO_ERROR;
97   switch (icu_collator->getAttribute(UCOL_CASE_FIRST, status)) {
98     case UCOL_LOWER_FIRST:
99       case_first = "lower";
100       break;
101     case UCOL_UPPER_FIRST:
102       case_first = "upper";
103       break;
104     default:
105       case_first = "false";
106   }
107   DCHECK(U_SUCCESS(status));
108 
109   const char* sensitivity = nullptr;
110   status = U_ZERO_ERROR;
111   switch (icu_collator->getAttribute(UCOL_STRENGTH, status)) {
112     case UCOL_PRIMARY: {
113       DCHECK(U_SUCCESS(status));
114       status = U_ZERO_ERROR;
115       // case level: true + s1 -> case, s1 -> base.
116       if (UCOL_ON == icu_collator->getAttribute(UCOL_CASE_LEVEL, status)) {
117         sensitivity = "case";
118       } else {
119         sensitivity = "base";
120       }
121       DCHECK(U_SUCCESS(status));
122       break;
123     }
124     case UCOL_SECONDARY:
125       sensitivity = "accent";
126       break;
127     case UCOL_TERTIARY:
128       sensitivity = "variant";
129       break;
130     case UCOL_QUATERNARY:
131       // We shouldn't get quaternary and identical from ICU, but if we do
132       // put them into variant.
133       sensitivity = "variant";
134       break;
135     default:
136       sensitivity = "variant";
137   }
138   DCHECK(U_SUCCESS(status));
139 
140   status = U_ZERO_ERROR;
141   bool ignore_punctuation = icu_collator->getAttribute(UCOL_ALTERNATE_HANDLING,
142                                                        status) == UCOL_SHIFTED;
143   DCHECK(U_SUCCESS(status));
144 
145   status = U_ZERO_ERROR;
146 
147   icu::Locale icu_locale(icu_collator->getLocale(ULOC_VALID_LOCALE, status));
148   DCHECK(U_SUCCESS(status));
149 
150   const char* collation = "default";
151   const char* usage = "sort";
152   const char* collation_key = "co";
153   status = U_ZERO_ERROR;
154   std::string collation_value =
155       icu_locale.getUnicodeKeywordValue<std::string>(collation_key, status);
156 
157   std::string locale;
158   if (U_SUCCESS(status)) {
159     if (collation_value == "search") {
160       usage = "search";
161 
162       // Search is disallowed as a collation value per spec. Let's
163       // use `default`, instead.
164       //
165       // https://tc39.github.io/ecma402/#sec-properties-of-intl-collator-instances
166       collation = "default";
167 
168       // We clone the icu::Locale because we don't want the
169       // icu_collator to be affected when we remove the collation key
170       // below.
171       icu::Locale new_icu_locale = icu_locale;
172 
173       // The spec forbids the search as a collation value in the
174       // locale tag, so let's filter it out.
175       status = U_ZERO_ERROR;
176       new_icu_locale.setUnicodeKeywordValue(collation_key, nullptr, status);
177       DCHECK(U_SUCCESS(status));
178 
179       locale = Intl::ToLanguageTag(new_icu_locale).FromJust();
180     } else {
181       collation = collation_value.c_str();
182       locale = Intl::ToLanguageTag(icu_locale).FromJust();
183     }
184   } else {
185     locale = Intl::ToLanguageTag(icu_locale).FromJust();
186   }
187 
188   // 5. For each row of Table 2, except the header row, in table order, do
189   //    ...
190   // Table 2: Resolved Options of Collator Instances
191   //  Internal Slot            Property               Extension Key
192   //    [[Locale]                "locale"
193   //    [[Usage]                 "usage"
194   //    [[Sensitivity]]          "sensitivity"
195   //    [[IgnorePunctuation]]    "ignorePunctuation"
196   //    [[Collation]]            "collation"
197   //    [[Numeric]]              "numeric"              kn
198   //    [[CaseFirst]]            "caseFirst"            kf
199 
200   // If the collator return the locale differ from what got requested, we stored
201   // it in the collator->locale. Otherwise, we just use the one from the
202   // collator.
203   if (collator->locale().length() != 0) {
204     // Get the locale from collator->locale() since we know in some cases
205     // collator won't be able to return the requested one, such as zh_CN.
206     Handle<String> locale_from_collator(collator->locale(), isolate);
207     Maybe<bool> maybe = JSReceiver::CreateDataProperty(
208         isolate, options, isolate->factory()->locale_string(),
209         locale_from_collator, Just(kDontThrow));
210     DCHECK(maybe.FromJust());
211     USE(maybe);
212   } else {
213     // Just return from the collator for most of the cases that we can recover
214     // from the collator.
215     CreateDataPropertyForOptions(
216         isolate, options, isolate->factory()->locale_string(), locale.c_str());
217   }
218 
219   CreateDataPropertyForOptions(isolate, options,
220                                isolate->factory()->usage_string(), usage);
221   CreateDataPropertyForOptions(
222       isolate, options, isolate->factory()->sensitivity_string(), sensitivity);
223   CreateDataPropertyForOptions(isolate, options,
224                                isolate->factory()->ignorePunctuation_string(),
225                                ignore_punctuation);
226   CreateDataPropertyForOptions(
227       isolate, options, isolate->factory()->collation_string(), collation);
228   CreateDataPropertyForOptions(isolate, options,
229                                isolate->factory()->numeric_string(), numeric);
230   CreateDataPropertyForOptions(
231       isolate, options, isolate->factory()->caseFirst_string(), case_first);
232   return options;
233 }
234 
235 namespace {
236 
ToCaseFirst(const char * str)237 CaseFirst ToCaseFirst(const char* str) {
238   if (strcmp(str, "upper") == 0) return CaseFirst::kUpper;
239   if (strcmp(str, "lower") == 0) return CaseFirst::kLower;
240   if (strcmp(str, "false") == 0) return CaseFirst::kFalse;
241   return CaseFirst::kUndefined;
242 }
243 
ToUColAttributeValue(CaseFirst case_first)244 UColAttributeValue ToUColAttributeValue(CaseFirst case_first) {
245   switch (case_first) {
246     case CaseFirst::kUpper:
247       return UCOL_UPPER_FIRST;
248     case CaseFirst::kLower:
249       return UCOL_LOWER_FIRST;
250     case CaseFirst::kFalse:
251     case CaseFirst::kUndefined:
252       return UCOL_OFF;
253   }
254 }
255 
SetNumericOption(icu::Collator * icu_collator,bool numeric)256 void SetNumericOption(icu::Collator* icu_collator, bool numeric) {
257   DCHECK_NOT_NULL(icu_collator);
258   UErrorCode status = U_ZERO_ERROR;
259   icu_collator->setAttribute(UCOL_NUMERIC_COLLATION,
260                              numeric ? UCOL_ON : UCOL_OFF, status);
261   DCHECK(U_SUCCESS(status));
262 }
263 
SetCaseFirstOption(icu::Collator * icu_collator,CaseFirst case_first)264 void SetCaseFirstOption(icu::Collator* icu_collator, CaseFirst case_first) {
265   DCHECK_NOT_NULL(icu_collator);
266   UErrorCode status = U_ZERO_ERROR;
267   icu_collator->setAttribute(UCOL_CASE_FIRST, ToUColAttributeValue(case_first),
268                              status);
269   DCHECK(U_SUCCESS(status));
270 }
271 
272 }  // anonymous namespace
273 
274 // static
New(Isolate * isolate,Handle<Map> map,Handle<Object> locales,Handle<Object> options_obj,const char * service)275 MaybeHandle<JSCollator> JSCollator::New(Isolate* isolate, Handle<Map> map,
276                                         Handle<Object> locales,
277                                         Handle<Object> options_obj,
278                                         const char* service) {
279   // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
280   Maybe<std::vector<std::string>> maybe_requested_locales =
281       Intl::CanonicalizeLocaleList(isolate, locales);
282   MAYBE_RETURN(maybe_requested_locales, Handle<JSCollator>());
283   std::vector<std::string> requested_locales =
284       maybe_requested_locales.FromJust();
285 
286   // 2. If options is undefined, then
287   if (options_obj->IsUndefined(isolate)) {
288     // 2. a. Let options be ObjectCreate(null).
289     options_obj = isolate->factory()->NewJSObjectWithNullProto();
290   } else {
291     // 3. Else
292     // 3. a. Let options be ? ToObject(options).
293     ASSIGN_RETURN_ON_EXCEPTION(isolate, options_obj,
294                                Object::ToObject(isolate, options_obj, service),
295                                JSCollator);
296   }
297 
298   // At this point, options_obj can either be a JSObject or a JSProxy only.
299   Handle<JSReceiver> options = Handle<JSReceiver>::cast(options_obj);
300 
301   // 4. Let usage be ? GetOption(options, "usage", "string", « "sort",
302   // "search" », "sort").
303   Maybe<Usage> maybe_usage = Intl::GetStringOption<Usage>(
304       isolate, options, "usage", service, {"sort", "search"},
305       {Usage::SORT, Usage::SEARCH}, Usage::SORT);
306   MAYBE_RETURN(maybe_usage, MaybeHandle<JSCollator>());
307   Usage usage = maybe_usage.FromJust();
308 
309   // 9. Let matcher be ? GetOption(options, "localeMatcher", "string",
310   // « "lookup", "best fit" », "best fit").
311   // 10. Set opt.[[localeMatcher]] to matcher.
312   Maybe<Intl::MatcherOption> maybe_locale_matcher =
313       Intl::GetLocaleMatcher(isolate, options, service);
314   MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSCollator>());
315   Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
316 
317   // x. Let _collation_ be ? GetOption(_options_, *"collation"*, *"string"*,
318   // *undefined*, *undefined*).
319   std::unique_ptr<char[]> collation_str = nullptr;
320   const std::vector<const char*> empty_values = {};
321   Maybe<bool> maybe_collation = Intl::GetStringOption(
322       isolate, options, "collation", empty_values, service, &collation_str);
323   MAYBE_RETURN(maybe_collation, MaybeHandle<JSCollator>());
324   // x. If _collation_ is not *undefined*, then
325   if (maybe_collation.FromJust() && collation_str != nullptr) {
326     // 1. If _collation_ does not match the Unicode Locale Identifier `type`
327     // nonterminal, throw a *RangeError* exception.
328     if (!JSLocale::Is38AlphaNumList(collation_str.get())) {
329       THROW_NEW_ERROR_RETURN_VALUE(
330           isolate,
331           NewRangeError(MessageTemplate::kInvalid,
332                         isolate->factory()->collation_string(),
333                         isolate->factory()->NewStringFromAsciiChecked(
334                             collation_str.get())),
335           MaybeHandle<JSCollator>());
336     }
337   }
338   // x. Set _opt_.[[co]] to _collation_.
339 
340   // 11. Let numeric be ? GetOption(options, "numeric", "boolean",
341   // undefined, undefined).
342   // 12. If numeric is not undefined, then
343   //    a. Let numeric be ! ToString(numeric).
344   //
345   // Note: We omit the ToString(numeric) operation as it's not
346   // observable. Intl::GetBoolOption returns a Boolean and
347   // ToString(Boolean) is not side-effecting.
348   //
349   // 13. Set opt.[[kn]] to numeric.
350   bool numeric;
351   Maybe<bool> found_numeric =
352       Intl::GetBoolOption(isolate, options, "numeric", service, &numeric);
353   MAYBE_RETURN(found_numeric, MaybeHandle<JSCollator>());
354 
355   // 14. Let caseFirst be ? GetOption(options, "caseFirst", "string",
356   //     « "upper", "lower", "false" », undefined).
357   Maybe<CaseFirst> maybe_case_first = GetCaseFirst(isolate, options, service);
358   MAYBE_RETURN(maybe_case_first, MaybeHandle<JSCollator>());
359   CaseFirst case_first = maybe_case_first.FromJust();
360 
361   // The relevant unicode extensions accepted by Collator as specified here:
362   // https://tc39.github.io/ecma402/#sec-intl-collator-internal-slots
363   //
364   // 16. Let relevantExtensionKeys be %Collator%.[[RelevantExtensionKeys]].
365   std::set<std::string> relevant_extension_keys{"co", "kn", "kf"};
366 
367   // 17. Let r be ResolveLocale(%Collator%.[[AvailableLocales]],
368   // requestedLocales, opt, %Collator%.[[RelevantExtensionKeys]],
369   // localeData).
370   Maybe<Intl::ResolvedLocale> maybe_resolve_locale =
371       Intl::ResolveLocale(isolate, JSCollator::GetAvailableLocales(),
372                           requested_locales, matcher, relevant_extension_keys);
373   if (maybe_resolve_locale.IsNothing()) {
374     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
375                     JSCollator);
376   }
377   Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
378 
379   // 18. Set collator.[[Locale]] to r.[[locale]].
380   icu::Locale icu_locale = r.icu_locale;
381   DCHECK(!icu_locale.isBogus());
382 
383   // 19. Let collation be r.[[co]].
384   UErrorCode status = U_ZERO_ERROR;
385   if (collation_str != nullptr) {
386     auto co_extension_it = r.extensions.find("co");
387     if (co_extension_it != r.extensions.end() &&
388         co_extension_it->second != collation_str.get()) {
389       icu_locale.setUnicodeKeywordValue("co", nullptr, status);
390       DCHECK(U_SUCCESS(status));
391     }
392   }
393 
394   // 5. Set collator.[[Usage]] to usage.
395   //
396   // 6. If usage is "sort", then
397   //    a. Let localeData be %Collator%.[[SortLocaleData]].
398   // 7. Else,
399   //    a. Let localeData be %Collator%.[[SearchLocaleData]].
400   //
401   // The Intl spec doesn't allow us to use "search" as an extension
402   // value for collation as per:
403   // https://tc39.github.io/ecma402/#sec-intl-collator-internal-slots
404   //
405   // But the only way to pass the value "search" for collation from
406   // the options object to ICU is to use the 'co' extension keyword.
407   //
408   // This will need to be filtered out when creating the
409   // resolvedOptions object.
410   if (usage == Usage::SEARCH) {
411     UErrorCode status = U_ZERO_ERROR;
412     icu_locale.setUnicodeKeywordValue("co", "search", status);
413     DCHECK(U_SUCCESS(status));
414   } else {
415     if (collation_str != nullptr &&
416         Intl::IsValidCollation(icu_locale, collation_str.get())) {
417       icu_locale.setUnicodeKeywordValue("co", collation_str.get(), status);
418       DCHECK(U_SUCCESS(status));
419     }
420   }
421 
422   // 20. If collation is null, let collation be "default".
423   // 21. Set collator.[[Collation]] to collation.
424   //
425   // We don't store the collation value as per the above two steps
426   // here. The collation value can be looked up from icu::Collator on
427   // demand, as part of Intl.Collator.prototype.resolvedOptions.
428 
429   std::unique_ptr<icu::Collator> icu_collator(
430       icu::Collator::createInstance(icu_locale, status));
431   if (U_FAILURE(status) || icu_collator.get() == nullptr) {
432     status = U_ZERO_ERROR;
433     // Remove extensions and try again.
434     icu::Locale no_extension_locale(icu_locale.getBaseName());
435     icu_collator.reset(
436         icu::Collator::createInstance(no_extension_locale, status));
437 
438     if (U_FAILURE(status) || icu_collator.get() == nullptr) {
439       THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
440                       JSCollator);
441     }
442   }
443   DCHECK(U_SUCCESS(status));
444 
445   icu::Locale collator_locale(
446       icu_collator->getLocale(ULOC_VALID_LOCALE, status));
447 
448   // 22. If relevantExtensionKeys contains "kn", then
449   //     a. Set collator.[[Numeric]] to ! SameValue(r.[[kn]], "true").
450   //
451   // If the numeric value is passed in through the options object,
452   // then we use it. Otherwise, we check if the numeric value is
453   // passed in through the unicode extensions.
454   status = U_ZERO_ERROR;
455   if (found_numeric.FromJust()) {
456     SetNumericOption(icu_collator.get(), numeric);
457   } else {
458     auto kn_extension_it = r.extensions.find("kn");
459     if (kn_extension_it != r.extensions.end()) {
460       SetNumericOption(icu_collator.get(), (kn_extension_it->second == "true"));
461     }
462   }
463 
464   // 23. If relevantExtensionKeys contains "kf", then
465   //     a. Set collator.[[CaseFirst]] to r.[[kf]].
466   //
467   // If the caseFirst value is passed in through the options object,
468   // then we use it. Otherwise, we check if the caseFirst value is
469   // passed in through the unicode extensions.
470   if (case_first != CaseFirst::kUndefined) {
471     SetCaseFirstOption(icu_collator.get(), case_first);
472   } else {
473     auto kf_extension_it = r.extensions.find("kf");
474     if (kf_extension_it != r.extensions.end()) {
475       SetCaseFirstOption(icu_collator.get(),
476                          ToCaseFirst(kf_extension_it->second.c_str()));
477     }
478   }
479 
480   // Normalization is always on, by the spec. We are free to optimize
481   // if the strings are already normalized (but we don't have a way to tell
482   // that right now).
483   status = U_ZERO_ERROR;
484   icu_collator->setAttribute(UCOL_NORMALIZATION_MODE, UCOL_ON, status);
485   DCHECK(U_SUCCESS(status));
486 
487   // 24. Let sensitivity be ? GetOption(options, "sensitivity",
488   // "string", « "base", "accent", "case", "variant" », undefined).
489   Maybe<Sensitivity> maybe_sensitivity = Intl::GetStringOption<Sensitivity>(
490       isolate, options, "sensitivity", service,
491       {"base", "accent", "case", "variant"},
492       {Sensitivity::kBase, Sensitivity::kAccent, Sensitivity::kCase,
493        Sensitivity::kVariant},
494       Sensitivity::kUndefined);
495   MAYBE_RETURN(maybe_sensitivity, MaybeHandle<JSCollator>());
496   Sensitivity sensitivity = maybe_sensitivity.FromJust();
497 
498   // 25. If sensitivity is undefined, then
499   if (sensitivity == Sensitivity::kUndefined) {
500     // 25. a. If usage is "sort", then
501     if (usage == Usage::SORT) {
502       // 25. a. i. Let sensitivity be "variant".
503       sensitivity = Sensitivity::kVariant;
504     }
505   }
506   // 26. Set collator.[[Sensitivity]] to sensitivity.
507   switch (sensitivity) {
508     case Sensitivity::kBase:
509       icu_collator->setStrength(icu::Collator::PRIMARY);
510       break;
511     case Sensitivity::kAccent:
512       icu_collator->setStrength(icu::Collator::SECONDARY);
513       break;
514     case Sensitivity::kCase:
515       icu_collator->setStrength(icu::Collator::PRIMARY);
516       status = U_ZERO_ERROR;
517       icu_collator->setAttribute(UCOL_CASE_LEVEL, UCOL_ON, status);
518       DCHECK(U_SUCCESS(status));
519       break;
520     case Sensitivity::kVariant:
521       icu_collator->setStrength(icu::Collator::TERTIARY);
522       break;
523     case Sensitivity::kUndefined:
524       break;
525   }
526 
527   // 27.Let ignorePunctuation be ? GetOption(options,
528   // "ignorePunctuation", "boolean", undefined, false).
529   bool ignore_punctuation;
530   Maybe<bool> found_ignore_punctuation = Intl::GetBoolOption(
531       isolate, options, "ignorePunctuation", service, &ignore_punctuation);
532   MAYBE_RETURN(found_ignore_punctuation, MaybeHandle<JSCollator>());
533 
534   // 28. Set collator.[[IgnorePunctuation]] to ignorePunctuation.
535   if (found_ignore_punctuation.FromJust() && ignore_punctuation) {
536     status = U_ZERO_ERROR;
537     icu_collator->setAttribute(UCOL_ALTERNATE_HANDLING, UCOL_SHIFTED, status);
538     DCHECK(U_SUCCESS(status));
539   }
540 
541   Handle<Managed<icu::Collator>> managed_collator =
542       Managed<icu::Collator>::FromUniquePtr(isolate, 0,
543                                             std::move(icu_collator));
544 
545   // We only need to do so if it is different from the collator would return.
546   Handle<String> locale_str = isolate->factory()->NewStringFromAsciiChecked(
547       (collator_locale != icu_locale) ? r.locale.c_str() : "");
548   // Now all properties are ready, so we can allocate the result object.
549   Handle<JSCollator> collator = Handle<JSCollator>::cast(
550       isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
551   DisallowHeapAllocation no_gc;
552   collator->set_icu_collator(*managed_collator);
553   collator->set_locale(*locale_str);
554 
555   // 29. Return collator.
556   return collator;
557 }
558 
559 namespace {
560 
561 class CollatorAvailableLocales {
562  public:
CollatorAvailableLocales()563   CollatorAvailableLocales() {
564     int32_t num_locales = 0;
565     const icu::Locale* icu_available_locales =
566         icu::Collator::getAvailableLocales(num_locales);
567     std::vector<std::string> locales;
568     for (int32_t i = 0; i < num_locales; ++i) {
569       locales.push_back(
570           Intl::ToLanguageTag(icu_available_locales[i]).FromJust());
571     }
572 #define U_ICUDATA_COLL U_ICUDATA_NAME U_TREE_SEPARATOR_STRING "coll"
573     set_ = Intl::BuildLocaleSet(locales, U_ICUDATA_COLL, nullptr);
574 #undef U_ICUDATA_COLL
575   }
576   virtual ~CollatorAvailableLocales() = default;
Get() const577   const std::set<std::string>& Get() const { return set_; }
578 
579  private:
580   std::set<std::string> set_;
581 };
582 
583 }  // namespace
584 
GetAvailableLocales()585 const std::set<std::string>& JSCollator::GetAvailableLocales() {
586   static base::LazyInstance<CollatorAvailableLocales>::type available_locales =
587       LAZY_INSTANCE_INITIALIZER;
588   return available_locales.Pointer()->Get();
589 }
590 
591 }  // namespace internal
592 }  // namespace v8
593