tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }