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 "mozilla/Assertions.h"
8
9 #include "js/LocaleSensitive.h"
10
11 #include "nsIObserver.h"
12 #include "nsComponentManagerUtils.h"
13 #include "nsServiceManagerUtils.h"
14 #include "mozilla/CycleCollectedJSContext.h"
15 #include "mozilla/intl/LocaleService.h"
16 #include "mozilla/Preferences.h"
17
18 #include "xpcpublic.h"
19
20 using namespace mozilla;
21 using mozilla::intl::LocaleService;
22
23 class XPCLocaleObserver : public nsIObserver {
24 public:
25 NS_DECL_ISUPPORTS
26 NS_DECL_NSIOBSERVER
27
28 void Init();
29
30 private:
31 virtual ~XPCLocaleObserver() = default;
32 };
33
34 NS_IMPL_ISUPPORTS(XPCLocaleObserver, nsIObserver);
35
Init()36 void XPCLocaleObserver::Init() {
37 nsCOMPtr<nsIObserverService> observerService =
38 mozilla::services::GetObserverService();
39
40 observerService->AddObserver(this, "intl:app-locales-changed", false);
41
42 Preferences::AddStrongObserver(this, "javascript.use_us_english_locale");
43 }
44
45 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)46 XPCLocaleObserver::Observe(nsISupports* aSubject, const char* aTopic,
47 const char16_t* aData) {
48 if (!strcmp(aTopic, "intl:app-locales-changed") ||
49 (!strcmp(aTopic, "nsPref:changed") &&
50 !NS_strcmp(aData, u"javascript.use_us_english_locale"))) {
51 JSRuntime* rt = CycleCollectedJSRuntime::Get()->Runtime();
52 if (!xpc_LocalizeRuntime(rt)) {
53 return NS_ERROR_OUT_OF_MEMORY;
54 }
55 return NS_OK;
56 }
57
58 return NS_ERROR_UNEXPECTED;
59 }
60
61 /**
62 * JS locale callbacks implemented by XPCOM modules. These are theoretically
63 * safe for use on multiple threads. Unfortunately, the intl code underlying
64 * these XPCOM modules doesn't yet support this, so in practice
65 * XPCLocaleCallbacks are limited to the main thread.
66 */
67 struct XPCLocaleCallbacks : public JSLocaleCallbacks {
XPCLocaleCallbacksXPCLocaleCallbacks68 XPCLocaleCallbacks() {
69 MOZ_COUNT_CTOR(XPCLocaleCallbacks);
70
71 // Disable the toLocaleUpper/Lower case hooks to use the standard,
72 // locale-insensitive definition from String.prototype. (These hooks are
73 // only consulted when JS_HAS_INTL_API is not set.) Since JS_HAS_INTL_API
74 // is always set, these hooks should be disabled.
75 localeToUpperCase = nullptr;
76 localeToLowerCase = nullptr;
77 localeCompare = nullptr;
78 localeToUnicode = nullptr;
79
80 // It's going to be retained by the ObserverService.
81 RefPtr<XPCLocaleObserver> locObs = new XPCLocaleObserver();
82 locObs->Init();
83 }
84
~XPCLocaleCallbacksXPCLocaleCallbacks85 ~XPCLocaleCallbacks() {
86 AssertThreadSafety();
87 MOZ_COUNT_DTOR(XPCLocaleCallbacks);
88 }
89
90 /**
91 * Return the XPCLocaleCallbacks that's hidden away in |rt|. (This impl uses
92 * the locale callbacks struct to store away its per-context data.)
93 */
ThisXPCLocaleCallbacks94 static XPCLocaleCallbacks* This(JSRuntime* rt) {
95 // Locale information for |cx| was associated using xpc_LocalizeContext;
96 // assert and double-check this.
97 const JSLocaleCallbacks* lc = JS_GetLocaleCallbacks(rt);
98 MOZ_ASSERT(lc);
99 MOZ_ASSERT(lc->localeToUpperCase == nullptr);
100 MOZ_ASSERT(lc->localeToLowerCase == nullptr);
101 MOZ_ASSERT(lc->localeCompare == nullptr);
102 MOZ_ASSERT(lc->localeToUnicode == nullptr);
103
104 const XPCLocaleCallbacks* ths = static_cast<const XPCLocaleCallbacks*>(lc);
105 ths->AssertThreadSafety();
106 return const_cast<XPCLocaleCallbacks*>(ths);
107 }
108
109 private:
AssertThreadSafetyXPCLocaleCallbacks110 void AssertThreadSafety() const {
111 NS_ASSERT_OWNINGTHREAD(XPCLocaleCallbacks);
112 }
113
114 NS_DECL_OWNINGTHREAD
115 };
116
xpc_LocalizeRuntime(JSRuntime * rt)117 bool xpc_LocalizeRuntime(JSRuntime* rt) {
118 // We want to assign the locale callbacks only the first time we
119 // localize the context.
120 // All consequent calls to this function are result of language changes
121 // and should not assign it again.
122 const JSLocaleCallbacks* lc = JS_GetLocaleCallbacks(rt);
123 if (!lc) {
124 JS_SetLocaleCallbacks(rt, new XPCLocaleCallbacks());
125 }
126
127 // Set the default locale.
128
129 // Check a pref to see if we should use US English locale regardless
130 // of the system locale.
131 if (Preferences::GetBool("javascript.use_us_english_locale", false)) {
132 return JS_SetDefaultLocale(rt, "en-US");
133 }
134
135 // No pref has been found, so get the default locale from the
136 // regional prefs locales.
137 AutoTArray<nsCString, 10> rpLocales;
138 LocaleService::GetInstance()->GetRegionalPrefsLocales(rpLocales);
139
140 MOZ_ASSERT(rpLocales.Length() > 0);
141 return JS_SetDefaultLocale(rt, rpLocales[0].get());
142 }
143
xpc_DelocalizeRuntime(JSRuntime * rt)144 void xpc_DelocalizeRuntime(JSRuntime* rt) {
145 const XPCLocaleCallbacks* lc = XPCLocaleCallbacks::This(rt);
146 JS_SetLocaleCallbacks(rt, nullptr);
147 delete lc;
148 }
149