diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 63906258f1d6..6410f009d820 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -84,14 +84,6 @@ #include "nsXPCOMCIDInternal.h" #include "nsIXULRuntime.h" -// For locale aware string methods -#include "plstr.h" -#include "nsIPlatformCharset.h" -#include "nsICharsetConverterManager.h" -#include "nsUnicharUtils.h" -#include "nsILocaleService.h" -#include "nsICollation.h" -#include "nsCollationCID.h" #include "nsDOMClassInfo.h" #include "jsdbgapi.h" // for JS_ClearWatchPointsForObject @@ -224,10 +216,6 @@ static PRTime sMaxChromeScriptRunTime; static nsIScriptSecurityManager *sSecurityManager; -static nsICollation *gCollation; - -static nsIUnicodeDecoder *gDecoder; - // nsUserActivityObserver observes user-interaction-active and // user-interaction-inactive notifications. It counts the number of // notifications and if the number is bigger than NS_CC_SOFT_LIMIT_ACTIVE @@ -644,166 +632,6 @@ NS_ScriptErrorReporter(JSContext *cx, #endif } -static JSBool -LocaleToUnicode(JSContext *cx, const char *src, jsval *rval) -{ - nsresult rv; - - if (!gDecoder) { - // use app default locale - nsCOMPtr localeService = - do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv); - if (NS_SUCCEEDED(rv)) { - nsCOMPtr appLocale; - rv = localeService->GetApplicationLocale(getter_AddRefs(appLocale)); - if (NS_SUCCEEDED(rv)) { - nsAutoString localeStr; - rv = appLocale-> - GetCategory(NS_LITERAL_STRING(NSILOCALE_TIME), localeStr); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get app locale info"); - - nsCOMPtr platformCharset = - do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv); - - if (NS_SUCCEEDED(rv)) { - nsCAutoString charset; - rv = platformCharset->GetDefaultCharsetForLocale(localeStr, charset); - if (NS_SUCCEEDED(rv)) { - // get/create unicode decoder for charset - nsCOMPtr ccm = - do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); - if (NS_SUCCEEDED(rv)) - ccm->GetUnicodeDecoder(charset.get(), &gDecoder); - } - } - } - } - } - - JSString *str = nsnull; - PRInt32 srcLength = PL_strlen(src); - - if (gDecoder) { - PRInt32 unicharLength = srcLength; - PRUnichar *unichars = - (PRUnichar *)JS_malloc(cx, (srcLength + 1) * sizeof(PRUnichar)); - if (unichars) { - rv = gDecoder->Convert(src, &srcLength, unichars, &unicharLength); - if (NS_SUCCEEDED(rv)) { - // terminate the returned string - unichars[unicharLength] = 0; - - // nsIUnicodeDecoder::Convert may use fewer than srcLength PRUnichars - if (unicharLength + 1 < srcLength + 1) { - PRUnichar *shrunkUnichars = - (PRUnichar *)JS_realloc(cx, unichars, - (unicharLength + 1) * sizeof(PRUnichar)); - if (shrunkUnichars) - unichars = shrunkUnichars; - } - str = JS_NewUCString(cx, - reinterpret_cast(unichars), - unicharLength); - } - if (!str) - JS_free(cx, unichars); - } - } - - if (!str) { - nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_OUT_OF_MEMORY); - return JS_FALSE; - } - - *rval = STRING_TO_JSVAL(str); - return JS_TRUE; -} - - -static JSBool -ChangeCase(JSContext *cx, JSString *src, jsval *rval, - void(* changeCaseFnc)(const nsAString&, nsAString&)) -{ - nsDependentJSString depStr; - if (!depStr.init(cx, src)) { - return JS_FALSE; - } - - nsAutoString result; - changeCaseFnc(depStr, result); - - JSString *ucstr = JS_NewUCStringCopyN(cx, (jschar*)result.get(), result.Length()); - if (!ucstr) { - return JS_FALSE; - } - - *rval = STRING_TO_JSVAL(ucstr); - - return JS_TRUE; -} - -static JSBool -LocaleToUpperCase(JSContext *cx, JSString *src, jsval *rval) -{ - return ChangeCase(cx, src, rval, ToUpperCase); -} - -static JSBool -LocaleToLowerCase(JSContext *cx, JSString *src, jsval *rval) -{ - return ChangeCase(cx, src, rval, ToLowerCase); -} - -static JSBool -LocaleCompare(JSContext *cx, JSString *src1, JSString *src2, jsval *rval) -{ - nsresult rv; - - if (!gCollation) { - nsCOMPtr localeService = - do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv); - - if (NS_SUCCEEDED(rv)) { - nsCOMPtr locale; - rv = localeService->GetApplicationLocale(getter_AddRefs(locale)); - - if (NS_SUCCEEDED(rv)) { - nsCOMPtr colFactory = - do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv); - - if (NS_SUCCEEDED(rv)) { - rv = colFactory->CreateCollation(locale, &gCollation); - } - } - } - - if (NS_FAILED(rv)) { - nsDOMClassInfo::ThrowJSException(cx, rv); - - return JS_FALSE; - } - } - - nsDependentJSString depStr1, depStr2; - if (!depStr1.init(cx, src1) || !depStr2.init(cx, src2)) { - return JS_FALSE; - } - - PRInt32 result; - rv = gCollation->CompareString(nsICollation::kCollationStrengthDefault, - depStr1, depStr2, &result); - - if (NS_FAILED(rv)) { - nsDOMClassInfo::ThrowJSException(cx, rv); - - return JS_FALSE; - } - - *rval = INT_TO_JSVAL(result); - - return JS_TRUE; -} - #ifdef DEBUG // A couple of useful functions to call when you're debugging. nsGlobalWindow * @@ -1311,15 +1139,7 @@ nsJSContext::nsJSContext(JSRuntime *aRuntime) ::JS_SetOperationCallback(mContext, DOMOperationCallback); - static JSLocaleCallbacks localeCallbacks = - { - LocaleToUpperCase, - LocaleToLowerCase, - LocaleCompare, - LocaleToUnicode - }; - - ::JS_SetLocaleCallbacks(mContext, &localeCallbacks); + xpc_LocalizeContext(mContext); } mIsInitialized = PR_FALSE; mTerminations = nsnull; @@ -1350,8 +1170,6 @@ nsJSContext::~nsJSContext() NS_IF_RELEASE(sRuntimeService); NS_IF_RELEASE(sSecurityManager); - NS_IF_RELEASE(gCollation); - NS_IF_RELEASE(gDecoder); } } @@ -3952,7 +3770,6 @@ nsJSRuntime::Startup() sDidShutdown = PR_FALSE; sContextCount = 0; sSecurityManager = nsnull; - gCollation = nsnull; } static int @@ -4240,8 +4057,6 @@ nsJSRuntime::Shutdown() } NS_IF_RELEASE(sRuntimeService); NS_IF_RELEASE(sSecurityManager); - NS_IF_RELEASE(gCollation); - NS_IF_RELEASE(gDecoder); } sDidShutdown = PR_TRUE; diff --git a/js/src/xpconnect/shell/xpcshell.cpp b/js/src/xpconnect/shell/xpcshell.cpp index 86fd9579d700..0b2927aa1316 100644 --- a/js/src/xpconnect/shell/xpcshell.cpp +++ b/js/src/xpconnect/shell/xpcshell.cpp @@ -78,6 +78,7 @@ #include "nsCOMPtr.h" #include "nsAutoPtr.h" #include "nsIXPCSecurityManager.h" +#include "xpcpublic.h" #ifdef XP_MACOSX #include "xpcshellMacUtils.h" #endif @@ -1902,6 +1903,8 @@ main(int argc, char **argv) return 1; } + xpc_LocalizeContext(cx); + nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID()); if (!xpc) { printf("failed to get nsXPConnect service!\n"); diff --git a/js/src/xpconnect/src/Makefile.in b/js/src/xpconnect/src/Makefile.in index 345ef3d9d838..b106e0495cce 100644 --- a/js/src/xpconnect/src/Makefile.in +++ b/js/src/xpconnect/src/Makefile.in @@ -75,6 +75,7 @@ CPPSRCS = \ xpcexception.cpp \ xpcjsid.cpp \ xpcjsruntime.cpp \ + xpclocale.cpp \ xpclog.cpp \ xpcmaps.cpp \ xpcmodule.cpp \ diff --git a/js/src/xpconnect/src/xpclocale.cpp b/js/src/xpconnect/src/xpclocale.cpp new file mode 100644 index 000000000000..67f2915c39d0 --- /dev/null +++ b/js/src/xpconnect/src/xpclocale.cpp @@ -0,0 +1,392 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at: + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Code. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Chris Jones + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "prinit.h" +#include "plstr.h" + +#include "jsapi.h" + +#include "nsCollationCID.h" +#include "nsDOMClassInfo.h" +#include "nsJSUtils.h" +#include "nsICharsetConverterManager.h" +#include "nsIPlatformCharset.h" +#include "nsILocaleService.h" +#include "nsICollation.h" +#include "nsIServiceManager.h" +#include "nsUnicharUtils.h" + +/** + * JS locale callbacks implemented by XPCOM modules. This + * implementation is "safe" up to the following restrictions + * + * - All JSContexts for which xpc_LocalizeContext() is called belong + * to the same JSRuntime + * + * - Each JSContext is destroyed on the thread on which its locale + * functions are called. + * + * Unfortunately, the intl code underlying these XPCOM modules doesn't + * yet support this model, so in practice XPCLocaleCallbacks are + * limited to the main thread. + */ +struct XPCLocaleCallbacks : public JSLocaleCallbacks +{ + /** + * Return the XPCLocaleCallbacks that's hidden away in |cx|, or null + * if there isn't one. (This impl uses the locale callbacks struct + * to store away its per-context data.) + * + * NB: If the returned XPCLocaleCallbacks hasn't yet been bound to a + * thread, then a side effect of calling MaybeThis() is to bind it + * to the calling thread. + */ + static XPCLocaleCallbacks* + MaybeThis(JSContext* cx) + { + JSLocaleCallbacks* lc = JS_GetLocaleCallbacks(cx); + return (lc && + lc->localeToUpperCase == LocaleToUpperCase && + lc->localeToLowerCase == LocaleToLowerCase && + lc->localeCompare == LocaleCompare && + lc->localeToUnicode == LocaleToUnicode) ? This(cx) : nsnull; + } + + static JSBool + ChangeCase(JSContext* cx, JSString* src, jsval* rval, + void(*changeCaseFnc)(const nsAString&, nsAString&)) + { + nsDependentJSString depStr; + if (!depStr.init(cx, src)) { + return JS_FALSE; + } + + nsAutoString result; + changeCaseFnc(depStr, result); + + JSString *ucstr = + JS_NewUCStringCopyN(cx, (jschar*)result.get(), result.Length()); + if (!ucstr) { + return JS_FALSE; + } + + *rval = STRING_TO_JSVAL(ucstr); + + return JS_TRUE; + } + + static JSBool + LocaleToUpperCase(JSContext *cx, JSString *src, jsval *rval) + { + return ChangeCase(cx, src, rval, ToUpperCase); + } + + static JSBool + LocaleToLowerCase(JSContext *cx, JSString *src, jsval *rval) + { + return ChangeCase(cx, src, rval, ToLowerCase); + } + + /** + * Return an XPCLocaleCallbacks out of |cx|. Callers must know that + * |cx| has an XPCLocaleCallbacks; i.e., the checks in MaybeThis() + * would be pointless to run from the calling context. + * + * NB: If the returned XPCLocaleCallbacks hasn't yet been bound to a + * thread, then a side effect of calling This() is to bind it to the + * calling thread. + */ + static XPCLocaleCallbacks* + This(JSContext* cx) + { + XPCLocaleCallbacks* ths = + static_cast(JS_GetLocaleCallbacks(cx)); + ths->AssertThreadSafety(); + return ths; + } + + static JSBool + LocaleToUnicode(JSContext* cx, const char* src, jsval* rval) + { + return This(cx)->ToUnicode(cx, src, rval); + } + + static JSBool + LocaleCompare(JSContext *cx, JSString *src1, JSString *src2, jsval *rval) + { + return This(cx)->Compare(cx, src1, src2, rval); + } + + XPCLocaleCallbacks() +#ifdef DEBUG + : mThread(nsnull) +#endif + { + MOZ_COUNT_CTOR(XPCLocaleCallbacks); + + localeToUpperCase = LocaleToUpperCase; + localeToLowerCase = LocaleToLowerCase; + localeCompare = LocaleCompare; + localeToUnicode = LocaleToUnicode; + localeGetErrorMessage = nsnull; + } + + ~XPCLocaleCallbacks() + { + MOZ_COUNT_DTOR(XPCLocaleCallbacks); + AssertThreadSafety(); + } + + JSBool + ToUnicode(JSContext* cx, const char* src, jsval* rval) + { + nsresult rv; + + if (!mDecoder) { + // use app default locale + nsCOMPtr localeService = + do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr appLocale; + rv = localeService->GetApplicationLocale(getter_AddRefs(appLocale)); + if (NS_SUCCEEDED(rv)) { + nsAutoString localeStr; + rv = appLocale-> + GetCategory(NS_LITERAL_STRING(NSILOCALE_TIME), localeStr); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get app locale info"); + + nsCOMPtr platformCharset = + do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) { + nsCAutoString charset; + rv = platformCharset->GetDefaultCharsetForLocale(localeStr, charset); + if (NS_SUCCEEDED(rv)) { + // get/create unicode decoder for charset + nsCOMPtr ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + ccm->GetUnicodeDecoder(charset.get(), getter_AddRefs(mDecoder)); + } + } + } + } + } + + JSString *str = nsnull; + PRInt32 srcLength = PL_strlen(src); + + if (mDecoder) { + PRInt32 unicharLength = srcLength; + PRUnichar *unichars = + (PRUnichar *)JS_malloc(cx, (srcLength + 1) * sizeof(PRUnichar)); + if (unichars) { + rv = mDecoder->Convert(src, &srcLength, unichars, &unicharLength); + if (NS_SUCCEEDED(rv)) { + // terminate the returned string + unichars[unicharLength] = 0; + + // nsIUnicodeDecoder::Convert may use fewer than srcLength PRUnichars + if (unicharLength + 1 < srcLength + 1) { + PRUnichar *shrunkUnichars = + (PRUnichar *)JS_realloc(cx, unichars, + (unicharLength + 1) * sizeof(PRUnichar)); + if (shrunkUnichars) + unichars = shrunkUnichars; + } + str = JS_NewUCString(cx, + reinterpret_cast(unichars), + unicharLength); + } + if (!str) + JS_free(cx, unichars); + } + } + + if (!str) { + nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_OUT_OF_MEMORY); + return JS_FALSE; + } + + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; + } + + JSBool + Compare(JSContext *cx, JSString *src1, JSString *src2, jsval *rval) + { + nsresult rv; + + if (!mCollation) { + nsCOMPtr localeService = + do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr locale; + rv = localeService->GetApplicationLocale(getter_AddRefs(locale)); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr colFactory = + do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) { + rv = colFactory->CreateCollation(locale, getter_AddRefs(mCollation)); + } + } + } + + if (NS_FAILED(rv)) { + nsDOMClassInfo::ThrowJSException(cx, rv); + + return JS_FALSE; + } + } + + nsDependentJSString depStr1, depStr2; + if (!depStr1.init(cx, src1) || !depStr2.init(cx, src2)) { + return JS_FALSE; + } + + PRInt32 result; + rv = mCollation->CompareString(nsICollation::kCollationStrengthDefault, + depStr1, depStr2, &result); + + if (NS_FAILED(rv)) { + nsDOMClassInfo::ThrowJSException(cx, rv); + + return JS_FALSE; + } + + *rval = INT_TO_JSVAL(result); + + return JS_TRUE; + } + + nsCOMPtr mCollation; + nsCOMPtr mDecoder; + +#ifdef DEBUG + PRThread* mThread; + + // Assert that |this| being used in a way consistent with its + // restrictions. If |this| hasn't been bound to a thread yet, then + // it will be bound to calling thread. + void AssertThreadSafety() + { + NS_ABORT_IF_FALSE(!mThread || mThread == PR_GetCurrentThread(), + "XPCLocaleCallbacks used unsafely!"); + if (!mThread) { + mThread = PR_GetCurrentThread(); + } + } +#else + void AssertThreadSafety() { } +#endif // DEBUG +}; + + +/** + * There can only be one JSRuntime in which JSContexts are hooked with + * XPCLocaleCallbacks. |sHookedRuntime| is it. + * + * Initializing the JSContextCallback must be thread safe. + * |sOldContextCallback| and |sHookedRuntime| are protected by + * |sHookRuntime|. After that, however, the context callback itself + * doesn't need to be thread safe, since it operates on + * JSContext-local data. + */ +static PRCallOnceType sHookRuntime; +static JSContextCallback sOldContextCallback; +#ifdef DEBUG +static JSRuntime* sHookedRuntime; +#endif // DEBUG + +static JSBool +DelocalizeContextCallback(JSContext *cx, uintN contextOp) +{ + NS_ABORT_IF_FALSE(JS_GetRuntime(cx) == sHookedRuntime, "unknown runtime!"); + + JSBool ok = JS_TRUE; + if (sOldContextCallback && !sOldContextCallback(cx, contextOp)) { + ok = JS_FALSE; + // Even if the old callback fails, we still have to march on or + // else we might leak the intl stuff hooked onto |cx| + } + + if (contextOp == JSCONTEXT_DESTROY) { + if (XPCLocaleCallbacks* lc = XPCLocaleCallbacks::MaybeThis(cx)) { + // This is a JSContext for which xpc_LocalizeContext() was called. + JS_SetLocaleCallbacks(cx, nsnull); + delete lc; + } + } + + return ok; +} + +static PRStatus +HookRuntime(void* arg) +{ + JSRuntime* rt = static_cast(arg); + + NS_ABORT_IF_FALSE(!sHookedRuntime && !sOldContextCallback, + "PRCallOnce called twice?"); + + // XXX it appears that in practice we only have to worry about + // xpconnect's context hook, and it chains properly. However, it + // *will* stomp our callback on shutdown. + sOldContextCallback = JS_SetContextCallback(rt, DelocalizeContextCallback); +#ifdef DEBUG + sHookedRuntime = rt; +#endif + + return PR_SUCCESS; +} + +NS_EXPORT_(void) +xpc_LocalizeContext(JSContext *cx) +{ + JSRuntime* rt = JS_GetRuntime(cx); + PR_CallOnceWithArg(&sHookRuntime, HookRuntime, rt); + + NS_ABORT_IF_FALSE(sHookedRuntime == rt, "created multiple JSRuntimes?"); + + JS_SetLocaleCallbacks(cx, new XPCLocaleCallbacks()); +} diff --git a/js/src/xpconnect/src/xpcpublic.h b/js/src/xpconnect/src/xpcpublic.h index 890383fc599d..dfbea9cbf456 100644 --- a/js/src/xpconnect/src/xpcpublic.h +++ b/js/src/xpconnect/src/xpcpublic.h @@ -43,7 +43,6 @@ #include "jsapi.h" #include "nsISupports.h" #include "jsobj.h" -#include "nsAString.h" #include "nsIPrincipal.h" #include "nsWrapperCache.h" @@ -60,6 +59,10 @@ xpc_CreateMTGlobalObject(JSContext *cx, JSClass *clasp, nsISupports *ptr, JSObject **global, JSCompartment **compartment); +// XXX where should this live? +NS_EXPORT_(void) +xpc_LocalizeContext(JSContext *cx); + nsresult xpc_MorphSlimWrapper(JSContext *cx, nsISupports *tomorph); diff --git a/js/src/xpconnect/tests/unit/test_localeCompare.js b/js/src/xpconnect/tests/unit/test_localeCompare.js new file mode 100644 index 000000000000..08560d1ae92c --- /dev/null +++ b/js/src/xpconnect/tests/unit/test_localeCompare.js @@ -0,0 +1,6 @@ +function run_test() { + do_check_true("C".localeCompare("D") < 0); + do_check_true("D".localeCompare("C") > 0); + do_check_true("\u010C".localeCompare("D") < 0); + do_check_true("D".localeCompare("\u010C") > 0); +}