diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h index 7ed44251820..90d8142e5e9 100644 --- a/dlls/jscript/jscript.h +++ b/dlls/jscript/jscript.h @@ -449,6 +449,7 @@ HRESULT regexp_string_match(script_ctx_t*,jsdisp_t*,jsstr_t*,jsval_t*) DECLSPEC_ BOOL bool_obj_value(jsdisp_t*) DECLSPEC_HIDDEN; unsigned array_get_length(jsdisp_t*) DECLSPEC_HIDDEN; +HRESULT localize_number(script_ctx_t*,DOUBLE,BOOL,jsstr_t**) DECLSPEC_HIDDEN; HRESULT JSGlobal_eval(script_ctx_t*,jsval_t,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN; HRESULT Object_get_proto_(script_ctx_t*,jsval_t,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN; diff --git a/dlls/jscript/number.c b/dlls/jscript/number.c index be733fb5af5..9470fbf15a0 100644 --- a/dlls/jscript/number.c +++ b/dlls/jscript/number.c @@ -17,6 +17,7 @@ */ #include +#include #include #include "jscript.h" @@ -341,11 +342,90 @@ static HRESULT Number_toString(script_ctx_t *ctx, jsval_t vthis, WORD flags, uns return S_OK; } +HRESULT localize_number(script_ctx_t *ctx, DOUBLE val, BOOL new_format, jsstr_t **ret) +{ + WCHAR buf[316], decimal[8], thousands[8], *numstr; + NUMBERFMTW *format = NULL, format_buf; + LCID lcid = ctx->lcid; + _locale_t locale; + unsigned convlen; + jsstr_t *str; + int len; + + /* FIXME: Localize this */ + if(!isfinite(val)) + return to_string(ctx, jsval_number(val), ret); + + /* Native never uses an exponent, even if the number is very large, it will in fact + return all the digits (with thousands separators). jscript.dll uses two digits for + fraction even if they are zero (likely default numDigits) and always returns them, + while mshtml's jscript uses 3 digits and trims trailing zeros (on same locale). + This is even for very small numbers, such as 0.0000999, which will simply be 0. */ + if(!(locale = _create_locale(LC_ALL, "C"))) + return E_OUTOFMEMORY; + len = _swprintf_l(buf, ARRAY_SIZE(buf), L"%.3f", locale, val); + _free_locale(locale); + + if(new_format) { + WCHAR grouping[10]; + + format = &format_buf; + format->NumDigits = 3; + while(buf[--len] == '0') + format->NumDigits--; + + /* same logic as VarFormatNumber */ + grouping[2] = '\0'; + if(!GetLocaleInfoW(lcid, LOCALE_SGROUPING, grouping, ARRAY_SIZE(grouping))) + format->Grouping = 3; + else + format->Grouping = (grouping[2] == '2' ? 32 : grouping[0] - '0'); + + if(!GetLocaleInfoW(lcid, LOCALE_ILZERO | LOCALE_RETURN_NUMBER, (WCHAR*)&format->LeadingZero, 2)) + format->LeadingZero = 0; + if(!GetLocaleInfoW(lcid, LOCALE_INEGNUMBER | LOCALE_RETURN_NUMBER, (WCHAR*)&format->NegativeOrder, 2)) + format->NegativeOrder = 1; + format->lpDecimalSep = decimal; + if(!GetLocaleInfoW(lcid, LOCALE_SDECIMAL, decimal, ARRAY_SIZE(decimal))) + wcscpy(decimal, L"."); + format->lpThousandSep = thousands; + if(!GetLocaleInfoW(lcid, LOCALE_STHOUSAND, thousands, ARRAY_SIZE(thousands))) + wcscpy(thousands, L","); + } + + if(!(convlen = GetNumberFormatW(lcid, 0, buf, format, NULL, 0)) || + !(str = jsstr_alloc_buf(convlen - 1, &numstr))) + return E_OUTOFMEMORY; + + if(!GetNumberFormatW(lcid, 0, buf, format, numstr, convlen)) { + jsstr_release(str); + return E_OUTOFMEMORY; + } + + *ret = str; + return S_OK; +} + static HRESULT Number_toLocaleString(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) { - FIXME("\n"); - return E_NOTIMPL; + jsstr_t *str; + HRESULT hres; + DOUBLE val; + + TRACE("\n"); + + hres = numberval_this(vthis, &val); + if(FAILED(hres)) + return hres; + + if(r) { + hres = localize_number(ctx, val, ctx->version >= SCRIPTLANGUAGEVERSION_ES5, &str); + if(FAILED(hres)) + return hres; + *r = jsval_string(str); + } + return S_OK; } static HRESULT Number_toFixed(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, diff --git a/dlls/jscript/tests/api.js b/dlls/jscript/tests/api.js index 1efc0239cc3..865394687c4 100644 --- a/dlls/jscript/tests/api.js +++ b/dlls/jscript/tests/api.js @@ -1365,6 +1365,11 @@ ok(tmp === "0", "num().toString = " + tmp); tmp = (new Number(5.5)).toString(2); ok(tmp === "101.1", "num(5.5).toString(2) = " + tmp); +tmp = (new Number(12)).toLocaleString(); +ok(tmp.indexOf(String.fromCharCode(0)) == -1, "invalid null byte"); +tmp = Number.prototype.toLocaleString.call(NaN); +ok(tmp.indexOf(String.fromCharCode(0)) == -1, "invalid null byte"); + tmp = (new Number(3)).toFixed(3); ok(tmp === "3.000", "num(3).toFixed(3) = " + tmp); tmp = (new Number(3)).toFixed(); @@ -2594,6 +2599,8 @@ testException(function() {arr.test();}, "E_NO_PROPERTY"); testException(function() {[1,2,3].sort(nullDisp);}, "E_JSCRIPT_EXPECTED"); testException(function() {Number.prototype.toString.call(arr);}, "E_NOT_NUM"); testException(function() {Number.prototype.toFixed.call(arr);}, "E_NOT_NUM"); +testException(function() {Number.prototype.toLocaleString.call(arr);}, "E_NOT_NUM"); +testException(function() {Number.prototype.toLocaleString.call(null);}, "E_NOT_NUM"); testException(function() {(new Number(3)).toString(1);}, "E_INVALID_CALL_ARG"); testException(function() {(new Number(3)).toFixed(21);}, "E_FRACTION_DIGITS_OUT_OF_RANGE"); testException(function() {(new Number(1)).toPrecision(0);}, "E_PRECISION_OUT_OF_RANGE"); diff --git a/dlls/jscript/tests/run.c b/dlls/jscript/tests/run.c index e4408be2662..ebdc8dd1b56 100644 --- a/dlls/jscript/tests/run.c +++ b/dlls/jscript/tests/run.c @@ -186,6 +186,7 @@ static BOOL strict_dispid_check, testing_expr; static const char *test_name = "(null)"; static IDispatch *script_disp; static int invoke_version; +static BOOL use_english; static IActiveScriptError *script_error; static IActiveScript *script_engine; static const CLSID *engine_clsid = &CLSID_JScript; @@ -1834,7 +1835,7 @@ static ULONG WINAPI ActiveScriptSite_Release(IActiveScriptSite *iface) static HRESULT WINAPI ActiveScriptSite_GetLCID(IActiveScriptSite *iface, LCID *plcid) { - *plcid = GetUserDefaultLCID(); + *plcid = use_english ? MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT) : GetUserDefaultLCID(); return S_OK; } @@ -2996,6 +2997,39 @@ static void test_default_value(void) close_script(script); } +static void test_number_localization(void) +{ + static struct { + const WCHAR *num; + const WCHAR *expect; + } tests[] = { + { L"0", L"0.00" }, + { L"+1234.5", L"1,234.50" }, + { L"-1337.7331", L"-1,337.73" }, + { L"-0.0123", L"-0.01" }, + { L"-0.0198", L"-0.02" }, + { L"0.004", L"0.00" }, + { L"65536.5", L"65,536.50" }, + { L"NaN", L"NaN" } + }; + static const WCHAR fmt[] = L"Number.prototype.toLocaleString.call(%s)"; + WCHAR script_buf[ARRAY_SIZE(fmt) + 32]; + HRESULT hres; + unsigned i; + VARIANT v; + + use_english = TRUE; + for(i = 0; i < ARRAY_SIZE(tests); i++) { + swprintf(script_buf, ARRAY_SIZE(script_buf), fmt, tests[i].num); + hres = parse_script_expr(script_buf, &v, NULL); + ok(hres == S_OK, "[%u] parse_script_expr failed: %08lx\n", i, hres); + ok(V_VT(&v) == VT_BSTR, "[%u] V_VT(v) = %d\n", i, V_VT(&v)); + ok(!lstrcmpW(V_BSTR(&v), tests[i].expect), "[%u] got %s\n", i, wine_dbgstr_w(V_BSTR(&v))); + VariantClear(&v); + } + use_english = FALSE; +} + static void test_script_exprs(void) { VARIANT v; @@ -3060,6 +3094,7 @@ static void test_script_exprs(void) ok(!lstrcmpW(V_BSTR(&v), L"wine"), "V_BSTR(v) = %s\n", wine_dbgstr_w(V_BSTR(&v))); VariantClear(&v); + test_number_localization(); test_default_value(); test_propputref(); test_retval(); diff --git a/dlls/mshtml/tests/es5.js b/dlls/mshtml/tests/es5.js index 489220790b2..43640543851 100644 --- a/dlls/mshtml/tests/es5.js +++ b/dlls/mshtml/tests/es5.js @@ -28,6 +28,7 @@ var JS_E_REGEXP_EXPECTED = 0x800a1398; var JS_E_INVALID_WRITABLE_PROP_DESC = 0x800a13ac; var JS_E_NONCONFIGURABLE_REDEFINED = 0x800a13d6; var JS_E_NONWRITABLE_MODIFIED = 0x800a13d7; +var JS_E_WRONG_THIS = 0x800a13fc; var tests = []; @@ -68,6 +69,55 @@ sync_test("toISOString", function() { expect_exception(function() { new Date(31494784780800001).toISOString(); }); }); +sync_test("Number toLocaleString", function() { + var r = Number.prototype.toLocaleString.length; + ok(r === 0, "length = " + r); + var tests = [ + [ 0.0, "0" ], + [ 1234.5, "1,234.5" ], + [ -1337.7331, "-1,337.733" ], + [ -0.0123, "-0.012" ], + [-0.0198, "-0.02" ], + [ 0.004, "0.004" ], + [ 99.004, "99.004" ], + [ 99.0004, "99" ], + [ 65536.5, "65,536.5" ], + [ NaN, "NaN" ] + ]; + + if(external.isEnglish) { + for(var i = 0; i < tests.length; i++) { + r = Number.prototype.toLocaleString.call(tests[i][0]); + ok(r === tests[i][1], "[" + i + "] got " + r); + } + } + + try { + Number.prototype.toLocaleString.call("50"); + ok(false, "expected exception calling it on string"); + }catch(ex) { + var n = ex.number >>> 0; + todo_wine. + ok(n === JS_E_WRONG_THIS, "called on string threw " + n); + } + try { + Number.prototype.toLocaleString.call(undefined); + ok(false, "expected exception calling it on undefined"); + }catch(ex) { + var n = ex.number >>> 0; + todo_wine. + ok(n === JS_E_WRONG_THIS, "called on undefined threw " + n); + } + try { + Number.prototype.toLocaleString.call(external.nullDisp); + ok(false, "expected exception calling it on nullDisp"); + }catch(ex) { + var n = ex.number >>> 0; + todo_wine. + ok(n === JS_E_WRONG_THIS, "called on nullDisp threw " + n); + } +}); + sync_test("indexOf", function() { function expect(array, args, exr) { var r = Array.prototype.indexOf.apply(array, args); diff --git a/dlls/mshtml/tests/script.c b/dlls/mshtml/tests/script.c index ceb4fc7c833..7bf2eb62cb6 100644 --- a/dlls/mshtml/tests/script.c +++ b/dlls/mshtml/tests/script.c @@ -153,13 +153,14 @@ DEFINE_EXPECT(GetTypeInfo); #define DISPID_EXTERNAL_WRITESTREAM 0x300006 #define DISPID_EXTERNAL_GETVT 0x300007 #define DISPID_EXTERNAL_NULL_DISP 0x300008 +#define DISPID_EXTERNAL_IS_ENGLISH 0x300009 static const GUID CLSID_TestScript = {0x178fc163,0xf585,0x4e24,{0x9c,0x13,0x4b,0xb7,0xfa,0xf8,0x07,0x46}}; static const GUID CLSID_TestActiveX = {0x178fc163,0xf585,0x4e24,{0x9c,0x13,0x4b,0xb7,0xfa,0xf8,0x06,0x46}}; -static BOOL is_ie9plus; +static BOOL is_ie9plus, is_english; static IHTMLDocument2 *notif_doc; static IOleDocumentView *view; static IDispatchEx *window_dispex; @@ -599,6 +600,10 @@ static HRESULT WINAPI externalDisp_GetDispID(IDispatchEx *iface, BSTR bstrName, *pid = DISPID_EXTERNAL_NULL_DISP; return S_OK; } + if(!lstrcmpW(bstrName, L"isEnglish")) { + *pid = DISPID_EXTERNAL_IS_ENGLISH; + return S_OK; + } ok(0, "unexpected name %s\n", wine_dbgstr_w(bstrName)); return DISP_E_UNKNOWNNAME; @@ -784,6 +789,21 @@ static HRESULT WINAPI externalDisp_InvokeEx(IDispatchEx *iface, DISPID id, LCID V_DISPATCH(pvarRes) = NULL; return S_OK; + case DISPID_EXTERNAL_IS_ENGLISH: + ok(wFlags == INVOKE_PROPERTYGET, "wFlags = %x\n", wFlags); + ok(pdp != NULL, "pdp == NULL\n"); + ok(!pdp->rgvarg, "rgvarg != NULL\n"); + ok(!pdp->rgdispidNamedArgs, "rgdispidNamedArgs != NULL\n"); + ok(!pdp->cArgs, "cArgs = %d\n", pdp->cArgs); + ok(!pdp->cNamedArgs, "cNamedArgs = %d\n", pdp->cNamedArgs); + ok(pvarRes != NULL, "pvarRes == NULL\n"); + ok(V_VT(pvarRes) == VT_EMPTY, "V_VT(pvarRes) = %d\n", V_VT(pvarRes)); + ok(pei != NULL, "pei == NULL\n"); + + V_VT(pvarRes) = VT_BOOL; + V_BOOL(pvarRes) = is_english ? VARIANT_TRUE : VARIANT_FALSE; + return S_OK; + default: ok(0, "unexpected call\n"); return E_NOTIMPL; @@ -3743,6 +3763,19 @@ static HWND create_container_window(void) 300, 300, NULL, NULL, NULL, NULL); } +static void detect_locale(void) +{ + HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); + LANGID (WINAPI *pGetThreadUILanguage)(void) = (void*)GetProcAddress(kernel32, "GetThreadUILanguage"); + + is_english = ((!pGetThreadUILanguage || PRIMARYLANGID(pGetThreadUILanguage()) == LANG_ENGLISH) && + PRIMARYLANGID(GetUserDefaultUILanguage()) == LANG_ENGLISH && + PRIMARYLANGID(GetUserDefaultLangID()) == LANG_ENGLISH); + + if(!is_english) + skip("Skipping some tests in non-English locale\n"); +} + static BOOL check_ie(void) { IHTMLDocument2 *doc; @@ -3779,6 +3812,7 @@ START_TEST(script) CoInitialize(NULL); container_hwnd = create_container_window(); + detect_locale(); if(argc > 2) { init_protocol_handler(); run_script_as_http_with_mode(argv[2], NULL, "11");