ICUUtils.cpp (5851B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #ifdef MOZILLA_INTERNAL_API 6 7 # include "mozilla/Assertions.h" 8 # include "mozilla/UniquePtr.h" 9 10 # include "ICUUtils.h" 11 # include "mozilla/ClearOnShutdown.h" 12 # include "mozilla/StaticPrefs_dom.h" 13 # include "mozilla/intl/LocaleService.h" 14 # include "mozilla/intl/FormatBuffer.h" 15 # include "mozilla/intl/NumberFormat.h" 16 # include "mozilla/intl/NumberParser.h" 17 # include "nsIContent.h" 18 # include "mozilla/dom/Document.h" 19 # include "nsString.h" 20 21 using namespace mozilla; 22 using mozilla::intl::LocaleService; 23 24 already_AddRefed<nsAtom> ICUUtils::LanguageTagIterForContent::GetNext() { 25 if (mCurrentFallbackIndex < 0) { 26 mCurrentFallbackIndex = 0; 27 // Try the language specified by a 'lang'/'xml:lang' attribute on mContent 28 // or any ancestor, if such an attribute is specified: 29 if (auto* lang = mContent->GetLang()) { 30 return do_AddRef(lang); 31 } 32 } 33 34 if (mCurrentFallbackIndex < 1) { 35 mCurrentFallbackIndex = 1; 36 // Else try the language specified by any Content-Language HTTP header or 37 // pragma directive: 38 if (nsAtom* lang = mContent->OwnerDoc()->GetContentLanguage()) { 39 return do_AddRef(lang); 40 } 41 } 42 43 if (mCurrentFallbackIndex < 2) { 44 mCurrentFallbackIndex = 2; 45 // Else take the app's locale (or en-US, if spoof English applies): 46 if (mContent->OwnerDoc()->ShouldResistFingerprinting(RFPTarget::JSLocale)) { 47 return NS_Atomize(nsRFPService::GetSpoofedJSLocale()); 48 } 49 nsAutoCString appLocale; 50 LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale); 51 return NS_Atomize(appLocale); 52 } 53 54 // TODO: Probably not worth it, but maybe have a fourth fallback to using 55 // the OS locale? 56 return nullptr; 57 } 58 59 /* static */ 60 bool ICUUtils::LocalizeNumber(double aValue, 61 LanguageTagIterForContent& aLangTags, 62 nsAString& aLocalizedValue) { 63 MOZ_ASSERT(aLangTags.IsAtStart(), "Don't call Next() before passing"); 64 MOZ_ASSERT(NS_IsMainThread()); 65 using LangToFormatterCache = 66 nsTHashMap<RefPtr<nsAtom>, UniquePtr<intl::NumberFormat>>; 67 68 static StaticAutoPtr<LangToFormatterCache> sCache; 69 if (!sCache) { 70 sCache = new LangToFormatterCache(); 71 ClearOnShutdown(&sCache); 72 } 73 74 intl::NumberFormatOptions options; 75 if (StaticPrefs::dom_forms_number_grouping()) { 76 options.mGrouping = intl::NumberFormatOptions::Grouping::Always; 77 } else { 78 options.mGrouping = intl::NumberFormatOptions::Grouping::Never; 79 } 80 81 // ICU default is a maximum of 3 significant fractional digits. We don't 82 // want that limit, so we set it to the maximum that a double can represent 83 // (14-16 decimal fractional digits). 84 options.mFractionDigits = Some(std::make_pair(0, 16)); 85 86 while (RefPtr<nsAtom> langTag = aLangTags.GetNext()) { 87 auto& formatter = sCache->LookupOrInsertWith(langTag, [&] { 88 nsAutoCString tag; 89 langTag->ToUTF8String(tag); 90 if (tag.FindChar('\0') != kNotFound) { 91 return UniquePtr<intl::NumberFormat>(); 92 } 93 return intl::NumberFormat::TryCreate(tag, options).unwrapOr(nullptr); 94 }); 95 if (!formatter) { 96 continue; 97 } 98 intl::nsTStringToBufferAdapter adapter(aLocalizedValue); 99 if (formatter->format(aValue, adapter).isOk()) { 100 return true; 101 } 102 } 103 return false; 104 } 105 106 /* static */ 107 double ICUUtils::ParseNumber(const nsAString& aValue, 108 LanguageTagIterForContent& aLangTags) { 109 MOZ_ASSERT(aLangTags.IsAtStart(), "Don't call Next() before passing"); 110 using LangToParserCache = 111 nsTHashMap<RefPtr<nsAtom>, UniquePtr<intl::NumberParser>>; 112 static StaticAutoPtr<LangToParserCache> sCache; 113 if (aValue.IsEmpty()) { 114 return std::numeric_limits<float>::quiet_NaN(); 115 } 116 117 if (!sCache) { 118 sCache = new LangToParserCache(); 119 ClearOnShutdown(&sCache); 120 } 121 122 const Span<const char16_t> value(aValue.BeginReading(), aValue.Length()); 123 124 while (RefPtr<nsAtom> langTag = aLangTags.GetNext()) { 125 auto& parser = sCache->LookupOrInsertWith(langTag, [&] { 126 nsAutoCString tag; 127 langTag->ToUTF8String(tag); 128 if (tag.FindChar('\0') != kNotFound) { 129 return UniquePtr<intl::NumberParser>(); 130 } 131 return intl::NumberParser::TryCreate( 132 tag, StaticPrefs::dom_forms_number_grouping()) 133 .unwrapOr(nullptr); 134 }); 135 if (!parser) { 136 continue; 137 } 138 static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2, 139 "Unexpected character size - the following cast is unsafe"); 140 auto parseResult = parser->ParseDouble(value); 141 if (!parseResult.isOk()) { 142 continue; 143 } 144 std::pair<double, int32_t> parsed = parseResult.unwrap(); 145 if (parsed.second == static_cast<int32_t>(value.Length())) { 146 return parsed.first; 147 } 148 } 149 return std::numeric_limits<float>::quiet_NaN(); 150 } 151 152 /* static */ 153 void ICUUtils::AssignUCharArrayToString(UChar* aICUString, int32_t aLength, 154 nsAString& aMozString) { 155 // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can 156 // cast here. 157 158 static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2, 159 "Unexpected character size - the following cast is unsafe"); 160 161 aMozString.Assign((const nsAString::char_type*)aICUString, aLength); 162 163 NS_ASSERTION((int32_t)aMozString.Length() == aLength, "Conversion failed"); 164 } 165 166 /* static */ 167 nsresult ICUUtils::ICUErrorToNsResult(const intl::ICUError aError) { 168 switch (aError) { 169 case intl::ICUError::OutOfMemory: 170 return NS_ERROR_OUT_OF_MEMORY; 171 172 default: 173 return NS_ERROR_FAILURE; 174 } 175 } 176 177 #endif /* MOZILLA_INTERNAL_API */