From: Gabriel Ivăncescu gabrielopcode@gmail.com
Behavior depends on mode. Javascript used inside mshtml allows non-JS objects that expose "length" to be used in apply(). For ES5 and above, even JS objects that expose "length" are allowed. Javascript not in HTML mode does not, however.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/function.c | 126 ++++++++++++++++++++++++++++-- dlls/jscript/tests/api.js | 2 + dlls/mshtml/tests/documentmode.js | 66 ++++++++++++++++ 3 files changed, 187 insertions(+), 7 deletions(-)
diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index ffb55af0922..14424e6661f 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -453,6 +453,109 @@ static HRESULT array_to_args(script_ctx_t *ctx, jsdisp_t *arg_array, unsigned *a return S_OK; }
+static HRESULT disp_to_args(script_ctx_t *ctx, IDispatch *disp, unsigned *argc, jsval_t **ret) +{ + IDispatchEx *dispex; + DWORD length, i; + jsval_t *argv; + DISPID dispid; + EXCEPINFO ei; + UINT err = 0; + HRESULT hres; + VARIANT var; + BSTR name; + + if(!(name = SysAllocString(L"length"))) + return E_OUTOFMEMORY; + hres = IDispatch_QueryInterface(disp, &IID_IDispatchEx, (void**)&dispex); + if(SUCCEEDED(hres) && dispex) + hres = IDispatchEx_GetDispID(dispex, name, fdexNameCaseSensitive, &dispid); + else { + hres = IDispatch_GetIDsOfNames(disp, &IID_NULL, &name, 1, 0, &dispid); + dispex = NULL; + } + SysFreeString(name); + if(SUCCEEDED(hres) && dispid == DISPID_UNKNOWN) + hres = DISP_E_UNKNOWNNAME; + if(FAILED(hres)) { + if(hres == DISP_E_UNKNOWNNAME) + hres = JS_E_JSCRIPT_EXPECTED; + goto fail; + } + + if(dispex) + hres = IDispatchEx_InvokeEx(dispex, dispid, ctx->lcid, DISPATCH_PROPERTYGET, NULL, + &var, &ei, &ctx->jscaller->IServiceProvider_iface); + else + hres = IDispatch_Invoke(disp, dispid, &IID_NULL, ctx->lcid, DISPATCH_PROPERTYGET, NULL, &var, &ei, &err); + if(FAILED(hres)) { + if(hres == DISP_E_EXCEPTION) + handle_dispatch_exception(ctx, &ei); + if(hres == DISP_E_MEMBERNOTFOUND) + hres = JS_E_JSCRIPT_EXPECTED; + goto fail; + } + + if(FAILED(VariantChangeType(&var, &var, 0, VT_UI4))) { + VariantClear(&var); + hres = JS_E_JSCRIPT_EXPECTED; + goto fail; + } + length = V_UI4(&var); + + argv = malloc(length * sizeof(*argv)); + if(!argv) { + hres = E_OUTOFMEMORY; + goto fail; + } + + for(i = 0; i < length; i++) { + WCHAR buf[12]; + + swprintf(buf, ARRAY_SIZE(buf), L"%u", i); + if(!(name = SysAllocString(buf))) + hres = E_OUTOFMEMORY; + else { + if(dispex) + hres = IDispatchEx_GetDispID(dispex, name, fdexNameCaseSensitive, &dispid); + else + hres = IDispatch_GetIDsOfNames(disp, &IID_NULL, &name, 1, 0, &dispid); + SysFreeString(name); + } + if(SUCCEEDED(hres)) { + if(dispex) + hres = IDispatchEx_InvokeEx(dispex, dispid, ctx->lcid, DISPATCH_PROPERTYGET, NULL, + &var, &ei, &ctx->jscaller->IServiceProvider_iface); + else + hres = IDispatch_Invoke(disp, dispid, &IID_NULL, ctx->lcid, DISPATCH_PROPERTYGET, NULL, &var, &ei, &err); + if(SUCCEEDED(hres)) { + hres = variant_to_jsval(ctx, &var, &argv[i]); + VariantClear(&var); + }else if(hres == DISP_E_EXCEPTION) { + handle_dispatch_exception(ctx, &ei); + } + } + if(FAILED(hres)) { + if(hres == DISP_E_UNKNOWNNAME || hres == DISP_E_MEMBERNOTFOUND) { + argv[i] = jsval_undefined(); + continue; + } + while(i--) + jsval_release(argv[i]); + free(argv); + goto fail; + } + } + + *argc = length; + *ret = argv; + hres = S_OK; +fail: + if(dispex) + IDispatchEx_Release(dispex); + return hres; +} + static HRESULT Function_apply(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) { jsval_t this_val = jsval_undefined(); @@ -484,22 +587,31 @@ static HRESULT Function_apply(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsi
if(argc >= 2) { jsdisp_t *arg_array = NULL; + IDispatch *obj = NULL;
if(is_object_instance(argv[1])) { - arg_array = iface_to_jsdisp(get_object(argv[1])); - if(arg_array && - (!is_class(arg_array, JSCLASS_ARRAY) && !is_class(arg_array, JSCLASS_ARGUMENTS) )) { - jsdisp_release(arg_array); - arg_array = NULL; + obj = get_object(argv[1]); + arg_array = iface_to_jsdisp(obj); + + if(ctx->version < SCRIPTLANGUAGEVERSION_ES5) { + if(!arg_array) { + if(!ctx->html_mode) + obj = NULL; + }else if(!is_class(arg_array, JSCLASS_ARRAY) && !is_class(arg_array, JSCLASS_ARGUMENTS)) { + jsdisp_release(arg_array); + arg_array = NULL; + obj = NULL; + } } }
if(arg_array) { hres = array_to_args(ctx, arg_array, &cnt, &args); jsdisp_release(arg_array); + }else if(obj) { + hres = disp_to_args(ctx, obj, &cnt, &args); }else { - FIXME("throw TypeError\n"); - hres = E_FAIL; + hres = JS_E_JSCRIPT_EXPECTED; } }
diff --git a/dlls/jscript/tests/api.js b/dlls/jscript/tests/api.js index aea7cac63dc..8c054fff058 100644 --- a/dlls/jscript/tests/api.js +++ b/dlls/jscript/tests/api.js @@ -2918,6 +2918,8 @@ testFunctionThis("toString"); testFunctionThis("call"); testFunctionThis("apply");
+testException(function() {(function (a, b) {}).apply(null, testObj)}, "E_JSCRIPT_EXPECTED"); + function testArrayHostThis(func) { testException(function() { Array.prototype[func].call(testObj); }, "E_JSCRIPT_EXPECTED"); } diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index 8e8b87d7e36..562761a022f 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -502,6 +502,72 @@ sync_test("builtin_obj", function() { f.call = function() { }; ok(f.apply === 0, "changed f.apply = ", f.apply); ok(f.call instanceof Function, "changed f.call not instance of Function"); + + e = Array.isArray(document.body.childNodes); + ok(e === false, "isArray(childNodes) returned " + e); + } + + (function(a, b, c) { + ok(a === document.body.childNodes[0], "a = " + a); + ok(b === document.body.childNodes[1], "b = " + b); + ok(c === document.body.childNodes[2], "c = " + c); + }).apply(null, document.body.childNodes); + + elem1[0] = "a"; + elem1[1] = "b"; + if(v < 9) { + try { + (function(a, b) {}).apply(null, elem1); + }catch(ex) { + e = ex.number; + } + todo_wine. + ok(e === 0xa13a4 - 0x80000000, "[function.apply with elem without length] e = " + e); + }else { + (function(a, b) { + ok(a === undefined, "a = " + a); + ok(b === undefined, "b = " + b); + }).apply(null, elem1); + } + + elem1.length = 2; + (function(a, b) { + ok(a === "a", "a = " + a); + ok(b === "b", "b = " + b); + }).apply(null, elem1); + + elem1 = new Object; + elem1[0] = "c"; + elem1[1] = "d"; + if(v < 9) { + try { + (function(c, d) {}).apply(null, elem1); + }catch(ex) { + e = ex.number; + } + todo_wine. + ok(e === 0xa13a4 - 0x80000000, "[function.apply with Object without length] e = " + e); + }else { + (function(c, d) { + ok(c === undefined, "c = " + c); + ok(d === undefined, "d = " + d); + }).apply(null, elem1); + } + + elem1.length = 2; + if(v < 9) { + try { + (function(c, d) {}).apply(null, elem1); + }catch(ex) { + e = ex.number; + } + todo_wine. + ok(e === 0xa13a4 - 0x80000000, "[function.apply with Object with length] e = " + e); + }else { + (function(c, d) { + ok(c === "c", "c = " + c); + ok(d === "d", "d = " + d); + }).apply(null, elem1); } });