GlobalIntlData.cpp (9815B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "builtin/intl/GlobalIntlData.h" 8 9 #include "mozilla/Assertions.h" 10 #include "mozilla/Span.h" 11 12 #include "builtin/intl/Collator.h" 13 #include "builtin/intl/CommonFunctions.h" 14 #include "builtin/intl/DateTimeFormat.h" 15 #include "builtin/intl/FormatBuffer.h" 16 #include "builtin/intl/LocaleNegotiation.h" 17 #include "builtin/intl/NumberFormat.h" 18 #include "builtin/temporal/TimeZone.h" 19 #include "gc/Tracer.h" 20 #include "js/RootingAPI.h" 21 #include "js/TracingAPI.h" 22 #include "js/Value.h" 23 #include "vm/DateTime.h" 24 #include "vm/JSContext.h" 25 #include "vm/Realm.h" 26 27 #include "vm/JSObject-inl.h" 28 29 using namespace js; 30 31 void js::intl::GlobalIntlData::resetCollator() { 32 collatorLocale_ = nullptr; 33 collator_ = nullptr; 34 } 35 36 void js::intl::GlobalIntlData::resetNumberFormat() { 37 numberFormatLocale_ = nullptr; 38 numberFormat_ = nullptr; 39 } 40 41 void js::intl::GlobalIntlData::resetDateTimeFormat() { 42 dateTimeFormatLocale_ = nullptr; 43 dateTimeFormatToLocaleAll_ = nullptr; 44 dateTimeFormatToLocaleDate_ = nullptr; 45 dateTimeFormatToLocaleTime_ = nullptr; 46 } 47 48 bool js::intl::GlobalIntlData::ensureRealmLocale(JSContext* cx) { 49 const char* locale = cx->realm()->getLocale(); 50 if (!locale) { 51 ReportOutOfMemory(cx); 52 return false; 53 } 54 55 if (!realmLocale_ || !StringEqualsAscii(realmLocale_, locale)) { 56 realmLocale_ = NewStringCopyZ<CanGC>(cx, locale); 57 if (!realmLocale_) { 58 return false; 59 } 60 61 // Clear the cached default locale. 62 defaultLocale_ = nullptr; 63 64 // Clear all cached instances when the realm locale has changed. 65 resetCollator(); 66 resetNumberFormat(); 67 resetDateTimeFormat(); 68 } 69 70 return true; 71 } 72 73 bool js::intl::GlobalIntlData::ensureRealmTimeZone(JSContext* cx) { 74 TimeZoneIdentifierVector timeZoneId; 75 if (!DateTimeInfo::timeZoneId(cx->realm()->getDateTimeInfo(), timeZoneId)) { 76 ReportOutOfMemory(cx); 77 return false; 78 } 79 80 if (!realmTimeZone_ || !StringEqualsAscii(realmTimeZone_, timeZoneId.begin(), 81 timeZoneId.length())) { 82 realmTimeZone_ = NewStringCopy<CanGC>( 83 cx, static_cast<mozilla::Span<const char>>(timeZoneId)); 84 if (!realmTimeZone_) { 85 return false; 86 } 87 88 // Clear the cached default time zone. 89 defaultTimeZone_ = nullptr; 90 defaultTimeZoneObject_ = nullptr; 91 92 // Clear all cached DateTimeFormat instances when the time zone has changed. 93 resetDateTimeFormat(); 94 } 95 96 return true; 97 } 98 99 JSLinearString* js::intl::GlobalIntlData::defaultLocale(JSContext* cx) { 100 // Ensure the realm locale didn't change. 101 if (!ensureRealmLocale(cx)) { 102 return nullptr; 103 } 104 105 // If we didn't have a cache hit, compute the candidate default locale. 106 if (!defaultLocale_) { 107 // Cache the computed locale until the realm locale changes. 108 defaultLocale_ = ComputeDefaultLocale(cx); 109 } 110 return defaultLocale_; 111 } 112 113 JSLinearString* js::intl::GlobalIntlData::defaultTimeZone(JSContext* cx) { 114 // Ensure the realm time zone didn't change. 115 if (!ensureRealmTimeZone(cx)) { 116 return nullptr; 117 } 118 119 // If we didn't have a cache hit, compute the default time zone. 120 if (!defaultTimeZone_) { 121 // Cache the computed time zone until the realm time zone changes. 122 defaultTimeZone_ = temporal::ComputeSystemTimeZoneIdentifier(cx); 123 } 124 return defaultTimeZone_; 125 } 126 127 static inline bool EqualLocale(const JSLinearString* str1, 128 const JSLinearString* str2) { 129 if (str1 && str2) { 130 return EqualStrings(str1, str2); 131 } 132 return !str1 && !str2; 133 } 134 135 static inline Value LocaleOrDefault(JSLinearString* locale) { 136 if (locale) { 137 return StringValue(locale); 138 } 139 return UndefinedValue(); 140 } 141 142 CollatorObject* js::intl::GlobalIntlData::getOrCreateCollator( 143 JSContext* cx, Handle<JSLinearString*> locale) { 144 // Ensure the realm locale didn't change. 145 if (!ensureRealmLocale(cx)) { 146 return nullptr; 147 } 148 149 // Ensure the cached locale matches the requested locale. 150 if (!EqualLocale(collatorLocale_, locale)) { 151 resetCollator(); 152 collatorLocale_ = locale; 153 } 154 155 if (!collator_) { 156 Rooted<Value> locales(cx, LocaleOrDefault(locale)); 157 auto* collator = CreateCollator(cx, locales, UndefinedHandleValue); 158 if (!collator) { 159 return nullptr; 160 } 161 collator_ = collator; 162 } 163 164 return &collator_->as<CollatorObject>(); 165 } 166 167 NumberFormatObject* js::intl::GlobalIntlData::getOrCreateNumberFormat( 168 JSContext* cx, Handle<JSLinearString*> locale) { 169 // Ensure the realm locale didn't change. 170 if (!ensureRealmLocale(cx)) { 171 return nullptr; 172 } 173 174 // Ensure the cached locale matches the requested locale. 175 if (!EqualLocale(numberFormatLocale_, locale)) { 176 resetNumberFormat(); 177 numberFormatLocale_ = locale; 178 } 179 180 if (!numberFormat_) { 181 Rooted<Value> locales(cx, LocaleOrDefault(locale)); 182 auto* numberFormat = CreateNumberFormat(cx, locales, UndefinedHandleValue); 183 if (!numberFormat) { 184 return nullptr; 185 } 186 numberFormat_ = numberFormat; 187 } 188 189 return &numberFormat_->as<NumberFormatObject>(); 190 } 191 192 DateTimeFormatObject* js::intl::GlobalIntlData::getOrCreateDateTimeFormat( 193 JSContext* cx, DateTimeFormatKind kind, Handle<JSLinearString*> locale) { 194 // Ensure the realm didn't change. 195 if (!ensureRealmLocale(cx)) { 196 return nullptr; 197 } 198 199 // Ensure the realm time zone didn't change. 200 if (!ensureRealmTimeZone(cx)) { 201 return nullptr; 202 } 203 204 // Ensure the cached locale matches the requested locale. 205 if (!EqualLocale(dateTimeFormatLocale_, locale)) { 206 resetDateTimeFormat(); 207 dateTimeFormatLocale_ = locale; 208 } 209 210 JSObject* dtfObject = nullptr; 211 switch (kind) { 212 case DateTimeFormatKind::All: 213 dtfObject = dateTimeFormatToLocaleAll_; 214 break; 215 case DateTimeFormatKind::Date: 216 dtfObject = dateTimeFormatToLocaleDate_; 217 break; 218 case DateTimeFormatKind::Time: 219 dtfObject = dateTimeFormatToLocaleTime_; 220 break; 221 } 222 223 if (!dtfObject) { 224 Rooted<Value> locales(cx, LocaleOrDefault(locale)); 225 auto* dateTimeFormat = 226 CreateDateTimeFormat(cx, locales, UndefinedHandleValue, kind); 227 if (!dateTimeFormat) { 228 return nullptr; 229 } 230 231 switch (kind) { 232 case DateTimeFormatKind::All: 233 dateTimeFormatToLocaleAll_ = dateTimeFormat; 234 break; 235 case DateTimeFormatKind::Date: 236 dateTimeFormatToLocaleDate_ = dateTimeFormat; 237 break; 238 case DateTimeFormatKind::Time: 239 dateTimeFormatToLocaleTime_ = dateTimeFormat; 240 break; 241 } 242 243 dtfObject = dateTimeFormat; 244 } 245 246 return &dtfObject->as<DateTimeFormatObject>(); 247 } 248 249 temporal::TimeZoneObject* js::intl::GlobalIntlData::getOrCreateDefaultTimeZone( 250 JSContext* cx) { 251 // Ensure the realm time zone didn't change. 252 if (!ensureRealmTimeZone(cx)) { 253 return nullptr; 254 } 255 256 // If we didn't have a cache hit, compute the default time zone. 257 if (!defaultTimeZoneObject_) { 258 Rooted<JSLinearString*> identifier(cx, defaultTimeZone(cx)); 259 if (!identifier) { 260 return nullptr; 261 } 262 263 auto* timeZone = temporal::CreateTimeZoneObject(cx, identifier, identifier); 264 if (!timeZone) { 265 return nullptr; 266 } 267 defaultTimeZoneObject_ = timeZone; 268 } 269 270 return &defaultTimeZoneObject_->as<temporal::TimeZoneObject>(); 271 } 272 273 temporal::TimeZoneObject* js::intl::GlobalIntlData::getOrCreateTimeZone( 274 JSContext* cx, Handle<JSLinearString*> identifier, 275 Handle<JSLinearString*> primaryIdentifier) { 276 // If there's a cached time zone, check if the identifiers are equal. 277 if (timeZoneObject_) { 278 auto* timeZone = &timeZoneObject_->as<temporal::TimeZoneObject>(); 279 if (EqualStrings(timeZone->identifier(), identifier)) { 280 // Primary identifier must match when the identifiers are equal. 281 MOZ_ASSERT( 282 EqualStrings(timeZone->primaryIdentifier(), primaryIdentifier)); 283 284 // Return the cached time zone. 285 return timeZone; 286 } 287 } 288 289 // If we didn't have a cache hit, create a new time zone. 290 auto* timeZone = 291 temporal::CreateTimeZoneObject(cx, identifier, primaryIdentifier); 292 if (!timeZone) { 293 return nullptr; 294 } 295 timeZoneObject_ = timeZone; 296 297 return &timeZone->as<temporal::TimeZoneObject>(); 298 } 299 300 void js::intl::GlobalIntlData::trace(JSTracer* trc) { 301 TraceNullableEdge(trc, &realmLocale_, "GlobalIntlData::realmLocale_"); 302 TraceNullableEdge(trc, &defaultLocale_, "GlobalIntlData::defaultLocale_"); 303 304 TraceNullableEdge(trc, &realmTimeZone_, "GlobalIntlData::realmTimeZone_"); 305 TraceNullableEdge(trc, &defaultTimeZone_, "GlobalIntlData::defaultTimeZone_"); 306 TraceNullableEdge(trc, &defaultTimeZoneObject_, 307 "GlobalIntlData::defaultTimeZoneObject_"); 308 TraceNullableEdge(trc, &timeZoneObject_, "GlobalIntlData::timeZoneObject_"); 309 310 TraceNullableEdge(trc, &collatorLocale_, "GlobalIntlData::collatorLocale_"); 311 TraceNullableEdge(trc, &collator_, "GlobalIntlData::collator_"); 312 313 TraceNullableEdge(trc, &numberFormatLocale_, 314 "GlobalIntlData::numberFormatLocale_"); 315 TraceNullableEdge(trc, &numberFormat_, "GlobalIntlData::numberFormat_"); 316 317 TraceNullableEdge(trc, &dateTimeFormatLocale_, 318 "GlobalIntlData::dateTimeFormatLocale_"); 319 TraceNullableEdge(trc, &dateTimeFormatToLocaleAll_, 320 "GlobalIntlData::dateTimeFormatToLocaleAll_"); 321 TraceNullableEdge(trc, &dateTimeFormatToLocaleDate_, 322 "GlobalIntlData::dateTimeFormatToLocaleDate_"); 323 TraceNullableEdge(trc, &dateTimeFormatToLocaleTime_, 324 "GlobalIntlData::dateTimeFormatToLocaleTime_"); 325 }