941711bbd0176d72dd71b8a520a3843e01358a87
[WebKit-https.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 Vector<String> canonicalizeLocaleList(ExecState* exec, JSValue locales)
431 {
432     // 9.2.1 CanonicalizeLocaleList (locales)
433     VM& vm = exec->vm();
434     JSGlobalObject* globalObject = exec->callee()->globalObject();
435     Vector<String> seen;
436
437     // 1. If locales is undefined, then a. Return a new empty List.
438     if (locales.isUndefined())
439         return seen;
440
441     // 2. Let seen be an empty List.
442     // Done before to also return in step 1, if needed.
443
444     // 3. If Type(locales) is String, then
445     JSObject* localesObject;
446     if (locales.isString()) {
447         //  a. Let aLocales be CreateArrayFromList(«locales»).
448         JSArray* localesArray = JSArray::tryCreateUninitialized(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 1);
449         localesArray->initializeIndex(vm, 0, locales);
450         // 4. Let O be ToObject(aLocales).
451         localesObject = localesArray;
452     } else {
453         // 4. Let O be ToObject(aLocales).
454         localesObject = locales.toObject(exec);
455     }
456
457     // 5. ReturnIfAbrupt(O).
458     if (exec->hadException())
459         return Vector<String>();
460
461     // 6. Let len be ToLength(Get(O, "length")).
462     JSValue lengthProperty = localesObject->get(exec, vm.propertyNames->length);
463     if (exec->hadException())
464         return Vector<String>();
465
466     double length = lengthProperty.toLength(exec);
467     if (exec->hadException())
468         return Vector<String>();
469
470     // Keep track of locales that have been added to the list.
471     HashSet<String> seenSet;
472
473     // 7. Let k be 0.
474     // 8. Repeat, while k < len
475     for (double k = 0; k < length; ++k) {
476         // a. Let Pk be ToString(k).
477         // Not needed because hasProperty and get take an int for numeric key.
478
479         // b. Let kPresent be HasProperty(O, Pk).
480         bool kPresent = localesObject->hasProperty(exec, k);
481
482         // c. ReturnIfAbrupt(kPresent).
483         if (exec->hadException())
484             return Vector<String>();
485
486         // d. If kPresent is true, then
487         if (kPresent) {
488             // i. Let kValue be Get(O, Pk).
489             JSValue kValue = localesObject->get(exec, k);
490
491             // ii. ReturnIfAbrupt(kValue).
492             if (exec->hadException())
493                 return Vector<String>();
494
495             // iii. If Type(kValue) is not String or Object, throw a TypeError exception.
496             if (!kValue.isString() && !kValue.isObject()) {
497                 throwTypeError(exec, ASCIILiteral("locale value must be a string or object"));
498                 return Vector<String>();
499             }
500
501             // iv. Let tag be ToString(kValue).
502             JSString* tag = kValue.toString(exec);
503
504             // v. ReturnIfAbrupt(tag).
505             if (exec->hadException())
506                 return Vector<String>();
507
508             // vi. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
509             // vii. Let canonicalizedTag be CanonicalizeLanguageTag(tag).
510             String canonicalizedTag = canonicalizeLanguageTag(tag->value(exec));
511             if (canonicalizedTag.isNull()) {
512                 exec->vm().throwException(exec, createRangeError(exec, String::format("invalid language tag: %s", tag->value(exec).utf8().data())));
513                 return Vector<String>();
514             }
515
516             // viii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
517             if (seenSet.add(canonicalizedTag).isNewEntry)
518                 seen.append(canonicalizedTag);
519         }
520         // e. Increase k by 1.
521     }
522
523     return seen;
524 }
525
526 static String bestAvailableLocale(const HashSet<String>& availableLocales, const String& locale)
527 {
528     // 9.2.2 BestAvailableLocale (availableLocales, locale)
529     // 1. Let candidate be locale.
530     String candidate = locale;
531
532     // 2. Repeat
533     while (!candidate.isEmpty()) {
534         // a. If availableLocales contains an element equal to candidate, then return candidate.
535         if (availableLocales.contains(candidate))
536             return candidate;
537
538         // b. Let pos be the character index of the last occurrence of "-" (U+002D) within candidate. If that character does not occur, return undefined.
539         size_t pos = candidate.reverseFind('-');
540         if (pos == notFound)
541             return String();
542
543         // c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, then decrease pos by 2.
544         if (pos >= 2 && candidate[pos - 2] == '-')
545             pos -= 2;
546
547         // d. Let candidate be the substring of candidate from position 0, inclusive, to position pos, exclusive.
548         candidate = candidate.substring(0, pos);
549     }
550
551     return String();
552 }
553
554 static JSArray* lookupSupportedLocales(ExecState* exec, const HashSet<String>& availableLocales, const Vector<String>& requestedLocales)
555 {
556     // 9.2.6 LookupSupportedLocales (availableLocales, requestedLocales)
557
558     // 1. Let rLocales be CreateArrayFromList(requestedLocales).
559     // Already an array.
560
561     // 2. Let len be ToLength(Get(rLocales, "length")).
562     size_t len = requestedLocales.size();
563
564     // 3. Let subset be an empty List.
565     VM& vm = exec->vm();
566     JSGlobalObject* globalObject = exec->callee()->globalObject();
567     JSArray* subset = JSArray::tryCreateUninitialized(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithUndecided), 0);
568     if (!subset) {
569         throwOutOfMemoryError(exec);
570         return nullptr;
571     }
572
573     // 4. Let k be 0.
574     // 5. Repeat while k < len
575     for (size_t k = 0; k < len; ++k) {
576         // a. Let Pk be ToString(k).
577         // b. Let locale be Get(rLocales, Pk).
578         // c. ReturnIfAbrupt(locale).
579         String locale = requestedLocales[k];
580
581         // d. Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences removed.
582         Vector<String> parts;
583         locale.split('-', parts);
584         StringBuilder builder;
585         size_t partsSize = parts.size();
586         if (partsSize > 0)
587             builder.append(parts[0]);
588         for (size_t p = 1; p < partsSize; ++p) {
589             if (parts[p] == "u") {
590                 // Skip the u- and anything that follows until another singleton.
591                 // While the next part is part of the unicode extension, skip it.
592                 while (p + 1 < partsSize && parts[p + 1].length() > 1)
593                     ++p;
594             } else {
595                 builder.append('-');
596                 builder.append(parts[p]);
597             }
598         }
599         String noExtensionsLocale = builder.toString();
600
601         // e. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
602         String availableLocale = bestAvailableLocale(availableLocales, noExtensionsLocale);
603
604         // f. If availableLocale is not undefined, then append locale to the end of subset.
605         if (!availableLocale.isNull())
606             subset->push(exec, jsString(exec, locale));
607
608         // g. Increment k by 1.
609     }
610
611     // 6. Return subset.
612     return subset;
613 }
614
615 static JSArray* bestFitSupportedLocales(ExecState* exec, const HashSet<String>& availableLocales, const Vector<String>& requestedLocales)
616 {
617     // 9.2.7 BestFitSupportedLocales (availableLocales, requestedLocales)
618     // FIXME: Implement something better than lookup.
619     return lookupSupportedLocales(exec, availableLocales, requestedLocales);
620 }
621
622 JSValue supportedLocales(ExecState* exec, const HashSet<String>& availableLocales, const Vector<String>& requestedLocales, JSValue options)
623 {
624     // 9.2.8 SupportedLocales (availableLocales, requestedLocales, options)
625     VM& vm = exec->vm();
626     String matcher;
627
628     // 1. If options is not undefined, then
629     if (!options.isUndefined()) {
630         // a. Let matcher be GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
631         const HashSet<String> matchers({ ASCIILiteral("lookup"), ASCIILiteral("best fit") });
632         matcher = getIntlStringOption(exec, options, vm.propertyNames->localeMatcher, matchers, "localeMatcher must be either \"lookup\" or \"best fit\"", ASCIILiteral("best fit"));
633         // b. ReturnIfAbrupt(matcher).
634         if (exec->hadException())
635             return jsUndefined();
636     } else {
637         // 2. Else, let matcher be "best fit".
638         matcher = ASCIILiteral("best fit");
639     }
640
641     JSArray* supportedLocales;
642     // 3. If matcher is "best fit",
643     if (matcher == "best fit") {
644         // a. Let MatcherOperation be the abstract operation BestFitSupportedLocales.
645         // 5. Let supportedLocales be MatcherOperation(availableLocales, requestedLocales).
646         supportedLocales = bestFitSupportedLocales(exec, availableLocales, requestedLocales);
647     } else {
648         // 4. Else
649         // a. Let MatcherOperation be the abstract operation LookupSupportedLocales.
650         // 5. Let supportedLocales be MatcherOperation(availableLocales, requestedLocales).
651         supportedLocales = lookupSupportedLocales(exec, availableLocales, requestedLocales);
652     }
653
654     if (exec->hadException())
655         return jsUndefined();
656
657     // 6. Let subset be CreateArrayFromList(supportedLocales).
658     // Already an array.
659
660     // 7. Let keys be subset.[[OwnPropertyKeys]]().
661     PropertyNameArray keys(exec, PropertyNameMode::Strings);
662     supportedLocales->getOwnPropertyNames(supportedLocales, exec, keys, EnumerationMode());
663
664     PropertyDescriptor desc;
665     desc.setConfigurable(false);
666     desc.setWritable(false);
667
668     // 8. Repeat for each element P of keys in List order,
669     size_t len = keys.size();
670     for (size_t i = 0; i < len; ++i) {
671         // a. Let desc be PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }.
672         // Created above for reuse.
673
674         // b. Let status be DefinePropertyOrThrow(subset, P, desc).
675         supportedLocales->defineOwnProperty(supportedLocales, exec, keys[i], desc, true);
676
677         // c. Assert: status is not abrupt completion.
678         if (exec->hadException())
679             return jsUndefined();
680     }
681
682     // 9. Return subset.
683     return supportedLocales;
684 }
685
686 } // namespace JSC
687
688 #endif // ENABLE(INTL)