jscript: Implement Array.prototype.toLocaleString.

Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com>
Signed-off-by: Jacek Caban <jacek@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Gabriel Ivăncescu 2022-05-03 18:17:08 +03:00 committed by Alexandre Julliard
parent 41782ec12e
commit a8057c2e14
7 changed files with 245 additions and 6 deletions

View File

@ -229,7 +229,7 @@ done:
}
static HRESULT array_join(script_ctx_t *ctx, jsdisp_t *array, DWORD length, const WCHAR *sep,
unsigned seplen, jsval_t *r)
unsigned seplen, HRESULT (*to_string)(script_ctx_t*,jsval_t,jsstr_t**), jsval_t *r)
{
jsstr_t **str_tab, *ret = NULL;
jsval_t val;
@ -339,11 +339,11 @@ static HRESULT Array_join(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned
if(FAILED(hres))
goto done;
hres = array_join(ctx, jsthis, length, sep, jsstr_length(sep_str), r);
hres = array_join(ctx, jsthis, length, sep, jsstr_length(sep_str), to_string, r);
jsstr_release(sep_str);
}else {
hres = array_join(ctx, jsthis, length, L",", 1, r);
hres = array_join(ctx, jsthis, length, L",", 1, to_string, r);
}
done:
@ -947,14 +947,80 @@ static HRESULT Array_toString(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsi
if(!array)
return JS_E_ARRAY_EXPECTED;
return array_join(ctx, &array->dispex, array->length, L",", 1, r);
return array_join(ctx, &array->dispex, array->length, L",", 1, to_string, r);
}
static HRESULT to_locale_string(script_ctx_t *ctx, jsval_t val, jsstr_t **str)
{
jsdisp_t *jsdisp;
IDispatch *obj;
HRESULT hres;
switch(jsval_type(val)) {
case JSV_OBJECT:
hres = disp_call_name(ctx, get_object(val), L"toLocaleString", DISPATCH_METHOD, 0, NULL, &val);
if(FAILED(hres)) {
if(hres == JS_E_INVALID_PROPERTY && ctx->version >= SCRIPTLANGUAGEVERSION_ES5)
hres = JS_E_FUNCTION_EXPECTED;
return hres;
}
break;
case JSV_NUMBER:
if(ctx->version >= SCRIPTLANGUAGEVERSION_ES5)
return localize_number(ctx, get_number(val), FALSE, str);
/* fall through */
default:
if(ctx->version >= SCRIPTLANGUAGEVERSION_ES5)
break;
hres = to_object(ctx, val, &obj);
if(FAILED(hres))
return hres;
jsdisp = as_jsdisp(obj);
hres = jsdisp_call_name(jsdisp, L"toLocaleString", DISPATCH_METHOD, 0, NULL, &val);
jsdisp_release(jsdisp);
if(FAILED(hres))
return hres;
break;
}
return to_string(ctx, val, str);
}
static HRESULT Array_toLocaleString(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
FIXME("\n");
return E_NOTIMPL;
jsdisp_t *jsthis;
UINT32 length;
HRESULT hres;
WCHAR buf[5];
int len;
TRACE("\n");
if(ctx->version < SCRIPTLANGUAGEVERSION_ES5) {
ArrayInstance *array = array_this(vthis);
if(!array)
return JS_E_ARRAY_EXPECTED;
jsthis = jsdisp_addref(&array->dispex);
length = array->length;
}else {
hres = get_length(ctx, vthis, &jsthis, &length);
if(FAILED(hres))
return hres;
}
if(!(len = GetLocaleInfoW(ctx->lcid, LOCALE_SLIST, buf, ARRAY_SIZE(buf) - 1))) {
buf[len++] = ',';
len++;
}
buf[len - 1] = ' ';
buf[len] = '\0';
hres = array_join(ctx, jsthis, length, buf, len, to_locale_string, r);
jsdisp_release(jsthis);
return hres;
}
static HRESULT Array_forEach(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,

View File

@ -2146,6 +2146,33 @@ HRESULT disp_call(script_ctx_t *ctx, IDispatch *disp, DISPID id, WORD flags, uns
return hres;
}
HRESULT disp_call_name(script_ctx_t *ctx, IDispatch *disp, const WCHAR *name, WORD flags, unsigned argc, jsval_t *argv, jsval_t *ret)
{
IDispatchEx *dispex;
jsdisp_t *jsdisp;
HRESULT hres;
DISPID id;
BSTR bstr;
if((jsdisp = to_jsdisp(disp)) && jsdisp->ctx == ctx)
return jsdisp_call_name(jsdisp, name, flags, argc, argv, ret);
if(!(bstr = SysAllocString(name)))
return E_OUTOFMEMORY;
hres = IDispatch_QueryInterface(disp, &IID_IDispatchEx, (void**)&dispex);
if(SUCCEEDED(hres) && dispex) {
hres = IDispatchEx_GetDispID(dispex, bstr, make_grfdex(ctx, fdexNameCaseSensitive), &id);
IDispatchEx_Release(dispex);
}else {
hres = IDispatch_GetIDsOfNames(disp, &IID_NULL, &bstr, 1, 0, &id);
}
SysFreeString(bstr);
if(FAILED(hres))
return hres;
return disp_call(ctx, disp, id, flags, argc, argv, ret);
}
HRESULT disp_call_value(script_ctx_t *ctx, IDispatch *disp, IDispatch *jsthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{

View File

@ -241,6 +241,7 @@ HRESULT init_dispex(jsdisp_t*,script_ctx_t*,const builtin_info_t*,jsdisp_t*) DEC
HRESULT init_dispex_from_constr(jsdisp_t*,script_ctx_t*,const builtin_info_t*,jsdisp_t*) DECLSPEC_HIDDEN;
HRESULT disp_call(script_ctx_t*,IDispatch*,DISPID,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;
HRESULT disp_call_name(script_ctx_t*,IDispatch*,const WCHAR*,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;
HRESULT disp_call_value(script_ctx_t*,IDispatch*,IDispatch*,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;
HRESULT jsdisp_call_value(jsdisp_t*,IDispatch*,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;
HRESULT jsdisp_call(jsdisp_t*,DISPID,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;

View File

@ -2597,6 +2597,7 @@ testException(function() {date.setTime();}, "E_ARG_NOT_OPT");
testException(function() {date.setYear();}, "E_ARG_NOT_OPT");
testException(function() {arr.test();}, "E_NO_PROPERTY");
testException(function() {[1,2,3].sort(nullDisp);}, "E_JSCRIPT_EXPECTED");
testException(function() {var o = new Object(); o.length = 1; o[0] = "a"; Array.prototype.toLocaleString.call(o);}, "E_NOT_ARRAY");
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");

View File

@ -112,6 +112,8 @@ DEFINE_EXPECT(testobj_onlydispid_i);
DEFINE_EXPECT(testobj_notexists_d);
DEFINE_EXPECT(testobj_newenum);
DEFINE_EXPECT(testobj_getidfail_d);
DEFINE_EXPECT(testobj_tolocalestr_d);
DEFINE_EXPECT(testobj_tolocalestr_i);
DEFINE_EXPECT(enumvariant_next_0);
DEFINE_EXPECT(enumvariant_next_1);
DEFINE_EXPECT(enumvariant_reset);
@ -178,6 +180,7 @@ DEFINE_EXPECT(BindHandler);
#define DISPID_TESTOBJ_PROP 0x2000
#define DISPID_TESTOBJ_ONLYDISPID 0x2001
#define DISPID_TESTOBJ_WITHPROP 0x2002
#define DISPID_TESTOBJ_TOLOCALESTR 0x2003
#define JS_E_OUT_OF_MEMORY 0x800a03ec
#define JS_E_INVALID_CHAR 0x800a03f6
@ -477,6 +480,12 @@ static HRESULT WINAPI testObj_GetDispID(IDispatchEx *iface, BSTR bstrName, DWORD
*pid = DISPID_TESTOBJ_ONLYDISPID;
return S_OK;
}
if(!lstrcmpW(bstrName, L"toLocaleString")) {
CHECK_EXPECT(testobj_tolocalestr_d);
test_grfdex(grfdex, fdexNameCaseSensitive);
*pid = DISPID_TESTOBJ_TOLOCALESTR;
return S_OK;
}
if(!lstrcmpW(bstrName, L"notExists")) {
CHECK_EXPECT(testobj_notexists_d);
test_grfdex(grfdex, fdexNameCaseSensitive);
@ -557,6 +566,22 @@ static HRESULT WINAPI testObj_InvokeEx(IDispatchEx *iface, DISPID id, LCID lcid,
V_VT(pvarRes) = VT_I4;
V_I4(pvarRes) = 1;
return S_OK;
case DISPID_TESTOBJ_TOLOCALESTR:
CHECK_EXPECT(testobj_tolocalestr_i);
ok(wFlags == DISPATCH_METHOD, "wFlags = %x\n", wFlags);
ok(pdp != NULL, "pdp == 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_I4;
V_I4(pvarRes) = 1234;
return S_OK;
}
@ -3032,6 +3057,7 @@ static void test_number_localization(void)
static void test_script_exprs(void)
{
WCHAR buf[64], sep[4];
VARIANT v;
HRESULT hres;
@ -3094,6 +3120,25 @@ 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);
if(!GetLocaleInfoW(GetUserDefaultLCID(), LOCALE_SLIST, sep, ARRAY_SIZE(sep))) wcscpy(sep, L",");
swprintf(buf, ARRAY_SIZE(buf), L"12%s 12%s undefined undefined undefined%s 12", sep, sep, sep);
hres = parse_script_expr(L"var arr = [5]; arr.toLocaleString = function(a,b,c) {return a+' '+b+' '+c;};"
L"Number.prototype.toLocaleString = function() {return 12;};"
L"[1,2,arr,3].toLocaleString('foo','bar','baz')", &v, NULL);
ok(hres == S_OK, "parse_script_expr failed: %08lx\n", hres);
ok(V_VT(&v) == VT_BSTR, "V_VT(v) = %d\n", V_VT(&v));
ok(!lstrcmpW(V_BSTR(&v), buf), "V_BSTR(v) = %s\n", wine_dbgstr_w(V_BSTR(&v)));
VariantClear(&v);
hres = parse_script_expr(L"delete Object.prototype.toLocaleString; Array.prototype.toLocaleString.call([])", &v, NULL);
ok(hres == S_OK, "parse_script_expr failed: %08lx\n", hres);
ok(V_VT(&v) == VT_BSTR, "V_VT(v) = %d\n", V_VT(&v));
ok(!lstrcmpW(V_BSTR(&v), L""), "V_BSTR(v) = %s\n", wine_dbgstr_w(V_BSTR(&v)));
VariantClear(&v);
hres = parse_script_expr(L"delete Object.prototype.toLocaleString; Array.prototype.toLocaleString.call(['a'])", &v, NULL);
ok(hres == 0x800a01b6, "parse_script_expr failed: %08lx\n", hres);
test_number_localization();
test_default_value();
test_propputref();
@ -3691,6 +3736,12 @@ static BOOL run_tests(void)
CHECK_CALLED(testobj_withprop_d);
CHECK_CALLED(testobj_withprop_i);
SET_EXPECT(testobj_tolocalestr_d);
SET_EXPECT(testobj_tolocalestr_i);
run_script(L"var t = [testObj].toLocaleString(); ok(t === '1234', 't = ' + t);");
CHECK_CALLED(testobj_tolocalestr_d);
CHECK_CALLED(testobj_tolocalestr_i);
run_script(L"@set @t=2\nok(@t === 2, '@t = ' + @t);");
SET_EXPECT(global_success_d);

View File

@ -69,6 +69,70 @@ sync_test("toISOString", function() {
expect_exception(function() { new Date(31494784780800001).toISOString(); });
});
sync_test("Array toLocaleString", function() {
var r = Array.prototype.toLocaleString.length, old = Number.prototype.toLocaleString;
var s = external.listSeparator + ' ';
ok(r === 0, "length = " + r);
r = [5];
r.toLocaleString = function(a, b, c) { return a + " " + b + " " + c; };
Number.prototype.toLocaleString = function() { return "aBc"; };
r = [new Number(3), r, new Number(12)].toLocaleString("foo", "bar", "baz");
ok(r === "aBc"+s+"undefined undefined undefined"+s+"aBc", "toLocaleString returned " + r);
r = [3].toLocaleString(); /* primitive number value not affected */
if(external.isEnglish)
ok(r === "3.00", "[3].toLocaleString returned " + r);
else
ok(r !== "aBc", "[3].toLocaleString returned " + r);
Number.prototype.toLocaleString = old;
r = Object.create(null);
r.toString = function() { return "foo"; }
try {
Array.prototype.toLocaleString.call([r]);
ok(false, "expected exception calling it on array with object without toLocaleString");
}catch(ex) {
var n = ex.number >>> 0;
ok(n === JS_E_FUNCTION_EXPECTED, "called on array with object without toLocaleString threw " + n);
}
r = { length: 2 };
r[0] = { toLocaleString: function() { return "foo"; } }
r[1] = { toLocaleString: function() { return "bar"; } }
r = Array.prototype.toLocaleString.call(r);
ok(r === "foo"+s+"bar", "toLocaleString on array-like object returned " + r);
r = Array.prototype.toLocaleString.call({});
ok(r === "", "toLocaleString on {} returned " + r);
r = Array.prototype.toLocaleString.call("ab");
ok(r === "a"+s+"b", "toLocaleString on 'ab' returned " + r);
try {
Array.prototype.toLocaleString.call(undefined);
ok(false, "expected exception calling it on undefined");
}catch(ex) {
var n = ex.number >>> 0;
ok(n === JS_E_OBJECT_EXPECTED, "called on undefined threw " + n);
}
try {
Array.prototype.toLocaleString.call(null);
ok(false, "expected exception calling it on null");
}catch(ex) {
var n = ex.number >>> 0;
ok(n === JS_E_OBJECT_EXPECTED, "called on null threw " + n);
}
try {
Array.prototype.toLocaleString.call(external.nullDisp);
ok(false, "expected exception calling it on nullDisp");
}catch(ex) {
var n = ex.number >>> 0;
ok(n === JS_E_OBJECT_EXPECTED, "called on nullDisp threw " + n);
}
});
sync_test("Number toLocaleString", function() {
var r = Number.prototype.toLocaleString.length;
ok(r === 0, "length = " + r);

View File

@ -154,6 +154,7 @@ DEFINE_EXPECT(GetTypeInfo);
#define DISPID_EXTERNAL_GETVT 0x300007
#define DISPID_EXTERNAL_NULL_DISP 0x300008
#define DISPID_EXTERNAL_IS_ENGLISH 0x300009
#define DISPID_EXTERNAL_LIST_SEP 0x30000A
static const GUID CLSID_TestScript =
{0x178fc163,0xf585,0x4e24,{0x9c,0x13,0x4b,0xb7,0xfa,0xf8,0x07,0x46}};
@ -604,6 +605,10 @@ static HRESULT WINAPI externalDisp_GetDispID(IDispatchEx *iface, BSTR bstrName,
*pid = DISPID_EXTERNAL_IS_ENGLISH;
return S_OK;
}
if(!lstrcmpW(bstrName, L"listSeparator")) {
*pid = DISPID_EXTERNAL_LIST_SEP;
return S_OK;
}
ok(0, "unexpected name %s\n", wine_dbgstr_w(bstrName));
return DISP_E_UNKNOWNNAME;
@ -804,6 +809,30 @@ static HRESULT WINAPI externalDisp_InvokeEx(IDispatchEx *iface, DISPID id, LCID
V_BOOL(pvarRes) = is_english ? VARIANT_TRUE : VARIANT_FALSE;
return S_OK;
case DISPID_EXTERNAL_LIST_SEP: {
WCHAR buf[4];
int len;
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");
if(!(len = GetLocaleInfoW(GetUserDefaultLCID(), LOCALE_SLIST, buf, ARRAY_SIZE(buf))))
buf[len++] = ',';
else
len--;
V_VT(pvarRes) = VT_BSTR;
V_BSTR(pvarRes) = SysAllocStringLen(buf, len);
return S_OK;
}
default:
ok(0, "unexpected call\n");
return E_NOTIMPL;