Signed-off-by: Gabriel Ivăncescu <gabrielopcode(a)gmail.com>
---
VariantChangeTypeEx from VT_R8 to VT_BSTR uses the same method with
_create_locale and _swprintf_l, but with unusable format for this, so I
think it should be fine.
dlls/jscript/jscript.h | 1 +
dlls/jscript/number.c | 84 +++++++++++++++++++++++++++++++++++++-
dlls/jscript/tests/api.js | 7 ++++
dlls/jscript/tests/run.c | 37 ++++++++++++++++-
dlls/mshtml/tests/es5.js | 50 +++++++++++++++++++++++
dlls/mshtml/tests/script.c | 36 +++++++++++++++-
6 files changed, 211 insertions(+), 4 deletions(-)
diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h
index 7ed4425..90d8142 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 be733fb..9470fbf 100644
--- a/dlls/jscript/number.c
+++ b/dlls/jscript/number.c
@@ -17,6 +17,7 @@
*/
#include <math.h>
+#include <locale.h>
#include <assert.h>
#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 1efc023..8653946 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 e4408be..ebdc8dd 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 4892207..4364054 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 ceb4fc7..7bf2eb6 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");
--
2.34.1