Bug 1333184 - Introduce OSPreferences API. r?jfkthame draft
authorZibi Braniecki <gandalf@mozilla.com>
Sun, 05 Feb 2017 11:41:43 -0800
changeset 479437 a14cdcb4bb563a37b66ff09aac91de83ed98f7a9
parent 479078 157ac74dfab5933e6a0dfbdcd2c9b4f160674c93
child 544687 95de0a45a3a95c449fdfed56e0802901b5148cac
push id44257
push userzbraniecki@mozilla.com
push dateMon, 06 Feb 2017 17:58:22 +0000
reviewersjfkthame
bugs1333184
milestone54.0a1
Bug 1333184 - Introduce OSPreferences API. r?jfkthame MozReview-Commit-ID: ALvLGtBmRgn
intl/locale/OSPreferences.cpp
intl/locale/OSPreferences.h
intl/locale/mac/OSPreferences_mac.cpp
intl/locale/mac/moz.build
intl/locale/moz.build
intl/locale/tests/gtest/TestOSPreferences.cpp
intl/locale/tests/gtest/moz.build
intl/locale/unix/OSPreferences_unix.cpp
intl/locale/unix/moz.build
intl/locale/windows/OSPreferences_win.cpp
intl/locale/windows/moz.build
new file mode 100644
--- /dev/null
+++ b/intl/locale/OSPreferences.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is a shared part of the OSPreferences API implementation.
+ * It defines helper methods and public methods that are calling
+ * platform-specific private methods.
+ */
+
+#include "OSPreferences.h"
+#include "mozilla/ClearOnShutdown.h"
+
+using namespace mozilla::intl;
+
+mozilla::StaticAutoPtr<OSPreferences> OSPreferences::sInstance;
+
+OSPreferences* OSPreferences::GetInstance()
+{
+  if (!sInstance) {
+    sInstance = new OSPreferences();
+    ClearOnShutdown(&sInstance);
+  }
+  return sInstance;
+}
+
+bool
+OSPreferences::GetSystemLocales(nsTArray<nsCString>& aRetVal)
+{
+  bool status = true;
+  if (mSystemLocales.IsEmpty()) {
+    status = ReadSystemLocales(mSystemLocales);
+  }
+  aRetVal = mSystemLocales;
+  return status;
+}
+
+/**
+ * This method should be called by every method of OSPreferences that
+ * retrieves a locale id from external source.
+ *
+ * It attempts to retrieve as much of the locale ID as possible, cutting
+ * out bits that are not understood (non-strict behavior of ICU).
+ *
+ * It returns true if the canonicalization was successful.
+ */
+bool
+OSPreferences::CanonicalizeLanguageTag(nsCString& aLoc)
+{
+  char langTag[512];
+
+  UErrorCode status = U_ZERO_ERROR;
+
+  int32_t langTagLen =
+    uloc_toLanguageTag(aLoc.get(), langTag, sizeof(langTag) - 1, false, &status);
+
+  if (U_FAILURE(status)) {
+    return false;
+  }
+
+  aLoc.Assign(langTag, langTagLen);
+  return true;
+}
new file mode 100644
--- /dev/null
+++ b/intl/locale/OSPreferences.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_intl_IntlOSPreferences_h__
+#define mozilla_intl_IntlOSPreferences_h__
+
+#include "mozilla/StaticPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "unicode/uloc.h"
+
+namespace mozilla {
+namespace intl {
+
+/**
+ * OSPreferences API provides a set of methods for retrieving information from
+ * the host environment on topics such as:
+ *   - Internationalization
+ *   - Localization
+ *   - Regional preferences
+ *
+ * The API is meant to remain as simple as possible, relaying information from
+ * the host environment to the user without too much logic.
+ *
+ * Saying that, there are two exceptions to that paradigm.
+ *
+ * First one is normalization. We do intend to translate host environment
+ * concepts to unified Intl/L10n vocabulary used by Mozilla.
+ * That means that we will format locale IDs, timezone names, currencies etc.
+ * into a chosen format.
+ *
+ * Second is caching. This API does cache values and where possible will
+ * hook into the environment for some event-driven cache invalidation.
+ *
+ * This means that on platforms that do not support a mechanism to
+ * notify apps about changes, new OS-level settings may not be reflected
+ * in the app until it is relaunched.
+ */
+class OSPreferences
+{
+
+public:
+  static OSPreferences* GetInstance();
+
+  /**
+   * Returns a list of locales used by the host environment.
+   *
+   * The result is a sorted list and we expect that the OS attempts to
+   * use the top locale from the list for which it has data.
+   *
+   * Each element of the list is a valid locale ID that can be passed to ICU
+   * and ECMA402 Intl APIs,
+   * At the same time each element is a valid BCP47 language tag that can be
+   * used for language negotiation.
+   *
+   * Example: ["en-US", "de", "pl", "sr-Cyrl", "zh-Hans-HK"]
+   *
+   * The return bool value indicates whether the function successfully
+   * resolved at least one locale.
+   *
+   * Usage:
+   *   nsTArray<nsCString> systemLocales;
+   *   OSPreferences::GetInstance()->GetSystemLocales(systemLocales);
+   */
+  bool GetSystemLocales(nsTArray<nsCString>& aRetVal);
+
+protected:
+  nsTArray<nsCString> mSystemLocales;
+
+private:
+  static StaticAutoPtr<OSPreferences> sInstance;
+
+  static bool CanonicalizeLanguageTag(nsCString& aLoc);
+
+  /**
+   * This is a host environment specific method that will be implemented
+   * separately for each platform.
+   *
+   * It is only called when the cache is empty or invalidated.
+   *
+   * The return value indicates whether the function successfully
+   * resolved at least one locale.
+   */
+  bool ReadSystemLocales(nsTArray<nsCString>& aRetVal);
+};
+
+} // intl
+} // namespace mozilla
+
+#endif /* mozilla_intl_IntlOSPreferences_h__ */
new file mode 100644
--- /dev/null
+++ b/intl/locale/mac/OSPreferences_mac.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OSPreferences.h"
+#include <Carbon/Carbon.h>
+
+using namespace mozilla::intl;
+
+bool
+OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList)
+{
+  MOZ_ASSERT(aLocaleList.IsEmpty());
+
+  // Get string representation of user's current locale
+  CFLocaleRef userLocaleRef = ::CFLocaleCopyCurrent();
+  CFStringRef userLocaleStr = ::CFLocaleGetIdentifier(userLocaleRef);
+
+  AutoTArray<UniChar, 32> buffer;
+  int size = ::CFStringGetLength(userLocaleStr);
+
+  CFRange range = ::CFRangeMake(0, size);
+  ::CFStringGetCharacters(userLocaleStr, range, buffer.Elements());
+
+  // Convert the locale string to the format that Mozilla expects
+  NS_LossyConvertUTF16toASCII locale(
+      reinterpret_cast<const char16_t*>(buffer.Elements()), buffer.Length());
+
+  CFRelease(userLocaleRef);
+
+  if (CanonicalizeLanguageTag(locale)) {
+    aLocaleList.AppendElement(locale);
+    return true;
+  }
+
+  return false;
+}
--- a/intl/locale/mac/moz.build
+++ b/intl/locale/mac/moz.build
@@ -4,12 +4,15 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 UNIFIED_SOURCES += [
     'nsCollationMacUC.cpp',
     'nsMacCharset.cpp',
 ]
 
+if CONFIG['ENABLE_INTL_API']:
+    UNIFIED_SOURCES += ['OSPreferences_mac.cpp']
+
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '..',
 ]
--- a/intl/locale/moz.build
+++ b/intl/locale/moz.build
@@ -1,16 +1,19 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
+if CONFIG['ENABLE_INTL_API']:
+    SOURCES += ['OSPreferences.cpp']
+
 toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
 
 if toolkit == 'windows':
     DIRS += ['windows']
 elif toolkit == 'cocoa':
     DIRS += ['mac']
 else:
     DIRS += ['unix']
new file mode 100644
--- /dev/null
+++ b/intl/locale/tests/gtest/TestOSPreferences.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "OSPreferences.h"
+
+using namespace mozilla::intl;
+
+/**
+ * We test that on all platforms we test against (irrelevant of the tier),
+ * we will be able to retrieve at least a single locale out of the system.
+ *
+ * In theory, that may not be true, but if we encounter such platform we should
+ * decide how to handle this and special case and this test should make
+ * it not happen without us noticing.
+ */
+TEST(Intl_Locale_OSPreferences, GetSystemLocales) {
+  nsTArray<nsCString> systemLocales;
+  ASSERT_TRUE(OSPreferences::GetInstance()->GetSystemLocales(systemLocales));
+
+  ASSERT_FALSE(systemLocales.IsEmpty());
+}
--- a/intl/locale/tests/gtest/moz.build
+++ b/intl/locale/tests/gtest/moz.build
@@ -3,13 +3,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 UNIFIED_SOURCES += [
     'TestLocaleService.cpp',
 ]
 
+if CONFIG['ENABLE_INTL_API']:
+    UNIFIED_SOURCES += ['TestOSPreferences.cpp']
+
 LOCAL_INCLUDES += [
     '/intl/locale',
 ]
 
 FINAL_LIBRARY = 'xul-gtest'
new file mode 100644
--- /dev/null
+++ b/intl/locale/unix/OSPreferences_unix.cpp
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OSPreferences.h"
+
+using namespace mozilla::intl;
+
+bool
+OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList)
+{
+  MOZ_ASSERT(aLocaleList.IsEmpty());
+
+  nsAutoCString defaultLang(uloc_getDefault());
+
+  if (CanonicalizeLanguageTag(defaultLang)) {
+    aLocaleList.AppendElement(defaultLang);
+    return true;
+  }
+  return false;
+}
--- a/intl/locale/unix/moz.build
+++ b/intl/locale/unix/moz.build
@@ -4,16 +4,19 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 SOURCES += [
     'nsCollationUnix.cpp',
     'nsPosixLocale.cpp',
 ]
 
+if CONFIG['ENABLE_INTL_API']:
+    SOURCES += ['OSPreferences_unix.cpp']
+
 if CONFIG['OS_TARGET'] == 'Android':
     SOURCES += [
         'nsAndroidCharset.cpp',
     ]
 else:
     SOURCES += [
         'nsUNIXCharset.cpp',
     ]
new file mode 100644
--- /dev/null
+++ b/intl/locale/windows/OSPreferences_win.cpp
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OSPreferences.h"
+#include "nsWin32Locale.h"
+
+using namespace mozilla::intl;
+
+bool
+OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList)
+{
+  MOZ_ASSERT(aLocaleList.IsEmpty());
+
+  nsAutoString locale;
+
+  LCID win_lcid = GetSystemDefaultLCID();
+  nsWin32Locale::GetXPLocale(win_lcid, locale);
+
+  NS_LossyConvertUTF16toASCII loc(locale);
+
+  if (CanonicalizeLanguageTag(loc)) {
+    aLocaleList.AppendElement(loc);
+    return true;
+  }
+  return false;
+}
--- a/intl/locale/windows/moz.build
+++ b/intl/locale/windows/moz.build
@@ -5,16 +5,19 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 SOURCES += [
     'nsCollationWin.cpp',
     'nsWin32Locale.cpp',
     'nsWinCharset.cpp',
 ]
 
+if CONFIG['ENABLE_INTL_API']:
+    SOURCES += ['OSPreferences_win.cpp']
+
 FINAL_LIBRARY = 'xul'
 
 GENERATED_FILES = [
     'wincharset.properties.h',
 ]
 wincharset = GENERATED_FILES['wincharset.properties.h']
 wincharset.script = '../props2arrays.py'
 wincharset.inputs = ['wincharset.properties']