[PATCH 4/8] jscript: Implement Array.prototype.toLocaleString.

Gabriel Ivăncescu gabrielopcode at gmail.com
Fri Apr 15 08:00:24 CDT 2022


Signed-off-by: Gabriel Ivăncescu <gabrielopcode at gmail.com>
---
 dlls/jscript/array.c      | 112 ++++++++++++++++++++++++++++++++++++--
 dlls/jscript/jscript.h    |   1 +
 dlls/jscript/number.c     |   2 +-
 dlls/jscript/tests/api.js |   1 +
 dlls/jscript/tests/run.c  |  49 +++++++++++++++++
 dlls/mshtml/tests/es5.js  |  53 ++++++++++++++++++
 6 files changed, 211 insertions(+), 7 deletions(-)

diff --git a/dlls/jscript/array.c b/dlls/jscript/array.c
index ee72579..b9faa5a 100644
--- a/dlls/jscript/array.c
+++ b/dlls/jscript/array.c
@@ -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,114 @@ 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)
+{
+    DISPPARAMS dp = { 0 };
+    IDispatchEx *dispex;
+    jsdisp_t *jsdisp;
+    IDispatch *obj;
+    EXCEPINFO ei;
+    HRESULT hres;
+    UINT err = 0;
+    VARIANT var;
+    DISPID id;
+    BSTR bstr;
+
+    switch(jsval_type(val)) {
+    case JSV_OBJECT:
+        obj = get_object(val);
+        if((jsdisp = to_jsdisp(obj))) {
+            hres = jsdisp_call_name(jsdisp, 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;
+        }
+
+        if(!(bstr = SysAllocString(L"toLocaleString")))
+            return E_OUTOFMEMORY;
+
+        V_VT(&var) = VT_EMPTY;
+        hres = IDispatch_QueryInterface(obj, &IID_IDispatchEx, (void**)&dispex);
+        if(SUCCEEDED(hres) && dispex) {
+            hres = IDispatchEx_GetDispID(dispex, bstr, make_grfdex(ctx, fdexNameCaseSensitive), &id);
+            if(SUCCEEDED(hres)) {
+                hres = IDispatchEx_InvokeEx(dispex, id, ctx->lcid, DISPATCH_METHOD, &dp, &var, &ei, &ctx->jscaller->IServiceProvider_iface);
+                if(hres == DISP_E_EXCEPTION)
+                    disp_fill_exception(ctx, &ei);
+            }
+            IDispatchEx_Release(dispex);
+        }else {
+            hres = IDispatch_GetIDsOfNames(obj, &IID_NULL, &bstr, 1, 0, &id);
+            if(SUCCEEDED(hres)) {
+                hres = IDispatch_Invoke(obj, id, &IID_NULL, ctx->lcid, DISPATCH_METHOD, &dp, &var, &ei, &err);
+                if(hres == DISP_E_EXCEPTION)
+                    disp_fill_exception(ctx, &ei);
+            }
+        }
+        SysFreeString(bstr);
+        if(FAILED(hres))
+            return hres;
+
+        hres = variant_to_jsval(ctx, &var, &val);
+        VariantClear(&var);
+        break;
+    case JSV_NUMBER:
+        if(ctx->version >= SCRIPTLANGUAGEVERSION_ES5) {
+            hres = Number_toLocaleString(ctx, val, 0, 0, NULL, &val);
+            if(SUCCEEDED(hres))
+                *str = get_string(val);
+            return hres;
+        }
+        /* 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;
+
+    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;
+    }
+
+    hres = array_join(ctx, jsthis, length, L", ", 2, 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,
diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h
index 8cf386d..a9b56fb 100644
--- a/dlls/jscript/jscript.h
+++ b/dlls/jscript/jscript.h
@@ -452,6 +452,7 @@ BOOL bool_obj_value(jsdisp_t*) DECLSPEC_HIDDEN;
 unsigned array_get_length(jsdisp_t*) DECLSPEC_HIDDEN;
 
 HRESULT JSGlobal_eval(script_ctx_t*,jsval_t,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;
+HRESULT Number_toLocaleString(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;
 HRESULT Object_set_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 c487f10..cc0f224 100644
--- a/dlls/jscript/number.c
+++ b/dlls/jscript/number.c
@@ -342,7 +342,7 @@ static HRESULT Number_toString(script_ctx_t *ctx, jsval_t vthis, WORD flags, uns
     return S_OK;
 }
 
-static HRESULT Number_toLocaleString(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
+HRESULT Number_toLocaleString(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
         jsval_t *r)
 {
     WCHAR buf[314], *numstr, *p, *frac = NULL;
diff --git a/dlls/jscript/tests/api.js b/dlls/jscript/tests/api.js
index 8653946..e81bbea 100644
--- a/dlls/jscript/tests/api.js
+++ b/dlls/jscript/tests/api.js
@@ -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");
diff --git a/dlls/jscript/tests/run.c b/dlls/jscript/tests/run.c
index e4408be..baa52a8 100644
--- a/dlls/jscript/tests/run.c
+++ b/dlls/jscript/tests/run.c
@@ -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
@@ -476,6 +479,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);
@@ -556,6 +565,23 @@ 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->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_I4;
+        V_I4(pvarRes) = 1234;
+
         return S_OK;
     }
 
@@ -3060,6 +3086,23 @@ 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);
 
+    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), L"12, 12, undefined undefined undefined, 12"), "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_default_value();
     test_propputref();
     test_retval();
@@ -3656,6 +3699,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);
diff --git a/dlls/mshtml/tests/es5.js b/dlls/mshtml/tests/es5.js
index 4892207..c57d85f 100644
--- a/dlls/mshtml/tests/es5.js
+++ b/dlls/mshtml/tests/es5.js
@@ -68,6 +68,59 @@ 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;
+    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, undefined undefined undefined, aBc", "toLocaleString returned " + r);
+
+    r = [3].toLocaleString();  /* primitive number value not affected */
+    ok(r !== "aBc", "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, 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, 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);
+    }
+});
+
 sync_test("indexOf", function() {
     function expect(array, args, exr) {
         var r = Array.prototype.indexOf.apply(array, args);
-- 
2.34.1




More information about the wine-devel mailing list