1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : * vim: sw=2 ts=8 et :
3 : */
4 : /* ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at:
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is Mozilla Code.
18 : *
19 : * The Initial Developer of the Original Code is
20 : * The Mozilla Foundation
21 : * Portions created by the Initial Developer are Copyright (C) 2010
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributor(s):
25 : * Chris Jones <jones.chris.g@gmail.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "prinit.h"
42 : #include "plstr.h"
43 :
44 : #include "jsapi.h"
45 :
46 : #include "nsCollationCID.h"
47 : #include "nsDOMClassInfo.h"
48 : #include "nsJSUtils.h"
49 : #include "nsICharsetConverterManager.h"
50 : #include "nsIPlatformCharset.h"
51 : #include "nsILocaleService.h"
52 : #include "nsICollation.h"
53 : #include "nsIServiceManager.h"
54 : #include "nsUnicharUtils.h"
55 :
56 : /**
57 : * JS locale callbacks implemented by XPCOM modules. This
58 : * implementation is "safe" up to the following restrictions
59 : *
60 : * - All JSContexts for which xpc_LocalizeContext() is called belong
61 : * to the same JSRuntime
62 : *
63 : * - Each JSContext is destroyed on the thread on which its locale
64 : * functions are called.
65 : *
66 : * Unfortunately, the intl code underlying these XPCOM modules doesn't
67 : * yet support this model, so in practice XPCLocaleCallbacks are
68 : * limited to the main thread.
69 : */
70 : struct XPCLocaleCallbacks : public JSLocaleCallbacks
71 : {
72 : /**
73 : * Return the XPCLocaleCallbacks that's hidden away in |cx|, or null
74 : * if there isn't one. (This impl uses the locale callbacks struct
75 : * to store away its per-context data.)
76 : *
77 : * NB: If the returned XPCLocaleCallbacks hasn't yet been bound to a
78 : * thread, then a side effect of calling MaybeThis() is to bind it
79 : * to the calling thread.
80 : */
81 : static XPCLocaleCallbacks*
82 7674 : MaybeThis(JSContext* cx)
83 : {
84 7674 : JSLocaleCallbacks* lc = JS_GetLocaleCallbacks(cx);
85 : return (lc &&
86 : lc->localeToUpperCase == LocaleToUpperCase &&
87 : lc->localeToLowerCase == LocaleToLowerCase &&
88 : lc->localeCompare == LocaleCompare &&
89 7674 : lc->localeToUnicode == LocaleToUnicode) ? This(cx) : nsnull;
90 : }
91 :
92 : static JSBool
93 0 : ChangeCase(JSContext* cx, JSString* src, jsval* rval,
94 : void(*changeCaseFnc)(const nsAString&, nsAString&))
95 : {
96 0 : nsDependentJSString depStr;
97 0 : if (!depStr.init(cx, src)) {
98 0 : return false;
99 : }
100 :
101 0 : nsAutoString result;
102 0 : changeCaseFnc(depStr, result);
103 :
104 : JSString *ucstr =
105 0 : JS_NewUCStringCopyN(cx, (jschar*)result.get(), result.Length());
106 0 : if (!ucstr) {
107 0 : return false;
108 : }
109 :
110 0 : *rval = STRING_TO_JSVAL(ucstr);
111 :
112 0 : return true;
113 : }
114 :
115 : static JSBool
116 0 : LocaleToUpperCase(JSContext *cx, JSString *src, jsval *rval)
117 : {
118 0 : return ChangeCase(cx, src, rval, ToUpperCase);
119 : }
120 :
121 : static JSBool
122 0 : LocaleToLowerCase(JSContext *cx, JSString *src, jsval *rval)
123 : {
124 0 : return ChangeCase(cx, src, rval, ToLowerCase);
125 : }
126 :
127 : /**
128 : * Return an XPCLocaleCallbacks out of |cx|. Callers must know that
129 : * |cx| has an XPCLocaleCallbacks; i.e., the checks in MaybeThis()
130 : * would be pointless to run from the calling context.
131 : *
132 : * NB: If the returned XPCLocaleCallbacks hasn't yet been bound to a
133 : * thread, then a side effect of calling This() is to bind it to the
134 : * calling thread.
135 : */
136 : static XPCLocaleCallbacks*
137 2799 : This(JSContext* cx)
138 : {
139 : XPCLocaleCallbacks* ths =
140 2799 : static_cast<XPCLocaleCallbacks*>(JS_GetLocaleCallbacks(cx));
141 2799 : ths->AssertThreadSafety();
142 2799 : return ths;
143 : }
144 :
145 : static JSBool
146 362 : LocaleToUnicode(JSContext* cx, const char* src, jsval* rval)
147 : {
148 362 : return This(cx)->ToUnicode(cx, src, rval);
149 : }
150 :
151 : static JSBool
152 85 : LocaleCompare(JSContext *cx, JSString *src1, JSString *src2, jsval *rval)
153 : {
154 85 : return This(cx)->Compare(cx, src1, src2, rval);
155 : }
156 :
157 2352 : XPCLocaleCallbacks()
158 : #ifdef DEBUG
159 2352 : : mThread(nsnull)
160 : #endif
161 : {
162 2352 : MOZ_COUNT_CTOR(XPCLocaleCallbacks);
163 :
164 2352 : localeToUpperCase = LocaleToUpperCase;
165 2352 : localeToLowerCase = LocaleToLowerCase;
166 2352 : localeCompare = LocaleCompare;
167 2352 : localeToUnicode = LocaleToUnicode;
168 2352 : localeGetErrorMessage = nsnull;
169 2352 : }
170 :
171 2352 : ~XPCLocaleCallbacks()
172 2352 : {
173 2352 : MOZ_COUNT_DTOR(XPCLocaleCallbacks);
174 2352 : AssertThreadSafety();
175 2352 : }
176 :
177 : JSBool
178 362 : ToUnicode(JSContext* cx, const char* src, jsval* rval)
179 : {
180 : nsresult rv;
181 :
182 362 : if (!mDecoder) {
183 : // use app default locale
184 : nsCOMPtr<nsILocaleService> localeService =
185 138 : do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
186 69 : if (NS_SUCCEEDED(rv)) {
187 138 : nsCOMPtr<nsILocale> appLocale;
188 69 : rv = localeService->GetApplicationLocale(getter_AddRefs(appLocale));
189 69 : if (NS_SUCCEEDED(rv)) {
190 138 : nsAutoString localeStr;
191 69 : rv = appLocale->
192 69 : GetCategory(NS_LITERAL_STRING(NSILOCALE_TIME), localeStr);
193 69 : NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get app locale info");
194 :
195 : nsCOMPtr<nsIPlatformCharset> platformCharset =
196 138 : do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv);
197 :
198 69 : if (NS_SUCCEEDED(rv)) {
199 138 : nsCAutoString charset;
200 69 : rv = platformCharset->GetDefaultCharsetForLocale(localeStr, charset);
201 69 : if (NS_SUCCEEDED(rv)) {
202 : // get/create unicode decoder for charset
203 : nsCOMPtr<nsICharsetConverterManager> ccm =
204 138 : do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
205 69 : if (NS_SUCCEEDED(rv))
206 69 : ccm->GetUnicodeDecoder(charset.get(), getter_AddRefs(mDecoder));
207 : }
208 : }
209 : }
210 : }
211 : }
212 :
213 362 : JSString *str = nsnull;
214 362 : PRInt32 srcLength = PL_strlen(src);
215 :
216 362 : if (mDecoder) {
217 362 : PRInt32 unicharLength = srcLength;
218 : PRUnichar *unichars =
219 362 : (PRUnichar *)JS_malloc(cx, (srcLength + 1) * sizeof(PRUnichar));
220 362 : if (unichars) {
221 362 : rv = mDecoder->Convert(src, &srcLength, unichars, &unicharLength);
222 362 : if (NS_SUCCEEDED(rv)) {
223 : // terminate the returned string
224 362 : unichars[unicharLength] = 0;
225 :
226 : // nsIUnicodeDecoder::Convert may use fewer than srcLength PRUnichars
227 362 : if (unicharLength + 1 < srcLength + 1) {
228 : PRUnichar *shrunkUnichars =
229 : (PRUnichar *)JS_realloc(cx, unichars,
230 0 : (unicharLength + 1) * sizeof(PRUnichar));
231 0 : if (shrunkUnichars)
232 0 : unichars = shrunkUnichars;
233 : }
234 : str = JS_NewUCString(cx,
235 : reinterpret_cast<jschar*>(unichars),
236 362 : unicharLength);
237 : }
238 362 : if (!str)
239 0 : JS_free(cx, unichars);
240 : }
241 : }
242 :
243 362 : if (!str) {
244 0 : nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_OUT_OF_MEMORY);
245 0 : return false;
246 : }
247 :
248 362 : *rval = STRING_TO_JSVAL(str);
249 362 : return true;
250 : }
251 :
252 : JSBool
253 85 : Compare(JSContext *cx, JSString *src1, JSString *src2, jsval *rval)
254 : {
255 : nsresult rv;
256 :
257 85 : if (!mCollation) {
258 : nsCOMPtr<nsILocaleService> localeService =
259 20 : do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
260 :
261 10 : if (NS_SUCCEEDED(rv)) {
262 20 : nsCOMPtr<nsILocale> locale;
263 10 : rv = localeService->GetApplicationLocale(getter_AddRefs(locale));
264 :
265 10 : if (NS_SUCCEEDED(rv)) {
266 : nsCOMPtr<nsICollationFactory> colFactory =
267 20 : do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv);
268 :
269 10 : if (NS_SUCCEEDED(rv)) {
270 10 : rv = colFactory->CreateCollation(locale, getter_AddRefs(mCollation));
271 : }
272 : }
273 : }
274 :
275 10 : if (NS_FAILED(rv)) {
276 0 : nsDOMClassInfo::ThrowJSException(cx, rv);
277 :
278 0 : return false;
279 : }
280 : }
281 :
282 170 : nsDependentJSString depStr1, depStr2;
283 85 : if (!depStr1.init(cx, src1) || !depStr2.init(cx, src2)) {
284 0 : return false;
285 : }
286 :
287 : PRInt32 result;
288 85 : rv = mCollation->CompareString(nsICollation::kCollationStrengthDefault,
289 85 : depStr1, depStr2, &result);
290 :
291 85 : if (NS_FAILED(rv)) {
292 0 : nsDOMClassInfo::ThrowJSException(cx, rv);
293 :
294 0 : return false;
295 : }
296 :
297 85 : *rval = INT_TO_JSVAL(result);
298 :
299 85 : return true;
300 : }
301 :
302 : nsCOMPtr<nsICollation> mCollation;
303 : nsCOMPtr<nsIUnicodeDecoder> mDecoder;
304 :
305 : #ifdef DEBUG
306 : PRThread* mThread;
307 :
308 : // Assert that |this| being used in a way consistent with its
309 : // restrictions. If |this| hasn't been bound to a thread yet, then
310 : // it will be bound to calling thread.
311 5151 : void AssertThreadSafety()
312 : {
313 5151 : NS_ABORT_IF_FALSE(!mThread || mThread == PR_GetCurrentThread(),
314 : "XPCLocaleCallbacks used unsafely!");
315 5151 : if (!mThread) {
316 2352 : mThread = PR_GetCurrentThread();
317 : }
318 5151 : }
319 : #else
320 : void AssertThreadSafety() { }
321 : #endif // DEBUG
322 : };
323 :
324 :
325 : /**
326 : * There can only be one JSRuntime in which JSContexts are hooked with
327 : * XPCLocaleCallbacks. |sHookedRuntime| is it.
328 : *
329 : * Initializing the JSContextCallback must be thread safe.
330 : * |sOldContextCallback| and |sHookedRuntime| are protected by
331 : * |sHookRuntime|. After that, however, the context callback itself
332 : * doesn't need to be thread safe, since it operates on
333 : * JSContext-local data.
334 : */
335 : static PRCallOnceType sHookRuntime;
336 : static JSContextCallback sOldContextCallback;
337 : #ifdef DEBUG
338 : static JSRuntime* sHookedRuntime;
339 : #endif // DEBUG
340 :
341 : static JSBool
342 12568 : DelocalizeContextCallback(JSContext *cx, unsigned contextOp)
343 : {
344 12568 : NS_ABORT_IF_FALSE(JS_GetRuntime(cx) == sHookedRuntime, "unknown runtime!");
345 :
346 12568 : JSBool ok = true;
347 12568 : if (sOldContextCallback && !sOldContextCallback(cx, contextOp)) {
348 0 : ok = false;
349 : // Even if the old callback fails, we still have to march on or
350 : // else we might leak the intl stuff hooked onto |cx|
351 : }
352 :
353 12568 : if (contextOp == JSCONTEXT_DESTROY) {
354 7674 : if (XPCLocaleCallbacks* lc = XPCLocaleCallbacks::MaybeThis(cx)) {
355 : // This is a JSContext for which xpc_LocalizeContext() was called.
356 2352 : JS_SetLocaleCallbacks(cx, nsnull);
357 2352 : delete lc;
358 : }
359 : }
360 :
361 12568 : return ok;
362 : }
363 :
364 : static PRStatus
365 1391 : HookRuntime(void* arg)
366 : {
367 1391 : JSRuntime* rt = static_cast<JSRuntime*>(arg);
368 :
369 1391 : NS_ABORT_IF_FALSE(!sHookedRuntime && !sOldContextCallback,
370 : "PRCallOnce called twice?");
371 :
372 : // XXX it appears that in practice we only have to worry about
373 : // xpconnect's context hook, and it chains properly. However, it
374 : // *will* stomp our callback on shutdown.
375 1391 : sOldContextCallback = JS_SetContextCallback(rt, DelocalizeContextCallback);
376 : #ifdef DEBUG
377 1391 : sHookedRuntime = rt;
378 : #endif
379 :
380 1391 : return PR_SUCCESS;
381 : }
382 :
383 : NS_EXPORT_(void)
384 2352 : xpc_LocalizeContext(JSContext *cx)
385 : {
386 2352 : JSRuntime* rt = JS_GetRuntime(cx);
387 2352 : PR_CallOnceWithArg(&sHookRuntime, HookRuntime, rt);
388 :
389 2352 : NS_ABORT_IF_FALSE(sHookedRuntime == rt, "created multiple JSRuntimes?");
390 :
391 2352 : JS_SetLocaleCallbacks(cx, new XPCLocaleCallbacks());
392 2352 : }
|