-- v4: mshtml: Move Option constructor to the window rather than the prototype. mshtml: Move Image constructor to the window rather than the prototype. mshtml: Validate builtin host functions in IE9+ using prototype_id rather mshtml: Store the prototype_id of the last member that contains the needed mshtml: Define "create" from XMLHttpRequest constructor as a jscript prop mshtml: Don't expose "create" from Option constructor in IE9+ modes. mshtml: Don't expose "create" from Image constructor in IE9+ modes.
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/htmlimg.c | 11 ++++++----- dlls/mshtml/tests/documentmode.js | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/dlls/mshtml/htmlimg.c b/dlls/mshtml/htmlimg.c index f6a4b992ab6..5716bbd7ac4 100644 --- a/dlls/mshtml/htmlimg.c +++ b/dlls/mshtml/htmlimg.c @@ -864,10 +864,11 @@ static HRESULT HTMLImageElementFactory_value(DispatchEx *dispex, LCID lcid, return S_OK; }
-static const tid_t HTMLImageElementFactory_iface_tids[] = { - IHTMLImageElementFactory_tid, - 0 -}; +static void HTMLImageElementFactory_init_dispex_info(dispex_data_t *info, compat_mode_t mode) +{ + if(mode < COMPAT_MODE_IE9) + dispex_info_add_interface(info, IHTMLImageElementFactory_tid, NULL); +}
static const dispex_static_data_vtbl_t HTMLImageElementFactory_dispex_vtbl = { .query_interface = HTMLImageElementFactory_query_interface, @@ -882,7 +883,7 @@ static dispex_static_data_t HTMLImageElementFactory_dispex = { .constructor_id = PROT_HTMLImageElement, .vtbl = &HTMLImageElementFactory_dispex_vtbl, .disp_tid = IHTMLImageElementFactory_tid, - .iface_tids = HTMLImageElementFactory_iface_tids, + .init_info = HTMLImageElementFactory_init_dispex_info, };
HRESULT HTMLImageElementFactory_Create(HTMLInnerWindow *window, HTMLImageElementFactory **ret_val) diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index 5bbd8b098f6..cb1bafe034c 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -906,6 +906,20 @@ sync_test("style_props", function() { } });
+sync_test("constructor props", function() { + function test_exposed(constructor, prop, expect) { + if(expect) + ok(prop in window[constructor], prop + " not found in " + constructor + " constructor."); + else + ok(!(prop in window[constructor]), prop + " found in " + constructor + " constructor."); + } + var v = document.documentMode; + + test_exposed("Image", "create", v < 9); + test_exposed("XMLHttpRequest", "create", true); + if(v >= 11) test_exposed("MutationObserver", "create", false); +}); + sync_test("createElement_inline_attr", function() { var v = document.documentMode, e, s;
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/htmlselect.c | 11 ++++++----- dlls/mshtml/tests/documentmode.js | 1 + 2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/dlls/mshtml/htmlselect.c b/dlls/mshtml/htmlselect.c index 2d02589db38..3bae8fe958b 100644 --- a/dlls/mshtml/htmlselect.c +++ b/dlls/mshtml/htmlselect.c @@ -510,10 +510,11 @@ static HRESULT HTMLOptionElementFactory_value(DispatchEx *dispex, LCID lcid, return S_OK; }
-static const tid_t HTMLOptionElementFactory_iface_tids[] = { - IHTMLOptionElementFactory_tid, - 0 -}; +static void HTMLImageElementFactory_init_dispex_info(dispex_data_t *info, compat_mode_t mode) +{ + if(mode < COMPAT_MODE_IE9) + dispex_info_add_interface(info, IHTMLOptionElementFactory_tid, NULL); +}
static const dispex_static_data_vtbl_t HTMLOptionElementFactory_dispex_vtbl = { .query_interface = HTMLOptionElementFactory_query_interface, @@ -528,7 +529,7 @@ static dispex_static_data_t HTMLOptionElementFactory_dispex = { .constructor_id = PROT_HTMLOptionElement, .vtbl = &HTMLOptionElementFactory_dispex_vtbl, .disp_tid = IHTMLOptionElementFactory_tid, - .iface_tids = HTMLOptionElementFactory_iface_tids, + .init_info = HTMLImageElementFactory_init_dispex_info, };
HRESULT HTMLOptionElementFactory_Create(HTMLInnerWindow *window, HTMLOptionElementFactory **ret_ptr) diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index cb1bafe034c..bb53a1a010f 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -916,6 +916,7 @@ sync_test("constructor props", function() { var v = document.documentMode;
test_exposed("Image", "create", v < 9); + test_exposed("Option", "create", v < 9); test_exposed("XMLHttpRequest", "create", true); if(v >= 11) test_exposed("MutationObserver", "create", false); });
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/function.c | 90 ++++++++++++++++++++++++++----- dlls/mshtml/tests/documentmode.js | 15 ++++++ 2 files changed, 93 insertions(+), 12 deletions(-)
diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index 9f1f53a96ae..52a96c9f69a 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -76,6 +76,11 @@ typedef struct { IWineJSDispatchHost *host_iface; } HostConstructor;
+typedef struct { + FunctionInstance function; + HostConstructor *constr; +} HostConstructor_create; + typedef struct { jsdisp_t jsdisp; jsval_t *buf; @@ -1102,14 +1107,6 @@ static ULONG HostConstructor_release(jsdisp_t *jsdisp) return IWineJSDispatchHost_Release(constr->host_iface); }
-static HRESULT HostConstructor_lookup_prop(jsdisp_t *jsdisp, const WCHAR *name, unsigned flags, struct property_info *desc) -{ - HostConstructor *constr = (HostConstructor*)jsdisp; - HRESULT hres = IWineJSDispatchHost_LookupProperty(constr->host_iface, name, flags, desc); - assert(hres != S_OK || (desc->flags & PROPF_METHOD)); /* external properties are not allowed */ - return hres; -} - static const builtin_info_t HostConstructor_info = { .class = JSCLASS_FUNCTION, .addref = HostConstructor_addref, @@ -1117,7 +1114,6 @@ static const builtin_info_t HostConstructor_info = { .call = Function_value, .destructor = Function_destructor, .gc_traverse = Function_gc_traverse, - .lookup_prop = HostConstructor_lookup_prop, };
static HRESULT HostConstructor_call(script_ctx_t *ctx, FunctionInstance *func, jsval_t vthis, unsigned flags, @@ -1187,9 +1183,55 @@ static const function_vtbl_t HostConstructorVtbl = { HostConstructor_gc_traverse };
+static HRESULT HostConstructor_create_call(script_ctx_t *ctx, FunctionInstance *func, jsval_t vthis, unsigned flags, + unsigned argc, jsval_t *argv, jsval_t *r) +{ + HostConstructor_create *function = (HostConstructor_create*)func; + + /* only allow calls since it's a method */ + if(!(flags & DISPATCH_METHOD)) + return E_UNEXPECTED; + + return HostConstructor_call(ctx, &function->constr->function, vthis, DISPATCH_CONSTRUCT, argc, argv, r); +} + +static HRESULT HostConstructor_create_toString(FunctionInstance *func, jsstr_t **ret) +{ + return native_function_string(L"create", ret); +} + +static function_code_t *HostConstructor_create_get_code(FunctionInstance *function) +{ + return NULL; +} + +static void HostConstructor_create_destructor(FunctionInstance *func) +{ + HostConstructor_create *function = (HostConstructor_create*)func; + + if(function->constr) + jsdisp_release(&function->constr->function.dispex); +} + +static HRESULT HostConstructor_create_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, FunctionInstance *func) +{ + HostConstructor_create *function = (HostConstructor_create*)func; + + return gc_process_linked_obj(gc_ctx, op, &function->function.dispex, &function->constr->function.dispex, (void**)&function->constr); +} + +static const function_vtbl_t HostConstructor_create_vtbl = { + HostConstructor_create_call, + HostConstructor_create_toString, + HostConstructor_create_get_code, + HostConstructor_create_destructor, + HostConstructor_create_gc_traverse +}; + HRESULT init_host_constructor(script_ctx_t *ctx, IWineJSDispatchHost *host_constr, IWineJSDispatch *prototype, IWineJSDispatch **ret) { + struct property_info desc; HostConstructor *function; HRESULT hres;
@@ -1201,13 +1243,37 @@ HRESULT init_host_constructor(script_ctx_t *ctx, IWineJSDispatchHost *host_const
hres = jsdisp_define_data_property(&function->function.dispex, L"prototype", PROPF_WRITABLE | PROPF_CONFIGURABLE, jsval_disp((IDispatch *)prototype)); - if(FAILED(hres)) { - IWineJSDispatch_Free(&function->function.dispex.IWineJSDispatch_iface); - return hres; + if(FAILED(hres)) + goto fail; + + hres = IWineJSDispatchHost_LookupProperty(host_constr, L"create", fdexNameCaseSensitive, &desc); + if(SUCCEEDED(hres)) { + HostConstructor_create *create; + + assert(desc.flags & PROPF_METHOD); + + hres = create_function(ctx, &HostFunction_info, &HostConstructor_create_vtbl, sizeof(*create), + PROPF_METHOD, NULL, (void**)&create); + if(FAILED(hres)) + goto fail; + + create->constr = function; + jsdisp_addref(&function->function.dispex); + + hres = jsdisp_define_data_property(&function->function.dispex, L"create", desc.flags & PROPF_ALL, jsval_obj(&create->function.dispex)); + jsdisp_release(&create->function.dispex); + if(FAILED(hres)) + goto fail; + }else if(hres != DISP_E_UNKNOWNNAME) { + goto fail; }
*ret = &function->function.dispex.IWineJSDispatch_iface; return S_OK; + +fail: + IWineJSDispatch_Free(&function->function.dispex.IWineJSDispatch_iface); + return hres; }
static HRESULT BindFunction_get_arguments(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index bb53a1a010f..33d000fc35f 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -3996,3 +3996,18 @@ sync_test("prototype props", function() { check(Text, [ "removeNode", "replaceNode", "replaceWholeText", "splitText", "swapNode", "wholeText" ], [ "replaceWholeText", "wholeText" ]); check(UIEvent, [ "detail", "initUIEvent", "view" ], null, [ "deviceSessionId" ]); }); + +sync_test("constructors", function() { + var v = document.documentMode; + if(v < 9) + return; + + ok(XMLHttpRequest.create() instanceof XMLHttpRequest, "XMLHttpRequest.create did not return XMLHttpRequest instance"); + ok(XMLHttpRequest.create.call(Object) instanceof XMLHttpRequest, "XMLHttpRequest.create with Object 'this' did not return XMLHttpRequest instance"); + try { + new XMLHttpRequest.create(); + ok(false, "new XMLHttpRequest.create() did not throw"); + }catch(e) { + ok(e.number === 0x0ffff - 0x80000000, "new XMLHttpRequest.create() threw " + e.number); + } +});
From: Gabriel Ivăncescu gabrielopcode@gmail.com
And use it instead of on_prototype boolean.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/dispex.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/dlls/mshtml/dispex.c b/dlls/mshtml/dispex.c index 77eec049912..1dc98698e7d 100644 --- a/dlls/mshtml/dispex.c +++ b/dlls/mshtml/dispex.c @@ -55,7 +55,7 @@ typedef struct { tid_t tid; BSTR name; dispex_hook_invoke_t hook; - BOOLEAN on_prototype; + prototype_id_t prototype_id; SHORT call_vtbl_off; SHORT put_vtbl_off; SHORT get_vtbl_off; @@ -517,31 +517,32 @@ static int __cdecl func_name_cmp(const void *p1, const void *p2) return wcsicmp((*(func_info_t* const*)p1)->name, (*(func_info_t* const*)p2)->name); }
-static BOOL find_prototype_member(const dispex_data_t *info, DISPID id) +static prototype_id_t find_prototype_member(const dispex_data_t *info, DISPID id) { compat_mode_t compat_mode = info->compat_mode; + prototype_id_t ret = PROT_NONE;
if(compat_mode < COMPAT_MODE_IE9) - return FALSE; + return ret;
if(!info->is_prototype) { if(!info->desc->id) - return FALSE; + return ret; info = info->desc->prototype_info[compat_mode - COMPAT_MODE_IE9]; }else { if(!info->desc->prototype_id) - return FALSE; + return ret; info = object_descriptors[info->desc->prototype_id]->prototype_info[compat_mode - COMPAT_MODE_IE9]; }
for(;;) { if(bsearch(&id, info->funcs, info->func_cnt, sizeof(info->funcs[0]), dispid_cmp)) - return TRUE; + ret = info->desc->id; if(!info->desc->prototype_id) break; info = object_descriptors[info->desc->prototype_id]->prototype_info[compat_mode - COMPAT_MODE_IE9]; } - return FALSE; + return ret; }
static const char *object_names[] = { @@ -616,11 +617,11 @@ static dispex_data_t *preprocess_dispex_data(dispex_static_data_t *desc, compat_
data->name_table = malloc(data->func_cnt * sizeof(func_info_t*)); for(i=0; i < data->func_cnt; i++) { + data->funcs[i].prototype_id = find_prototype_member(data, data->funcs[i].id); + /* Don't expose properties that are exposed by object's prototype */ - if(find_prototype_member(data, data->funcs[i].id)) { - data->funcs[i].on_prototype = TRUE; + if(data->funcs[i].prototype_id != PROT_NONE) continue; - } data->name_table[data->name_cnt++] = data->funcs+i; } qsort(data->name_table, data->name_cnt, sizeof(func_info_t*), func_name_cmp); @@ -2511,7 +2512,7 @@ HRESULT dispex_next_id(DispatchEx *dispex, DISPID id, BOOL enum_all_own_props, D }
while(func < dispex->info->funcs + dispex->info->func_cnt) { - if(enum_all_own_props ? (!func->on_prototype) : (func->func_disp_idx == -1)) { + if(enum_all_own_props ? (func->prototype_id == PROT_NONE) : (func->func_disp_idx == -1)) { *ret = func->id; return S_OK; }
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/function.c | 2 +- dlls/jscript/jsdisp.idl | 4 ++-- dlls/mshtml/dispex.c | 21 ++++++++++++++------- dlls/mshtml/htmlwindow.c | 4 ++-- dlls/mshtml/tests/documentmode.js | 14 ++++++++++++++ 5 files changed, 33 insertions(+), 12 deletions(-)
diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index 52a96c9f69a..27e8a9000be 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -67,7 +67,7 @@ typedef struct { FunctionInstance function; const WCHAR *name; UINT32 id; - UINT32 iid; + INT32 iid; UINT32 flags; } HostFunction;
diff --git a/dlls/jscript/jsdisp.idl b/dlls/jscript/jsdisp.idl index 5f1384bce78..cfd1beba55d 100644 --- a/dlls/jscript/jsdisp.idl +++ b/dlls/jscript/jsdisp.idl @@ -27,7 +27,7 @@ struct property_info UINT32 flags; const WCHAR *name; UINT32 index; - UINT32 iid; + INT32 iid; };
const unsigned int PROPF_METHOD = 0x0100; @@ -69,7 +69,7 @@ interface IWineJSDispatchHost : IDispatchEx HRESULT SetProperty(DISPID id, LCID lcid, VARIANT *v, EXCEPINFO *ei, IServiceProvider *caller); HRESULT DeleteProperty(DISPID id); HRESULT ConfigureProperty(DISPID id, UINT32 flags); - HRESULT CallFunction(DISPID id, UINT32 iid, DWORD flags, DISPPARAMS *dp, VARIANT *ret, EXCEPINFO *ei, IServiceProvider *caller); + HRESULT CallFunction(DISPID id, INT32 iid, DWORD flags, DISPPARAMS *dp, VARIANT *ret, EXCEPINFO *ei, IServiceProvider *caller); HRESULT Construct(LCID lcid, DWORD flags, DISPPARAMS *dp, VARIANT *ret, EXCEPINFO *ei, IServiceProvider *caller); HRESULT GetOuterDispatch(IWineJSDispatchHost **ret); HRESULT ToString(BSTR *str); diff --git a/dlls/mshtml/dispex.c b/dlls/mshtml/dispex.c index 1dc98698e7d..95cddfd9c17 100644 --- a/dlls/mshtml/dispex.c +++ b/dlls/mshtml/dispex.c @@ -1076,7 +1076,7 @@ static HRESULT function_apply(func_disp_t *func, DISPPARAMS *dp, LCID lcid, VARI } }
- hres = IWineJSDispatchHost_CallFunction(this_iface, func->info->id, func->info->tid, DISPATCH_METHOD, ¶ms, res, ei, caller); + hres = IWineJSDispatchHost_CallFunction(this_iface, func->info->id, -func->info->tid, DISPATCH_METHOD, ¶ms, res, ei, caller);
fail: while(argc--) @@ -1103,7 +1103,7 @@ static HRESULT function_call(func_disp_t *func, DISPPARAMS *dp, LCID lcid, VARIA if(FAILED(hres)) return CTL_E_ILLEGALFUNCTIONCALL;
- hres = IWineJSDispatchHost_CallFunction(this_iface, func->info->id, func->info->tid, DISPATCH_METHOD, ¶ms, res, ei, caller); + hres = IWineJSDispatchHost_CallFunction(this_iface, func->info->id, -func->info->tid, DISPATCH_METHOD, ¶ms, res, ei, caller); IWineJSDispatchHost_Release(this_iface); return (hres == E_UNEXPECTED) ? CTL_E_ILLEGALFUNCTIONCALL : hres; } @@ -2594,13 +2594,13 @@ static HRESULT get_host_property_descriptor(DispatchEx *This, DISPID id, struct desc->flags = PROPF_CONFIGURABLE; desc->name = func->name; if(func->func_disp_idx >= 0) { - desc->iid = func->tid; + desc->iid = This->info->desc->id; desc->flags |= PROPF_METHOD | PROPF_WRITABLE; }else { if(func->func_disp_idx == -1) desc->flags |= PROPF_ENUMERABLE; if(This->info->is_prototype) { - desc->iid = func->tid; + desc->iid = This->info->desc->id; if(func->put_vtbl_off) desc->flags |= PROPF_WRITABLE; }else { @@ -2701,19 +2701,26 @@ static HRESULT WINAPI JSDispatchHost_ConfigureProperty(IWineJSDispatchHost *ifac return S_OK; }
-static HRESULT WINAPI JSDispatchHost_CallFunction(IWineJSDispatchHost *iface, DISPID id, UINT32 iid, DWORD flags, +static HRESULT WINAPI JSDispatchHost_CallFunction(IWineJSDispatchHost *iface, DISPID id, INT32 iid, DWORD flags, DISPPARAMS *dp, VARIANT *ret, EXCEPINFO *ei, IServiceProvider *caller) { DispatchEx *This = impl_from_IWineJSDispatchHost(iface); func_info_t *func; HRESULT hres;
- TRACE("%s (%p)->(%lx %x %lx %p %p %p %p)\n", This->info->name, This, id, iid, flags, dp, ret, ei, caller); + TRACE("%s (%p)->(%lx %d %lx %p %p %p %p)\n", This->info->name, This, id, iid, flags, dp, ret, ei, caller);
hres = get_builtin_func(This->info, id, &func); - if(FAILED(hres) || func->tid != iid) + if(FAILED(hres)) return E_UNEXPECTED;
+ if(iid <= 0) { + if(func->tid != -iid) + return E_UNEXPECTED; + }else if(iid != func->prototype_id) { + return E_UNEXPECTED; + } + switch(flags) { case DISPATCH_METHOD: assert(func->func_disp_idx >= 0); diff --git a/dlls/mshtml/htmlwindow.c b/dlls/mshtml/htmlwindow.c index ffcaf86a361..c7e45439545 100644 --- a/dlls/mshtml/htmlwindow.c +++ b/dlls/mshtml/htmlwindow.c @@ -3491,8 +3491,8 @@ static HRESULT WINAPI WindowDispEx_ConfigureProperty(IWineJSDispatchHost *iface, return IWineJSDispatchHost_ConfigureProperty(&This->base.inner_window->event_target.dispex.IWineJSDispatchHost_iface, id, flags); }
-static HRESULT WINAPI WindowDispEx_CallFunction(IWineJSDispatchHost *iface, DISPID id, UINT32 iid, DWORD flags, DISPPARAMS *dp, - VARIANT *ret, EXCEPINFO *ei, IServiceProvider *caller) +static HRESULT WINAPI WindowDispEx_CallFunction(IWineJSDispatchHost *iface, DISPID id, INT32 iid, DWORD flags, + DISPPARAMS *dp, VARIANT *ret, EXCEPINFO *ei, IServiceProvider *caller) { HTMLOuterWindow *This = impl_from_IWineJSDispatchHost(iface);
diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index 33d000fc35f..332c4c6192c 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -3146,6 +3146,13 @@ sync_test("__proto__", function() { ok(e.number === 0xa13b6 - 0x80000000 && e.name === "TypeError", "changing __proto__ on non-extensible object threw exception " + e.number + " (" + e.name + ")"); } + + obj = document.createElement("img"); + obj.__proto__ = ctor.prototype; + document.body.setAttribute.call(obj, "height", "101"); + r = document.body.getAttribute.call(obj, "height"); + ok(r === "101", "getAttribute(height) = " + r); + ok(!("getAttribute" in obj), "getAttribute exposed in obj"); });
sync_test("__defineGetter__", function() { @@ -3765,6 +3772,13 @@ sync_test("prototypes", function() { check(Attr.prototype, Node.prototype, "attr prototype"); check(document.createDocumentFragment(), DocumentFragment.prototype, "fragment"); check(DocumentFragment.prototype, Node.prototype, "fragment prototype"); + + try { + HTMLAreaElement.prototype.toString.call(document.createElement("a")); + ok(false, "Area element's toString on Anchor element didn't fail"); + } catch(e) { + ok(e.number == 0xffff - 0x80000000, "Area element's toString on Anchor element threw exception " + e.number); + } });
sync_test("prototype props", function() {
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/dispex.c | 6 ++-- dlls/mshtml/htmlimg.c | 17 +++++----- dlls/mshtml/htmlwindow.c | 56 +++++++++++++++---------------- dlls/mshtml/mshtml_private.h | 18 +++++++--- dlls/mshtml/tests/documentmode.js | 12 ++++++- 5 files changed, 65 insertions(+), 44 deletions(-)
diff --git a/dlls/mshtml/dispex.c b/dlls/mshtml/dispex.c index 95cddfd9c17..3015e3e3b0f 100644 --- a/dlls/mshtml/dispex.c +++ b/dlls/mshtml/dispex.c @@ -2978,6 +2978,7 @@ dispex_static_data_t *object_descriptors[] = { NULL, #define X(name) &name ## _dispex, ALL_PROTOTYPES + ALL_ALT_CTORS #undef X };
@@ -3124,8 +3125,6 @@ HRESULT get_constructor(HTMLInnerWindow *script_global, prototype_id_t id, Dispa { dispex_static_data_t *info;
- assert(script_global->doc->document_mode >= COMPAT_MODE_IE9); - if(script_global->constructors[id]) { *ret = script_global->constructors[id]; return S_OK; @@ -3138,6 +3137,9 @@ HRESULT get_constructor(HTMLInnerWindow *script_global, prototype_id_t id, Dispa return hres; }else { struct constructor *constr; + + assert(script_global->doc->document_mode >= COMPAT_MODE_IE9); + if(!(constr = calloc(sizeof(*constr), 1))) return E_OUTOFMEMORY;
diff --git a/dlls/mshtml/htmlimg.c b/dlls/mshtml/htmlimg.c index 5716bbd7ac4..55eac4252b9 100644 --- a/dlls/mshtml/htmlimg.c +++ b/dlls/mshtml/htmlimg.c @@ -878,15 +878,16 @@ static const dispex_static_data_vtbl_t HTMLImageElementFactory_dispex_vtbl = { .value = HTMLImageElementFactory_value, };
-static dispex_static_data_t HTMLImageElementFactory_dispex = { - .name = "Function", - .constructor_id = PROT_HTMLImageElement, - .vtbl = &HTMLImageElementFactory_dispex_vtbl, - .disp_tid = IHTMLImageElementFactory_tid, - .init_info = HTMLImageElementFactory_init_dispex_info, +dispex_static_data_t HTMLImageElementFactory_dispex = { + .name = "Function", + .constructor_id = PROT_HTMLImageElement, + .init_constructor = HTMLImageElementFactory_Create, + .vtbl = &HTMLImageElementFactory_dispex_vtbl, + .disp_tid = IHTMLImageElementFactory_tid, + .init_info = HTMLImageElementFactory_init_dispex_info, };
-HRESULT HTMLImageElementFactory_Create(HTMLInnerWindow *window, HTMLImageElementFactory **ret_val) +HRESULT HTMLImageElementFactory_Create(HTMLInnerWindow *window, DispatchEx **ret_val) { HTMLImageElementFactory *ret;
@@ -901,6 +902,6 @@ HRESULT HTMLImageElementFactory_Create(HTMLInnerWindow *window, HTMLImageElement init_dispatch(&ret->dispex, &HTMLImageElementFactory_dispex, window, dispex_compat_mode(&window->event_target.dispex));
- *ret_val = ret; + *ret_val = &ret->dispex; return S_OK; } diff --git a/dlls/mshtml/htmlwindow.c b/dlls/mshtml/htmlwindow.c index c7e45439545..562e6b279a3 100644 --- a/dlls/mshtml/htmlwindow.c +++ b/dlls/mshtml/htmlwindow.c @@ -673,21 +673,16 @@ static HRESULT WINAPI HTMLWindow2_get_Image(IHTMLWindow2 *iface, IHTMLImageEleme { HTMLWindow *This = impl_from_IHTMLWindow2(iface); HTMLInnerWindow *window = This->inner_window; + DispatchEx *constr; + HRESULT hres;
TRACE("(%p)->(%p)\n", This, p);
- if(!window->image_factory) { - HRESULT hres; - - hres = HTMLImageElementFactory_Create(window, &window->image_factory); - if(FAILED(hres)) - return hres; - } - - *p = &window->image_factory->IHTMLImageElementFactory_iface; - IHTMLImageElementFactory_AddRef(*p); + hres = get_constructor(window, CTOR_HTMLImageElementFactory, &constr); + if(FAILED(hres)) + return hres;
- return S_OK; + return IWineJSDispatchHost_QueryInterface(&constr->IWineJSDispatchHost_iface, &IID_IHTMLImageElementFactory, (void**)p); }
static HRESULT WINAPI HTMLWindow2_get_location(IHTMLWindow2 *iface, IHTMLLocation **p) @@ -3700,8 +3695,6 @@ static void HTMLWindow_traverse(DispatchEx *dispex, nsCycleCollectionTraversalCa note_cc_edge((nsISupports*)&This->doc->node.IHTMLDOMNode_iface, "doc", cb); if(This->console) note_cc_edge((nsISupports*)This->console, "console", cb); - if(This->image_factory) - note_cc_edge((nsISupports*)&This->image_factory->IHTMLImageElementFactory_iface, "image_factory", cb); if(This->option_factory) note_cc_edge((nsISupports*)&This->option_factory->IHTMLOptionElementFactory_iface, "option_factory", cb); if(This->screen) @@ -3742,11 +3735,6 @@ static void HTMLWindow_unlink(DispatchEx *dispex)
release_event_target(&This->event_target);
- if(This->image_factory) { - HTMLImageElementFactory *image_factory = This->image_factory; - This->image_factory = NULL; - IHTMLImageElementFactory_Release(&image_factory->IHTMLImageElementFactory_iface); - } if(This->option_factory) { HTMLOptionElementFactory *option_factory = This->option_factory; This->option_factory = NULL; @@ -3818,18 +3806,24 @@ static int CDECL cmp_name(const void *x, const void *y)
static HRESULT HTMLWindow_find_dispid(DispatchEx *dispex, const WCHAR *name, DWORD grfdex, DISPID *dispid) { + compat_mode_t compat_mode = dispex_compat_mode(dispex); HTMLInnerWindow *This = impl_from_DispatchEx(dispex); HTMLOuterWindow *frame; global_prop_t *prop; HTMLElement *elem; HRESULT hres;
- if(dispex_compat_mode(dispex) >= COMPAT_MODE_IE9) { + if(compat_mode >= COMPAT_MODE_IE9) { const WCHAR **constr_name = bsearch(&name, constructor_names, ARRAYSIZE(constructor_names) , sizeof(constructor_names[0]), cmp_name); - if(constr_name) { - prototype_id_t id = constr_name - constructor_names + 1; - compat_mode_t compat_mode = dispex_compat_mode(dispex); + prototype_id_t id = 0; + + if(constr_name) + id = constr_name - constructor_names + 1; + else if(!wcscmp(name, L"Image")) + id = CTOR_HTMLImageElementFactory; + + if(id) { DispatchEx *constr; VARIANT v;
@@ -4188,15 +4182,18 @@ static HRESULT IHTMLWindow6_postMessage_hook(DispatchEx *dispex, WORD flags, DIS
static void HTMLWindow_init_dispex_info(dispex_data_t *info, compat_mode_t compat_mode) { - static const dispex_hook_t window2_hooks[] = { - {DISPID_IHTMLWINDOW2_LOCATION, IHTMLWindow2_location_hook}, - {DISPID_UNKNOWN} - }; static const dispex_hook_t window2_ie11_hooks[] = { - {DISPID_IHTMLWINDOW2_LOCATION, IHTMLWindow2_location_hook}, - {DISPID_IHTMLWINDOW2_EXECSCRIPT, NULL}, + {DISPID_IHTMLWINDOW2_EXECSCRIPT}, + + /* IE9+ */ + {DISPID_IHTMLWINDOW2_IMAGE}, + + /* Common for all modes */ + {DISPID_IHTMLWINDOW2_LOCATION, IHTMLWindow2_location_hook}, {DISPID_UNKNOWN} }; + const dispex_hook_t *const window2_ie9_hooks = window2_ie11_hooks + 1; + const dispex_hook_t *const window2_hooks = window2_ie9_hooks + 1; static const dispex_hook_t window3_hooks[] = { {DISPID_IHTMLWINDOW3_SETTIMEOUT, IHTMLWindow3_setTimeout_hook}, {DISPID_UNKNOWN} @@ -4228,7 +4225,8 @@ static void HTMLWindow_init_dispex_info(dispex_data_t *info, compat_mode_t compa dispex_info_add_interface(info, IHTMLWindow5_tid, NULL); dispex_info_add_interface(info, IHTMLWindow4_tid, compat_mode >= COMPAT_MODE_IE11 ? window4_ie11_hooks : NULL); dispex_info_add_interface(info, IHTMLWindow3_tid, compat_mode >= COMPAT_MODE_IE11 ? window3_ie11_hooks : window3_hooks); - dispex_info_add_interface(info, IHTMLWindow2_tid, compat_mode >= COMPAT_MODE_IE11 ? window2_ie11_hooks : window2_hooks); + dispex_info_add_interface(info, IHTMLWindow2_tid, compat_mode >= COMPAT_MODE_IE11 ? window2_ie11_hooks : + compat_mode >= COMPAT_MODE_IE9 ? window2_ie9_hooks : window2_hooks); EventTarget_init_dispex_info(info, compat_mode); }
diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index f306cff68c6..58dad8a68e0 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -504,12 +504,22 @@ typedef struct { X(Window) \ X(XMLHttpRequest)
+/* Extra constructors without new prototypes */ +#define ALL_ALT_CTORS \ + X(HTMLImageElementFactory) + typedef enum { PROT_NONE, #define X(name) PROT_##name, ALL_PROTOTYPES #undef X PROT_LAST, + + PROT_LAST_ = PROT_LAST - 1, /* align alt ctors enum */ +#define X(name) CTOR_##name, + ALL_ALT_CTORS +#undef X + CTOR_LAST } prototype_id_t;
struct dispex_static_data_t { @@ -533,9 +543,10 @@ struct dispex_static_data_t {
#define X(name) extern dispex_static_data_t name ## _dispex; ALL_PROTOTYPES +ALL_ALT_CTORS #undef X
-extern dispex_static_data_t *object_descriptors[PROT_LAST]; +extern dispex_static_data_t *object_descriptors[CTOR_LAST];
typedef HRESULT (*dispex_hook_invoke_t)(DispatchEx*,WORD,DISPPARAMS*,VARIANT*, EXCEPINFO*,IServiceProvider*); @@ -771,7 +782,6 @@ struct HTMLInnerWindow {
IHTMLEventObj *event;
- HTMLImageElementFactory *image_factory; HTMLOptionElementFactory *option_factory; IHTMLScreen *screen; OmHistory *history; @@ -801,7 +811,7 @@ struct HTMLInnerWindow { ULONG redirect_count;
DispatchEx *prototypes[PROT_LAST]; - DispatchEx *constructors[PROT_LAST]; + DispatchEx *constructors[CTOR_LAST];
ULONGLONG navigation_start_time; ULONGLONG unload_event_start_time; @@ -1170,7 +1180,7 @@ HRESULT update_window_doc(HTMLInnerWindow*); HTMLOuterWindow *mozwindow_to_window(const mozIDOMWindowProxy*); void get_top_window(HTMLOuterWindow*,HTMLOuterWindow**); HRESULT HTMLOptionElementFactory_Create(HTMLInnerWindow*,HTMLOptionElementFactory**); -HRESULT HTMLImageElementFactory_Create(HTMLInnerWindow*,HTMLImageElementFactory**); +HRESULT HTMLImageElementFactory_Create(HTMLInnerWindow*,DispatchEx**); HRESULT HTMLXMLHttpRequestFactory_Create(HTMLInnerWindow*,DispatchEx**); HRESULT create_location(HTMLOuterWindow*,HTMLLocation**); HRESULT create_navigator(HTMLInnerWindow*,IOmNavigator**); diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index 332c4c6192c..a5ba8bed94d 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -4012,10 +4012,20 @@ sync_test("prototype props", function() { });
sync_test("constructors", function() { - var v = document.documentMode; + var v = document.documentMode, i, r; if(v < 9) return;
+ var ctors = [ "Image", "MutationObserver", "XMLHttpRequest" ]; + for(i = 0; i < ctors.length; i++) { + r = ctors[i]; + if(!(r in window)) + continue; + ok(window.hasOwnProperty(r), r + " not prop of window"); + ok(!Object.getPrototypeOf(window).hasOwnProperty(r), r + " is a prop of window's prototype"); + } + ok(window.Image.prototype === window.HTMLImageElement.prototype, "Image.prototype != HTMLImageElement.prototype"); + ok(XMLHttpRequest.create() instanceof XMLHttpRequest, "XMLHttpRequest.create did not return XMLHttpRequest instance"); ok(XMLHttpRequest.create.call(Object) instanceof XMLHttpRequest, "XMLHttpRequest.create with Object 'this' did not return XMLHttpRequest instance"); try {
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/htmlselect.c | 17 +++++++++-------- dlls/mshtml/htmlwindow.c | 29 ++++++++++------------------- dlls/mshtml/mshtml_private.h | 6 +++--- dlls/mshtml/tests/documentmode.js | 3 ++- 4 files changed, 24 insertions(+), 31 deletions(-)
diff --git a/dlls/mshtml/htmlselect.c b/dlls/mshtml/htmlselect.c index 3bae8fe958b..b599286916b 100644 --- a/dlls/mshtml/htmlselect.c +++ b/dlls/mshtml/htmlselect.c @@ -524,15 +524,16 @@ static const dispex_static_data_vtbl_t HTMLOptionElementFactory_dispex_vtbl = { .value = HTMLOptionElementFactory_value, };
-static dispex_static_data_t HTMLOptionElementFactory_dispex = { - .name = "Function", - .constructor_id = PROT_HTMLOptionElement, - .vtbl = &HTMLOptionElementFactory_dispex_vtbl, - .disp_tid = IHTMLOptionElementFactory_tid, - .init_info = HTMLImageElementFactory_init_dispex_info, +dispex_static_data_t HTMLOptionElementFactory_dispex = { + .name = "Function", + .constructor_id = PROT_HTMLOptionElement, + .init_constructor = HTMLOptionElementFactory_Create, + .vtbl = &HTMLOptionElementFactory_dispex_vtbl, + .disp_tid = IHTMLOptionElementFactory_tid, + .init_info = HTMLImageElementFactory_init_dispex_info, };
-HRESULT HTMLOptionElementFactory_Create(HTMLInnerWindow *window, HTMLOptionElementFactory **ret_ptr) +HRESULT HTMLOptionElementFactory_Create(HTMLInnerWindow *window, DispatchEx **ret_ptr) { HTMLOptionElementFactory *ret;
@@ -547,7 +548,7 @@ HRESULT HTMLOptionElementFactory_Create(HTMLInnerWindow *window, HTMLOptionEleme init_dispatch(&ret->dispex, &HTMLOptionElementFactory_dispex, window, dispex_compat_mode(&window->event_target.dispex));
- *ret_ptr = ret; + *ret_ptr = &ret->dispex; return S_OK; }
diff --git a/dlls/mshtml/htmlwindow.c b/dlls/mshtml/htmlwindow.c index 562e6b279a3..5588b13723d 100644 --- a/dlls/mshtml/htmlwindow.c +++ b/dlls/mshtml/htmlwindow.c @@ -1226,21 +1226,16 @@ static HRESULT WINAPI HTMLWindow2_get_Option(IHTMLWindow2 *iface, IHTMLOptionEle { HTMLWindow *This = impl_from_IHTMLWindow2(iface); HTMLInnerWindow *window = This->inner_window; + DispatchEx *constr; + HRESULT hres;
TRACE("(%p)->(%p)\n", This, p);
- if(!window->option_factory) { - HRESULT hres; - - hres = HTMLOptionElementFactory_Create(window, &window->option_factory); - if(FAILED(hres)) - return hres; - } - - *p = &window->option_factory->IHTMLOptionElementFactory_iface; - IHTMLOptionElementFactory_AddRef(*p); + hres = get_constructor(window, CTOR_HTMLOptionElementFactory, &constr); + if(FAILED(hres)) + return hres;
- return S_OK; + return IWineJSDispatchHost_QueryInterface(&constr->IWineJSDispatchHost_iface, &IID_IHTMLOptionElementFactory, (void**)p); }
static HRESULT WINAPI HTMLWindow2_focus(IHTMLWindow2 *iface) @@ -3695,8 +3690,6 @@ static void HTMLWindow_traverse(DispatchEx *dispex, nsCycleCollectionTraversalCa note_cc_edge((nsISupports*)&This->doc->node.IHTMLDOMNode_iface, "doc", cb); if(This->console) note_cc_edge((nsISupports*)This->console, "console", cb); - if(This->option_factory) - note_cc_edge((nsISupports*)&This->option_factory->IHTMLOptionElementFactory_iface, "option_factory", cb); if(This->screen) note_cc_edge((nsISupports*)This->screen, "screen", cb); if(This->history) @@ -3735,11 +3728,6 @@ static void HTMLWindow_unlink(DispatchEx *dispex)
release_event_target(&This->event_target);
- if(This->option_factory) { - HTMLOptionElementFactory *option_factory = This->option_factory; - This->option_factory = NULL; - IHTMLOptionElementFactory_Release(&option_factory->IHTMLOptionElementFactory_iface); - } unlink_ref(&This->screen); if(This->history) { OmHistory *history = This->history; @@ -3822,6 +3810,8 @@ static HRESULT HTMLWindow_find_dispid(DispatchEx *dispex, const WCHAR *name, DWO id = constr_name - constructor_names + 1; else if(!wcscmp(name, L"Image")) id = CTOR_HTMLImageElementFactory; + else if(!wcscmp(name, L"Option")) + id = CTOR_HTMLOptionElementFactory;
if(id) { DispatchEx *constr; @@ -4187,13 +4177,14 @@ static void HTMLWindow_init_dispex_info(dispex_data_t *info, compat_mode_t compa
/* IE9+ */ {DISPID_IHTMLWINDOW2_IMAGE}, + {DISPID_IHTMLWINDOW2_OPTION},
/* Common for all modes */ {DISPID_IHTMLWINDOW2_LOCATION, IHTMLWindow2_location_hook}, {DISPID_UNKNOWN} }; const dispex_hook_t *const window2_ie9_hooks = window2_ie11_hooks + 1; - const dispex_hook_t *const window2_hooks = window2_ie9_hooks + 1; + const dispex_hook_t *const window2_hooks = window2_ie9_hooks + 2; static const dispex_hook_t window3_hooks[] = { {DISPID_IHTMLWINDOW3_SETTIMEOUT, IHTMLWindow3_setTimeout_hook}, {DISPID_UNKNOWN} diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index 58dad8a68e0..5575a07ff4f 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -506,7 +506,8 @@ typedef struct {
/* Extra constructors without new prototypes */ #define ALL_ALT_CTORS \ - X(HTMLImageElementFactory) + X(HTMLImageElementFactory) \ + X(HTMLOptionElementFactory)
typedef enum { PROT_NONE, @@ -782,7 +783,6 @@ struct HTMLInnerWindow {
IHTMLEventObj *event;
- HTMLOptionElementFactory *option_factory; IHTMLScreen *screen; OmHistory *history; IOmNavigator *navigator; @@ -1179,7 +1179,7 @@ HRESULT create_outer_window(GeckoBrowser*,mozIDOMWindowProxy*,HTMLOuterWindow*,H HRESULT update_window_doc(HTMLInnerWindow*); HTMLOuterWindow *mozwindow_to_window(const mozIDOMWindowProxy*); void get_top_window(HTMLOuterWindow*,HTMLOuterWindow**); -HRESULT HTMLOptionElementFactory_Create(HTMLInnerWindow*,HTMLOptionElementFactory**); +HRESULT HTMLOptionElementFactory_Create(HTMLInnerWindow*,DispatchEx**); HRESULT HTMLImageElementFactory_Create(HTMLInnerWindow*,DispatchEx**); HRESULT HTMLXMLHttpRequestFactory_Create(HTMLInnerWindow*,DispatchEx**); HRESULT create_location(HTMLOuterWindow*,HTMLLocation**); diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index a5ba8bed94d..c4d65685d2f 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -4016,7 +4016,7 @@ sync_test("constructors", function() { if(v < 9) return;
- var ctors = [ "Image", "MutationObserver", "XMLHttpRequest" ]; + var ctors = [ "Image", "MutationObserver", "Option", "XMLHttpRequest" ]; for(i = 0; i < ctors.length; i++) { r = ctors[i]; if(!(r in window)) @@ -4025,6 +4025,7 @@ sync_test("constructors", function() { ok(!Object.getPrototypeOf(window).hasOwnProperty(r), r + " is a prop of window's prototype"); } ok(window.Image.prototype === window.HTMLImageElement.prototype, "Image.prototype != HTMLImageElement.prototype"); + ok(window.Option.prototype === window.HTMLOptionElement.prototype, "Option.prototype != HTMLOptionElement.prototype");
ok(XMLHttpRequest.create() instanceof XMLHttpRequest, "XMLHttpRequest.create did not return XMLHttpRequest instance"); ok(XMLHttpRequest.create.call(Object) instanceof XMLHttpRequest, "XMLHttpRequest.create with Object 'this' did not return XMLHttpRequest instance");
On Tue Apr 15 16:17:21 2025 +0000, Gabriel Ivăncescu wrote:
Ugh, while this works, it creates a cyclic ref when XMLHttpRequest is used, which won't even get cleaned up now (it needs the CC integration). I've been thinking of ways how to do avoid it or create it on demand (like for InterpretedFunction's prototype), but I wasn't able to find a solution. Because it's a method, we can't use on-demand getter or on-demand function (especially since it can get stored to a variable and called way later). We also can't convert HostConstructor to pure jsdisp since it's referenced from the mshtml window (so there would be no point, it would still be a cross mshtml-jscript cyclic ref). Storing a direct ref to the window (since we only need the window to construct) is pointless since it's also a cyclic ref (in fact, just having the prop should be?), i.e. window->XMLHttpRequest ctor->create->window. So there's really no way apart from creating it on demand which is not possible because it's a method. I guess I could add a flag or something to hijack the create_builtin_function somehow if that's even worth attempting, but anyway I'll let it for next MR if desirable. Also I couldn't reuse HostConstructor since the function itself doesn't work with `new` operator (added tests) so I added a new function type that's mostly a wrapper.
We don't need to store window reference, we have `get_script_global` now. I hope hope we can avoid a new functiton type.
On Tue Apr 15 17:27:51 2025 +0000, Jacek Caban wrote:
We don't need to store window reference, we have `get_script_global` now. I hope hope we can avoid a new functiton type.
Sadly I'm not sure if it's possible. I can't reuse `HostConstructor` as the type (say, by adding a new field) since its lifetime is tied to the mshtml object (it has addref/release), and the only other existing function type I can reuse is HostFunction, as the others have different props and the like, which I can't use since it expects tid and works on the "this" pointer, whereas create is bound to the constructor.
I can't use BindFunction for this reason (props) and also because it passes through DISPATCH_CONSTRUCT. Any ideas?
Sadly I'm not sure if it's possible. I can't reuse `HostConstructor` as the type (say, by adding a new field) since its lifetime is tied to the mshtml object (it has addref/release)
I guess we could have MSHTML object for that function for that function.
On Wed Apr 16 09:35:06 2025 +0000, Jacek Caban wrote:
Sadly I'm not sure if it's possible. I can't reuse `HostConstructor`
as the type (say, by adding a new field) since its lifetime is tied to the mshtml object (it has addref/release) I guess we could have MSHTML object for that function.
Isn't that a lot more complicated than adding a (mostly forwarding) function type in jscript?
On Wed Apr 16 13:30:30 2025 +0000, Gabriel Ivăncescu wrote:
Isn't that a lot more complicated than adding a (mostly forwarding) function type in jscript?
I don't expect it to be complicated and it would move the complexity to the layer it belongs.
On Wed Apr 16 14:19:37 2025 +0000, Jacek Caban wrote:
I don't expect it to be complicated and it would move the complexity to the layer it belongs.
So I've been trying to get this working without introducing a new object to keep it simple for such a niche thing. I've also consolidated all the functional constructors into a common struct with a common implementation, which I was planning to do at some point anyway and we'll need it when we add a lot more non-stub constructors, keeps boilerplate down. So I didn't mind it.
This also allows me to just "duplicate" the constructor for "create", albeit with different dispatch initialization (else it would result in infinite recursion).