From 9999e83d849dde54ab3bb81f6315e37905cf875e Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Fri, 19 Jan 2018 21:02:38 -0800 Subject: [PATCH] Bug 1431957 - Move String-based Intl-dependent functionality out of Intl.cpp, into jsstr.cpp. It may *depend* on Intl, but it *lives* on String, so it should be defined in String code for easiest searching. And with newly-slimmed builtin/intl/*.h headers, it's no real compile overhead to define this outside of Intl code. r=anba --HG-- extra : rebase_source : 6471c2d3028dba1f6ac9348d8b40c20f07e50cbd --- js/src/builtin/Intl.cpp | 117 ------------------------------ js/src/builtin/Intl.h | 22 ------ js/src/jsstr.cpp | 154 ++++++++++++++++++++++++++++++++++++++-- js/src/jsstr.h | 63 ++++++++++++++-- 4 files changed, 205 insertions(+), 151 deletions(-) diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 52d1e67732e2..74e51d9de174 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -63,123 +63,6 @@ using js::intl::IcuLocale; using js::intl::INITIAL_CHAR_BUFFER_SIZE; using js::intl::StringsAreEqual; -/******************** String ********************/ - -static const char* -CaseMappingLocale(JSContext* cx, JSString* str) -{ - JSLinearString* locale = str->ensureLinear(cx); - if (!locale) - return nullptr; - - MOZ_ASSERT(locale->length() >= 2, "locale is a valid language tag"); - - // Lithuanian, Turkish, and Azeri have language dependent case mappings. - static const char languagesWithSpecialCasing[][3] = { "lt", "tr", "az" }; - - // All strings in |languagesWithSpecialCasing| are of length two, so we - // only need to compare the first two characters to find a matching locale. - // ES2017 Intl, §9.2.2 BestAvailableLocale - if (locale->length() == 2 || locale->latin1OrTwoByteChar(2) == '-') { - for (const auto& language : languagesWithSpecialCasing) { - if (locale->latin1OrTwoByteChar(0) == language[0] && - locale->latin1OrTwoByteChar(1) == language[1]) - { - return language; - } - } - } - - return ""; // ICU root locale -} - -bool -js::intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 2); - MOZ_ASSERT(args[0].isString()); - MOZ_ASSERT(args[1].isString()); - - RootedString string(cx, args[0].toString()); - - const char* locale = CaseMappingLocale(cx, args[1].toString()); - if (!locale) - return false; - - // Call String.prototype.toLowerCase() for language independent casing. - if (StringsAreEqual(locale, "")) { - JSString* str = js::StringToLowerCase(cx, string); - if (!str) - return false; - - args.rval().setString(str); - return true; - } - - AutoStableStringChars inputChars(cx); - if (!inputChars.initTwoByte(cx, string)) - return false; - mozilla::Range input = inputChars.twoByteRange(); - - // Maximum case mapping length is three characters. - static_assert(JSString::MAX_LENGTH < INT32_MAX / 3, - "Case conversion doesn't overflow int32_t indices"); - - JSString* str = CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) { - return u_strToLower(chars, size, input.begin().get(), input.length(), locale, status); - }); - if (!str) - return false; - - args.rval().setString(str); - return true; -} - -bool -js::intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 2); - MOZ_ASSERT(args[0].isString()); - MOZ_ASSERT(args[1].isString()); - - RootedString string(cx, args[0].toString()); - - const char* locale = CaseMappingLocale(cx, args[1].toString()); - if (!locale) - return false; - - // Call String.prototype.toUpperCase() for language independent casing. - if (StringsAreEqual(locale, "")) { - JSString* str = js::StringToUpperCase(cx, string); - if (!str) - return false; - - args.rval().setString(str); - return true; - } - - AutoStableStringChars inputChars(cx); - if (!inputChars.initTwoByte(cx, string)) - return false; - mozilla::Range input = inputChars.twoByteRange(); - - // Maximum case mapping length is three characters. - static_assert(JSString::MAX_LENGTH < INT32_MAX / 3, - "Case conversion doesn't overflow int32_t indices"); - - JSString* str = CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) { - return u_strToUpper(chars, size, input.begin().get(), input.length(), locale, status); - }); - if (!str) - return false; - - args.rval().setString(str); - return true; -} - - /******************** Intl ********************/ bool diff --git a/js/src/builtin/Intl.h b/js/src/builtin/Intl.h index df1f8e98442f..4a0a7139a030 100644 --- a/js/src/builtin/Intl.h +++ b/js/src/builtin/Intl.h @@ -125,28 +125,6 @@ intl_GetLocaleInfo(JSContext* cx, unsigned argc, Value* vp); extern MOZ_MUST_USE bool intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp); - -/******************** String ********************/ - -/** - * Returns the input string converted to lower case based on the language - * specific case mappings for the input locale. - * - * Usage: lowerCase = intl_toLocaleLowerCase(string, locale) - */ -extern MOZ_MUST_USE bool -intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp); - -/** - * Returns the input string converted to upper case based on the language - * specific case mappings for the input locale. - * - * Usage: upperCase = intl_toLocaleUpperCase(string, locale) - */ -extern MOZ_MUST_USE bool -intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp); - - } // namespace js #endif /* builtin_Intl_h */ diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 700ea1e037fc..cc1475c772f1 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -31,6 +31,8 @@ #include "jstypes.h" #include "jsutil.h" +#include "builtin/intl/CommonFunctions.h" +#include "builtin/intl/ICUStubs.h" #include "builtin/RegExp.h" #include "jit/InlinableNatives.h" #include "js/Conversions.h" @@ -1011,7 +1013,85 @@ js::str_toLowerCase(JSContext* cx, unsigned argc, Value* vp) return true; } -#if !EXPOSE_INTL_API +static const char* +CaseMappingLocale(JSContext* cx, JSString* str) +{ + JSLinearString* locale = str->ensureLinear(cx); + if (!locale) + return nullptr; + + MOZ_ASSERT(locale->length() >= 2, "locale is a valid language tag"); + + // Lithuanian, Turkish, and Azeri have language dependent case mappings. + static const char languagesWithSpecialCasing[][3] = { "lt", "tr", "az" }; + + // All strings in |languagesWithSpecialCasing| are of length two, so we + // only need to compare the first two characters to find a matching locale. + // ES2017 Intl, §9.2.2 BestAvailableLocale + if (locale->length() == 2 || locale->latin1OrTwoByteChar(2) == '-') { + for (const auto& language : languagesWithSpecialCasing) { + if (locale->latin1OrTwoByteChar(0) == language[0] && + locale->latin1OrTwoByteChar(1) == language[1]) + { + return language; + } + } + } + + return ""; // ICU root locale +} + +bool +js::intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(args[0].isString()); + MOZ_ASSERT(args[1].isString()); + + RootedString string(cx, args[0].toString()); + + const char* locale = CaseMappingLocale(cx, args[1].toString()); + if (!locale) + return false; + + // Call String.prototype.toLowerCase() for language independent casing. + if (intl::StringsAreEqual(locale, "")) { + JSString* str = StringToLowerCase(cx, string); + if (!str) + return false; + + args.rval().setString(str); + return true; + } + + AutoStableStringChars inputChars(cx); + if (!inputChars.initTwoByte(cx, string)) + return false; + mozilla::Range input = inputChars.twoByteRange(); + + // Maximum case mapping length is three characters. + static_assert(JSString::MAX_LENGTH < INT32_MAX / 3, + "Case conversion doesn't overflow int32_t indices"); + + JSString* str = + intl::CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) { + return u_strToLower(chars, size, input.begin().get(), input.length(), locale, status); + }); + if (!str) + return false; + + args.rval().setString(str); + return true; +} + +#if EXPOSE_INTL_API + +// String.prototype.toLocaleLowerCase is self-hosted when Intl is exposed, +// with core functionality performed by the intrinsic above. + +#else + bool js::str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp) { @@ -1045,7 +1125,8 @@ js::str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp) args.rval().setString(result); return true; } -#endif /* !EXPOSE_INTL_API */ + +#endif // EXPOSE_INTL_API static inline bool CanUpperCaseSpecialCasing(Latin1Char charCode) @@ -1348,7 +1429,57 @@ js::str_toUpperCase(JSContext* cx, unsigned argc, Value* vp) return true; } -#if !EXPOSE_INTL_API +bool +js::intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(args[0].isString()); + MOZ_ASSERT(args[1].isString()); + + RootedString string(cx, args[0].toString()); + + const char* locale = CaseMappingLocale(cx, args[1].toString()); + if (!locale) + return false; + + // Call String.prototype.toUpperCase() for language independent casing. + if (intl::StringsAreEqual(locale, "")) { + JSString* str = js::StringToUpperCase(cx, string); + if (!str) + return false; + + args.rval().setString(str); + return true; + } + + AutoStableStringChars inputChars(cx); + if (!inputChars.initTwoByte(cx, string)) + return false; + mozilla::Range input = inputChars.twoByteRange(); + + // Maximum case mapping length is three characters. + static_assert(JSString::MAX_LENGTH < INT32_MAX / 3, + "Case conversion doesn't overflow int32_t indices"); + + JSString* str = + intl::CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) { + return u_strToUpper(chars, size, input.begin().get(), input.length(), locale, status); + }); + if (!str) + return false; + + args.rval().setString(str); + return true; +} + +#if EXPOSE_INTL_API + +// String.prototype.toLocaleLowerCase is self-hosted when Intl is exposed, +// with core functionality performed by the intrinsic above. + +#else + bool js::str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp) { @@ -1382,9 +1513,15 @@ js::str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp) args.rval().setString(result); return true; } -#endif /* !EXPOSE_INTL_API */ -#if !EXPOSE_INTL_API +#endif // EXPOSE_INTL_API + +#if EXPOSE_INTL_API + +// String.prototype.localeCompare is self-hosted when Intl is exposed. + +#else + bool js::str_localeCompare(JSContext* cx, unsigned argc, Value* vp) { @@ -1413,9 +1550,11 @@ js::str_localeCompare(JSContext* cx, unsigned argc, Value* vp) args.rval().setInt32(result); return true; } -#endif + +#endif // EXPOSE_INTL_API #if EXPOSE_INTL_API + // ES2017 draft rev 45e890512fd77add72cc0ee742785f9f6f6482de // 21.1.3.12 String.prototype.normalize ( [ form ] ) bool @@ -1549,7 +1688,8 @@ js::str_normalize(JSContext* cx, unsigned argc, Value* vp) args.rval().setString(ns); return true; } -#endif + +#endif // EXPOSE_INTL_API bool js::str_charAt(JSContext* cx, unsigned argc, Value* vp) diff --git a/js/src/jsstr.h b/js/src/jsstr.h index af025af15f18..9548bf0236ff 100644 --- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -344,19 +344,72 @@ str_trimLeft(JSContext* cx, unsigned argc, Value* vp); extern bool str_trimRight(JSContext* cx, unsigned argc, Value* vp); -#if !EXPOSE_INTL_API +/** + * Returns the input string converted to lower case based on the language + * specific case mappings for the input locale. + * + * This function only works #if EXPOSE_INTL_API; if not, it will *crash*. + * Govern yourself accordingly. + * + * Usage: lowerCase = intl_toLocaleLowerCase(string, locale) + */ +extern MOZ_MUST_USE bool +intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp); + +/** + * Returns the input string converted to upper case based on the language + * specific case mappings for the input locale. + * + * This function only works #if EXPOSE_INTL_API; if not, it will *crash*. + * Govern yourself accordingly. + * + * Usage: upperCase = intl_toLocaleUpperCase(string, locale) + */ +extern MOZ_MUST_USE bool +intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp); + +#if EXPOSE_INTL_API + +// When the Intl API is exposed, String.prototype.to{Lower,Upper}Case is +// self-hosted. The core functionality is provided by the intrinsics above. + +#else + +// When the Intl API is not exposed, String.prototype.to{Lower,Upper}Case are +// implemented in C++. + extern bool str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp); extern bool str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp); -extern bool -str_localeCompare(JSContext* cx, unsigned argc, Value* vp); -#else +#endif // EXPOSE_INTL_API + +#if EXPOSE_INTL_API + +// String.prototype.normalize is only implementable if ICU's normalization +// functionality is available. extern bool str_normalize(JSContext* cx, unsigned argc, Value* vp); -#endif + +#endif // EXPOSE_INTL_API + +#if EXPOSE_INTL_API + +// String.prototype.localeCompare is self-hosted when Intl functionality is +// exposed, and the only intrinsics it requires are provided in the +// implementation of Intl.Collator. + +#else + +// String.prototype.localeCompare is implemented in C++ (delegating to +// JSLocaleCallbacks) when Intl functionality is not exposed. + +extern bool +str_localeCompare(JSContext* cx, unsigned argc, Value* vp); + +#endif // EXPOSE_INTL_API extern bool str_concat(JSContext* cx, unsigned argc, Value* vp);