Line data Source code
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 "jsapi.h"
10 :
11 : #include "nsCollationCID.h"
12 : #include "nsJSUtils.h"
13 : #include "nsICollation.h"
14 : #include "nsIObserver.h"
15 : #include "nsNativeCharsetUtils.h"
16 : #include "nsComponentManagerUtils.h"
17 : #include "nsServiceManagerUtils.h"
18 : #include "mozilla/CycleCollectedJSContext.h"
19 : #include "mozilla/intl/LocaleService.h"
20 : #include "mozilla/Preferences.h"
21 :
22 : #include "xpcpublic.h"
23 :
24 : using namespace JS;
25 : using namespace mozilla;
26 : using mozilla::intl::LocaleService;
27 :
28 3 : class XPCLocaleObserver : public nsIObserver
29 : {
30 : public:
31 : NS_DECL_ISUPPORTS
32 : NS_DECL_NSIOBSERVER
33 :
34 : void Init();
35 :
36 : private:
37 0 : virtual ~XPCLocaleObserver() {};
38 : };
39 :
40 17 : NS_IMPL_ISUPPORTS(XPCLocaleObserver, nsIObserver);
41 :
42 : void
43 3 : XPCLocaleObserver::Init()
44 : {
45 : nsCOMPtr<nsIObserverService> observerService =
46 6 : mozilla::services::GetObserverService();
47 :
48 3 : observerService->AddObserver(this, "intl:app-locales-changed", false);
49 3 : }
50 :
51 : NS_IMETHODIMP
52 2 : XPCLocaleObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
53 : {
54 2 : if (!strcmp(aTopic, "intl:app-locales-changed")) {
55 2 : JSContext* cx = CycleCollectedJSContext::Get()->Context();
56 2 : if (!xpc_LocalizeContext(cx)) {
57 0 : return NS_ERROR_OUT_OF_MEMORY;
58 : }
59 2 : return NS_OK;
60 : }
61 :
62 0 : return NS_ERROR_UNEXPECTED;
63 : }
64 :
65 : /**
66 : * JS locale callbacks implemented by XPCOM modules. These are theoretically
67 : * safe for use on multiple threads. Unfortunately, the intl code underlying
68 : * these XPCOM modules doesn't yet support this, so in practice
69 : * XPCLocaleCallbacks are limited to the main thread.
70 : */
71 : struct XPCLocaleCallbacks : public JSLocaleCallbacks
72 : {
73 3 : XPCLocaleCallbacks()
74 3 : {
75 3 : MOZ_COUNT_CTOR(XPCLocaleCallbacks);
76 :
77 : // Disable the toLocaleUpper/Lower case hooks to use the standard,
78 : // locale-insensitive definition from String.prototype. (These hooks are
79 : // only consulted when EXPOSE_INTL_API is not set.)
80 3 : localeToUpperCase = nullptr;
81 3 : localeToLowerCase = nullptr;
82 3 : localeCompare = LocaleCompare;
83 3 : localeToUnicode = LocaleToUnicode;
84 :
85 : // It's going to be retained by the ObserverService.
86 6 : RefPtr<XPCLocaleObserver> locObs = new XPCLocaleObserver();
87 3 : locObs->Init();
88 3 : }
89 :
90 0 : ~XPCLocaleCallbacks()
91 0 : {
92 0 : AssertThreadSafety();
93 0 : MOZ_COUNT_DTOR(XPCLocaleCallbacks);
94 0 : }
95 :
96 : /**
97 : * Return the XPCLocaleCallbacks that's hidden away in |cx|. (This impl uses
98 : * the locale callbacks struct to store away its per-context data.)
99 : */
100 : static XPCLocaleCallbacks*
101 0 : This(JSContext* cx)
102 : {
103 : // Locale information for |cx| was associated using xpc_LocalizeContext;
104 : // assert and double-check this.
105 0 : const JSLocaleCallbacks* lc = JS_GetLocaleCallbacks(cx);
106 0 : MOZ_ASSERT(lc);
107 0 : MOZ_ASSERT(lc->localeToUpperCase == nullptr);
108 0 : MOZ_ASSERT(lc->localeToLowerCase == nullptr);
109 0 : MOZ_ASSERT(lc->localeCompare == LocaleCompare);
110 0 : MOZ_ASSERT(lc->localeToUnicode == LocaleToUnicode);
111 :
112 0 : const XPCLocaleCallbacks* ths = static_cast<const XPCLocaleCallbacks*>(lc);
113 0 : ths->AssertThreadSafety();
114 0 : return const_cast<XPCLocaleCallbacks*>(ths);
115 : }
116 :
117 : static bool
118 0 : LocaleToUnicode(JSContext* cx, const char* src, MutableHandleValue rval)
119 : {
120 0 : return This(cx)->ToUnicode(cx, src, rval);
121 : }
122 :
123 : static bool
124 0 : LocaleCompare(JSContext* cx, HandleString src1, HandleString src2, MutableHandleValue rval)
125 : {
126 0 : return This(cx)->Compare(cx, src1, src2, rval);
127 : }
128 :
129 : private:
130 : bool
131 0 : Compare(JSContext* cx, HandleString src1, HandleString src2, MutableHandleValue rval)
132 : {
133 : nsresult rv;
134 :
135 0 : if (!mCollation) {
136 : nsCOMPtr<nsICollationFactory> colFactory =
137 0 : do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv);
138 :
139 0 : if (NS_SUCCEEDED(rv)) {
140 0 : rv = colFactory->CreateCollation(getter_AddRefs(mCollation));
141 : }
142 :
143 0 : if (NS_FAILED(rv)) {
144 0 : xpc::Throw(cx, rv);
145 0 : return false;
146 : }
147 : }
148 :
149 0 : nsAutoJSString autoStr1, autoStr2;
150 0 : if (!autoStr1.init(cx, src1) || !autoStr2.init(cx, src2)) {
151 0 : return false;
152 : }
153 :
154 : int32_t result;
155 0 : rv = mCollation->CompareString(nsICollation::kCollationStrengthDefault,
156 0 : autoStr1, autoStr2, &result);
157 :
158 0 : if (NS_FAILED(rv)) {
159 0 : xpc::Throw(cx, rv);
160 0 : return false;
161 : }
162 :
163 0 : rval.setInt32(result);
164 0 : return true;
165 : }
166 :
167 : bool
168 0 : ToUnicode(JSContext* cx, const char* src, MutableHandleValue rval)
169 : {
170 : // This code is only used by our prioprietary toLocaleFormat method
171 : // and should be removed once we get rid of it.
172 : // toLocaleFormat is used in non-ICU scenarios where we don't have
173 : // access to any other date/time than the OS one, so we have to also
174 : // use the OS locale for unicode conversions.
175 : // See bug 1349470 for more details.
176 0 : nsAutoString result;
177 0 : NS_CopyNativeToUnicode(nsDependentCString(src), result);
178 : JSString* ucstr =
179 0 : JS_NewUCStringCopyN(cx, result.get(), result.Length());
180 0 : if (ucstr) {
181 0 : rval.setString(ucstr);
182 0 : return true;
183 : }
184 :
185 0 : xpc::Throw(cx, NS_ERROR_OUT_OF_MEMORY);
186 0 : return false;
187 : }
188 :
189 0 : void AssertThreadSafety() const
190 : {
191 0 : NS_ASSERT_OWNINGTHREAD(XPCLocaleCallbacks);
192 0 : }
193 :
194 : nsCOMPtr<nsICollation> mCollation;
195 :
196 : NS_DECL_OWNINGTHREAD
197 : };
198 :
199 : bool
200 5 : xpc_LocalizeContext(JSContext* cx)
201 : {
202 : // We want to assign the locale callbacks only the first time we
203 : // localize the context.
204 : // All consequent calls to this function are result of language changes
205 : // and should not assign it again.
206 5 : const JSLocaleCallbacks* lc = JS_GetLocaleCallbacks(cx);
207 5 : if (!lc) {
208 3 : JS_SetLocaleCallbacks(cx, new XPCLocaleCallbacks());
209 : }
210 :
211 : // Set the default locale.
212 :
213 : // Check a pref to see if we should use US English locale regardless
214 : // of the system locale.
215 5 : if (Preferences::GetBool("javascript.use_us_english_locale", false)) {
216 0 : return JS_SetDefaultLocale(cx, "en-US");
217 : }
218 :
219 : // No pref has been found, so get the default locale from the
220 : // application's locale.
221 10 : nsAutoCString appLocaleStr;
222 5 : LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocaleStr);
223 :
224 5 : return JS_SetDefaultLocale(cx, appLocaleStr.get());
225 : }
226 :
227 : void
228 0 : xpc_DelocalizeContext(JSContext* cx)
229 : {
230 0 : const XPCLocaleCallbacks* lc = XPCLocaleCallbacks::This(cx);
231 0 : JS_SetLocaleCallbacks(cx, nullptr);
232 0 : delete lc;
233 0 : }
|