tor-browser

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

OSPreferences_win.cpp (11278B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 *
      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 "OSPreferences.h"
      8 #include "mozilla/intl/Locale.h"
      9 #include "mozilla/intl/LocaleService.h"
     10 #include "nsReadableUtils.h"
     11 
     12 #include <windows.h>
     13 
     14 #ifndef __MINGW32__  // WinRT headers not yet supported by MinGW
     15 #  include <roapi.h>
     16 #  include <wrl.h>
     17 #  include <Windows.System.UserProfile.h>
     18 
     19 using namespace Microsoft::WRL;
     20 using namespace Microsoft::WRL::Wrappers;
     21 using namespace ABI::Windows::Foundation::Collections;
     22 using namespace ABI::Windows::System::UserProfile;
     23 #endif
     24 
     25 using namespace mozilla::intl;
     26 
     27 OSPreferences::OSPreferences() {}
     28 
     29 bool OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList) {
     30  MOZ_ASSERT(aLocaleList.IsEmpty());
     31 
     32 #ifndef __MINGW32__
     33  // Try to get language list from GlobalizationPreferences; if this fails,
     34  // we'll fall back to GetUserPreferredUILanguages.
     35  // Per MSDN, these APIs are not available prior to Win8.
     36  ComPtr<IGlobalizationPreferencesStatics> globalizationPrefs;
     37  ComPtr<IVectorView<HSTRING>> languages;
     38  uint32_t count;
     39  if (SUCCEEDED(RoGetActivationFactory(
     40          HStringReference(
     41              RuntimeClass_Windows_System_UserProfile_GlobalizationPreferences)
     42              .Get(),
     43          IID_PPV_ARGS(&globalizationPrefs))) &&
     44      SUCCEEDED(globalizationPrefs->get_Languages(&languages)) &&
     45      SUCCEEDED(languages->get_Size(&count))) {
     46    for (uint32_t i = 0; i < count; ++i) {
     47      HString lang;
     48      if (SUCCEEDED(languages->GetAt(i, lang.GetAddressOf()))) {
     49        unsigned int length;
     50        const wchar_t* text = lang.GetRawBuffer(&length);
     51        NS_LossyConvertUTF16toASCII loc(text, length);
     52        if (CanonicalizeLanguageTag(loc)) {
     53          if (!loc.Contains('-')) {
     54            // DirectWrite font-name code doesn't like to be given a bare
     55            // language code with no region subtag, but the
     56            // GlobalizationPreferences API may give us one (e.g. "ja").
     57            // So if there's no hyphen in the string at this point, we use
     58            // AddLikelySubtags to get a suitable region code to
     59            // go with it.
     60            Locale locale;
     61            auto result = LocaleParser::TryParse(loc, locale);
     62            if (result.isOk() && locale.AddLikelySubtags().isOk() &&
     63                locale.Region().Present()) {
     64              loc.Append('-');
     65              loc.Append(locale.Region().Span());
     66            }
     67          }
     68          aLocaleList.AppendElement(loc);
     69        }
     70      }
     71    }
     72  }
     73 #endif
     74 
     75  // Per MSDN, GetUserPreferredUILanguages is available from Vista onwards,
     76  // so we can use it unconditionally (although it may not work well!)
     77  if (aLocaleList.IsEmpty()) {
     78    // Note that according to the questioner at
     79    // https://stackoverflow.com/questions/52849233/getuserpreferreduilanguages-never-returns-more-than-two-languages,
     80    // this may not always return the full list of languages we'd expect.
     81    // We should always get at least the first-preference lang, though.
     82    ULONG numLanguages = 0;
     83    DWORD cchLanguagesBuffer = 0;
     84    if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages, nullptr,
     85                                     &cchLanguagesBuffer)) {
     86      return false;
     87    }
     88 
     89    AutoTArray<WCHAR, 64> locBuffer;
     90    locBuffer.SetCapacity(cchLanguagesBuffer);
     91    if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
     92                                     locBuffer.Elements(),
     93                                     &cchLanguagesBuffer)) {
     94      return false;
     95    }
     96 
     97    const WCHAR* start = locBuffer.Elements();
     98    const WCHAR* bufEnd = start + cchLanguagesBuffer;
     99    while (bufEnd - start > 1 && *start) {
    100      const WCHAR* end = start + 1;
    101      while (bufEnd - end > 1 && *end) {
    102        end++;
    103      }
    104      NS_LossyConvertUTF16toASCII loc(start, end - start);
    105      if (CanonicalizeLanguageTag(loc)) {
    106        aLocaleList.AppendElement(loc);
    107      }
    108      start = end + 1;
    109    }
    110  }
    111 
    112  return !aLocaleList.IsEmpty();
    113 }
    114 
    115 bool OSPreferences::ReadRegionalPrefsLocales(nsTArray<nsCString>& aLocaleList) {
    116  MOZ_ASSERT(aLocaleList.IsEmpty());
    117 
    118  WCHAR locale[LOCALE_NAME_MAX_LENGTH];
    119  if (NS_WARN_IF(!LCIDToLocaleName(LOCALE_USER_DEFAULT, locale,
    120                                   LOCALE_NAME_MAX_LENGTH, 0))) {
    121    return false;
    122  }
    123 
    124  NS_LossyConvertUTF16toASCII loc(locale);
    125 
    126  if (CanonicalizeLanguageTag(loc)) {
    127    aLocaleList.AppendElement(loc);
    128    return true;
    129  }
    130  return false;
    131 }
    132 
    133 static LCTYPE ToDateLCType(OSPreferences::DateTimeFormatStyle aFormatStyle) {
    134  switch (aFormatStyle) {
    135    case OSPreferences::DateTimeFormatStyle::None:
    136      return LOCALE_SLONGDATE;
    137    case OSPreferences::DateTimeFormatStyle::Short:
    138      return LOCALE_SSHORTDATE;
    139    case OSPreferences::DateTimeFormatStyle::Medium:
    140      return LOCALE_SSHORTDATE;
    141    case OSPreferences::DateTimeFormatStyle::Long:
    142      return LOCALE_SLONGDATE;
    143    case OSPreferences::DateTimeFormatStyle::Full:
    144      return LOCALE_SLONGDATE;
    145    case OSPreferences::DateTimeFormatStyle::Invalid:
    146    default:
    147      MOZ_ASSERT_UNREACHABLE("invalid date format");
    148      return LOCALE_SLONGDATE;
    149  }
    150 }
    151 
    152 static LCTYPE ToTimeLCType(OSPreferences::DateTimeFormatStyle aFormatStyle) {
    153  switch (aFormatStyle) {
    154    case OSPreferences::DateTimeFormatStyle::None:
    155      return LOCALE_STIMEFORMAT;
    156    case OSPreferences::DateTimeFormatStyle::Short:
    157      return LOCALE_SSHORTTIME;
    158    case OSPreferences::DateTimeFormatStyle::Medium:
    159      return LOCALE_SSHORTTIME;
    160    case OSPreferences::DateTimeFormatStyle::Long:
    161      return LOCALE_STIMEFORMAT;
    162    case OSPreferences::DateTimeFormatStyle::Full:
    163      return LOCALE_STIMEFORMAT;
    164    case OSPreferences::DateTimeFormatStyle::Invalid:
    165    default:
    166      MOZ_ASSERT_UNREACHABLE("invalid time format");
    167      return LOCALE_STIMEFORMAT;
    168  }
    169 }
    170 
    171 /**
    172 * Windows API includes regional preferences from the user only
    173 * if we pass empty locale string or if the locale string matches
    174 * the current locale.
    175 *
    176 * Since Windows API only allows us to retrieve two options - short/long
    177 * we map it to our four options as:
    178 *
    179 *   short  -> short
    180 *   medium -> short
    181 *   long   -> long
    182 *   full   -> long
    183 *
    184 * In order to produce a single date/time format, we use CLDR pattern
    185 * for combined date/time string, since Windows API does not provide an
    186 * option for this.
    187 */
    188 bool OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
    189                                        DateTimeFormatStyle aTimeStyle,
    190                                        const nsACString& aLocale,
    191                                        nsACString& aRetVal) {
    192  nsAutoString localeName;
    193  CopyASCIItoUTF16(aLocale, localeName);
    194 
    195  bool isDate = aDateStyle != DateTimeFormatStyle::None &&
    196                aDateStyle != DateTimeFormatStyle::Invalid;
    197  bool isTime = aTimeStyle != DateTimeFormatStyle::None &&
    198                aTimeStyle != DateTimeFormatStyle::Invalid;
    199 
    200  // If both date and time are wanted, we'll initially read them into a
    201  // local string, and then insert them into the overall date+time pattern;
    202  nsAutoString str;
    203  if (isDate && isTime) {
    204    if (!GetDateTimeConnectorPattern(aLocale, aRetVal)) {
    205      NS_WARNING("failed to get date/time connector");
    206      aRetVal.AssignLiteral("{1} {0}");
    207    }
    208  } else if (!isDate && !isTime) {
    209    aRetVal.Truncate(0);
    210    return true;
    211  }
    212 
    213  if (isDate) {
    214    LCTYPE lcType = ToDateLCType(aDateStyle);
    215    size_t len = GetLocaleInfoEx(
    216        reinterpret_cast<const wchar_t*>(localeName.BeginReading()), lcType,
    217        nullptr, 0);
    218    if (len == 0) {
    219      return false;
    220    }
    221 
    222    // We're doing it to ensure the terminator will fit when Windows writes the
    223    // data to its output buffer. See bug 1358159 for details.
    224    str.SetLength(len);
    225    GetLocaleInfoEx(reinterpret_cast<const wchar_t*>(localeName.BeginReading()),
    226                    lcType, (WCHAR*)str.BeginWriting(), len);
    227    str.SetLength(len - 1);  // -1 because len counts the null terminator
    228 
    229    // Windows uses "ddd" and "dddd" for abbreviated and full day names
    230    // respectively,
    231    //   https://msdn.microsoft.com/en-us/library/windows/desktop/dd317787(v=vs.85).aspx
    232    // but in a CLDR/ICU-style pattern these should be "EEE" and "EEEE".
    233    //   http://userguide.icu-project.org/formatparse/datetime
    234    // So we fix that up here.
    235    nsAString::const_iterator start, pos, end;
    236    start = str.BeginReading(pos);
    237    str.EndReading(end);
    238    if (FindInReadable(u"dddd"_ns, pos, end)) {
    239      str.ReplaceLiteral(pos - start, 4, u"EEEE");
    240    } else {
    241      pos = start;
    242      if (FindInReadable(u"ddd"_ns, pos, end)) {
    243        str.ReplaceLiteral(pos - start, 3, u"EEE");
    244      }
    245    }
    246 
    247    // Also, Windows uses lowercase "g" or "gg" for era, but ICU wants uppercase
    248    // "G" (it would interpret "g" as "modified Julian day"!). So fix that.
    249    int32_t index = str.FindChar('g');
    250    if (index >= 0) {
    251      str.Replace(index, 1, 'G');
    252      // If it was a double "gg", just drop the second one.
    253      index++;
    254      if (str.CharAt(index) == 'g') {
    255        str.Cut(index, 1);
    256      }
    257    }
    258 
    259    // If time was also requested, we need to substitute the date pattern from
    260    // Windows into the date+time format that we have in aRetVal.
    261    if (isTime) {
    262      nsACString::const_iterator start, pos, end;
    263      start = aRetVal.BeginReading(pos);
    264      aRetVal.EndReading(end);
    265      if (FindInReadable("{1}"_ns, pos, end)) {
    266        aRetVal.Replace(pos - start, 3, NS_ConvertUTF16toUTF8(str));
    267      }
    268    } else {
    269      aRetVal = NS_ConvertUTF16toUTF8(str);
    270    }
    271  }
    272 
    273  if (isTime) {
    274    LCTYPE lcType = ToTimeLCType(aTimeStyle);
    275    size_t len = GetLocaleInfoEx(
    276        reinterpret_cast<const wchar_t*>(localeName.BeginReading()), lcType,
    277        nullptr, 0);
    278    if (len == 0) {
    279      return false;
    280    }
    281 
    282    // We're doing it to ensure the terminator will fit when Windows writes the
    283    // data to its output buffer. See bug 1358159 for details.
    284    str.SetLength(len);
    285    GetLocaleInfoEx(reinterpret_cast<const wchar_t*>(localeName.BeginReading()),
    286                    lcType, (WCHAR*)str.BeginWriting(), len);
    287    str.SetLength(len - 1);
    288 
    289    // Windows uses "t" or "tt" for a "time marker" (am/pm indicator),
    290    //   https://msdn.microsoft.com/en-us/library/windows/desktop/dd318148(v=vs.85).aspx
    291    // but in a CLDR/ICU-style pattern that should be "a".
    292    //   http://userguide.icu-project.org/formatparse/datetime
    293    // So we fix that up here.
    294    int32_t index = str.FindChar('t');
    295    if (index >= 0) {
    296      str.Replace(index, 1, 'a');
    297      index++;
    298      if (str.CharAt(index) == 't') {
    299        str.Cut(index, 1);
    300      }
    301    }
    302 
    303    if (isDate) {
    304      nsACString::const_iterator start, pos, end;
    305      start = aRetVal.BeginReading(pos);
    306      aRetVal.EndReading(end);
    307      if (FindInReadable("{0}"_ns, pos, end)) {
    308        aRetVal.Replace(pos - start, 3, NS_ConvertUTF16toUTF8(str));
    309      }
    310    } else {
    311      aRetVal = NS_ConvertUTF16toUTF8(str);
    312    }
    313  }
    314 
    315  return true;
    316 }
    317 
    318 void OSPreferences::RemoveObservers() {}