tor-browser

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

OSPreferences.cpp (18608B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 /**
      7 * This is a shared part of the OSPreferences API implementation.
      8 * It defines helper methods and public methods that are calling
      9 * platform-specific private methods.
     10 */
     11 
     12 #include "OSPreferences.h"
     13 
     14 #include "mozilla/ClearOnShutdown.h"
     15 #include "mozilla/intl/DateTimePatternGenerator.h"
     16 #include "mozilla/intl/DateTimeFormat.h"
     17 #include "mozilla/intl/LocaleService.h"
     18 #include "mozilla/Preferences.h"
     19 #include "mozilla/Services.h"
     20 #include "nsIObserverService.h"
     21 
     22 using namespace mozilla::intl;
     23 
     24 NS_IMPL_ISUPPORTS(OSPreferences, mozIOSPreferences)
     25 
     26 mozilla::StaticRefPtr<OSPreferences> OSPreferences::sInstance;
     27 
     28 // Return a new strong reference to the instance, creating it if necessary.
     29 already_AddRefed<OSPreferences> OSPreferences::GetInstanceAddRefed() {
     30  RefPtr<OSPreferences> result = sInstance;
     31  if (!result) {
     32    MOZ_ASSERT(NS_IsMainThread(),
     33               "OSPreferences should be initialized on main thread!");
     34    if (!NS_IsMainThread()) {
     35      return nullptr;
     36    }
     37    sInstance = new OSPreferences();
     38    result = sInstance;
     39 
     40    DebugOnly<nsresult> rv = Preferences::RegisterPrefixCallback(
     41        PreferenceChanged, "intl.date_time.pattern_override");
     42    MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
     43 
     44    ClearOnShutdown(&sInstance);
     45  }
     46  return result.forget();
     47 }
     48 
     49 // Return a raw pointer to the instance: not for off-main-thread use,
     50 // because ClearOnShutdown means it could go away unexpectedly.
     51 OSPreferences* OSPreferences::GetInstance() {
     52  MOZ_ASSERT(NS_IsMainThread());
     53  if (!sInstance) {
     54    // This will create the static instance; then we just drop the extra
     55    // reference.
     56    RefPtr<OSPreferences> result = GetInstanceAddRefed();
     57  }
     58  return sInstance;
     59 }
     60 
     61 void OSPreferences::Refresh() {
     62  // TODO: in content processes, get system locales from the parent process
     63  // to ensure consistency and avoid depending on APIs that may be blocked
     64  // by sandboxing (see bug 1946691).
     65  // (Not strictly necessary for now as we don't currently use Refresh() on
     66  // Windows.)
     67 
     68  nsTArray<nsCString> newLocales;
     69  ReadSystemLocales(newLocales);
     70 
     71  if (mSystemLocales != newLocales) {
     72    mSystemLocales = std::move(newLocales);
     73    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     74    if (obs) {
     75      obs->NotifyObservers(nullptr, "intl:system-locales-changed", nullptr);
     76    }
     77  }
     78 }
     79 
     80 OSPreferences::~OSPreferences() {
     81  Preferences::UnregisterPrefixCallback(PreferenceChanged,
     82                                        "intl.date_time.pattern_override");
     83  RemoveObservers();
     84 }
     85 
     86 /*static*/
     87 void OSPreferences::PreferenceChanged(const char* aPrefName,
     88                                      void* /* aClosure */) {
     89  if (sInstance) {
     90    sInstance->mPatternCache.Clear();
     91  }
     92 }
     93 
     94 /**
     95 * This method should be called by every method of OSPreferences that
     96 * retrieves a locale id from external source.
     97 *
     98 * It attempts to retrieve as much of the locale ID as possible, cutting
     99 * out bits that are not understood (non-strict behavior of ICU).
    100 *
    101 * It returns true if the canonicalization was successful.
    102 */
    103 bool OSPreferences::CanonicalizeLanguageTag(nsCString& aLoc) {
    104  return LocaleService::CanonicalizeLanguageId(aLoc);
    105 }
    106 
    107 /**
    108 * This method retrieves from mozilla::intl the best pattern for a given
    109 * date/time style.
    110 */
    111 bool OSPreferences::GetDateTimePatternForStyle(DateTimeFormatStyle aDateStyle,
    112                                               DateTimeFormatStyle aTimeStyle,
    113                                               const nsACString& aLocale,
    114                                               nsACString& aRetVal) {
    115  DateTimeFormat::StyleBag style;
    116 
    117  switch (aTimeStyle) {
    118    case DateTimeFormatStyle::Short:
    119      style.time = Some(DateTimeFormat::Style::Short);
    120      break;
    121    case DateTimeFormatStyle::Medium:
    122      style.time = Some(DateTimeFormat::Style::Medium);
    123      break;
    124    case DateTimeFormatStyle::Long:
    125      style.time = Some(DateTimeFormat::Style::Long);
    126      break;
    127    case DateTimeFormatStyle::Full:
    128      style.time = Some(DateTimeFormat::Style::Full);
    129      break;
    130    case DateTimeFormatStyle::None:
    131    case DateTimeFormatStyle::Invalid:
    132      // Do nothing.
    133      break;
    134  }
    135 
    136  switch (aDateStyle) {
    137    case DateTimeFormatStyle::Short:
    138      style.date = Some(DateTimeFormat::Style::Short);
    139      break;
    140    case DateTimeFormatStyle::Medium:
    141      style.date = Some(DateTimeFormat::Style::Medium);
    142      break;
    143    case DateTimeFormatStyle::Long:
    144      style.date = Some(DateTimeFormat::Style::Long);
    145      break;
    146    case DateTimeFormatStyle::Full:
    147      style.date = Some(DateTimeFormat::Style::Full);
    148      break;
    149    case DateTimeFormatStyle::None:
    150    case DateTimeFormatStyle::Invalid:
    151      // Do nothing.
    152      break;
    153  }
    154 
    155  nsAutoCString locale;
    156  if (aLocale.IsEmpty()) {
    157    AutoTArray<nsCString, 10> regionalPrefsLocales;
    158    LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales);
    159    locale.Assign(regionalPrefsLocales[0]);
    160  } else {
    161    locale.Assign(aLocale);
    162  }
    163 
    164  auto genResult =
    165      DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
    166  if (genResult.isErr()) {
    167    return false;
    168  }
    169  auto generator = genResult.unwrap();
    170 
    171  auto dfResult = DateTimeFormat::TryCreateFromStyle(
    172      MakeStringSpan(locale.get()), style, generator.get(), Nothing());
    173  if (dfResult.isErr()) {
    174    return false;
    175  }
    176  auto df = dfResult.unwrap();
    177 
    178  DateTimeFormat::PatternVector pattern;
    179  auto patternResult = df->GetPattern(pattern);
    180  if (patternResult.isErr()) {
    181    return false;
    182  }
    183 
    184  aRetVal = NS_ConvertUTF16toUTF8(pattern.begin(), pattern.length());
    185  return true;
    186 }
    187 
    188 /**
    189 * This method retrieves from mozilla::intl the best skeleton for a given
    190 * date/time style.
    191 *
    192 * This is useful for cases where an OS does not provide its own patterns,
    193 * but provide ability to customize the skeleton, like alter hourCycle setting.
    194 *
    195 * The returned value is a skeleton that matches the styles.
    196 */
    197 bool OSPreferences::GetDateTimeSkeletonForStyle(DateTimeFormatStyle aDateStyle,
    198                                                DateTimeFormatStyle aTimeStyle,
    199                                                const nsACString& aLocale,
    200                                                nsACString& aRetVal) {
    201  nsAutoCString pattern;
    202  if (!GetDateTimePatternForStyle(aDateStyle, aTimeStyle, aLocale, pattern)) {
    203    return false;
    204  }
    205 
    206  auto genResult =
    207      DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
    208  if (genResult.isErr()) {
    209    return false;
    210  }
    211 
    212  nsAutoString patternAsUtf16 = NS_ConvertUTF8toUTF16(pattern);
    213  DateTimeFormat::SkeletonVector skeleton;
    214  auto generator = genResult.unwrap();
    215  auto skeletonResult = generator->GetSkeleton(patternAsUtf16, skeleton);
    216  if (skeletonResult.isErr()) {
    217    return false;
    218  }
    219 
    220  aRetVal = NS_ConvertUTF16toUTF8(skeleton.begin(), skeleton.length());
    221  return true;
    222 }
    223 
    224 /**
    225 * This method checks for preferences that override the defaults
    226 */
    227 bool OSPreferences::OverrideDateTimePattern(DateTimeFormatStyle aDateStyle,
    228                                            DateTimeFormatStyle aTimeStyle,
    229                                            const nsACString& aLocale,
    230                                            nsACString& aRetVal) {
    231  const auto PrefToMaybeString = [](const char* pref) -> Maybe<nsAutoCString> {
    232    nsAutoCString value;
    233    nsresult nr = Preferences::GetCString(pref, value);
    234    if (NS_FAILED(nr) || value.IsEmpty()) {
    235      return Nothing();
    236    }
    237    return Some(std::move(value));
    238  };
    239 
    240  Maybe<nsAutoCString> timeSkeleton;
    241  switch (aTimeStyle) {
    242    case DateTimeFormatStyle::Short:
    243      timeSkeleton =
    244          PrefToMaybeString("intl.date_time.pattern_override.time_short");
    245      break;
    246    case DateTimeFormatStyle::Medium:
    247      timeSkeleton =
    248          PrefToMaybeString("intl.date_time.pattern_override.time_medium");
    249      break;
    250    case DateTimeFormatStyle::Long:
    251      timeSkeleton =
    252          PrefToMaybeString("intl.date_time.pattern_override.time_long");
    253      break;
    254    case DateTimeFormatStyle::Full:
    255      timeSkeleton =
    256          PrefToMaybeString("intl.date_time.pattern_override.time_full");
    257      break;
    258    default:
    259      break;
    260  }
    261 
    262  Maybe<nsAutoCString> dateSkeleton;
    263  switch (aDateStyle) {
    264    case DateTimeFormatStyle::Short:
    265      dateSkeleton =
    266          PrefToMaybeString("intl.date_time.pattern_override.date_short");
    267      break;
    268    case DateTimeFormatStyle::Medium:
    269      dateSkeleton =
    270          PrefToMaybeString("intl.date_time.pattern_override.date_medium");
    271      break;
    272    case DateTimeFormatStyle::Long:
    273      dateSkeleton =
    274          PrefToMaybeString("intl.date_time.pattern_override.date_long");
    275      break;
    276    case DateTimeFormatStyle::Full:
    277      dateSkeleton =
    278          PrefToMaybeString("intl.date_time.pattern_override.date_full");
    279      break;
    280    default:
    281      break;
    282  }
    283 
    284  nsAutoCString locale;
    285  if (aLocale.IsEmpty()) {
    286    AutoTArray<nsCString, 10> regionalPrefsLocales;
    287    LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales);
    288    locale.Assign(regionalPrefsLocales[0]);
    289  } else {
    290    locale.Assign(aLocale);
    291  }
    292 
    293  const auto FillConnectorPattern = [&locale](
    294                                        const nsAutoCString& datePattern,
    295                                        const nsAutoCString& timePattern) {
    296    nsAutoCString pattern;
    297    GetDateTimeConnectorPattern(nsDependentCString(locale.get()), pattern);
    298    int32_t index = pattern.Find("{1}");
    299    if (index != kNotFound) {
    300      pattern.Replace(index, 3, datePattern);
    301    }
    302    index = pattern.Find("{0}");
    303    if (index != kNotFound) {
    304      pattern.Replace(index, 3, timePattern);
    305    }
    306    return pattern;
    307  };
    308 
    309  if (timeSkeleton && dateSkeleton) {
    310    aRetVal.Assign(FillConnectorPattern(*dateSkeleton, *timeSkeleton));
    311  } else if (timeSkeleton) {
    312    if (aDateStyle != DateTimeFormatStyle::None) {
    313      nsAutoCString pattern;
    314      if (!ReadDateTimePattern(aDateStyle, DateTimeFormatStyle::None, aLocale,
    315                               pattern) &&
    316          !GetDateTimePatternForStyle(aDateStyle, DateTimeFormatStyle::None,
    317                                      aLocale, pattern)) {
    318        return false;
    319      }
    320      aRetVal.Assign(FillConnectorPattern(pattern, *timeSkeleton));
    321    } else {
    322      aRetVal.Assign(*timeSkeleton);
    323    }
    324  } else if (dateSkeleton) {
    325    if (aTimeStyle != DateTimeFormatStyle::None) {
    326      nsAutoCString pattern;
    327      if (!ReadDateTimePattern(DateTimeFormatStyle::None, aTimeStyle, aLocale,
    328                               pattern) &&
    329          !GetDateTimePatternForStyle(DateTimeFormatStyle::None, aTimeStyle,
    330                                      aLocale, pattern)) {
    331        return false;
    332      }
    333      aRetVal.Assign(FillConnectorPattern(*dateSkeleton, pattern));
    334    } else {
    335      aRetVal.Assign(*dateSkeleton);
    336    }
    337  } else {
    338    return false;
    339  }
    340 
    341  return true;
    342 }
    343 
    344 /**
    345 * This function is a counterpart to GetDateTimeSkeletonForStyle.
    346 *
    347 * It takes a skeleton and returns the best available pattern for a given locale
    348 * that represents the provided skeleton.
    349 *
    350 * For example:
    351 * "Hm" skeleton for "en-US" will return "H:m"
    352 */
    353 bool OSPreferences::GetPatternForSkeleton(const nsACString& aSkeleton,
    354                                          const nsACString& aLocale,
    355                                          nsACString& aRetVal) {
    356  aRetVal.Truncate();
    357 
    358  auto genResult =
    359      DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
    360  if (genResult.isErr()) {
    361    return false;
    362  }
    363 
    364  nsAutoString skeletonAsUtf16 = NS_ConvertUTF8toUTF16(aSkeleton);
    365  DateTimeFormat::PatternVector pattern;
    366  auto generator = genResult.unwrap();
    367  auto patternResult = generator->GetBestPattern(skeletonAsUtf16, pattern);
    368  if (patternResult.isErr()) {
    369    return false;
    370  }
    371 
    372  aRetVal = NS_ConvertUTF16toUTF8(pattern.begin(), pattern.length());
    373  return true;
    374 }
    375 
    376 /**
    377 * This function returns a pattern that should be used to join date and time
    378 * patterns into a single date/time pattern string.
    379 *
    380 * It's useful for OSes that do not provide an API to retrieve such combined
    381 * pattern.
    382 *
    383 * An example output is "{1}, {0}".
    384 */
    385 bool OSPreferences::GetDateTimeConnectorPattern(const nsACString& aLocale,
    386                                                nsACString& aRetVal) {
    387  // Check for a valid override pref and use that if present.
    388  nsAutoCString value;
    389  nsresult nr = Preferences::GetCString(
    390      "intl.date_time.pattern_override.connector_short", value);
    391  if (NS_SUCCEEDED(nr) && value.Find("{0}") != kNotFound &&
    392      value.Find("{1}") != kNotFound) {
    393    aRetVal = std::move(value);
    394    return true;
    395  }
    396 
    397  auto genResult =
    398      DateTimePatternGenerator::TryCreate(PromiseFlatCString(aLocale).get());
    399  if (genResult.isErr()) {
    400    return false;
    401  }
    402 
    403  auto generator = genResult.unwrap();
    404  Span<const char16_t> result = generator->GetPlaceholderPattern();
    405  aRetVal = NS_ConvertUTF16toUTF8(result.data(), result.size());
    406  return true;
    407 }
    408 
    409 /**
    410 * mozIOSPreferences methods
    411 */
    412 NS_IMETHODIMP
    413 OSPreferences::GetSystemLocales(nsTArray<nsCString>& aRetVal) {
    414  if (!mSystemLocales.IsEmpty()) {
    415    aRetVal = mSystemLocales.Clone();
    416    return NS_OK;
    417  }
    418 
    419  if (ReadSystemLocales(aRetVal)) {
    420    mSystemLocales = aRetVal.Clone();
    421    return NS_OK;
    422  }
    423 
    424  // If we failed to get the system locale, we still need
    425  // to return something because there are tests out there that
    426  // depend on system locale to be set.
    427  aRetVal.AppendElement("en-US"_ns);
    428  return NS_ERROR_FAILURE;
    429 }
    430 
    431 NS_IMETHODIMP
    432 OSPreferences::GetSystemLocale(nsACString& aRetVal) {
    433  if (!mSystemLocales.IsEmpty()) {
    434    aRetVal = mSystemLocales[0];
    435  } else {
    436    AutoTArray<nsCString, 10> locales;
    437    GetSystemLocales(locales);
    438    if (!locales.IsEmpty()) {
    439      aRetVal = locales[0];
    440    }
    441  }
    442  return NS_OK;
    443 }
    444 
    445 NS_IMETHODIMP
    446 OSPreferences::GetRegionalPrefsLocales(nsTArray<nsCString>& aRetVal) {
    447  if (!mRegionalPrefsLocales.IsEmpty()) {
    448    aRetVal = mRegionalPrefsLocales.Clone();
    449    return NS_OK;
    450  }
    451 
    452  if (ReadRegionalPrefsLocales(aRetVal)) {
    453    mRegionalPrefsLocales = aRetVal.Clone();
    454    return NS_OK;
    455  }
    456 
    457  // If we failed to read regional prefs locales,
    458  // use system locales as last fallback.
    459  return GetSystemLocales(aRetVal);
    460 }
    461 
    462 static OSPreferences::DateTimeFormatStyle ToDateTimeFormatStyle(
    463    int32_t aTimeFormat) {
    464  switch (aTimeFormat) {
    465    // See mozIOSPreferences.idl for the integer values here.
    466    case 0:
    467      return OSPreferences::DateTimeFormatStyle::None;
    468    case 1:
    469      return OSPreferences::DateTimeFormatStyle::Short;
    470    case 2:
    471      return OSPreferences::DateTimeFormatStyle::Medium;
    472    case 3:
    473      return OSPreferences::DateTimeFormatStyle::Long;
    474    case 4:
    475      return OSPreferences::DateTimeFormatStyle::Full;
    476  }
    477  return OSPreferences::DateTimeFormatStyle::Invalid;
    478 }
    479 
    480 NS_IMETHODIMP
    481 OSPreferences::GetDateTimePattern(int32_t aDateFormatStyle,
    482                                  int32_t aTimeFormatStyle,
    483                                  const nsACString& aLocale,
    484                                  nsACString& aRetVal) {
    485  DateTimeFormatStyle dateStyle = ToDateTimeFormatStyle(aDateFormatStyle);
    486  if (dateStyle == DateTimeFormatStyle::Invalid) {
    487    return NS_ERROR_INVALID_ARG;
    488  }
    489  DateTimeFormatStyle timeStyle = ToDateTimeFormatStyle(aTimeFormatStyle);
    490  if (timeStyle == DateTimeFormatStyle::Invalid) {
    491    return NS_ERROR_INVALID_ARG;
    492  }
    493 
    494  // If the user is asking for None on both, date and time style,
    495  // let's exit early.
    496  if (timeStyle == DateTimeFormatStyle::None &&
    497      dateStyle == DateTimeFormatStyle::None) {
    498    return NS_OK;
    499  }
    500 
    501  // If the locale is not specified, default to first regional prefs locale
    502  const nsACString* locale = &aLocale;
    503  AutoTArray<nsCString, 10> rpLocales;
    504  if (aLocale.IsEmpty()) {
    505    LocaleService::GetInstance()->GetRegionalPrefsLocales(rpLocales);
    506    MOZ_ASSERT(rpLocales.Length() > 0);
    507    locale = &rpLocales[0];
    508  }
    509 
    510  // Create a cache key from the locale + style options
    511  nsAutoCString key(*locale);
    512  key.Append(':');
    513  key.AppendInt(aDateFormatStyle);
    514  key.Append(':');
    515  key.AppendInt(aTimeFormatStyle);
    516 
    517  nsCString pattern;
    518  if (mPatternCache.Get(key, &pattern)) {
    519    aRetVal = pattern;
    520    return NS_OK;
    521  }
    522 
    523  if (!OverrideDateTimePattern(dateStyle, timeStyle, *locale, pattern)) {
    524    if (!ReadDateTimePattern(dateStyle, timeStyle, *locale, pattern)) {
    525      if (!GetDateTimePatternForStyle(dateStyle, timeStyle, *locale, pattern)) {
    526        return NS_ERROR_FAILURE;
    527      }
    528    }
    529  }
    530 
    531  if (mPatternCache.Count() == kMaxCachedPatterns) {
    532    // Don't allow unlimited cache growth; just throw it away in the case of
    533    // pathological behavior where a page keeps requesting different formats
    534    // and locales.
    535    NS_WARNING("flushing DateTimePattern cache");
    536    mPatternCache.Clear();
    537  }
    538  mPatternCache.InsertOrUpdate(key, pattern);
    539 
    540  aRetVal = pattern;
    541  return NS_OK;
    542 }
    543 
    544 void OSPreferences::OverrideSkeletonHourCycle(bool aIs24Hour,
    545                                              nsAutoCString& aSkeleton) {
    546  if (aIs24Hour) {
    547    // If aSkeleton contains 'h' or 'K', replace with 'H' or 'k' respectively,
    548    // and delete 'a' if present.
    549    if (aSkeleton.FindChar('h') == -1 && aSkeleton.FindChar('K') == -1) {
    550      return;
    551    }
    552    for (int32_t i = 0; i < int32_t(aSkeleton.Length()); ++i) {
    553      switch (aSkeleton[i]) {
    554        case 'a':
    555          aSkeleton.Cut(i, 1);
    556          --i;
    557          break;
    558        case 'h':
    559          aSkeleton.SetCharAt('H', i);
    560          break;
    561        case 'K':
    562          aSkeleton.SetCharAt('k', i);
    563          break;
    564      }
    565    }
    566  } else {
    567    // If skeleton contains 'H' or 'k', replace with 'h' or 'K' respectively,
    568    // and add 'a' unless already present.
    569    if (aSkeleton.FindChar('H') == -1 && aSkeleton.FindChar('k') == -1) {
    570      return;
    571    }
    572    bool foundA = false;
    573    for (size_t i = 0; i < aSkeleton.Length(); ++i) {
    574      switch (aSkeleton[i]) {
    575        case 'a':
    576          foundA = true;
    577          break;
    578        case 'H':
    579          aSkeleton.SetCharAt('h', i);
    580          break;
    581        case 'k':
    582          aSkeleton.SetCharAt('K', i);
    583          break;
    584      }
    585    }
    586    if (!foundA) {
    587      aSkeleton.Append(char16_t('a'));
    588    }
    589  }
    590 }