[INTL] Implement supportedLocalesOf on Intl Constructors
[WebKit.git] / Source / JavaScriptCore / runtime / IntlObject.cpp
1 /*
2  * Copyright (C) 2015 Andy VanWagoner (thetalecrafter@gmail.com)
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "IntlObject.h"
28
29 #if ENABLE(INTL)
30
31 #include "Error.h"
32 #include "FunctionPrototype.h"
33 #include "IntlCollator.h"
34 #include "IntlCollatorConstructor.h"
35 #include "IntlCollatorPrototype.h"
36 #include "IntlDateTimeFormat.h"
37 #include "IntlDateTimeFormatConstructor.h"
38 #include "IntlDateTimeFormatPrototype.h"
39 #include "IntlNumberFormat.h"
40 #include "IntlNumberFormatConstructor.h"
41 #include "IntlNumberFormatPrototype.h"
42 #include "JSCInlines.h"
43 #include "JSCJSValueInlines.h"
44 #include "Lookup.h"
45 #include "ObjectPrototype.h"
46 #include <unicode/uloc.h>
47 #include <wtf/Assertions.h>
48
49 namespace JSC {
50
51 STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(IntlObject);
52
53 }
54
55 namespace JSC {
56
57 const ClassInfo IntlObject::s_info = { "Object", &Base::s_info, 0, CREATE_METHOD_TABLE(IntlObject) };
58
59 IntlObject::IntlObject(VM& vm, Structure* structure)
60     : JSNonFinalObject(vm, structure)
61 {
62 }
63
64 IntlObject* IntlObject::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
65 {
66     IntlObject* object = new (NotNull, allocateCell<IntlObject>(vm.heap)) IntlObject(vm, structure);
67     object->finishCreation(vm, globalObject);
68     return object;
69 }
70
71 void IntlObject::finishCreation(VM& vm, JSGlobalObject* globalObject)
72 {
73     Base::finishCreation(vm);
74     ASSERT(inherits(info()));
75
76     // Set up Collator.
77     IntlCollatorPrototype* collatorPrototype = IntlCollatorPrototype::create(vm, globalObject, IntlCollatorPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
78     Structure* collatorStructure = IntlCollator::createStructure(vm, globalObject, collatorPrototype);
79     IntlCollatorConstructor* collatorConstructor = IntlCollatorConstructor::create(vm, IntlCollatorConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()), collatorPrototype, collatorStructure);
80
81     collatorPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, collatorConstructor, DontEnum);
82
83     // Set up NumberFormat.
84     IntlNumberFormatPrototype* numberFormatPrototype = IntlNumberFormatPrototype::create(vm, globalObject, IntlNumberFormatPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
85     Structure* numberFormatStructure = IntlNumberFormat::createStructure(vm, globalObject, numberFormatPrototype);
86     IntlNumberFormatConstructor* numberFormatConstructor = IntlNumberFormatConstructor::create(vm, IntlNumberFormatConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()), numberFormatPrototype, numberFormatStructure);
87
88     numberFormatPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, numberFormatConstructor, DontEnum);
89
90     // Set up DateTimeFormat.
91     IntlDateTimeFormatPrototype* dateTimeFormatPrototype = IntlDateTimeFormatPrototype::create(vm, globalObject, IntlDateTimeFormatPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
92     Structure* dateTimeFormatStructure = IntlDateTimeFormat::createStructure(vm, globalObject, dateTimeFormatPrototype);
93     IntlDateTimeFormatConstructor* dateTimeFormatConstructor = IntlDateTimeFormatConstructor::create(vm, IntlDateTimeFormatConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()), dateTimeFormatPrototype, dateTimeFormatStructure);
94
95     dateTimeFormatPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, dateTimeFormatConstructor, DontEnum);
96
97     // 8.1 Properties of the Intl Object (ECMA-402 2.0)
98     putDirectWithoutTransition(vm, vm.propertyNames->Collator, collatorConstructor, DontEnum);
99     putDirectWithoutTransition(vm, vm.propertyNames->NumberFormat, numberFormatConstructor, DontEnum);
100     putDirectWithoutTransition(vm, vm.propertyNames->DateTimeFormat, dateTimeFormatConstructor, DontEnum);
101 }
102
103 Structure* IntlObject::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
104 {
105     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
106 }
107
108 static String getIntlStringOption(ExecState* exec, JSValue options, PropertyName property, const HashSet<String>& values, const char* notFound, String fallback)
109 {
110     // 9.2.9 GetOption (options, property, type, values, fallback)
111     // For type="string".
112
113     // 1. Let opts be ToObject(options).
114     JSObject* opts = options.toObject(exec);
115
116     // 2. ReturnIfAbrupt(opts).
117     if (exec->hadException())
118         return String();
119
120     // 3. Let value be Get(opts, property).
121     JSValue value = opts->get(exec, property);
122
123     // 4. ReturnIfAbrupt(value).
124     if (exec->hadException())
125         return String();
126
127     // 5. If value is not undefined, then
128     if (!value.isUndefined()) {
129         // a. Assert: type is "boolean" or "string".
130         // Function dedicated to "string".
131
132         // c. If type is "string", then
133         // i. Let value be ToString(value).
134         JSString* stringValue = value.toString(exec);
135
136         // ii. ReturnIfAbrupt(value).
137         if (exec->hadException())
138             return String();
139
140         // d. If values is not undefined, then
141         // i. If values does not contain an element equal to value, throw a RangeError exception.
142         if (!values.isEmpty() && !values.contains(stringValue->value(exec))) {
143             exec->vm().throwException(exec, createRangeError(exec, String(notFound)));
144             return String();
145         }
146
147         // e. Return value.
148         return stringValue->value(exec);
149     }
150
151     // 6. Else return fallback.
152     return fallback;
153 }
154
155 static String getPrivateUseLangTag(const Vector<String>& parts, size_t startIndex)
156 {
157     size_t numParts = parts.size();
158     size_t currentIndex = startIndex;
159
160     // Check for privateuse.
161     // privateuse = "x" 1*("-" (2*8alphanum))
162     StringBuilder privateuse;
163     while (currentIndex < numParts) {
164         const String& singleton = parts[currentIndex];
165         unsigned singletonLength = singleton.length();
166         bool isValid = (singletonLength == 1 && (singleton == "x" || singleton == "X"));
167         if (!isValid)
168             break;
169
170         if (currentIndex != startIndex)
171             privateuse.append('-');
172
173         ++currentIndex;
174         unsigned numExtParts = 0;
175         privateuse.append('x');
176         while (currentIndex < numParts) {
177             const String& extPart = parts[currentIndex];
178             unsigned extPartLength = extPart.length();
179
180             bool isValid = (extPartLength >= 2 && extPartLength <= 8 && extPart.isAllSpecialCharacters<isASCIIAlphanumeric>());
181             if (!isValid)
182                 break;
183
184             ++currentIndex;
185             ++numExtParts;
186             privateuse.append('-');
187             privateuse.append(extPart.convertToASCIILowercase());
188         }
189
190         // Requires at least one production.
191         if (!numExtParts)
192             return String();
193     }
194
195     // Leftovers makes it invalid.
196     if (currentIndex < numParts)
197         return String();
198
199     return privateuse.toString();
200 }
201
202 static String getCanonicalLangTag(const Vector<String>& parts)
203 {
204     ASSERT(!parts.isEmpty());
205
206     // Follows the grammar at https://www.rfc-editor.org/rfc/bcp/bcp47.txt
207     // langtag = language ["-" script] ["-" region] *("-" variant) *("-" extension) ["-" privateuse]
208
209     size_t numParts = parts.size();
210     // Check for language.
211     // language = 2*3ALPHA ["-" extlang] / 4ALPHA / 5*8ALPHA
212     size_t currentIndex = 0;
213     const String& language = parts[currentIndex];
214     unsigned languageLength = language.length();
215     bool canHaveExtlang = languageLength >= 2 && languageLength <= 3;
216     bool isValidLanguage = languageLength >= 2 && languageLength <= 8 && language.isAllSpecialCharacters<isASCIIAlpha>();
217     if (!isValidLanguage)
218         return String();
219
220     ++currentIndex;
221     StringBuilder canonical;
222     canonical.append(language.convertToASCIILowercase());
223
224     // Check for extlang.
225     // extlang = 3ALPHA *2("-" 3ALPHA)
226     if (canHaveExtlang) {
227         for (unsigned times = 0; times < 3 && currentIndex < numParts; ++times) {
228             const String& extlang = parts[currentIndex];
229             unsigned extlangLength = extlang.length();
230             if (extlangLength == 3 && extlang.isAllSpecialCharacters<isASCIIAlpha>()) {
231                 ++currentIndex;
232                 canonical.append('-');
233                 canonical.append(extlang.convertToASCIILowercase());
234             } else
235                 break;
236         }
237     }
238
239     // Check for script.
240     // script = 4ALPHA
241     if (currentIndex < numParts) {
242         const String& script = parts[currentIndex];
243         unsigned scriptLength = script.length();
244         if (scriptLength == 4 && script.isAllSpecialCharacters<isASCIIAlpha>()) {
245             ++currentIndex;
246             canonical.append('-');
247             canonical.append(toASCIIUpper(script[0]));
248             canonical.append(script.substring(1, 3).convertToASCIILowercase());
249         }
250     }
251
252     // Check for region.
253     // region = 2ALPHA / 3DIGIT
254     if (currentIndex < numParts) {
255         const String& region = parts[currentIndex];
256         unsigned regionLength = region.length();
257         bool isValidRegion = (
258             (regionLength == 2 && region.isAllSpecialCharacters<isASCIIAlpha>())
259             || (regionLength == 3 && region.isAllSpecialCharacters<isASCIIDigit>())
260         );
261         if (isValidRegion) {
262             ++currentIndex;
263             canonical.append('-');
264             canonical.append(region.upper());
265         }
266     }
267
268     // Check for variant.
269     // variant = 5*8alphanum / (DIGIT 3alphanum)
270     HashSet<String> subtags;
271     while (currentIndex < numParts) {
272         const String& variant = parts[currentIndex];
273         unsigned variantLength = variant.length();
274         bool isValidVariant = (
275             (variantLength >= 5 && variantLength <= 8 && variant.isAllSpecialCharacters<isASCIIAlphanumeric>())
276             || (variantLength == 4 && isASCIIDigit(variant[0]) && variant.substring(1, 3).isAllSpecialCharacters<isASCIIAlphanumeric>())
277         );
278         if (!isValidVariant)
279             break;
280
281         // Cannot include duplicate subtags (case insensitive).
282         String lowerVariant = variant.convertToASCIILowercase();
283         if (!subtags.add(lowerVariant).isNewEntry)
284             return String();
285
286         ++currentIndex;
287
288         // Reordering variant subtags is not required in the spec.
289         canonical.append('-');
290         canonical.append(lowerVariant);
291     }
292
293     // Check for extension.
294     // extension = singleton 1*("-" (2*8alphanum))
295     // singleton = alphanum except x or X
296     subtags.clear();
297     Vector<String> extensions;
298     while (currentIndex < numParts) {
299         const String& possibleSingleton = parts[currentIndex];
300         unsigned singletonLength = possibleSingleton.length();
301         bool isValidSingleton = (singletonLength == 1 && possibleSingleton != "x" && possibleSingleton != "X" && isASCIIAlphanumeric(possibleSingleton[0]));
302         if (!isValidSingleton)
303             break;
304
305         // Cannot include duplicate singleton (case insensitive).
306         String singleton = possibleSingleton.convertToASCIILowercase();
307         if (!subtags.add(singleton).isNewEntry)
308             return String();
309
310         ++currentIndex;
311         int numExtParts = 0;
312         StringBuilder extension;
313         extension.append(singleton);
314         while (currentIndex < numParts) {
315             const String& extPart = parts[currentIndex];
316             unsigned extPartLength = extPart.length();
317
318             bool isValid = (extPartLength >= 2 && extPartLength <= 8 && extPart.isAllSpecialCharacters<isASCIIAlphanumeric>());
319             if (!isValid)
320                 break;
321
322             ++currentIndex;
323             ++numExtParts;
324             extension.append('-');
325             extension.append(extPart.convertToASCIILowercase());
326         }
327
328         // Requires at least one production.
329         if (!numExtParts)
330             return String();
331
332         extensions.append(extension.toString());
333     }
334
335     // Add extensions to canonical sorted by singleton.
336     std::sort(
337         extensions.begin(),
338         extensions.end(),
339         [] (const String& a, const String& b) -> bool {
340             return a[0] < b[0];
341         }
342     );
343     size_t numExtenstions = extensions.size();
344     for (size_t i = 0; i < numExtenstions; ++i) {
345         canonical.append('-');
346         canonical.append(extensions[i]);
347     }
348
349     // Check for privateuse.
350     if (currentIndex < numParts) {
351         String privateuse = getPrivateUseLangTag(parts, currentIndex);
352         if (privateuse.isNull())
353             return String();
354         canonical.append('-');
355         canonical.append(privateuse);
356     }
357
358     // FIXME: Replace subtags with their preferred values.
359
360     return canonical.toString();
361 }
362
363 static String getGrandfatheredLangTag(const String& locale)
364 {
365     // grandfathered = irregular / regular
366     // FIXME: convert to a compile time hash table if this is causing performance issues.
367     HashMap<String, String> tagMap = {
368         // Irregular.
369         { ASCIILiteral("en-gb-oed"), ASCIILiteral("en-GB-oed") },
370         { ASCIILiteral("i-ami"), ASCIILiteral("ami") },
371         { ASCIILiteral("i-bnn"), ASCIILiteral("bnn") },
372         { ASCIILiteral("i-default"), ASCIILiteral("i-default") },
373         { ASCIILiteral("i-enochian"), ASCIILiteral("i-enochian") },
374         { ASCIILiteral("i-hak"), ASCIILiteral("hak") },
375         { ASCIILiteral("i-klingon"), ASCIILiteral("tlh") },
376         { ASCIILiteral("i-lux"), ASCIILiteral("lb") },
377         { ASCIILiteral("i-mingo"), ASCIILiteral("i-mingo") },
378         { ASCIILiteral("i-navajo"), ASCIILiteral("nv") },
379         { ASCIILiteral("i-pwn"), ASCIILiteral("pwn") },
380         { ASCIILiteral("i-tao"), ASCIILiteral("tao") },
381         { ASCIILiteral("i-tay"), ASCIILiteral("tay") },
382         { ASCIILiteral("i-tsu"), ASCIILiteral("tsu") },
383         { ASCIILiteral("sgn-be-fr"), ASCIILiteral("sfb") },
384         { ASCIILiteral("sgn-be-nl"), ASCIILiteral("vgt") },
385         { ASCIILiteral("sgn-ch-de"), ASCIILiteral("sgg") },
386         // Regular.
387         { ASCIILiteral("art-lojban"), ASCIILiteral("jbo") },
388         { ASCIILiteral("cel-gaulish"), ASCIILiteral("cel-gaulish") },
389         { ASCIILiteral("no-bok"), ASCIILiteral("nb") },
390         { ASCIILiteral("no-nyn"), ASCIILiteral("nn") },
391         { ASCIILiteral("zh-guoyu"), ASCIILiteral("cmn") },
392         { ASCIILiteral("zh-hakka"), ASCIILiteral("hak") },
393         { ASCIILiteral("zh-min"), ASCIILiteral("zh-min") },
394         { ASCIILiteral("zh-min-nan"), ASCIILiteral("nan") },
395         { ASCIILiteral("zh-xiang"), ASCIILiteral("hsn") }
396     };
397
398     return tagMap.get(locale.convertToASCIILowercase());
399 }
400
401 static String canonicalizeLanguageTag(const String& locale)
402 {
403     // 6.2.2 IsStructurallyValidLanguageTag (locale)
404     // 6.2.3 CanonicalizeLanguageTag (locale)
405     // These are done one after another in CanonicalizeLocaleList, so they are combined here to reduce duplication.
406     // https://www.rfc-editor.org/rfc/bcp/bcp47.txt
407
408     // Language-Tag = langtag / privateuse / grandfathered
409     String grandfather = getGrandfatheredLangTag(locale);
410     if (!grandfather.isNull())
411         return grandfather;
412
413     // FIXME: Replace redundant tags [RFC4647].
414
415     Vector<String> parts;
416     locale.split('-', true, parts);
417     if (!parts.isEmpty()) {
418         String langtag = getCanonicalLangTag(parts);
419         if (!langtag.isNull())
420             return langtag;
421
422         String privateuse = getPrivateUseLangTag(parts, 0);
423         if (!privateuse.isNull())
424             return privateuse;
425     }
426
427     return String();
428 }
429
430 JSArray* canonicalizeLocaleList(ExecState* exec, JSValue locales)
431 {
432     // 9.2.1 CanonicalizeLocaleList (locales)
433     VM& vm = exec->vm();
434     JSGlobalObject* globalObject = exec->callee()->globalObject();
435     JSArray* seen = JSArray::tryCreateUninitialized(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
436     if (!seen) {
437         throwOutOfMemoryError(exec);
438         return nullptr;
439     }
440
441     // 1. If locales is undefined, then a. Return a new empty List.
442     if (locales.isUndefined())
443         return seen;
444
445     // 2. Let seen be an empty List.
446     // Done before to also return in step 1, if needed.
447
448     // 3. If Type(locales) is String, then
449     JSObject* localesObject;
450     if (locales.isString()) {
451         //  a. Let aLocales be CreateArrayFromList(«locales»).
452         JSArray* localesArray = JSArray::tryCreateUninitialized(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 1);
453         localesArray->initializeIndex(vm, 0, locales);
454         // 4. Let O be ToObject(aLocales).
455         localesObject = localesArray;
456     } else {
457         // 4. Let O be ToObject(aLocales).
458         localesObject = locales.toObject(exec);
459     }
460
461     // 5. ReturnIfAbrupt(O).
462     if (exec->hadException())
463         return nullptr;
464
465     // 6. Let len be ToLength(Get(O, "length")).
466     JSValue lengthProperty = localesObject->get(exec, vm.propertyNames->length);
467     if (exec->hadException())
468         return nullptr;
469
470     double length = lengthProperty.toLength(exec);
471     if (exec->hadException())
472         return nullptr;
473
474     // Keep track of locales that have been added to the list.
475     HashSet<String> seenSet;
476
477     // 7. Let k be 0.
478     // 8. Repeat, while k < len
479     for (double k = 0; k < length; ++k) {
480         // a. Let Pk be ToString(k).
481         // Not needed because hasProperty and get take an int for numeric key.
482
483         // b. Let kPresent be HasProperty(O, Pk).
484         bool kPresent = localesObject->hasProperty(exec, k);
485
486         // c. ReturnIfAbrupt(kPresent).
487         if (exec->hadException())
488             return nullptr;
489
490         // d. If kPresent is true, then
491         if (kPresent) {
492             // i. Let kValue be Get(O, Pk).
493             JSValue kValue = localesObject->get(exec, k);
494
495             // ii. ReturnIfAbrupt(kValue).
496             if (exec->hadException())
497                 return nullptr;
498
499             // iii. If Type(kValue) is not String or Object, throw a TypeError exception.
500             if (!kValue.isString() && !kValue.isObject()) {
501                 throwTypeError(exec, ASCIILiteral("locale value must be a string or object"));
502                 return nullptr;
503             }
504
505             // iv. Let tag be ToString(kValue).
506             JSString* tag = kValue.toString(exec);
507
508             // v. ReturnIfAbrupt(tag).
509             if (exec->hadException())
510                 return nullptr;
511
512             // vi. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
513             // vii. Let canonicalizedTag be CanonicalizeLanguageTag(tag).
514             String canonicalizedTag = canonicalizeLanguageTag(tag->value(exec));
515             if (canonicalizedTag.isNull()) {
516                 exec->vm().throwException(exec, createRangeError(exec, String::format("invalid language tag: %s", tag->value(exec).utf8().data())));
517                 return nullptr;
518             }
519
520             // viii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
521             if (seenSet.add(canonicalizedTag).isNewEntry)
522                 seen->push(exec, jsString(exec, canonicalizedTag));
523         }
524         // e. Increase k by 1.
525     }
526
527     return seen;
528 }
529
530 static String bestAvailableLocale(const HashSet<String>& availableLocales, const String& locale)
531 {
532     // 9.2.2 BestAvailableLocale (availableLocales, locale)
533     // 1. Let candidate be locale.
534     String candidate = locale;
535
536     // 2. Repeat
537     while (!candidate.isEmpty()) {
538         // a. If availableLocales contains an element equal to candidate, then return candidate.
539         if (availableLocales.contains(candidate))
540             return candidate;
541
542         // b. Let pos be the character index of the last occurrence of "-" (U+002D) within candidate. If that character does not occur, return undefined.
543         size_t pos = candidate.reverseFind('-');
544         if (pos == notFound)
545             return String();
546
547         // c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, then decrease pos by 2.
548         if (pos >= 2 && candidate[pos - 2] == '-')
549             pos -= 2;
550
551         // d. Let candidate be the substring of candidate from position 0, inclusive, to position pos, exclusive.
552         candidate = candidate.substring(0, pos);
553     }
554
555     return String();
556 }
557
558 static JSArray* lookupSupportedLocales(ExecState* exec, const HashSet<String>& availableLocales, JSArray* requestedLocales)
559 {
560     // 9.2.6 LookupSupportedLocales (availableLocales, requestedLocales)
561
562     // 1. Let rLocales be CreateArrayFromList(requestedLocales).
563     // Already an array.
564
565     // 2. Let len be ToLength(Get(rLocales, "length")).
566     unsigned len = requestedLocales->length();
567
568     // 3. Let subset be an empty List.
569     VM& vm = exec->vm();
570     JSGlobalObject* globalObject = exec->callee()->globalObject();
571     JSArray* subset = JSArray::tryCreateUninitialized(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithUndecided), 0);
572     if (!subset) {
573         throwOutOfMemoryError(exec);
574         return nullptr;
575     }
576
577     // 4. Let k be 0.
578     // 5. Repeat while k < len
579     for (unsigned k = 0; k < len; ++k) {
580         // a. Let Pk be ToString(k).
581         // b. Let locale be Get(rLocales, Pk).
582         JSValue locale = requestedLocales->get(exec, k);
583
584         // c. ReturnIfAbrupt(locale).
585         if (exec->hadException())
586             return nullptr;
587
588         // d. Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences removed.
589         JSString* jsLocale = locale.toString(exec);
590         if (exec->hadException())
591             return nullptr;
592
593         String sLocale = jsLocale->value(exec);
594         Vector<String> parts;
595         sLocale.split('-', parts);
596         StringBuilder builder;
597         size_t partsSize = parts.size();
598         if (partsSize > 0)
599             builder.append(parts[0]);
600         for (size_t p = 1; p < partsSize; ++p) {
601             if (parts[p] == "u") {
602                 // Skip the u- and anything that follows until another singleton.
603                 // While the next part is part of the unicode extension, skip it.
604                 while (p + 1 < partsSize && parts[p + 1].length() > 1)
605                     ++p;
606             } else {
607                 builder.append('-');
608                 builder.append(parts[p]);
609             }
610         }
611         String noExtensionsLocale = builder.toString();
612
613         // e. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
614         String availableLocale = bestAvailableLocale(availableLocales, noExtensionsLocale);
615
616         // f. If availableLocale is not undefined, then append locale to the end of subset.
617         if (!availableLocale.isNull())
618             subset->push(exec, locale);
619
620         // g. Increment k by 1.
621     }
622
623     // 6. Return subset.
624     return subset;
625 }
626
627 static JSArray* bestFitSupportedLocales(ExecState* exec, const HashSet<String>& availableLocales, JSArray* requestedLocales)
628 {
629     // 9.2.7 BestFitSupportedLocales (availableLocales, requestedLocales)
630     // FIXME: Implement something better than lookup.
631     return lookupSupportedLocales(exec, availableLocales, requestedLocales);
632 }
633
634 JSValue supportedLocales(ExecState* exec, const HashSet<String>& availableLocales, JSArray* requestedLocales, JSValue options)
635 {
636     // 9.2.8 SupportedLocales (availableLocales, requestedLocales, options)
637     VM& vm = exec->vm();
638     String matcher;
639
640     // 1. If options is not undefined, then
641     if (!options.isUndefined()) {
642         // a. Let matcher be GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
643         const HashSet<String> matchers({ ASCIILiteral("lookup"), ASCIILiteral("best fit") });
644         matcher = getIntlStringOption(exec, options, vm.propertyNames->localeMatcher, matchers, "localeMatcher must be either \"lookup\" or \"best fit\"", ASCIILiteral("best fit"));
645         // b. ReturnIfAbrupt(matcher).
646         if (exec->hadException())
647             return jsUndefined();
648     } else {
649         // 2. Else, let matcher be "best fit".
650         matcher = ASCIILiteral("best fit");
651     }
652
653     JSArray* supportedLocales;
654     // 3. If matcher is "best fit",
655     if (matcher == "best fit") {
656         // a. Let MatcherOperation be the abstract operation BestFitSupportedLocales.
657         // 5. Let supportedLocales be MatcherOperation(availableLocales, requestedLocales).
658         supportedLocales = bestFitSupportedLocales(exec, availableLocales, requestedLocales);
659     } else {
660         // 4. Else
661         // a. Let MatcherOperation be the abstract operation LookupSupportedLocales.
662         // 5. Let supportedLocales be MatcherOperation(availableLocales, requestedLocales).
663         supportedLocales = lookupSupportedLocales(exec, availableLocales, requestedLocales);
664     }
665
666     if (exec->hadException())
667         return jsUndefined();
668
669     // 6. Let subset be CreateArrayFromList(supportedLocales).
670     // Already an array.
671
672     // 7. Let keys be subset.[[OwnPropertyKeys]]().
673     PropertyNameArray keys(exec, PropertyNameMode::Strings);
674     supportedLocales->getOwnPropertyNames(supportedLocales, exec, keys, EnumerationMode());
675
676     PropertyDescriptor desc;
677     desc.setConfigurable(false);
678     desc.setWritable(false);
679
680     // 8. Repeat for each element P of keys in List order,
681     size_t len = keys.size();
682     for (size_t i = 0; i < len; ++i) {
683         // a. Let desc be PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }.
684         // Created above for reuse.
685
686         // b. Let status be DefinePropertyOrThrow(subset, P, desc).
687         supportedLocales->defineOwnProperty(supportedLocales, exec, keys[i], desc, true);
688
689         // c. Assert: status is not abrupt completion.
690         if (exec->hadException())
691             return jsUndefined();
692     }
693
694     // 9. Return subset.
695     return supportedLocales;
696 }
697
698 } // namespace JSC
699
700 #endif // ENABLE(INTL)