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 #ifndef mozilla_intl_LocaleService_h__
7 #define mozilla_intl_LocaleService_h__
8 
9 #include "nsIObserver.h"
10 #include "nsString.h"
11 #include "nsTArray.h"
12 #include "nsWeakReference.h"
13 #include "MozLocaleBindings.h"
14 #include "mozilla/intl/ICU4CGlue.h"
15 #include "mozILocaleService.h"
16 
17 namespace mozilla {
18 namespace intl {
19 
20 /**
21  * LocaleService is a manager of language negotiation in Gecko.
22  *
23  * It's intended to be the core place for collecting available and
24  * requested languages and negotiating them to produce a fallback
25  * chain of locales for the application.
26  *
27  * Client / Server
28  *
29  * LocaleService may operate in one of two modes:
30  *
31  *   server
32  *     in the server mode, LocaleService is collecting and negotiating
33  *     languages. It also subscribes to relevant observers.
34  *     There should be at most one server per application instance.
35  *
36  *   client
37  *     in the client mode, LocaleService is not responsible for collecting
38  *     or reacting to any system changes. It still distributes information
39  *     about locales, but internally, it gets information from the server
40  * instance instead of collecting it on its own. This prevents any data
41  * desynchronization and minimizes the cost of running the service.
42  *
43  *   In both modes, all get* methods should work the same way and all
44  *   static methods are available.
45  *
46  *   In the server mode, other components may inform LocaleService about their
47  *   status either via calls to set* methods or via observer events.
48  *   In the client mode, only the process communication should provide data
49  *   to the LocaleService.
50  *
51  *   At the moment desktop apps use the parent process in the server mode, and
52  *   content processes in the client mode.
53  *
54  * Locale / Language
55  *
56  * The terms `Locale ID` and `Language ID` are used slightly differently
57  * by different organizations. Mozilla uses the term `Language ID` to describe
58  * a string that contains information about the language itself, script,
59  * region and variant. For example "en-Latn-US-mac" is a correct Language ID.
60  *
61  * Locale ID contains a Language ID plus a number of extension tags that
62  * contain information that go beyond language inforamation such as
63  * preferred currency, date/time formatting etc.
64  *
65  * An example of a Locale ID is `en-Latn-US-x-hc-h12-ca-gregory`
66  *
67  * At the moment we do not support full extension tag system, but we
68  * try to be specific when naming APIs, so the service is for locales,
69  * but we negotiate between languages etc.
70  */
71 class LocaleService final : public mozILocaleService,
72                             public nsIObserver,
73                             public nsSupportsWeakReference {
74  public:
75   NS_DECL_ISUPPORTS
76   NS_DECL_NSIOBSERVER
77   NS_DECL_MOZILOCALESERVICE
78 
79   /**
80    * List of available language negotiation strategies.
81    *
82    * See the mozILocaleService.idl for detailed description of the
83    * strategies.
84    */
85   static const int32_t kLangNegStrategyFiltering = 0;
86   static const int32_t kLangNegStrategyMatching = 1;
87   static const int32_t kLangNegStrategyLookup = 2;
88 
89   explicit LocaleService(bool aIsServer);
90 
91   /**
92    * Create (if necessary) and return a raw pointer to the singleton instance.
93    * Use this accessor in C++ code that just wants to call a method on the
94    * instance, but does not need to hold a reference, as in
95    *    nsAutoCString str;
96    *    LocaleService::GetInstance()->GetAppLocaleAsLangTag(str);
97    */
98   static LocaleService* GetInstance();
99 
100   /**
101    * Return an addRef'd pointer to the singleton instance. This is used by the
102    * XPCOM constructor that exists to support usage from JS.
103    */
GetInstanceAddRefed()104   static already_AddRefed<LocaleService> GetInstanceAddRefed() {
105     return RefPtr<LocaleService>(GetInstance()).forget();
106   }
107 
108   /**
109    * Canonicalize a Unicode Language Identifier string.
110    *
111    * The operation is:
112    *   * Normalizing casing (`eN-Us-Windows` -> `en-US-windows`)
113    *   * Switching `_` to `-` (`en_US` -> `en-US`)
114    *   * Rejecting invalid identifiers (`e21-X` sets aLocale to `und` and
115    * returns false)
116    *   * Normalizing Mozilla's `ja-JP-mac` to `ja-JP-macos`
117    *   * Cutting off POSIX dot postfix (`en-US.utf8` -> `en-US`)
118    *
119    * This operation should be used on any external input before
120    * it gets used in internal operations.
121    */
CanonicalizeLanguageId(nsACString & aLocale)122   static bool CanonicalizeLanguageId(nsACString& aLocale) {
123     return ffi::unic_langid_canonicalize(&aLocale);
124   }
125   /**
126    * This method should only be called in the client mode.
127    *
128    * It replaces all the language negotiation and is supposed to be called
129    * in order to bring the client LocaleService in sync with the server
130    * LocaleService.
131    *
132    * Currently, it's called by the IPC code.
133    */
134   void AssignAppLocales(const nsTArray<nsCString>& aAppLocales);
135   void AssignRequestedLocales(const nsTArray<nsCString>& aRequestedLocales);
136 
137   /**
138    * Those two functions allow to trigger cache invalidation on one of the
139    * three cached values.
140    *
141    * In most cases, the functions will be called by the observer in
142    * LocaleService itself, but in a couple special cases, we have the
143    * other component call this manually instead of sending a global event.
144    *
145    * If the result differs from the previous list, it will additionally
146    * trigger a corresponding event
147    *
148    * This code should be called only in the server mode..
149    */
150   void RequestedLocalesChanged();
151   void LocalesChanged();
152 
153   /**
154    * This function keeps the pref setting updated.
155    */
156   void WebExposedLocalesChanged();
157 
158   /**
159    * Returns whether the locale is RTL.
160    */
161   static bool IsLocaleRTL(const nsACString& aLocale);
162 
163   /**
164    * Returns whether the current app locale is RTL.
165    *
166    * This method respects this override:
167    *  - `intl.l10n.pseudo`
168    */
169   bool IsAppLocaleRTL();
170 
171   static bool LanguagesMatch(const nsACString& aRequested,
172                              const nsACString& aAvailable);
173 
174   bool IsServer();
175 
176   /**
177    * Create a component from intl/components with the current app's locale. This
178    * is a convenience method for efficient string management with the app
179    * locale.
180    */
181   template <typename T, typename... Args>
TryCreateComponent(Args...args)182   static Result<UniquePtr<T>, ICUError> TryCreateComponent(Args... args) {
183     // 32 is somewhat arbitrary for the length, but it should fit common
184     // locales, but locales such as the following will be heap allocated:
185     //
186     //  "de-u-ca-gregory-fw-mon-hc-h23-co-phonebk-ka-noignore-kb-false-kc-
187     //    false-kf-false-kh-false-kk-false-kn-false-kr-space-ks-level1-kv-space-cf-
188     //    standard-cu-eur-ms-metric-nu-latn-lb-strict-lw-normal-ss-none-tz-atvie-em-
189     //    default-rg-atzzzz-sd-atat1-va-posix"
190     nsAutoCStringN<32> appLocale;
191     mozilla::intl::LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale);
192 
193     return T::TryCreate(appLocale.get(), args...);
194   }
195 
196   /**
197    * Create a component from intl/components with a given locale, but fallback
198    * to the app locale if it doesn't work.
199    */
200   template <typename T, typename... Args>
TryCreateComponentWithLocale(const char * aLocale,Args...args)201   static Result<UniquePtr<T>, ICUError> TryCreateComponentWithLocale(
202       const char* aLocale, Args... args) {
203     auto result = T::TryCreate(aLocale, args...);
204     if (result.isOk()) {
205       return result;
206     }
207     return TryCreateComponent<T>(args...);
208   }
209 
210  private:
211   void NegotiateAppLocales(nsTArray<nsCString>& aRetVal);
212 
213   void InitPackagedLocales();
214 
215   void RemoveObservers();
216 
217   virtual ~LocaleService() = default;
218 
219   nsAutoCStringN<16> mDefaultLocale;
220   nsTArray<nsCString> mAppLocales;
221   nsTArray<nsCString> mRequestedLocales;
222   nsTArray<nsCString> mAvailableLocales;
223   nsTArray<nsCString> mPackagedLocales;
224   nsTArray<nsCString> mWebExposedLocales;
225   const bool mIsServer;
226 
227   static StaticRefPtr<LocaleService> sInstance;
228 };
229 }  // namespace intl
230 }  // namespace mozilla
231 
232 #endif /* mozilla_intl_LocaleService_h__ */
233