author | André Bargull <andre.bargull@gmail.com> |
Fri, 11 Oct 2019 19:24:41 +0000 | |
changeset 497315 | 4eca32dbe70f9c1d7803c2cb51a266842b4e77ab |
parent 497314 | 5dad4aa34c7fede26690a0d4bbbd9b8614b09dfa |
child 497316 | 64993f76caaf72ed630c44da86250d06dda52ccd |
push id | 36682 |
push user | ncsoregi@mozilla.com |
push date | Sat, 12 Oct 2019 09:52:03 +0000 |
treeherder | mozilla-central@06ea2371f897 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jwalden |
bugs | 1570370 |
milestone | 71.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/public/Class.h +++ b/js/public/Class.h @@ -745,17 +745,17 @@ static const uint32_t JSCLASS_FOREGROUND // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was // previously allowed, but is now an ES5 violation and thus unsupported. // // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at // the beginning of every global object's slots for use by the // application. static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5; static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT = - JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 40; + JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 41; #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | \ JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0) #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp) \ (((clasp)->flags & JSCLASS_IS_GLOBAL) && \ JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
--- a/js/src/builtin/intl/LanguageTag.cpp +++ b/js/src/builtin/intl/LanguageTag.cpp @@ -51,43 +51,58 @@ bool IsStructurallyValidLanguageTag( // unicode_language_subtag = alpha{2,3} | alpha{5,8}; size_t length = language.length(); const CharT* str = language.begin().get(); return ((2 <= length && length <= 3) || (5 <= length && length <= 8)) && std::all_of(str, str + length, mozilla::IsAsciiLowercaseAlpha<CharT>); } +template bool IsStructurallyValidLanguageTag( + const mozilla::Range<const Latin1Char>& language); +template bool IsStructurallyValidLanguageTag( + const mozilla::Range<const char16_t>& language); + template <typename CharT> bool IsStructurallyValidScriptTag(const mozilla::Range<const CharT>& script) { // Tell the analysis the |std::all_of| function can't GC. JS::AutoSuppressGCAnalysis nogc; // unicode_script_subtag = alpha{4} ; size_t length = script.length(); const CharT* str = script.begin().get(); return length == 4 && mozilla::IsAsciiUppercaseAlpha<CharT>(str[0]) && std::all_of(str + 1, str + length, mozilla::IsAsciiLowercaseAlpha<CharT>); } +template bool IsStructurallyValidScriptTag( + const mozilla::Range<const Latin1Char>& script); +template bool IsStructurallyValidScriptTag( + const mozilla::Range<const char16_t>& script); + template <typename CharT> bool IsStructurallyValidRegionTag(const mozilla::Range<const CharT>& region) { // Tell the analysis the |std::all_of| function can't GC. JS::AutoSuppressGCAnalysis nogc; // unicode_region_subtag = (alpha{2} | digit{3}) ; size_t length = region.length(); const CharT* str = region.begin().get(); return (length == 2 && std::all_of(str, str + length, mozilla::IsAsciiUppercaseAlpha<CharT>)) || (length == 3 && std::all_of(str, str + length, mozilla::IsAsciiDigit<CharT>)); } +template bool IsStructurallyValidRegionTag( + const mozilla::Range<const Latin1Char>& region); +template bool IsStructurallyValidRegionTag( + const mozilla::Range<const char16_t>& region); + bool IsStructurallyValidVariantTag(const ConstCharRange& variant) { // unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3}) ; auto isAsciiLowercaseAlphanumeric = [](char c) { return mozilla::IsAsciiLowercaseAlpha(c) || mozilla::IsAsciiDigit(c); }; size_t length = variant.length(); const char* str = variant.begin().get(); return ((5 <= length && length <= 8) || @@ -1491,10 +1506,92 @@ bool LanguageTagParser::canParseUnicodeE while (ts.isUnicodeExtensionType(tok)) { tok = ts.nextToken(); } // Return true if the complete input was successfully parsed. return tok.isNone(); } +bool ParseStandaloneLanguagTag(HandleLinearString str, LanguageSubtag& result) { + auto isLanguage = [](const auto* language, size_t length) { + // Tell the analysis the |std::all_of| function can't GC. + JS::AutoSuppressGCAnalysis nogc; + + using T = std::remove_pointer_t<decltype(language)>; + return length >= 2 && length != 4 && length <= 8 && + std::all_of(language, language + length, mozilla::IsAsciiAlpha<T>); + }; + + JS::AutoCheckCannotGC nogc; + if (str->hasLatin1Chars()) { + if (!isLanguage(str->latin1Chars(nogc), str->length())) { + return false; + } + result.set(str->latin1Range(nogc)); + } else { + if (!isLanguage(str->twoByteChars(nogc), str->length())) { + return false; + } + result.set(str->twoByteRange(nogc)); + } + result.toLowerCase(); + return true; +} + +bool ParseStandaloneScriptTag(HandleLinearString str, ScriptSubtag& result) { + auto isScript = [](const auto* script, size_t length) { + // Tell the analysis the |std::all_of| function can't GC. + JS::AutoSuppressGCAnalysis nogc; + + using T = std::remove_pointer_t<decltype(script)>; + return length == ScriptLength && + std::all_of(script, script + ScriptLength, mozilla::IsAsciiAlpha<T>); + }; + + JS::AutoCheckCannotGC nogc; + if (str->hasLatin1Chars()) { + if (!isScript(str->latin1Chars(nogc), str->length())) { + return false; + } + result.set(str->latin1Range(nogc)); + } else { + if (!isScript(str->twoByteChars(nogc), str->length())) { + return false; + } + result.set(str->twoByteRange(nogc)); + } + result.toTitleCase(); + return true; +} + +bool ParseStandaloneRegionTag(HandleLinearString str, RegionSubtag& result) { + auto isRegion = [](const auto* region, size_t length) { + // Tell the analysis the |std::all_of| function can't GC. + JS::AutoSuppressGCAnalysis nogc; + + using T = std::remove_pointer_t<decltype(region)>; + return (length == AlphaRegionLength && + std::all_of(region, region + AlphaRegionLength, + mozilla::IsAsciiAlpha<T>)) || + (length == DigitRegionLength && + std::all_of(region, region + DigitRegionLength, + mozilla::IsAsciiDigit<T>)); + }; + + JS::AutoCheckCannotGC nogc; + if (str->hasLatin1Chars()) { + if (!isRegion(str->latin1Chars(nogc), str->length())) { + return false; + } + result.set(str->latin1Range(nogc)); + } else { + if (!isRegion(str->twoByteChars(nogc), str->length())) { + return false; + } + result.set(str->twoByteRange(nogc)); + } + result.toUpperCase(); + return true; +} + } // namespace intl } // namespace js
--- a/js/src/builtin/intl/LanguageTag.h +++ b/js/src/builtin/intl/LanguageTag.h @@ -19,16 +19,17 @@ #include <stddef.h> #include <stdint.h> #include <string.h> #include <utility> #include "js/AllocPolicy.h" #include "js/GCAPI.h" #include "js/Result.h" +#include "js/RootingAPI.h" #include "js/Utility.h" #include "js/Vector.h" struct JSContext; class JSLinearString; class JSString; namespace js { @@ -677,13 +678,37 @@ class MOZ_STACK_CLASS LanguageTagParser static bool canParseUnicodeExtension(mozilla::Range<const char> extension); // Return true iff |unicodeType| can be parsed as a Unicode extension type. static bool canParseUnicodeExtensionType(JSLinearString* unicodeType); }; MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(LanguageTagParser::TokenKind) +/** + * Parse a string as a standalone |language| tag. If |str| is a standalone + * language tag, store it in case-normalized form in |result| and return true. + * Otherwise return false. + */ +MOZ_MUST_USE bool ParseStandaloneLanguagTag(JS::Handle<JSLinearString*> str, + LanguageSubtag& result); + +/** + * Parse a string as a standalone |script| tag. If |str| is a standalone script + * tag, store it in case-normalized form in |result| and return true. Otherwise + * return false. + */ +MOZ_MUST_USE bool ParseStandaloneScriptTag(JS::Handle<JSLinearString*> str, + ScriptSubtag& result); + +/** + * Parse a string as a standalone |region| tag. If |str| is a standalone region + * tag, store it in case-normalized form in |result| and return true. Otherwise + * return false. + */ +MOZ_MUST_USE bool ParseStandaloneRegionTag(JS::Handle<JSLinearString*> str, + RegionSubtag& result); + } // namespace intl } // namespace js #endif /* builtin_intl_LanguageTag_h */
--- a/js/src/builtin/intl/Locale.cpp +++ b/js/src/builtin/intl/Locale.cpp @@ -166,16 +166,35 @@ JSObject* js::CreateLocalePrototype(JSCo } JSObject* localeProto = CreateLocalePrototype(cx, intl, global); if (!localeProto) { return false; } global->setReservedSlot(LOCALE_PROTO, ObjectValue(*localeProto)); + + { + const Value& proto = global->getReservedSlot(NATIVE_LOCALE_PROTO); + if (!proto.isUndefined()) { + MOZ_ASSERT(proto.isObject()); + JS_ReportErrorASCII( + cx, + "the Locale constructor can't be added multiple times in the" + "same global"); + return false; + } + } + + localeProto = CreateNativeLocalePrototype(cx, intl, global); + if (!localeProto) { + return false; + } + + global->setReservedSlot(NATIVE_LOCALE_PROTO, ObjectValue(*localeProto)); return true; } bool js::AddLocaleConstructor(JSContext* cx, JS::Handle<JSObject*> intl) { return GlobalObject::addLocaleConstructor(cx, intl); } bool js::intl_CreateUninitializedLocale(JSContext* cx, unsigned argc,
--- a/js/src/builtin/intl/Locale.h +++ b/js/src/builtin/intl/Locale.h @@ -1,10 +1,10 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sts=4 et sw=4 tw=99: +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef builtin_intl_Locale_h #define builtin_intl_Locale_h #include <stdint.h> @@ -28,16 +28,48 @@ class LocaleObject : public NativeObject "INTERNALS_SLOT must match self-hosting define for internals " "object slot"); }; extern JSObject* CreateLocalePrototype(JSContext* cx, JS::Handle<JSObject*> Intl, JS::Handle<GlobalObject*> global); +class NativeLocaleObject : public NativeObject { + public: + static const JSClass class_; + + static constexpr uint32_t LANGUAGE_TAG_SLOT = 0; + static constexpr uint32_t BASENAME_SLOT = 1; + static constexpr uint32_t UNICODE_EXTENSION_SLOT = 2; + static constexpr uint32_t SLOT_COUNT = 3; + + /** + * Returns the complete language tag, including any extensions and privateuse + * subtags. + */ + JSString* languageTag() const { + return getFixedSlot(LANGUAGE_TAG_SLOT).toString(); + } + + /** + * Returns the basename subtags, i.e. excluding any extensions and privateuse + * subtags. + */ + JSString* baseName() const { return getFixedSlot(BASENAME_SLOT).toString(); } + + const Value& unicodeExtension() const { + return getFixedSlot(UNICODE_EXTENSION_SLOT); + } +}; + +extern JSObject* CreateNativeLocalePrototype(JSContext* cx, + JS::Handle<JSObject*> Intl, + JS::Handle<GlobalObject*> global); + /** * Creates an uninitialized Intl.Locale object. */ extern MOZ_MUST_USE bool intl_CreateUninitializedLocale(JSContext* cx, unsigned argc, Value* vp); /**
new file mode 100644 --- /dev/null +++ b/js/src/builtin/intl/NativeLocale.cpp @@ -0,0 +1,1261 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Intl.Locale implementation. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Casting.h" +#include "mozilla/Maybe.h" +#include "mozilla/Range.h" +#include "mozilla/TextUtils.h" + +#include <algorithm> +#include <iterator> +#include <string> +#include <string.h> +#include <utility> + +#include "jsapi.h" +#include "jsfriendapi.h" + +#include "builtin/intl/CommonFunctions.h" +#include "builtin/intl/LanguageTag.h" +#include "builtin/intl/Locale.h" +#include "gc/Rooting.h" +#include "js/Conversions.h" +#include "js/TypeDecls.h" +#include "js/Wrapper.h" +#include "util/StringBuffer.h" +#include "vm/GlobalObject.h" +#include "vm/JSContext.h" +#include "vm/StringType.h" + +#include "vm/JSObject-inl.h" +#include "vm/NativeObject-inl.h" + +using namespace js; +using namespace js::intl::LanguageTagLimits; + +using intl::LanguageTag; +using intl::LanguageTagParser; + +const JSClass NativeLocaleObject::class_ = { + js_Object_str, + JSCLASS_HAS_RESERVED_SLOTS(NativeLocaleObject::SLOT_COUNT), +}; + +static inline bool IsLocale(HandleValue v) { + return v.isObject() && v.toObject().is<NativeLocaleObject>(); +} + +// Return the length of the base-name subtags. +static size_t BaseNameLength(const LanguageTag& tag) { + size_t baseNameLength = tag.language().length(); + if (tag.script().length() > 0) { + baseNameLength += 1 + tag.script().length(); + } + if (tag.region().length() > 0) { + baseNameLength += 1 + tag.region().length(); + } + for (const auto& variant : tag.variants()) { + baseNameLength += 1 + strlen(variant.get()); + } + return baseNameLength; +} + +struct IndexAndLength { + size_t index; + size_t length; + + IndexAndLength(size_t index, size_t length) : index(index), length(length){}; + + template <typename T> + mozilla::Range<const T> rangeOf(const T* ptr) const { + return {ptr + index, length}; + } +}; + +// Compute the Unicode extension's index and length in the extension subtag. +static mozilla::Maybe<IndexAndLength> UnicodeExtensionPosition( + const LanguageTag& tag) { + size_t index = 0; + for (const auto& extension : tag.extensions()) { + size_t extensionLength = strlen(extension.get()); + if (extension[0] == 'u') { + return mozilla::Some(IndexAndLength{index, extensionLength}); + } + + // Add +1 to skip over the preceding separator. + index += 1 + extensionLength; + } + return mozilla::Nothing(); +} + +static NativeLocaleObject* CreateLocaleNativeObject(JSContext* cx, + HandleObject prototype, + const LanguageTag& tag) { + RootedObject proto(cx, prototype); + if (!proto) { + proto = GlobalObject::getOrCreateLocaleNativePrototype(cx, cx->global()); + if (!proto) { + return nullptr; + } + } + + JSStringBuilder sb(cx); + if (!tag.appendTo(cx, sb)) { + return nullptr; + } + + RootedString tagStr(cx, sb.finishString()); + if (!tagStr) { + return nullptr; + } + + size_t baseNameLength = BaseNameLength(tag); + + RootedString baseName(cx, NewDependentString(cx, tagStr, 0, baseNameLength)); + if (!baseName) { + return nullptr; + } + + RootedValue unicodeExtension(cx, UndefinedValue()); + if (auto result = UnicodeExtensionPosition(tag)) { + JSString* str = NewDependentString( + cx, tagStr, baseNameLength + 1 + result->index, result->length); + if (!str) { + return nullptr; + } + + unicodeExtension.setString(str); + } + + auto* locale = NewObjectWithGivenProto<NativeLocaleObject>(cx, proto); + if (!locale) { + return nullptr; + } + + locale->setFixedSlot(NativeLocaleObject::LANGUAGE_TAG_SLOT, + StringValue(tagStr)); + locale->setFixedSlot(NativeLocaleObject::BASENAME_SLOT, + StringValue(baseName)); + locale->setFixedSlot(NativeLocaleObject::UNICODE_EXTENSION_SLOT, + unicodeExtension); + + return locale; +} + +static inline bool IsValidUnicodeExtensionValue(JSLinearString* linear) { + return linear->length() > 0 && + LanguageTagParser::canParseUnicodeExtensionType(linear); +} + +/** Iterate through (sep keyword) in a valid, lowercased Unicode extension. */ +template <typename CharT> +class SepKeywordIterator { + const CharT* iter_; + const CharT* const end_; + + public: + SepKeywordIterator(const CharT* unicodeExtensionBegin, + const CharT* unicodeExtensionEnd) + : iter_(unicodeExtensionBegin), end_(unicodeExtensionEnd) {} + + /** + * Return (sep keyword) in the Unicode locale extension from begin to end. + * The first call after all (sep keyword) are consumed returns |nullptr|; no + * further calls are allowed. + */ + const CharT* next() { + MOZ_ASSERT(iter_ != nullptr, + "can't call next() once it's returned nullptr"); + + constexpr size_t SepKeyLength = 1 + UnicodeKeyLength; // "-co"/"-nu"/etc. + + MOZ_ASSERT(iter_ + SepKeyLength <= end_, + "overall Unicode locale extension or non-leading subtags must " + "be at least key-sized"); + + MOZ_ASSERT((iter_[0] == 'u' && iter_[1] == '-') || iter_[0] == '-'); + + while (true) { + // Skip past '-' so |std::char_traits::find| makes progress. Skipping + // 'u' is harmless -- skip or not, |find| returns the first '-'. + iter_++; + + // Find the next separator. + iter_ = std::char_traits<CharT>::find( + iter_, mozilla::PointerRangeSize(iter_, end_), CharT('-')); + if (!iter_) { + return nullptr; + } + + MOZ_ASSERT(iter_ + SepKeyLength <= end_, + "non-leading subtags in a Unicode locale extension are all " + "at least as long as a key"); + + if (iter_ + SepKeyLength == end_ || // key is terminal subtag + iter_[SepKeyLength] == '-') { // key is followed by more subtags + break; + } + } + + MOZ_ASSERT(iter_[0] == '-'); + MOZ_ASSERT(mozilla::IsAsciiLowercaseAlpha(iter_[1]) || + mozilla::IsAsciiDigit(iter_[1])); + MOZ_ASSERT(mozilla::IsAsciiLowercaseAlpha(iter_[2])); + MOZ_ASSERT_IF(iter_ + SepKeyLength < end_, iter_[SepKeyLength] == '-'); + return iter_; + } +}; + +/** + * 9.2.10 GetOption ( options, property, type, values, fallback ) + * + * If the requested property is present and not-undefined, set the result string + * to |ToString(value)|. Otherwise set the result string to nullptr. + */ +static bool GetStringOption(JSContext* cx, HandleObject options, + HandlePropertyName name, + MutableHandle<JSLinearString*> string) { + // Step 1. + RootedValue option(cx); + if (!GetProperty(cx, options, options, name, &option)) { + return false; + } + + // Step 2. + JSLinearString* linear = nullptr; + if (!option.isUndefined()) { + // Steps 2.a-b, 2.d (not applicable). + + // Steps 2.c, 2.e. + JSString* str = ToString(cx, option); + if (!str) { + return false; + } + linear = str->ensureLinear(cx); + if (!linear) { + return false; + } + } + + // Step 3. + string.set(linear); + return true; +} + +/** + * 9.2.10 GetOption ( options, property, type, values, fallback ) + * + * If the requested property is present and not-undefined, set the result string + * to |ToString(ToBoolean(value))|. Otherwise set the result string to nullptr. + */ +static bool GetBooleanOption(JSContext* cx, HandleObject options, + HandlePropertyName name, + MutableHandle<JSLinearString*> string) { + // Step 1. + RootedValue option(cx); + if (!GetProperty(cx, options, options, name, &option)) { + return false; + } + + // Step 2. + JSLinearString* linear = nullptr; + if (!option.isUndefined()) { + // Steps 2.a, 2.c-d (not applicable). + + // Steps 2.c, 2.e. + JSString* str = BooleanToString(cx, ToBoolean(option)); + MOZ_ALWAYS_TRUE(linear = str->ensureLinear(cx)); + } + + // Step 3. + string.set(linear); + return true; +} + +/** + * ApplyOptionsToTag ( tag, options ) + */ +static bool ApplyOptionsToTag(JSContext* cx, LanguageTag& tag, + HandleObject options) { + // Steps 1-2 (Already performed in caller). + + RootedLinearString option(cx); + + // Step 3. + if (!GetStringOption(cx, options, cx->names().language, &option)) { + return false; + } + + // Step 4. + intl::LanguageSubtag language; + if (option && !intl::ParseStandaloneLanguagTag(option, language)) { + if (UniqueChars str = StringToNewUTF8CharsZ(cx, *option)) { + JS_ReportErrorNumberUTF8(cx, js::GetErrorMessage, nullptr, + JSMSG_INVALID_OPTION_VALUE, "language", + str.get()); + } + return false; + } + + // Step 5. + if (!GetStringOption(cx, options, cx->names().script, &option)) { + return false; + } + + // Step 6. + intl::ScriptSubtag script; + if (option && !intl::ParseStandaloneScriptTag(option, script)) { + if (UniqueChars str = StringToNewUTF8CharsZ(cx, *option)) { + JS_ReportErrorNumberUTF8(cx, js::GetErrorMessage, nullptr, + JSMSG_INVALID_OPTION_VALUE, "script", str.get()); + } + return false; + } + + // Step 7. + if (!GetStringOption(cx, options, cx->names().region, &option)) { + return false; + } + + // Step 8. + intl::RegionSubtag region; + if (option && !intl::ParseStandaloneRegionTag(option, region)) { + if (UniqueChars str = StringToNewUTF8CharsZ(cx, *option)) { + JS_ReportErrorNumberUTF8(cx, js::GetErrorMessage, nullptr, + JSMSG_INVALID_OPTION_VALUE, "region", str.get()); + } + return false; + } + + // Step 9 (Already performed in caller). + + // Skip steps 10-13 when no subtags were modified. + if (language.length() > 0 || script.length() > 0 || region.length() > 0) { + // Step 10. + if (language.length() > 0) { + tag.setLanguage(language); + } + + // Step 11. + if (script.length() > 0) { + tag.setScript(script); + } + + // Step 12. + if (region.length() > 0) { + tag.setRegion(region); + } + + // Step 13. + // Optimized to only canonicalize the base-name subtags. All other + // canonicalization steps will happen later. + if (!tag.canonicalizeBaseName(cx)) { + return true; + } + } + + return true; +} + +/** + * ApplyUnicodeExtensionToTag( tag, options, relevantExtensionKeys ) + */ +static bool ApplyUnicodeExtensionToTag(JSContext* cx, LanguageTag& tag, + HandleLinearString calendar, + HandleLinearString collation, + HandleLinearString hourCycle, + HandleLinearString caseFirst, + HandleLinearString numeric, + HandleLinearString numberingSystem) { + // If no Unicode extensions were present in the options object, we can skip + // everything below and directly return. + if (!calendar && !collation && !caseFirst && !hourCycle && !numeric && + !numberingSystem) { + return true; + } + + Vector<char, 32> newExtension(cx); + if (!newExtension.append('u')) { + return false; + } + + // Check if there's an existing Unicode extension subtag. (The extension + // subtags aren't necessarily sorted, so we can't use binary search here.) + const UniqueChars* existingUnicodeExtension = + std::find_if(tag.extensions().begin(), tag.extensions().end(), + [](const auto& extension) { return extension[0] == 'u'; }); + + const char* unicodeExtensionEnd = nullptr; + const char* unicodeExtensionKeywords = nullptr; + if (existingUnicodeExtension != tag.extensions().end()) { + const char* unicodeExtension = existingUnicodeExtension->get(); + unicodeExtensionEnd = unicodeExtension + strlen(unicodeExtension); + + SepKeywordIterator<char> iter(unicodeExtension, unicodeExtensionEnd); + + // Find the start of the first keyword. + unicodeExtensionKeywords = iter.next(); + + // Copy any attributes present before the first keyword. + const char* attributesEnd = unicodeExtensionKeywords + ? unicodeExtensionKeywords + : unicodeExtensionEnd; + if (!newExtension.append(unicodeExtension + 1, attributesEnd)) { + return false; + } + } + + using UnicodeKeyWithSeparator = const char(&)[UnicodeKeyLength + 3]; + + auto appendKeyword = [&newExtension](UnicodeKeyWithSeparator key, + JSLinearString* value) { + if (!newExtension.append(key, UnicodeKeyLength + 2)) { + return false; + } + + JS::AutoCheckCannotGC nogc; + return value->hasLatin1Chars() + ? newExtension.append(value->latin1Chars(nogc), value->length()) + : newExtension.append(value->twoByteChars(nogc), + value->length()); + }; + + // Append the new keywords before any existing keywords. That way any previous + // keyword with the same key is detected as a duplicate when canonicalizing + // the Unicode extension subtag and gets discarded. + + size_t startNewKeywords = newExtension.length(); + + if (calendar) { + if (!appendKeyword("-ca-", calendar)) { + return false; + } + } + if (collation) { + if (!appendKeyword("-co-", collation)) { + return false; + } + } + if (hourCycle) { + if (!appendKeyword("-hc-", hourCycle)) { + return false; + } + } + if (caseFirst) { + if (!appendKeyword("-kf-", caseFirst)) { + return false; + } + } + if (numeric) { + if (!appendKeyword("-kn-", numeric)) { + return false; + } + } + if (numberingSystem) { + if (!appendKeyword("-nu-", numberingSystem)) { + return false; + } + } + + // Normalize the case of the new keywords. + std::transform(newExtension.begin() + startNewKeywords, newExtension.end(), + newExtension.begin() + startNewKeywords, [](char c) { + return mozilla::IsAsciiUppercaseAlpha(c) ? (c | 0x20) : c; + }); + + // Append the remaining keywords from the previous Unicode extension subtag. + if (unicodeExtensionKeywords) { + if (!newExtension.append(unicodeExtensionKeywords, unicodeExtensionEnd)) { + return false; + } + } + + // Null-terminate the new Unicode extension string. + if (!newExtension.append('\0')) { + return false; + } + + // Insert the new Unicode extension string into the language tag. + UniqueChars newExtensionChars(newExtension.extractOrCopyRawBuffer()); + if (!newExtensionChars) { + return false; + } + return tag.setUnicodeExtension(std::move(newExtensionChars)); +} + +/** + * Intl.Locale( tag[, options] ) + */ +static bool NativeLocale(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!ThrowIfNotConstructing(cx, args, "Intl.Locale")) { + return false; + } + + // Steps 2-6 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) { + return false; + } + + // Step 7. + if (args.length() == 0 || (!args[0].isString() && !args[0].isObject())) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_INVALID_LOCALES_ELEMENT); + return false; + } + + // Steps 8-9. + RootedString tagStr(cx); + if (args[0].isObject()) { + JSObject* obj = &args[0].toObject(); + if (obj->is<NativeLocaleObject>()) { + tagStr = obj->as<NativeLocaleObject>().languageTag(); + } else { + JSObject* unwrapped = CheckedUnwrapStatic(obj); + if (!unwrapped) { + ReportAccessDenied(cx); + return false; + } + + if (unwrapped->is<NativeLocaleObject>()) { + tagStr = unwrapped->as<NativeLocaleObject>().languageTag(); + if (!cx->compartment()->wrap(cx, &tagStr)) { + return false; + } + } else { + tagStr = ToString(cx, args[0]); + if (!tagStr) { + return false; + } + } + } + } else { + tagStr = args[0].toString(); + } + + RootedLinearString tagLinearStr(cx, tagStr->ensureLinear(cx)); + if (!tagLinearStr) { + return false; + } + + // ApplyOptionsToTag, steps 2 and 9. + LanguageTag tag(cx); + if (!LanguageTagParser::parse(cx, tagLinearStr, tag)) { + return false; + } + + if (!tag.canonicalizeBaseName(cx)) { + return false; + } + + // Steps 10-11. + if (args.hasDefined(1)) { + RootedObject options(cx, ToObject(cx, args[1])); + if (!options) { + return false; + } + + // Step 12. + if (!ApplyOptionsToTag(cx, tag, options)) { + return false; + } + + // Step 13 (not applicable). + + // Steps 14, 16. + RootedLinearString calendar(cx); + if (!GetStringOption(cx, options, cx->names().calendar, &calendar)) { + return false; + } + + // Step 15. + if (calendar) { + if (!IsValidUnicodeExtensionValue(calendar)) { + if (UniqueChars str = StringToNewUTF8CharsZ(cx, *calendar)) { + JS_ReportErrorNumberUTF8(cx, js::GetErrorMessage, nullptr, + JSMSG_INVALID_OPTION_VALUE, "calendar", + str.get()); + } + return false; + } + } + + // Steps 17, 19. + RootedLinearString collation(cx); + if (!GetStringOption(cx, options, cx->names().collation, &collation)) { + return false; + } + + // Step 18. + if (collation) { + if (!IsValidUnicodeExtensionValue(collation)) { + if (UniqueChars str = StringToNewUTF8CharsZ(cx, *collation)) { + JS_ReportErrorNumberUTF8(cx, js::GetErrorMessage, nullptr, + JSMSG_INVALID_OPTION_VALUE, "collation", + str.get()); + } + return false; + } + } + + // Steps 20-21. + RootedLinearString hourCycle(cx); + if (!GetStringOption(cx, options, cx->names().hourCycle, &hourCycle)) { + return false; + } + + if (hourCycle) { + if (!StringEqualsLiteral(hourCycle, "h11") && + !StringEqualsLiteral(hourCycle, "h12") && + !StringEqualsLiteral(hourCycle, "h23") && + !StringEqualsLiteral(hourCycle, "h24")) { + if (UniqueChars str = StringToNewUTF8CharsZ(cx, *hourCycle)) { + JS_ReportErrorNumberUTF8(cx, js::GetErrorMessage, nullptr, + JSMSG_INVALID_OPTION_VALUE, "hourCycle", + str.get()); + } + return false; + } + } + + // Steps 22-23. + RootedLinearString caseFirst(cx); + if (!GetStringOption(cx, options, cx->names().caseFirst, &caseFirst)) { + return false; + } + + if (caseFirst) { + if (!StringEqualsLiteral(caseFirst, "upper") && + !StringEqualsLiteral(caseFirst, "lower") && + !StringEqualsLiteral(caseFirst, "false")) { + if (UniqueChars str = StringToNewUTF8CharsZ(cx, *caseFirst)) { + JS_ReportErrorNumberUTF8(cx, js::GetErrorMessage, nullptr, + JSMSG_INVALID_OPTION_VALUE, "caseFirst", + str.get()); + } + return false; + } + } + + // Steps 24-26. + RootedLinearString numeric(cx); + if (!GetBooleanOption(cx, options, cx->names().numeric, &numeric)) { + return false; + } + + // Steps 27, 29. + RootedLinearString numberingSystem(cx); + if (!GetStringOption(cx, options, cx->names().numberingSystem, + &numberingSystem)) { + return false; + } + + // Step 28. + if (numberingSystem) { + if (!IsValidUnicodeExtensionValue(numberingSystem)) { + if (UniqueChars str = StringToNewUTF8CharsZ(cx, *numberingSystem)) { + JS_ReportErrorNumberUTF8(cx, js::GetErrorMessage, nullptr, + JSMSG_INVALID_OPTION_VALUE, + "numberingSystem", str.get()); + } + return false; + } + } + + // Step 30. + if (!ApplyUnicodeExtensionToTag(cx, tag, calendar, collation, hourCycle, + caseFirst, numeric, numberingSystem)) { + return false; + } + } + + // ApplyOptionsToTag, steps 9 and 13. + // ApplyUnicodeExtensionToTag, step 8. + if (!tag.canonicalizeExtensions( + cx, LanguageTag::UnicodeExtensionCanonicalForm::Yes)) { + return false; + } + + // Steps 6, 31-37. + JSObject* obj = CreateLocaleNativeObject(cx, proto, tag); + if (!obj) { + return false; + } + + // Step 38. + args.rval().setObject(*obj); + return true; +} + +using UnicodeKey = const char (&)[UnicodeKeyLength + 1]; + +// Returns the tuple [index, length] of the `type` in the `keyword` in Unicode +// locale extension |extension| that has |key| as its `key`. If `keyword` lacks +// a type, the returned |index| will be where `type` would have been, and +// |length| will be set to zero. +template <typename CharT> +static mozilla::Maybe<IndexAndLength> FindUnicodeExtensionType( + const CharT* extension, size_t length, UnicodeKey key) { + MOZ_ASSERT(extension[0] == 'u'); + MOZ_ASSERT(extension[1] == '-'); + + const CharT* end = extension + length; + + SepKeywordIterator<CharT> iter(extension, end); + + // Search all keywords until a match was found. + const CharT* beginKey; + while (true) { + beginKey = iter.next(); + if (!beginKey) { + return mozilla::Nothing(); + } + + // Add +1 to skip over the separator preceding the keyword. + MOZ_ASSERT(beginKey[0] == '-'); + beginKey++; + + // Exit the loop on the first match. + if (std::equal(beginKey, beginKey + UnicodeKeyLength, key)) { + break; + } + } + + // Skip over the key. + const CharT* beginType = beginKey + UnicodeKeyLength; + + // Find the start of the next keyword. + const CharT* endType = iter.next(); + + // No further keyword present, the current keyword ends the Unicode extension. + if (!endType) { + endType = end; + } + + // If the keyword has a type, skip over the separator preceding the type. + if (beginType != endType) { + MOZ_ASSERT(beginType[0] == '-'); + beginType++; + } + return mozilla::Some(IndexAndLength{size_t(beginType - extension), + size_t(endType - beginType)}); +} + +static inline auto FindUnicodeExtensionType(JSLinearString* unicodeExtension, + UnicodeKey key) { + JS::AutoCheckCannotGC nogc; + return unicodeExtension->hasLatin1Chars() + ? FindUnicodeExtensionType(unicodeExtension->latin1Chars(nogc), + unicodeExtension->length(), key) + : FindUnicodeExtensionType(unicodeExtension->twoByteChars(nogc), + unicodeExtension->length(), key); +} + +// Return the sequence of types for the Unicode extension keyword specified by +// key or undefined when the keyword isn't present. +static bool GetUnicodeExtension(JSContext* cx, NativeLocaleObject* locale, + UnicodeKey key, MutableHandleValue value) { + // Return undefined when no Unicode extension subtag is present. + const Value& unicodeExtensionValue = locale->unicodeExtension(); + if (unicodeExtensionValue.isUndefined()) { + value.setUndefined(); + return true; + } + + JSLinearString* unicodeExtension = + unicodeExtensionValue.toString()->ensureLinear(cx); + if (!unicodeExtension) { + return false; + } + + // Find the type of the requested key in the Unicode extension subtag. + auto result = FindUnicodeExtensionType(unicodeExtension, key); + + // Return undefined if the requested key isn't present in the extension. + if (!result) { + value.setUndefined(); + return true; + } + + size_t index = result->index; + size_t length = result->length; + + // Otherwise return the type value of the found keyword. + JSString* str = NewDependentString(cx, unicodeExtension, index, length); + if (!str) { + return false; + } + value.setString(str); + return true; +} + +struct BaseNamePartsResult { + IndexAndLength language; + mozilla::Maybe<IndexAndLength> script; + mozilla::Maybe<IndexAndLength> region; +}; + +// Returns [language-length, script-index, region-index, region-length]. +template <typename CharT> +static BaseNamePartsResult BaseNameParts(const CharT* baseName, size_t length) { + size_t languageLength; + size_t scriptIndex = 0; + size_t regionIndex = 0; + size_t regionLength = 0; + + // Search the first separator to find the end of the language subtag. + if (const CharT* sep = std::char_traits<CharT>::find(baseName, length, '-')) { + languageLength = sep - baseName; + + // Add +1 to skip over the separator character. + size_t nextSubtag = languageLength + 1; + + // Script subtags are always four characters long, but take care for a four + // character long variant subtag. These start with a digit. + if ((nextSubtag + ScriptLength == length || + (nextSubtag + ScriptLength < length && + baseName[nextSubtag + ScriptLength] == '-')) && + mozilla::IsAsciiAlpha(baseName[nextSubtag])) { + scriptIndex = nextSubtag; + nextSubtag = scriptIndex + ScriptLength + 1; + } + + // Region subtags can be either two or three characters long. + if (nextSubtag < length) { + for (size_t rlen : {AlphaRegionLength, DigitRegionLength}) { + MOZ_ASSERT(nextSubtag + rlen <= length); + if (nextSubtag + rlen == length || baseName[nextSubtag + rlen] == '-') { + regionIndex = nextSubtag; + regionLength = rlen; + break; + } + } + } + } else { + // No separator found, the base-name consists of just a language subtag. + languageLength = length; + } + + IndexAndLength language{0, languageLength}; + MOZ_ASSERT(intl::IsStructurallyValidLanguageTag(language.rangeOf(baseName))); + + mozilla::Maybe<IndexAndLength> script{}; + if (scriptIndex) { + script.emplace(scriptIndex, ScriptLength); + MOZ_ASSERT(intl::IsStructurallyValidScriptTag(script->rangeOf(baseName))); + } + + mozilla::Maybe<IndexAndLength> region{}; + if (regionIndex) { + region.emplace(regionIndex, regionLength); + MOZ_ASSERT(intl::IsStructurallyValidRegionTag(region->rangeOf(baseName))); + } + + return {language, script, region}; +} + +static inline auto BaseNameParts(JSLinearString* baseName) { + JS::AutoCheckCannotGC nogc; + return baseName->hasLatin1Chars() + ? BaseNameParts(baseName->latin1Chars(nogc), baseName->length()) + : BaseNameParts(baseName->twoByteChars(nogc), baseName->length()); +} + +// Intl.Locale.prototype.maximize () +static bool Locale_maximize(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + RootedLinearString tagStr(cx, locale->languageTag()->ensureLinear(cx)); + if (!tagStr) { + return false; + } + + LanguageTag tag(cx); + if (!LanguageTagParser::parse(cx, tagStr, tag)) { + return false; + } + + if (!tag.addLikelySubtags(cx)) { + return false; + } + + // Step 4. + auto* result = CreateLocaleNativeObject(cx, nullptr, tag); + if (!result) { + return false; + } + args.rval().setObject(*result); + return true; +} + +// Intl.Locale.prototype.maximize () +static bool Locale_maximize(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_maximize>(cx, args); +} + +// Intl.Locale.prototype.minimize () +static bool Locale_minimize(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + RootedLinearString tagStr(cx, locale->languageTag()->ensureLinear(cx)); + if (!tagStr) { + return false; + } + + LanguageTag tag(cx); + if (!LanguageTagParser::parse(cx, tagStr, tag)) { + return false; + } + + if (!tag.removeLikelySubtags(cx)) { + return false; + } + + // Step 4. + auto* result = CreateLocaleNativeObject(cx, nullptr, tag); + if (!result) { + return false; + } + args.rval().setObject(*result); + return true; +} + +// Intl.Locale.prototype.minimize () +static bool Locale_minimize(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_minimize>(cx, args); +} + +// Intl.Locale.prototype.toString () +static bool Locale_toString(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + args.rval().setString(locale->languageTag()); + return true; +} + +// Intl.Locale.prototype.toString () +static bool Locale_toString(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_toString>(cx, args); +} + +// get Intl.Locale.prototype.baseName +static bool Locale_baseName(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // FIXME: spec bug - invalid assertion in step 4. + // FIXME: spec bug - subtag production names not updated. + + // Steps 3, 5. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + args.rval().setString(locale->baseName()); + return true; +} + +// get Intl.Locale.prototype.baseName +static bool Locale_baseName(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_baseName>(cx, args); +} + +// get Intl.Locale.prototype.calendar +static bool Locale_calendar(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + return GetUnicodeExtension(cx, locale, "ca", args.rval()); +} + +// get Intl.Locale.prototype.calendar +static bool Locale_calendar(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_calendar>(cx, args); +} + +// get Intl.Locale.prototype.collation +static bool Locale_collation(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + return GetUnicodeExtension(cx, locale, "co", args.rval()); +} + +// get Intl.Locale.prototype.collation +static bool Locale_collation(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_collation>(cx, args); +} + +// get Intl.Locale.prototype.hourCycle +static bool Locale_hourCycle(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + return GetUnicodeExtension(cx, locale, "hc", args.rval()); +} + +// get Intl.Locale.prototype.hourCycle +static bool Locale_hourCycle(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_hourCycle>(cx, args); +} + +// get Intl.Locale.prototype.caseFirst +static bool Locale_caseFirst(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + return GetUnicodeExtension(cx, locale, "kf", args.rval()); +} + +// get Intl.Locale.prototype.caseFirst +static bool Locale_caseFirst(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_caseFirst>(cx, args); +} + +// get Intl.Locale.prototype.numeric +static bool Locale_numeric(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + RootedValue value(cx); + if (!GetUnicodeExtension(cx, locale, "kn", &value)) { + return false; + } + + // FIXME: spec bug - comparison should be against the empty string, too. + MOZ_ASSERT(value.isUndefined() || value.isString()); + args.rval().setBoolean(value.isString() && value.toString()->empty()); + return true; +} + +// get Intl.Locale.prototype.numeric +static bool Locale_numeric(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_numeric>(cx, args); +} + +// get Intl.Locale.prototype.numberingSystem +static bool Intl_Locale_numberingSystem(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + return GetUnicodeExtension(cx, locale, "nu", args.rval()); +} + +// get Intl.Locale.prototype.numberingSystem +static bool Locale_numberingSystem(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Intl_Locale_numberingSystem>(cx, args); +} + +// get Intl.Locale.prototype.language +static bool Locale_language(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + JSLinearString* baseName = locale->baseName()->ensureLinear(cx); + if (!baseName) { + return false; + } + + // Step 4 (Unnecessary assertion). + + auto language = BaseNameParts(baseName).language; + + size_t index = language.index; + size_t length = language.length; + + // Step 5. + // FIXME: spec bug - not all production names updated. + JSString* str = NewDependentString(cx, baseName, index, length); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +// get Intl.Locale.prototype.language +static bool Locale_language(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_language>(cx, args); +} + +// get Intl.Locale.prototype.script +static bool Locale_script(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + JSLinearString* baseName = locale->baseName()->ensureLinear(cx); + if (!baseName) { + return false; + } + + // Step 4 (Unnecessary assertion). + + auto script = BaseNameParts(baseName).script; + + // Step 5. + // FIXME: spec bug - not all production names updated. + if (!script) { + args.rval().setUndefined(); + return true; + } + + size_t index = script->index; + size_t length = script->length; + + // Step 6. + JSString* str = NewDependentString(cx, baseName, index, length); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +// get Intl.Locale.prototype.script +static bool Locale_script(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_script>(cx, args); +} + +// get Intl.Locale.prototype.region +static bool Locale_region(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsLocale(args.thisv())); + + // Step 3. + auto* locale = &args.thisv().toObject().as<NativeLocaleObject>(); + JSLinearString* baseName = locale->baseName()->ensureLinear(cx); + if (!baseName) { + return false; + } + + // Step 4 (Unnecessary assertion). + + auto region = BaseNameParts(baseName).region; + + // Step 5. + if (!region) { + args.rval().setUndefined(); + return true; + } + + size_t index = region->index; + size_t length = region->length; + + // Step 6. + JSString* str = NewDependentString(cx, baseName, index, length); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +// get Intl.Locale.prototype.region +static bool Locale_region(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsLocale, Locale_region>(cx, args); +} + +static bool Locale_toSource(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setString(cx->names().Locale); + return true; +} + +static const JSFunctionSpec locale_native_methods[] = { + JS_FN("maximize", Locale_maximize, 0, 0), + JS_FN("minimize", Locale_minimize, 0, 0), + JS_FN(js_toString_str, Locale_toString, 0, 0), + JS_FN(js_toSource_str, Locale_toSource, 0, 0), JS_FS_END}; + +static const JSPropertySpec locale_native_properties[] = { + JS_PSG("baseName", Locale_baseName, 0), + JS_PSG("calendar", Locale_calendar, 0), + JS_PSG("collation", Locale_collation, 0), + JS_PSG("hourCycle", Locale_hourCycle, 0), + JS_PSG("caseFirst", Locale_caseFirst, 0), + JS_PSG("numeric", Locale_numeric, 0), + JS_PSG("numberingSystem", Locale_numberingSystem, 0), + JS_PSG("language", Locale_language, 0), + JS_PSG("script", Locale_script, 0), + JS_PSG("region", Locale_region, 0), + JS_STRING_SYM_PS(toStringTag, "Intl.Locale", JSPROP_READONLY), + JS_PS_END}; + +JSObject* js::CreateNativeLocalePrototype(JSContext* cx, HandleObject Intl, + Handle<GlobalObject*> global) { + RootedFunction ctor(cx, GlobalObject::createConstructor( + cx, &NativeLocale, cx->names().Locale, 1)); + if (!ctor) { + return nullptr; + } + + RootedObject proto( + cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global)); + if (!proto) { + return nullptr; + } + + if (!LinkConstructorAndPrototype(cx, ctor, proto)) { + return nullptr; + } + + if (!DefinePropertiesAndFunctions(cx, proto, locale_native_properties, + locale_native_methods)) { + return nullptr; + } + + RootedValue ctorValue(cx, ObjectValue(*ctor)); + if (!DefineDataProperty(cx, Intl, cx->names().locale, ctorValue, 0)) { + return nullptr; + } + + return proto; +}
--- a/js/src/moz.build +++ b/js/src/moz.build @@ -378,16 +378,17 @@ if CONFIG['ENABLE_INTL_API']: UNIFIED_SOURCES += [ 'builtin/intl/Collator.cpp', 'builtin/intl/CommonFunctions.cpp', 'builtin/intl/DateTimeFormat.cpp', 'builtin/intl/IntlObject.cpp', 'builtin/intl/LanguageTag.cpp', 'builtin/intl/LanguageTagGenerated.cpp', 'builtin/intl/Locale.cpp', + 'builtin/intl/NativeLocale.cpp', 'builtin/intl/NumberFormat.cpp', 'builtin/intl/PluralRules.cpp', 'builtin/intl/RelativeTimeFormat.cpp', 'builtin/intl/SharedIntlData.cpp', 'builtin/intl/UnicodeExtensionsGenerated.cpp', ] if CONFIG['MOZ_INSTRUMENTS']:
--- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -59,29 +59,31 @@ MACRO(builder, builder, "builder") \ MACRO(by, by, "by") \ MACRO(byob, byob, "byob") \ MACRO(byteAlignment, byteAlignment, "byteAlignment") \ MACRO(byteLength, byteLength, "byteLength") \ MACRO(byteOffset, byteOffset, "byteOffset") \ MACRO(bytes, bytes, "bytes") \ MACRO(BYTES_PER_ELEMENT, BYTES_PER_ELEMENT, "BYTES_PER_ELEMENT") \ + MACRO(calendar, calendar, "calendar") \ MACRO(call, call, "call") \ MACRO(callContentFunction, callContentFunction, "callContentFunction") \ MACRO(callee, callee, "callee") \ MACRO(caller, caller, "caller") \ MACRO(callFunction, callFunction, "callFunction") \ MACRO(CallRegExpMethodIfWrapped, CallRegExpMethodIfWrapped, \ "CallRegExpMethodIfWrapped") \ MACRO(cancel, cancel, "cancel") \ MACRO(case, case_, "case") \ MACRO(caseFirst, caseFirst, "caseFirst") \ MACRO(catch, catch_, "catch") \ MACRO(class, class_, "class") \ MACRO(close, close, "close") \ + MACRO(collation, collation, "collation") \ MACRO(Collator, Collator, "Collator") \ MACRO(collections, collections, "collections") \ MACRO(columnNumber, columnNumber, "columnNumber") \ MACRO(comma, comma, ",") \ MACRO(compare, compare, "compare") \ MACRO(configurable, configurable, "configurable") \ MACRO(const, const_, "const") \ MACRO(construct, construct, "construct") \ @@ -203,16 +205,17 @@ MACRO(globalThis, globalThis, "globalThis") \ MACRO(group, group, "group") \ MACRO(Handle, Handle, "Handle") \ MACRO(has, has, "has") \ MACRO(hasOwn, hasOwn, "hasOwn") \ MACRO(hasOwnProperty, hasOwnProperty, "hasOwnProperty") \ MACRO(highWaterMark, highWaterMark, "highWaterMark") \ MACRO(hour, hour, "hour") \ + MACRO(hourCycle, hourCycle, "hourCycle") \ MACRO(if, if_, "if") \ MACRO(ignoreCase, ignoreCase, "ignoreCase") \ MACRO(ignorePunctuation, ignorePunctuation, "ignorePunctuation") \ MACRO(implements, implements, "implements") \ MACRO(import, import, "import") \ MACRO(in, in, "in") \ MACRO(includes, includes, "includes") \ MACRO(incumbentGlobal, incumbentGlobal, "incumbentGlobal") \ @@ -252,16 +255,17 @@ MACRO(isPrototypeOf, isPrototypeOf, "isPrototypeOf") \ MACRO(isStepStart, isStepStart, "isStepStart") \ MACRO(IterableToList, IterableToList, "IterableToList") \ MACRO(iterate, iterate, "iterate") \ MACRO(join, join, "join") \ MACRO(js, js, "js") \ MACRO(keys, keys, "keys") \ MACRO(label, label, "label") \ + MACRO(language, language, "language") \ MACRO(lastIndex, lastIndex, "lastIndex") \ MACRO(length, length, "length") \ MACRO(let, let, "let") \ MACRO(line, line, "line") \ MACRO(lineNumber, lineNumber, "lineNumber") \ MACRO(literal, literal, "literal") \ MACRO(loc, loc, "loc") \ MACRO(Locale, Locale, "Locale") \ @@ -308,16 +312,17 @@ MACRO(NFKC, NFKC, "NFKC") \ MACRO(NFKD, NFKD, "NFKD") \ MACRO(noFilename, noFilename, "noFilename") \ MACRO(nonincrementalReason, nonincrementalReason, "nonincrementalReason") \ MACRO(noStack, noStack, "noStack") \ MACRO(notation, notation, "notation") \ MACRO(notes, notes, "notes") \ MACRO(NumberFormat, NumberFormat, "NumberFormat") \ + MACRO(numberingSystem, numberingSystem, "numberingSystem") \ MACRO(numeric, numeric, "numeric") \ MACRO(objectArguments, objectArguments, "[object Arguments]") \ MACRO(objectArray, objectArray, "[object Array]") \ MACRO(objectBigInt, objectBigInt, "[object BigInt]") \ MACRO(objectBoolean, objectBoolean, "[object Boolean]") \ MACRO(objectDate, objectDate, "[object Date]") \ MACRO(objectError, objectError, "[object Error]") \ MACRO(objectFunction, objectFunction, "[object Function]") \ @@ -358,16 +363,17 @@ MACRO(reason, reason, "reason") \ MACRO(RegExpBuiltinExec, RegExpBuiltinExec, "RegExpBuiltinExec") \ MACRO(RegExpFlagsGetter, RegExpFlagsGetter, "$RegExpFlagsGetter") \ MACRO(RegExpMatcher, RegExpMatcher, "RegExpMatcher") \ MACRO(RegExpSearcher, RegExpSearcher, "RegExpSearcher") \ MACRO(RegExpStringIterator, RegExpStringIterator, "RegExp String Iterator") \ MACRO(RegExpTester, RegExpTester, "RegExpTester") \ MACRO(RegExp_prototype_Exec, RegExp_prototype_Exec, "RegExp_prototype_Exec") \ + MACRO(region, region, "region") \ MACRO(Reify, Reify, "Reify") \ MACRO(reject, reject, "reject") \ MACRO(rejected, rejected, "rejected") \ MACRO(relatedYear, relatedYear, "relatedYear") \ MACRO(RelativeTimeFormat, RelativeTimeFormat, "RelativeTimeFormat") \ MACRO(RelativeTimeFormatFormat, RelativeTimeFormatFormat, \ "Intl_RelativeTimeFormat_Format") \ MACRO(RequireObjectCoercible, RequireObjectCoercible, \
--- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -91,16 +91,17 @@ class GlobalObject : public NativeObject COLLATOR_PROTO, NUMBER_FORMAT, NUMBER_FORMAT_PROTO, DATE_TIME_FORMAT, DATE_TIME_FORMAT_PROTO, PLURAL_RULES_PROTO, RELATIVE_TIME_FORMAT_PROTO, LOCALE_PROTO, + NATIVE_LOCALE_PROTO, MODULE_PROTO, IMPORT_ENTRY_PROTO, EXPORT_ENTRY_PROTO, REQUESTED_MODULE_PROTO, REGEXP_STATICS, RUNTIME_CODEGEN_ENABLED, DEBUGGERS, INTRINSICS, @@ -544,16 +545,21 @@ class GlobalObject : public NativeObject } static JSObject* getOrCreateLocalePrototype(JSContext* cx, Handle<GlobalObject*> global) { return getOrCreateObject(cx, global, LOCALE_PROTO, initIntlObject); } #endif // ENABLE_INTL_API + static JSObject* getOrCreateLocaleNativePrototype( + JSContext* cx, Handle<GlobalObject*> global) { + return getOrCreateObject(cx, global, NATIVE_LOCALE_PROTO, initIntlObject); + } + static bool ensureModulePrototypesCreated(JSContext* cx, Handle<GlobalObject*> global); static JSObject* getOrCreateModulePrototype(JSContext* cx, Handle<GlobalObject*> global) { return getOrCreateObject(cx, global, MODULE_PROTO, initModuleProto); }