[PATCH 0/5] MR10045: jscript/mshtml: Implement mshtml cycle collection for jscript objects.
This implements mshtml's cycle collection for JS objects so that they can be part of gecko's CC graph and have circular refs dealt with. It only does this during a Full CC, not incremental phases as we can't be part of gecko's "purple buffer" without replacing the entire refcounting logic to gecko's (which doesn't seem desirable as it will be too slow, nor possible, since we still need the current one for normal jscript without gecko). Full CC is forced when doing jscript garbage collection, and otherwise we are ignoring reporting jscript objects during incremental collection. The way it does it now is to fakingly report the current wine-specific-refcount to gecko only when traversing. This is fine when we are doing Full CC without being part of a purple buffer, to integrate existing jscript objects' refcounting into the gecko CC. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045
From: Gabriel Ivăncescu <gabrielopcode@gmail.com> Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com> --- dlls/jscript/function.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index f4f9dd57d3b..5ff28e6e9ee 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -1067,17 +1067,12 @@ static void HostFunction_destructor(FunctionInstance *func) { } -static HRESULT HostFunction_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, FunctionInstance *func) -{ - return S_OK; -} - static const function_vtbl_t HostFunctionVtbl = { HostFunction_call, HostFunction_toString, HostFunction_get_code, HostFunction_destructor, - HostFunction_gc_traverse + no_gc_traverse, }; HRESULT create_host_function(script_ctx_t *ctx, const struct property_info *desc, DWORD flags, jsdisp_t **ret) @@ -1202,17 +1197,12 @@ static void HostConstructor_destructor(FunctionInstance *func) { } -static HRESULT HostConstructor_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, FunctionInstance *func) -{ - return S_OK; -} - static const function_vtbl_t HostConstructorVtbl = { HostConstructor_call, HostConstructor_toString, HostConstructor_get_code, HostConstructor_destructor, - HostConstructor_gc_traverse + no_gc_traverse, }; HRESULT init_host_constructor(script_ctx_t *ctx, IWineJSDispatchHost *host_constr, const WCHAR *method_name, IWineJSDispatch **ret) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10045
From: Gabriel Ivăncescu <gabrielopcode@gmail.com> Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com> --- dlls/jscript/jsdisp.idl | 17 +++++++++++++++++ dlls/mshtml/mshtml_private.h | 15 --------------- dlls/mshtml/nsiface.idl | 2 -- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/dlls/jscript/jsdisp.idl b/dlls/jscript/jsdisp.idl index 4783edbfef5..93ede1bbc2b 100644 --- a/dlls/jscript/jsdisp.idl +++ b/dlls/jscript/jsdisp.idl @@ -43,6 +43,23 @@ const unsigned int HOSTOBJ_CONSTRUCTOR = 0x0001; const unsigned int HOSTOBJ_VOLATILE_FILL = 0x0002; const unsigned int HOSTOBJ_VOLATILE_PROPS = 0x0004; +cpp_quote("DEFINE_GUID(IID_nsCycleCollectionISupports, 0xc61eac14,0x5f7a,0x4481,0x96,0x5e,0x7e,0xaa,0x6e,0xff,0xa8,0x5f);") +cpp_quote("DEFINE_GUID(IID_nsXPCOMCycleCollectionParticipant, 0x9674489b,0x1f6f,0x4550,0xa7,0x30, 0xcc,0xae,0xdd,0x10,0x4c,0xf9);") + +typedef struct { + void *vtbl; + int ref_flags; + void *callbacks; +} ExternalCycleCollectionParticipant; + +typedef struct nsCycleCollectionTraversalCallback nsCycleCollectionTraversalCallback; + +typedef struct { + HRESULT (__stdcall *traverse)(void*,void*,nsCycleCollectionTraversalCallback*); + HRESULT (__stdcall *unlink)(void*); + void (__stdcall *delete_cycle_collectable)(void*); +} CCObjCallback; + interface IWineJSDispatchHost; [ diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index ce872249e62..32317c0aa9a 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -368,7 +368,6 @@ typedef struct dispex_dynamic_data_t dispex_dynamic_data_t; #define MSHTML_CUSTOM_DISPID_CNT (MSHTML_DISPID_CUSTOM_MAX-MSHTML_DISPID_CUSTOM_MIN) typedef struct DispatchEx DispatchEx; -typedef struct nsCycleCollectionTraversalCallback nsCycleCollectionTraversalCallback; typedef struct dispex_static_data_t dispex_static_data_t; typedef struct { @@ -623,20 +622,6 @@ struct DispatchEx { } \ DISPEX_IDISPATCH_NOUNK_IMPL(prefix, iface_name, dispex) -typedef struct { - void *vtbl; - int ref_flags; - void *callbacks; -} ExternalCycleCollectionParticipant; - -typedef struct { - nsresult (NSAPI *traverse)(void*,void*,nsCycleCollectionTraversalCallback*); - nsresult (NSAPI *unlink)(void*); - void (NSAPI *delete_cycle_collectable)(void*); -} CCObjCallback; - -DEFINE_GUID(IID_nsXPCOMCycleCollectionParticipant, 0x9674489b,0x1f6f,0x4550,0xa7,0x30, 0xcc,0xae,0xdd,0x10,0x4c,0xf9); - extern nsrefcnt (__cdecl *ccref_incr)(nsCycleCollectingAutoRefCnt*,nsISupports*); extern nsrefcnt (__cdecl *ccref_decr)(nsCycleCollectingAutoRefCnt*,nsISupports*,ExternalCycleCollectionParticipant*); extern void (__cdecl *ccref_init)(nsCycleCollectingAutoRefCnt*,nsrefcnt); diff --git a/dlls/mshtml/nsiface.idl b/dlls/mshtml/nsiface.idl index 8bba7d9f83b..810b799894e 100644 --- a/dlls/mshtml/nsiface.idl +++ b/dlls/mshtml/nsiface.idl @@ -4432,5 +4432,3 @@ interface nsIXMLHttpRequest : nsISupports nsresult GetMozSystem(bool *aMozSystem); nsresult GetResponseBuffer(void *buffer, uint32_t buffer_size, uint32_t *_retval); } - -cpp_quote("DEFINE_GUID(IID_nsCycleCollectionISupports, 0xc61eac14,0x5f7a,0x4481,0x96,0x5e,0x7e,0xaa,0x6e,0xff,0xa8,0x5f);") -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10045
From: Gabriel Ivăncescu <gabrielopcode@gmail.com> To remove redundancy since we'll need the host dispatch anyway. Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com> --- dlls/jscript/dispex.c | 43 +++++++++++++++++------------------------ dlls/jscript/function.c | 27 ++++++++++---------------- dlls/jscript/jscript.h | 3 +-- 3 files changed, 29 insertions(+), 44 deletions(-) diff --git a/dlls/jscript/dispex.c b/dlls/jscript/dispex.c index 14eb30b03c5..143618e556f 100644 --- a/dlls/jscript/dispex.c +++ b/dlls/jscript/dispex.c @@ -947,7 +947,7 @@ HRESULT gc_run(script_ctx_t *ctx) } LIST_FOR_EACH_ENTRY(obj, &thread_data->objects, jsdisp_t, entry) { /* Skip objects with external reference counter */ - if(obj->builtin_info->addref) { + if(obj->builtin_info->get_host_disp) { obj->gc_marked = FALSE; continue; } @@ -1880,8 +1880,8 @@ static void jsdisp_free(jsdisp_t *obj) jsdisp_t *jsdisp_addref(jsdisp_t *obj) { - if(obj->builtin_info->addref) - obj->builtin_info->addref(obj); + if(obj->builtin_info->get_host_disp) + IWineJSDispatchHost_AddRef(obj->builtin_info->get_host_disp(obj)); else ++obj->ref; return obj; @@ -1891,8 +1891,8 @@ ULONG jsdisp_release(jsdisp_t *obj) { ULONG ref; - if(obj->builtin_info->release) - return obj->builtin_info->release(obj); + if(obj->builtin_info->get_host_disp) + return IWineJSDispatchHost_Release(obj->builtin_info->get_host_disp(obj)); ref = --obj->ref; if(!ref) @@ -1934,8 +1934,8 @@ static HRESULT WINAPI DispatchEx_QueryInterface(IWineJSDispatch *iface, REFIID r static ULONG WINAPI DispatchEx_AddRef(IWineJSDispatch *iface) { jsdisp_t *This = impl_from_IWineJSDispatch(iface); - if(This->builtin_info->addref) - return This->builtin_info->addref(This); + if(This->builtin_info->get_host_disp) + return IWineJSDispatchHost_AddRef(This->builtin_info->get_host_disp(This)); jsdisp_addref(This); return This->ref; } @@ -3528,16 +3528,10 @@ static inline HostObject *HostObject_from_jsdisp(jsdisp_t *jsdisp) return CONTAINING_RECORD(jsdisp, HostObject, jsdisp); } -static ULONG HostObject_addref(jsdisp_t *jsdisp) +static IWineJSDispatchHost *HostObject_get_host_disp(jsdisp_t *jsdisp) { HostObject *This = HostObject_from_jsdisp(jsdisp); - return IWineJSDispatchHost_AddRef(This->host_iface); -} - -static ULONG HostObject_release(jsdisp_t *jsdisp) -{ - HostObject *This = HostObject_from_jsdisp(jsdisp); - return IWineJSDispatchHost_Release(This->host_iface); + return This->host_iface; } static HRESULT HostObject_lookup_prop(jsdisp_t *jsdisp, const WCHAR *name, unsigned flags, struct property_info *desc) @@ -3623,16 +3617,15 @@ static HRESULT HostObject_to_string(jsdisp_t *jsdisp, jsstr_t **ret) } static const builtin_info_t HostObject_info = { - .class = JSCLASS_HOST, - .addref = HostObject_addref, - .release = HostObject_release, - .lookup_prop = HostObject_lookup_prop, - .prop_get = HostObject_prop_get, - .prop_put = HostObject_prop_put, - .prop_delete = HostObject_prop_delete, - .prop_config = HostObject_prop_config, - .fill_props = HostObject_fill_props, - .to_string = HostObject_to_string, + .class = JSCLASS_HOST, + .get_host_disp = HostObject_get_host_disp, + .lookup_prop = HostObject_lookup_prop, + .prop_get = HostObject_prop_get, + .prop_put = HostObject_prop_put, + .prop_delete = HostObject_prop_delete, + .prop_config = HostObject_prop_config, + .fill_props = HostObject_fill_props, + .to_string = HostObject_to_string, }; HRESULT init_host_object(script_ctx_t *ctx, IWineJSDispatchHost *host_iface, IWineJSDispatch *prototype_iface, diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index 5ff28e6e9ee..3570fff72c4 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -1096,16 +1096,10 @@ HRESULT create_host_function(script_ctx_t *ctx, const struct property_info *desc return S_OK; } -static ULONG HostConstructor_addref(jsdisp_t *jsdisp) +static IWineJSDispatchHost *HostConstructor_get_host_disp(jsdisp_t *jsdisp) { HostConstructor *constr = (HostConstructor*)jsdisp; - return IWineJSDispatchHost_AddRef(constr->host_iface); -} - -static ULONG HostConstructor_release(jsdisp_t *jsdisp) -{ - HostConstructor *constr = (HostConstructor*)jsdisp; - return IWineJSDispatchHost_Release(constr->host_iface); + return constr->host_iface; } static HRESULT HostConstructor_lookup_prop(jsdisp_t *jsdisp, const WCHAR *name, unsigned flags, struct property_info *desc) @@ -1117,15 +1111,14 @@ static HRESULT HostConstructor_lookup_prop(jsdisp_t *jsdisp, const WCHAR *name, } static const builtin_info_t HostConstructor_info = { - .class = JSCLASS_FUNCTION, - .addref = HostConstructor_addref, - .release = HostConstructor_release, - .call = Function_value, - .destructor = Function_destructor, - .props_cnt = ARRAY_SIZE(HostFunction_props), - .props = HostFunction_props, - .gc_traverse = Function_gc_traverse, - .lookup_prop = HostConstructor_lookup_prop, + .class = JSCLASS_FUNCTION, + .call = Function_value, + .destructor = Function_destructor, + .props_cnt = ARRAY_SIZE(HostFunction_props), + .props = HostFunction_props, + .get_host_disp = HostConstructor_get_host_disp, + .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, diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h index f484b059bff..9aee626cb8d 100644 --- a/dlls/jscript/jscript.h +++ b/dlls/jscript/jscript.h @@ -181,8 +181,7 @@ typedef struct { DWORD props_cnt; const builtin_prop_t *props; void (*destructor)(jsdisp_t*); - ULONG (*addref)(jsdisp_t*); - ULONG (*release)(jsdisp_t*); + IWineJSDispatchHost *(*get_host_disp)(jsdisp_t*); void (*on_put)(jsdisp_t*,const WCHAR*); HRESULT (*lookup_prop)(jsdisp_t*,const WCHAR*,unsigned,struct property_info*); HRESULT (*prop_get)(jsdisp_t*,unsigned,jsval_t*); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10045
From: Gabriel Ivăncescu <gabrielopcode@gmail.com> Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com> --- dlls/jscript/dispex.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dlls/jscript/dispex.c b/dlls/jscript/dispex.c index 143618e556f..a70fadf09be 100644 --- a/dlls/jscript/dispex.c +++ b/dlls/jscript/dispex.c @@ -3660,16 +3660,12 @@ HRESULT init_host_object(script_ctx_t *ctx, IWineJSDispatchHost *host_iface, IWi IWineJSDispatchHost *get_host_dispatch(IDispatch *disp) { IWineJSDispatchHost *ret; - HostObject *host_obj; jsdisp_t *jsdisp; - if(!(jsdisp = to_jsdisp(disp))) - return NULL; - if(jsdisp->builtin_info != &HostObject_info) + if(!(jsdisp = to_jsdisp(disp)) || !jsdisp->builtin_info->get_host_disp) return NULL; - host_obj = HostObject_from_jsdisp(jsdisp); - IWineJSDispatchHost_GetOuterDispatch(host_obj->host_iface, &ret); + IWineJSDispatchHost_GetOuterDispatch(jsdisp->builtin_info->get_host_disp(jsdisp), &ret); return ret; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10045
From: Gabriel Ivăncescu <gabrielopcode@gmail.com> Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com> --- dlls/jscript/arraybuf.c | 22 ++++++++- dlls/jscript/dispex.c | 88 ++++++++++++++++++++++++++++++++++++ dlls/jscript/engine.c | 24 +++++++++- dlls/jscript/enumerator.c | 23 +++++++++- dlls/jscript/function.c | 84 ++++++++++++++++++++++++++++++---- dlls/jscript/jscript.c | 11 +++++ dlls/jscript/jscript.h | 20 +++++++- dlls/jscript/jscript_main.c | 2 +- dlls/jscript/jsdisp.idl | 15 ++++++ dlls/jscript/jsregexp.c | 13 +++++- dlls/jscript/set.c | 30 ++++++++++++ dlls/mshtml/dispex.c | 31 +++++++++++++ dlls/mshtml/htmlwindow.c | 24 ++++++++++ dlls/mshtml/main.c | 10 ++++ dlls/mshtml/mshtml_private.h | 4 ++ dlls/mshtml/nsembed.c | 12 ++++- 16 files changed, 393 insertions(+), 20 deletions(-) diff --git a/dlls/jscript/arraybuf.c b/dlls/jscript/arraybuf.c index c99b96e5cad..5401b96faac 100644 --- a/dlls/jscript/arraybuf.c +++ b/dlls/jscript/arraybuf.c @@ -648,18 +648,27 @@ static HRESULT DataView_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op o return gc_process_linked_obj(gc_ctx, op, dispex, &view->buffer->dispex, (void**)&view->buffer); } +static void DataView_cc_traverse(jsdisp_t *dispex, nsCycleCollectionTraversalCallback *cb) +{ + DataViewInstance *view = dataview_from_jsdisp(dispex); + if(view->buffer) + cc_api.note_edge((IUnknown*)&view->buffer->dispex.IWineJSDispatch_iface, "buffer", cb); +} + static const builtin_info_t DataView_info = { .class = JSCLASS_DATAVIEW, .props_cnt = ARRAY_SIZE(DataView_props), .props = DataView_props, .destructor = DataView_destructor, - .gc_traverse = DataView_gc_traverse + .gc_traverse = DataView_gc_traverse, + .cc_traverse = DataView_cc_traverse }; static const builtin_info_t DataViewInst_info = { .class = JSCLASS_DATAVIEW, .destructor = DataView_destructor, - .gc_traverse = DataView_gc_traverse + .gc_traverse = DataView_gc_traverse, + .cc_traverse = DataView_cc_traverse }; static HRESULT DataViewConstr_value(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, @@ -1038,6 +1047,13 @@ static HRESULT TypedArray_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op return gc_process_linked_obj(gc_ctx, op, dispex, &typedarr->buffer->dispex, (void**)&typedarr->buffer); } +static void TypedArray_cc_traverse(jsdisp_t *dispex, nsCycleCollectionTraversalCallback *cb) +{ + TypedArrayInstance *typedarr = typedarr_from_jsdisp(dispex); + if(typedarr->buffer) + cc_api.note_edge((IUnknown*)&typedarr->buffer->dispex.IWineJSDispatch_iface, "buffer", cb); +} + static const builtin_prop_t TypedArrayInst_props[] = { {L"buffer", NULL, 0, TypedArray_get_buffer}, {L"byteLength", NULL, 0, TypedArray_get_byteLength}, @@ -1234,6 +1250,7 @@ static const builtin_info_t name##_info = .prop_put = name##_prop_put, \ .fill_props = TypedArray_fill_props, \ .gc_traverse = TypedArray_gc_traverse, \ + .cc_traverse = TypedArray_cc_traverse, \ }; \ \ static const builtin_info_t name##Inst_info = \ @@ -1247,6 +1264,7 @@ static const builtin_info_t name##Inst_info = .prop_put = name##_prop_put, \ .fill_props = TypedArray_fill_props, \ .gc_traverse = TypedArray_gc_traverse, \ + .cc_traverse = TypedArray_cc_traverse, \ }; \ static HRESULT name ## Constr_value(script_ctx_t *ctx, jsval_t jsthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) \ { \ diff --git a/dlls/jscript/dispex.c b/dlls/jscript/dispex.c index a70fadf09be..6953dbebf21 100644 --- a/dlls/jscript/dispex.c +++ b/dlls/jscript/dispex.c @@ -924,6 +924,11 @@ HRESULT gc_run(script_ctx_t *ctx) if(thread_data->gc_is_unlinking) return S_OK; + thread_data->gc_is_unlinking = TRUE; + if(cc_api.collect) + cc_api.collect(); + thread_data->gc_is_unlinking = FALSE; + if(!(head = malloc(sizeof(*head)))) return E_OUTOFMEMORY; head->next = NULL; @@ -1921,6 +1926,21 @@ static HRESULT WINAPI DispatchEx_QueryInterface(IWineJSDispatch *iface, REFIID r }else if(IsEqualGUID(&IID_IWineJSDispatch, riid)) { TRACE("(%p)->(IID_IWineJSDispatch %p)\n", This, ppv); *ppv = &This->IWineJSDispatch_iface; + }else if(IsEqualGUID(&IID_nsXPCOMCycleCollectionParticipant, riid)) { + /* Only expose these during a full CC, as we can't have their refs change between incremental CC phases */ + if(!This->builtin_info->get_host_disp && cc_api.is_full_cc && cc_api.is_full_cc()) { + *ppv = &cc_api.participant; + return S_OK; + } + *ppv = NULL; + return E_NOINTERFACE; + }else if(IsEqualGUID(&IID_nsCycleCollectionISupports, riid)) { + if(!This->builtin_info->get_host_disp && cc_api.is_full_cc && cc_api.is_full_cc()) { + *ppv = &This->IWineJSDispatch_iface; + return S_OK; + } + *ppv = NULL; + return E_NOINTERFACE; }else { WARN("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv); *ppv = NULL; @@ -2369,6 +2389,41 @@ static void WINAPI WineJSDispatch_Free(IWineJSDispatch *iface) jsdisp_free(This); } +static void WINAPI WineJSDispatch_Traverse(IWineJSDispatch *iface, nsCycleCollectionTraversalCallback *cb) +{ + jsdisp_t *This = impl_from_IWineJSDispatch(iface); + note_edge_t note_edge = cc_api.note_edge; + dispex_prop_t *prop = This->props, *end; + + for(end = prop + This->prop_cnt; prop < end; prop++) { + switch(prop->type) { + case PROP_JSVAL: + if(is_object_instance(prop->u.val)) + note_edge(get_edge_obj(get_object(prop->u.val)), "prop", cb); + break; + case PROP_ACCESSOR: + if(prop->u.accessor.getter) + note_edge(jsdisp_get_edge_obj(prop->u.accessor.getter), "prop", cb); + if(prop->u.accessor.setter) + note_edge(jsdisp_get_edge_obj(prop->u.accessor.setter), "prop", cb); + break; + default: + break; + } + } + + if(This->prototype) + note_edge(jsdisp_get_edge_obj(This->prototype), "prototype", cb); + + if(This->builtin_info->cc_traverse) + This->builtin_info->cc_traverse(This, cb); +} + +static void WINAPI WineJSDispatch_Unlink(IWineJSDispatch *iface) +{ + unlink_jsdisp(impl_from_IWineJSDispatch(iface)); +} + static HRESULT WINAPI WineJSDispatch_GetPropertyFlags(IWineJSDispatch *iface, DISPID id, UINT32 *ret) { jsdisp_t *This = impl_from_IWineJSDispatch(iface); @@ -2454,6 +2509,8 @@ static IWineJSDispatchVtbl DispatchExVtbl = { DispatchEx_GetNextDispID, DispatchEx_GetNameSpaceParent, WineJSDispatch_Free, + WineJSDispatch_Traverse, + WineJSDispatch_Unlink, WineJSDispatch_GetPropertyFlags, WineJSDispatch_DefineProperty, WineJSDispatch_UpdateProperty, @@ -2570,6 +2627,34 @@ jsdisp_t *iface_to_jsdisp(IDispatch *iface) : NULL; } +static HRESULT WINAPI jsdisp_cc_traverse(void *ccp, void *p, nsCycleCollectionTraversalCallback *cb) +{ + jsdisp_t *This = impl_from_IWineJSDispatch(p); + + cc_api.describe_node(This->ref, "jsdisp", cb); + WineJSDispatch_Traverse(&This->IWineJSDispatch_iface, cb); + return S_OK; +} + +static HRESULT WINAPI jsdisp_cc_unlink(void *p) +{ + unlink_jsdisp(impl_from_IWineJSDispatch(p)); + return S_OK; +} + +struct jshost_cc_api cc_api; + +void init_cc_api(IWineJSDispatchHost *host_obj) +{ + static const CCObjCallback jsdisp_ccp_callback = { + jsdisp_cc_traverse, + jsdisp_cc_unlink, + NULL /* delete_cycle_collectable shouldn't ever be called, since we're never part of the purple buffer */ + }; + + IWineJSDispatchHost_InitCC(host_obj, &cc_api, &jsdisp_ccp_callback); +} + HRESULT jsdisp_get_id(jsdisp_t *jsdisp, const WCHAR *name, DWORD flags, DISPID *id) { dispex_prop_t *prop; @@ -3635,6 +3720,9 @@ HRESULT init_host_object(script_ctx_t *ctx, IWineJSDispatchHost *host_iface, IWi jsdisp_t *prototype; HRESULT hres; + if(!cc_api.note_edge) + init_cc_api(host_iface); + if(!(host_obj = calloc(1, sizeof(*host_obj)))) return E_OUTOFMEMORY; diff --git a/dlls/jscript/engine.c b/dlls/jscript/engine.c index 243b922c8f1..1fdee902f71 100644 --- a/dlls/jscript/engine.c +++ b/dlls/jscript/engine.c @@ -555,13 +555,35 @@ static HRESULT scope_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, return scope->obj && (jsobj = to_jsdisp(scope->obj)) ? gc_process_linked_obj(gc_ctx, op, dispex, jsobj, (void**)&scope->obj) : S_OK; } +static void scope_cc_traverse(jsdisp_t *dispex, nsCycleCollectionTraversalCallback *cb) +{ + scope_chain_t *scope = scope_from_dispex(dispex); + note_edge_t note_edge = cc_api.note_edge; + + if(scope->detached_vars) { + struct vars_buffer *vars = scope->detached_vars; + unsigned i, cnt = vars->argc; + + for(i = 0; i < cnt; i++) + if(is_object_instance(vars->var[i])) + note_edge(get_edge_obj(get_object(vars->var[i])), "var", cb); + } + + if(scope->next) + note_edge((IUnknown*)&scope->next->dispex.IWineJSDispatch_iface, "next", cb); + + if(scope->obj) + note_edge(get_edge_obj(scope->obj), "obj", cb); +} + static const builtin_info_t scope_info = { JSCLASS_NONE, .destructor = scope_destructor, .lookup_prop = scope_lookup_prop, .prop_get = scope_prop_get, .prop_put = scope_prop_put, - .gc_traverse = scope_gc_traverse + .gc_traverse = scope_gc_traverse, + .cc_traverse = scope_cc_traverse }; static HRESULT scope_push(script_ctx_t *ctx, scope_chain_t *scope, IDispatch *obj, scope_chain_t **ret) diff --git a/dlls/jscript/enumerator.c b/dlls/jscript/enumerator.c index 1f57f6b5173..fa841aa3060 100644 --- a/dlls/jscript/enumerator.c +++ b/dlls/jscript/enumerator.c @@ -91,7 +91,25 @@ static void Enumerator_destructor(jsdisp_t *dispex) static HRESULT Enumerator_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *dispex) { - return gc_process_linked_val(gc_ctx, op, dispex, &enumerator_from_jsdisp(dispex)->item); + EnumeratorInstance *This = enumerator_from_jsdisp(dispex); + + if(op == GC_TRAVERSE_UNLINK) { + IEnumVARIANT *enumvar = This->enumvar; + if(enumvar) { + This->enumvar = NULL; + IEnumVARIANT_Release(enumvar); + } + } + return gc_process_linked_val(gc_ctx, op, dispex, &This->item); +} + +static void Enumerator_cc_traverse(jsdisp_t *dispex, nsCycleCollectionTraversalCallback *cb) +{ + EnumeratorInstance *This = enumerator_from_jsdisp(dispex); + if(This->enumvar) + cc_api.note_edge((IUnknown*)This->enumvar, "enumvar", cb); + if(is_object_instance(This->item)) + cc_api.note_edge(get_edge_obj(get_object(This->item)), "item", cb); } static HRESULT Enumerator_atEnd(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, @@ -189,7 +207,8 @@ static const builtin_info_t Enumerator_info = { static const builtin_info_t EnumeratorInst_info = { .class = JSCLASS_ENUMERATOR, .destructor = Enumerator_destructor, - .gc_traverse = Enumerator_gc_traverse + .gc_traverse = Enumerator_gc_traverse, + .cc_traverse = Enumerator_cc_traverse }; static HRESULT alloc_enumerator(script_ctx_t *ctx, jsdisp_t *object_prototype, EnumeratorInstance **ret) diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index 3570fff72c4..85afe51a3bb 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -40,6 +40,7 @@ struct _function_vtbl_t { function_code_t* (*get_code)(FunctionInstance*); void (*destructor)(FunctionInstance*); HRESULT (*gc_traverse)(struct gc_ctx*,enum gc_traverse_op,FunctionInstance*); + void (*cc_traverse)(FunctionInstance*,nsCycleCollectionTraversalCallback*); }; typedef struct { @@ -91,6 +92,10 @@ static HRESULT no_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, Fun return S_OK; } +static void no_cc_traverse(FunctionInstance *function, nsCycleCollectionTraversalCallback *cb) +{ +} + static inline FunctionInstance *function_from_jsdisp(jsdisp_t *jsdisp) { return CONTAINING_RECORD(jsdisp, FunctionInstance, dispex); @@ -202,6 +207,22 @@ static HRESULT Arguments_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op return S_OK; } +static void Arguments_cc_traverse(jsdisp_t *jsdisp, nsCycleCollectionTraversalCallback *cb) +{ + ArgumentsInstance *arguments = arguments_from_jsdisp(jsdisp); + note_edge_t note_edge = cc_api.note_edge; + unsigned i; + + if(arguments->buf) { + for(i = 0; i < arguments->argc; i++) + if(is_object_instance(arguments->buf[i])) + note_edge(get_edge_obj(get_object(arguments->buf[i])), "buf", cb); + } + + if(arguments->scope) + note_edge((IUnknown*)&arguments->scope->dispex.IWineJSDispatch_iface, "scope", cb); +} + static HRESULT Arguments_get_caller(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) { ArgumentsInstance *arguments = arguments_from_jsdisp(jsthis); @@ -243,7 +264,8 @@ static const builtin_info_t Arguments_info = { .prop_get = Arguments_prop_get, .prop_put = Arguments_prop_put, .fill_props = Arguments_fill_props, - .gc_traverse = Arguments_gc_traverse + .gc_traverse = Arguments_gc_traverse, + .cc_traverse = Arguments_cc_traverse }; static const builtin_info_t Arguments_ES5_info = { @@ -254,7 +276,8 @@ static const builtin_info_t Arguments_ES5_info = { .prop_get = Arguments_prop_get, .prop_put = Arguments_prop_put, .fill_props = Arguments_fill_props, - .gc_traverse = Arguments_gc_traverse + .gc_traverse = Arguments_gc_traverse, + .cc_traverse = Arguments_cc_traverse }; HRESULT setup_arguments_object(script_ctx_t *ctx, call_frame_t *frame) @@ -678,6 +701,12 @@ static HRESULT Function_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op o return function->vtbl->gc_traverse(gc_ctx, op, function); } +static void Function_cc_traverse(jsdisp_t *dispex, nsCycleCollectionTraversalCallback *cb) +{ + FunctionInstance *function = function_from_jsdisp(dispex); + return function->vtbl->cc_traverse(function, cb); +} + static const builtin_prop_t Function_props[] = { {L"apply", Function_apply, PROPF_METHOD|2}, {L"arguments", NULL, PROPF_HTML, Function_get_arguments}, @@ -694,7 +723,8 @@ static const builtin_info_t Function_info = { .props_cnt = ARRAY_SIZE(Function_props), .props = Function_props, .destructor = Function_destructor, - .gc_traverse = Function_gc_traverse + .gc_traverse = Function_gc_traverse, + .cc_traverse = Function_cc_traverse }; static const builtin_prop_t FunctionInst_props[] = { @@ -709,7 +739,8 @@ static const builtin_info_t FunctionInst_info = { .props_cnt = ARRAY_SIZE(FunctionInst_props), .props = FunctionInst_props, .destructor = Function_destructor, - .gc_traverse = Function_gc_traverse + .gc_traverse = Function_gc_traverse, + .cc_traverse = Function_cc_traverse }; static HRESULT create_function(script_ctx_t *ctx, const builtin_info_t *builtin_info, const function_vtbl_t *vtbl, size_t size, @@ -771,7 +802,8 @@ static const function_vtbl_t NativeFunctionVtbl = { NativeFunction_toString, NativeFunction_get_code, NativeFunction_destructor, - no_gc_traverse + no_gc_traverse, + no_cc_traverse }; HRESULT create_builtin_function(script_ctx_t *ctx, builtin_invoke_t value_proc, const WCHAR *name, @@ -873,7 +905,8 @@ static const builtin_info_t InterpretedFunction_info = { .props_cnt = ARRAY_SIZE(InterpretedFunction_props), .props = InterpretedFunction_props, .destructor = Function_destructor, - .gc_traverse = Function_gc_traverse + .gc_traverse = Function_gc_traverse, + .cc_traverse = Function_cc_traverse }; static HRESULT InterpretedFunction_call(script_ctx_t *ctx, FunctionInstance *func, jsval_t vthis, unsigned flags, @@ -946,12 +979,20 @@ static HRESULT InterpretedFunction_gc_traverse(struct gc_ctx *gc_ctx, enum gc_tr (void**)&function->scope_chain); } +static void InterpretedFunction_cc_traverse(FunctionInstance *func, nsCycleCollectionTraversalCallback *cb) +{ + InterpretedFunction *function = (InterpretedFunction*)func; + if(function->scope_chain) + cc_api.note_edge((IUnknown*)&function->scope_chain->dispex.IWineJSDispatch_iface, "scope_chain", cb); +} + static const function_vtbl_t InterpretedFunctionVtbl = { InterpretedFunction_call, InterpretedFunction_toString, InterpretedFunction_get_code, InterpretedFunction_destructor, - InterpretedFunction_gc_traverse + InterpretedFunction_gc_traverse, + InterpretedFunction_cc_traverse }; HRESULT create_source_function(script_ctx_t *ctx, bytecode_t *code, function_code_t *func_code, @@ -990,7 +1031,8 @@ static const builtin_info_t HostFunction_info = { .destructor = Function_destructor, .props_cnt = ARRAY_SIZE(HostFunction_props), .props = HostFunction_props, - .gc_traverse = Function_gc_traverse + .gc_traverse = Function_gc_traverse, + .cc_traverse = Function_cc_traverse }; static HRESULT HostFunction_call(script_ctx_t *ctx, FunctionInstance *func, jsval_t vthis, unsigned flags, @@ -1073,6 +1115,7 @@ static const function_vtbl_t HostFunctionVtbl = { HostFunction_get_code, HostFunction_destructor, no_gc_traverse, + no_cc_traverse }; HRESULT create_host_function(script_ctx_t *ctx, const struct property_info *desc, DWORD flags, jsdisp_t **ret) @@ -1118,6 +1161,7 @@ static const builtin_info_t HostConstructor_info = { .props = HostFunction_props, .get_host_disp = HostConstructor_get_host_disp, .gc_traverse = Function_gc_traverse, + .cc_traverse = Function_cc_traverse, .lookup_prop = HostConstructor_lookup_prop, }; @@ -1196,6 +1240,7 @@ static const function_vtbl_t HostConstructorVtbl = { HostConstructor_get_code, HostConstructor_destructor, no_gc_traverse, + no_cc_traverse }; HRESULT init_host_constructor(script_ctx_t *ctx, IWineJSDispatchHost *host_constr, const WCHAR *method_name, IWineJSDispatch **ret) @@ -1236,7 +1281,8 @@ static const builtin_info_t BindFunction_info = { .props_cnt = ARRAY_SIZE(BindFunction_props), .props = BindFunction_props, .destructor = Function_destructor, - .gc_traverse = Function_gc_traverse + .gc_traverse = Function_gc_traverse, + .cc_traverse = Function_cc_traverse }; static HRESULT BindFunction_call(script_ctx_t *ctx, FunctionInstance *func, jsval_t vthis, unsigned flags, @@ -1311,12 +1357,30 @@ static HRESULT BindFunction_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_ return gc_process_linked_val(gc_ctx, op, &function->function.dispex, &function->this); } +static void BindFunction_cc_traverse(FunctionInstance *func, nsCycleCollectionTraversalCallback *cb) +{ + BindFunction *function = (BindFunction*)func; + note_edge_t note_edge = cc_api.note_edge; + unsigned i; + + for(i = 0; i < function->argc; i++) + if(is_object_instance(function->args[i])) + note_edge(get_edge_obj(get_object(function->args[i])), "arg", cb); + + if(function->target) + note_edge(jsdisp_get_edge_obj(&function->target->dispex), "target", cb); + + if(is_object_instance(function->this)) + note_edge(get_edge_obj(get_object(function->this)), "this", cb); +} + static const function_vtbl_t BindFunctionVtbl = { BindFunction_call, BindFunction_toString, BindFunction_get_code, BindFunction_destructor, - BindFunction_gc_traverse + BindFunction_gc_traverse, + BindFunction_cc_traverse }; static HRESULT create_bind_function(script_ctx_t *ctx, FunctionInstance *target, jsval_t bound_this, unsigned argc, diff --git a/dlls/jscript/jscript.c b/dlls/jscript/jscript.c index 30727a5ab2b..2daf013589c 100644 --- a/dlls/jscript/jscript.c +++ b/dlls/jscript/jscript.c @@ -912,6 +912,17 @@ static HRESULT WINAPI JScript_AddNamedItem(IActiveScript *iface, WARN("object does not implement IDispatch\n"); return hres; } + + if(!cc_api.note_edge && This->ctx->html_mode) { + IWineJSDispatchHost *host; + + /* Init CC API to get rid of cycles in IE8 and below as well */ + hres = IDispatch_QueryInterface(disp, &IID_IWineJSDispatchHost, (void**)&host); + if(SUCCEEDED(hres) && host) { + init_cc_api(host); + IWineJSDispatchHost_Release(host); + } + } } item = malloc(sizeof(*item)); diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h index 9aee626cb8d..9d1aceae7a2 100644 --- a/dlls/jscript/jscript.h +++ b/dlls/jscript/jscript.h @@ -28,12 +28,12 @@ #include "ole2.h" #include "dispex.h" #include "activscp.h" -#include "jsdisp.h" #include "resource.h" #include "wine/list.h" #include "wine/rbtree.h" +#include "jsdisp.h" typedef struct _jsval_t jsval_t; typedef struct _jsstr_t jsstr_t; @@ -60,6 +60,7 @@ heap_pool_t *heap_pool_mark(heap_pool_t*); typedef struct jsdisp_t jsdisp_t; +extern struct jshost_cc_api cc_api; extern HINSTANCE jscript_hinstance ; HRESULT get_dispatch_typeinfo(ITypeInfo**); @@ -191,6 +192,7 @@ typedef struct { HRESULT (*fill_props)(jsdisp_t*); HRESULT (*to_string)(jsdisp_t*,jsstr_t**); HRESULT (*gc_traverse)(struct gc_ctx*,enum gc_traverse_op,jsdisp_t*); + void (*cc_traverse)(jsdisp_t*,nsCycleCollectionTraversalCallback*); } builtin_info_t; struct jsdisp_t { @@ -245,6 +247,7 @@ HRESULT init_dispex_from_constr(jsdisp_t*,script_ctx_t*,const builtin_info_t*,js HRESULT init_host_object(script_ctx_t*,IWineJSDispatchHost*,IWineJSDispatch*,UINT32,IWineJSDispatch**); HRESULT init_host_constructor(script_ctx_t*,IWineJSDispatchHost*,const WCHAR*,IWineJSDispatch**); HRESULT fill_globals(script_ctx_t*,IWineJSDispatchHost*); +void init_cc_api(IWineJSDispatchHost*); HRESULT disp_call(script_ctx_t*,IDispatch*,DISPID,WORD,unsigned,jsval_t*,jsval_t*); HRESULT disp_call_name(script_ctx_t*,IDispatch*,const WCHAR*,WORD,unsigned,jsval_t*,jsval_t*); @@ -526,6 +529,21 @@ static inline HRESULT disp_call_value(script_ctx_t *ctx, IDispatch *disp, jsval_ return disp_call_value_with_caller(ctx, disp, vthis, flags, argc, argv, r, &ctx->jscaller->IServiceProvider_iface); } +static inline IUnknown *jsdisp_get_edge_obj(jsdisp_t *jsdisp) +{ + if(jsdisp->builtin_info->get_host_disp) + return (IUnknown*)jsdisp->builtin_info->get_host_disp(jsdisp); + return (IUnknown*)&jsdisp->IWineJSDispatch_iface; +} + +static inline IUnknown *get_edge_obj(IDispatch *disp) +{ + jsdisp_t *jsdisp = to_jsdisp(disp); + if(jsdisp) + return jsdisp_get_edge_obj(jsdisp); + return (IUnknown*)disp; +} + #define MAKE_JSERROR(code) MAKE_HRESULT(SEVERITY_ERROR, FACILITY_CONTROL, code) #define JS_E_TO_PRIMITIVE MAKE_JSERROR(IDS_TO_PRIMITIVE) diff --git a/dlls/jscript/jscript_main.c b/dlls/jscript/jscript_main.c index 6ccab362051..ccaa12761c2 100644 --- a/dlls/jscript/jscript_main.c +++ b/dlls/jscript/jscript_main.c @@ -27,7 +27,6 @@ #include "mshtmhst.h" #include "rpcproxy.h" #include "jscript_classes.h" -#include "jsdisp.h" #include "wine/debug.h" @@ -208,6 +207,7 @@ BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpv) if (lpv) break; if (dispatch_typeinfo) ITypeInfo_Release(dispatch_typeinfo); if(jscript_tls != TLS_OUT_OF_INDEXES) TlsFree(jscript_tls); + list_remove(&cc_api.entry); free_strings(); break; } diff --git a/dlls/jscript/jsdisp.idl b/dlls/jscript/jsdisp.idl index 93ede1bbc2b..7b9e54e1eb8 100644 --- a/dlls/jscript/jsdisp.idl +++ b/dlls/jscript/jsdisp.idl @@ -60,6 +60,18 @@ typedef struct { void (__stdcall *delete_cycle_collectable)(void*); } CCObjCallback; +typedef void (__cdecl *note_edge_t)(IUnknown*,const char*,nsCycleCollectionTraversalCallback*); + +struct jshost_cc_api +{ + ExternalCycleCollectionParticipant participant; + BOOL (__cdecl *is_full_cc)(void); + void (__cdecl *collect)(void); + void (__cdecl *describe_node)(ULONG ref, const char *obj_name, nsCycleCollectionTraversalCallback *cb); + note_edge_t note_edge; + struct list entry; +}; + interface IWineJSDispatchHost; [ @@ -70,6 +82,8 @@ interface IWineJSDispatchHost; interface IWineJSDispatch : IDispatchEx { void Free(); + void Traverse(nsCycleCollectionTraversalCallback *cb); + void Unlink(); HRESULT GetPropertyFlags(DISPID id, UINT32 *ret); HRESULT DefineProperty(const WCHAR *name, unsigned int flags, VARIANT *v); HRESULT UpdateProperty(struct property_info *desc); @@ -95,6 +109,7 @@ interface IWineJSDispatchHost : IDispatchEx HRESULT FillProperties(); HRESULT GetOuterDispatch(IWineJSDispatchHost **ret); HRESULT ToString(BSTR *str); + void InitCC(struct jshost_cc_api *cc_api, const CCObjCallback *callback); } const unsigned int SCRIPTLANGUAGEVERSION_HTML = 0x400; diff --git a/dlls/jscript/jsregexp.c b/dlls/jscript/jsregexp.c index 979f994ee55..1daaa0eac03 100644 --- a/dlls/jscript/jsregexp.c +++ b/dlls/jscript/jsregexp.c @@ -560,6 +560,13 @@ static HRESULT RegExp_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, return gc_process_linked_val(gc_ctx, op, dispex, ®exp_from_jsdisp(dispex)->last_index_val); } +static void RegExp_cc_traverse(jsdisp_t *dispex, nsCycleCollectionTraversalCallback *cb) +{ + RegExpInstance *This = regexp_from_jsdisp(dispex); + if(is_object_instance(This->last_index_val)) + cc_api.note_edge(get_edge_obj(get_object(This->last_index_val)), "last_index_val", cb); +} + static const builtin_prop_t RegExp_props[] = { {L"exec", RegExp_exec, PROPF_METHOD|1}, {L"global", NULL,0, RegExp_get_global}, @@ -577,7 +584,8 @@ static const builtin_info_t RegExp_info = { .props_cnt = ARRAY_SIZE(RegExp_props), .props = RegExp_props, .destructor = RegExp_destructor, - .gc_traverse = RegExp_gc_traverse + .gc_traverse = RegExp_gc_traverse, + .cc_traverse = RegExp_cc_traverse }; static const builtin_prop_t RegExpInst_props[] = { @@ -594,7 +602,8 @@ static const builtin_info_t RegExpInst_info = { .props_cnt = ARRAY_SIZE(RegExpInst_props), .props = RegExpInst_props, .destructor = RegExp_destructor, - .gc_traverse = RegExp_gc_traverse + .gc_traverse = RegExp_gc_traverse, + .cc_traverse = RegExp_cc_traverse }; static HRESULT alloc_regexp(script_ctx_t *ctx, jsstr_t *str, jsdisp_t *object_prototype, RegExpInstance **ret) diff --git a/dlls/jscript/set.c b/dlls/jscript/set.c index c114e3fd6e2..16535cc46eb 100644 --- a/dlls/jscript/set.c +++ b/dlls/jscript/set.c @@ -385,6 +385,20 @@ static HRESULT Map_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, js return S_OK; } +static void Map_cc_traverse(jsdisp_t *dispex, nsCycleCollectionTraversalCallback *cb) +{ + note_edge_t note_edge = cc_api.note_edge; + MapInstance *map = (MapInstance*)dispex; + struct jsval_map_entry *entry; + + LIST_FOR_EACH_ENTRY(entry, &map->entries, struct jsval_map_entry, list_entry) { + if(is_object_instance(entry->key)) + note_edge(get_edge_obj(get_object(entry->key)), "key", cb); + if(is_object_instance(entry->value)) + note_edge(get_edge_obj(get_object(entry->value)), "value", cb); + } +} + static const builtin_prop_t Map_prototype_props[] = { {L"clear", Map_clear, PROPF_METHOD}, {L"delete" , Map_delete, PROPF_METHOD|1}, @@ -412,6 +426,7 @@ static const builtin_info_t Map_info = { .props = Map_props, .destructor = Map_destructor, .gc_traverse = Map_gc_traverse, + .cc_traverse = Map_cc_traverse }; static HRESULT Map_constructor(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, @@ -565,6 +580,7 @@ static const builtin_info_t Set_info = { .props = Map_props, .destructor = Map_destructor, .gc_traverse = Map_gc_traverse, + .cc_traverse = Map_cc_traverse }; static HRESULT Set_constructor(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, @@ -832,6 +848,19 @@ static HRESULT WeakMap_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op return S_OK; } +static void WeakMap_cc_traverse(jsdisp_t *dispex, nsCycleCollectionTraversalCallback *cb) +{ + WeakMapInstance *weakmap = (WeakMapInstance*)dispex; + note_edge_t note_edge = cc_api.note_edge; + struct weakmap_entry *entry; + + /* FIXME: WeakMaps need special handling (see above), but we can't do that with this API. + This will possibly leak objects that need the CC until the WeakMap has no more refs to it. */ + RB_FOR_EACH_ENTRY(entry, &weakmap->map, struct weakmap_entry, entry) + if(is_object_instance(entry->value)) + note_edge(get_edge_obj(get_object(entry->value)), "value", cb); +} + static const builtin_prop_t WeakMap_prototype_props[] = { {L"clear", WeakMap_clear, PROPF_METHOD}, {L"delete", WeakMap_delete, PROPF_METHOD|1}, @@ -852,6 +881,7 @@ static const builtin_info_t WeakMap_info = { .call = WeakMap_value, .destructor = WeakMap_destructor, .gc_traverse = WeakMap_gc_traverse, + .cc_traverse = WeakMap_cc_traverse }; static HRESULT WeakMap_constructor(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, diff --git a/dlls/mshtml/dispex.c b/dlls/mshtml/dispex.c index a6b8afb0543..1df23829daa 100644 --- a/dlls/mshtml/dispex.c +++ b/dlls/mshtml/dispex.c @@ -2873,6 +2873,30 @@ static HRESULT WINAPI JSDispatchHost_ToString(IWineJSDispatchHost *iface, BSTR * return dispex_to_string(This, str); } +static BOOL __cdecl is_full_cc(void) +{ + thread_data_t *thread_data = get_thread_data(FALSE); + return thread_data ? thread_data->full_cc_in_progress : FALSE; +} + +static void __cdecl describe_node(ULONG ref, const char *obj_name, nsCycleCollectionTraversalCallback *cb) +{ + nsCycleCollectingAutoRefCnt ccref; + + ccref_init(&ccref, ref); + describe_cc_node(&ccref, obj_name, cb); +} + +static void WINAPI JSDispatchHost_InitCC(IWineJSDispatchHost *iface, struct jshost_cc_api *cc_api, const CCObjCallback *callback) +{ + ccp_init(&cc_api->participant, callback); + cc_api->is_full_cc = is_full_cc; + cc_api->collect = cc_api_collect; + cc_api->describe_node = describe_node; + cc_api->note_edge = (note_edge_t)note_cc_edge; + list_add_tail(&cc_api_list, &cc_api->entry); +} + static IWineJSDispatchHostVtbl JSDispatchHostVtbl = { DispatchEx_QueryInterface, DispatchEx_AddRef, @@ -2900,6 +2924,7 @@ static IWineJSDispatchHostVtbl JSDispatchHostVtbl = { JSDispatchHost_FillProperties, JSDispatchHost_GetOuterDispatch, JSDispatchHost_ToString, + JSDispatchHost_InitCC }; struct EnumVARIANT { @@ -3097,6 +3122,9 @@ static nsresult NSAPI dispex_traverse(void *ccp, void *p, nsCycleCollectionTrave if(This->info->vtbl->traverse) This->info->vtbl->traverse(This, cb); + if(This->jsdisp) + IWineJSDispatch_Traverse(This->jsdisp, cb); + if(!This->dynamic_data) return NS_OK; @@ -3152,6 +3180,9 @@ static nsresult NSAPI dispex_unlink(void *p) if(This->info->vtbl->unlink) This->info->vtbl->unlink(This); + if(This->jsdisp) + IWineJSDispatch_Unlink(This->jsdisp); + dispex_props_unlink(This); return NS_OK; } diff --git a/dlls/mshtml/htmlwindow.c b/dlls/mshtml/htmlwindow.c index 1977c6137ba..c6b654f3372 100644 --- a/dlls/mshtml/htmlwindow.c +++ b/dlls/mshtml/htmlwindow.c @@ -65,6 +65,22 @@ HTMLOuterWindow *mozwindow_to_window(const mozIDOMWindowProxy *mozwindow) return entry ? WINE_RB_ENTRY_VALUE(entry, HTMLOuterWindow, entry) : NULL; } +void __cdecl cc_api_collect(void) +{ + nsIDOMWindowUtils *window_utils = NULL; + HTMLOuterWindow *window; + + /* We can't rely on GetScriptGlobal as this can be initialized before any scripts are set up */ + if(!window_map.root || !(window = WINE_RB_ENTRY_VALUE(window_map.root, HTMLOuterWindow, entry))->browser) + return; + get_nsinterface((nsISupports*)window->browser->content_window->nswindow, &IID_nsIDOMWindowUtils, (void**)&window_utils); + + if(window_utils) { + cycle_collect(window_utils); + nsIDOMWindowUtils_Release(window_utils); + } +} + static HRESULT get_location(HTMLOuterWindow *This, HTMLLocation **ret) { if(!This->location) { @@ -3551,6 +3567,13 @@ static HRESULT WINAPI WindowDispEx_ToString(IWineJSDispatchHost *iface, BSTR *st return IWineJSDispatchHost_ToString(&This->base.inner_window->event_target.dispex.IWineJSDispatchHost_iface, str); } +static void WINAPI WindowDispEx_InitCC(IWineJSDispatchHost *iface, struct jshost_cc_api *cc_api, const CCObjCallback *callback) +{ + HTMLOuterWindow *This = impl_from_IWineJSDispatchHost(iface); + + IWineJSDispatchHost_InitCC(&This->base.inner_window->event_target.dispex.IWineJSDispatchHost_iface, cc_api, callback); +} + static const IWineJSDispatchHostVtbl WindowDispExVtbl = { WindowDispEx_QueryInterface, WindowDispEx_AddRef, @@ -3578,6 +3601,7 @@ static const IWineJSDispatchHostVtbl WindowDispExVtbl = { WindowDispEx_FillProperties, WindowDispEx_GetOuterDispatch, WindowDispEx_ToString, + WindowDispEx_InitCC }; static inline HTMLOuterWindow *impl_from_IEventTarget(IEventTarget *iface) diff --git a/dlls/mshtml/main.c b/dlls/mshtml/main.c index a45a2c111dc..1999a878dea 100644 --- a/dlls/mshtml/main.c +++ b/dlls/mshtml/main.c @@ -64,6 +64,7 @@ static IMultiLanguage2 *mlang; static IInternetSecurityManager *security_manager; static unsigned global_max_compat_mode = COMPAT_MODE_IE11; static struct list compat_config = LIST_INIT(compat_config); +struct list cc_api_list = LIST_INIT(cc_api_list); typedef struct { struct list entry; @@ -377,6 +378,15 @@ static void process_detach(void) close_gecko(); release_typelib(); + while(!list_empty(&cc_api_list)) { + struct jshost_cc_api *entry = LIST_ENTRY(list_head(&cc_api_list), struct jshost_cc_api, entry); + entry->is_full_cc = NULL; + entry->collect = NULL; + entry->describe_node = NULL; + entry->note_edge = NULL; + list_remove(&entry->entry); + } + while(!list_empty(&compat_config)) { config = LIST_ENTRY(list_head(&compat_config), compat_config_t, entry); list_remove(&config->entry); diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index 32317c0aa9a..b0e545a5744 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -563,6 +563,7 @@ ALL_OBJECTS #undef X extern dispex_static_data_t *object_descriptors[OBJID_LAST]; +extern struct list cc_api_list; typedef HRESULT (*dispex_hook_invoke_t)(DispatchEx*,WORD,DISPPARAMS*,VARIANT*, EXCEPINFO*,IServiceProvider*); @@ -1222,6 +1223,8 @@ void ConnectionPointContainer_Destroy(ConnectionPointContainer*); HRESULT create_gecko_browser(HTMLDocumentObj*,GeckoBrowser**); void detach_gecko_browser(GeckoBrowser*); +void cycle_collect(nsIDOMWindowUtils*); +void __cdecl cc_api_collect(void); DWORD get_compat_mode_version(compat_mode_t compat_mode); compat_mode_t lock_document_mode(HTMLDocumentNode*); @@ -1529,6 +1532,7 @@ typedef struct { struct list *pending_xhr_events_tail; struct wine_rb_tree session_storage_map; void *blocking_xhr; + unsigned full_cc_in_progress; unsigned tasks_locked; BOOL timer_blocked; } thread_data_t; diff --git a/dlls/mshtml/nsembed.c b/dlls/mshtml/nsembed.c index 62e1922cf6c..6422387aa1a 100644 --- a/dlls/mshtml/nsembed.c +++ b/dlls/mshtml/nsembed.c @@ -2174,6 +2174,16 @@ static const nsISupportsWeakReferenceVtbl nsSupportsWeakReferenceVtbl = { nsSupportsWeakReference_GetWeakReference }; +void cycle_collect(nsIDOMWindowUtils *window_utils) +{ + thread_data_t *thread_data = get_thread_data(TRUE); + if(thread_data) { + thread_data->full_cc_in_progress++; + nsIDOMWindowUtils_CycleCollect(window_utils, NULL, 0); + thread_data->full_cc_in_progress--; + } +} + static HRESULT init_browser(GeckoBrowser *browser) { mozIDOMWindowProxy *mozwindow; @@ -2397,7 +2407,7 @@ void detach_gecko_browser(GeckoBrowser *This) /* Force cycle collection */ if(window_utils) { - nsIDOMWindowUtils_CycleCollect(window_utils, NULL, 0); + cycle_collect(window_utils); nsIDOMWindowUtils_Release(window_utils); } } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10045
we can't be part of gecko's "purple buffer" without replacing the entire refcounting logic to gecko's (which doesn't seem desirable as it will be too slow, nor possible, since we still need the current one for normal jscript without gecko).
How does Gecko handle it? If I remember correctly, its JS object don't have a ref-count at all. Could you please describe they are handled in CC? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045#note_128980
On Mon Feb 9 16:30:06 2026 +0000, Jacek Caban wrote:
we can't be part of gecko's "purple buffer" without replacing the entire refcounting logic to gecko's (which doesn't seem desirable as it will be too slow, nor possible, since we still need the current one for normal jscript without gecko). How does Gecko handle it? If I remember correctly, its JS object don't have a ref-count at all. Could you please describe they are handled in CC? From what I have seen, and I could be wrong on the details, it's that gecko has two separate collectors. The cycle collector (CC), which is what I use here because it deals with refcounted objects, and the garbage collector (GC) used by its JS engine. As you said, JS objects don't have refcounts, they rely on a generational tracing GC that manages JS objects by tracing roots (marking reachable objects, and collecting unreachable ones). Root is like the stack or global scope and so on.
The top comments in nsCycleCollector.cpp also mention how there's some downsides to their JS<->C++ object interactions this way, so it's probably not ideal for us. Furthermore, I don't think it's even feasible in the first place because our JS objects can become "C++ Objects" when exposed via COM (and even though it seems unlikely, it's possible in theory and we have tests for it already, I don't think we should aim for a wrong upstream approach). And so they need a proper refcount, which gecko's JS objects don't have. Note that the fake refcount I mentioned only applies to what we tell gecko, it's not user visible nor something exposed to the COM world. There's also the fact we rely on refcounting in our jscript engine, and we definitely need it for standalone jscript (without mshtml, or even pre IE9). I haven't got too deep into the details of how gecko's GC works (not the CC), nor its API. I don't even know if it's available to us since it's not XPCOM (and it's internal only). I don't think it's something we can, or should, use. But let me know if you have some ideas to pursue. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045#note_129228
On Mon Feb 9 16:30:06 2026 +0000, Gabriel Ivăncescu wrote:
From what I have seen, and I could be wrong on the details, it's that gecko has two separate collectors. The cycle collector (CC), which is what I use here because it deals with refcounted objects, and the garbage collector (GC) used by its JS engine. As you said, JS objects don't have refcounts, they rely on a generational tracing GC that manages JS objects by tracing roots (marking reachable objects, and collecting unreachable ones). Root is like the stack or global scope and so on. The top comments in nsCycleCollector.cpp also mention how there's some downsides to their JS<->C++ object interactions this way, so it's probably not ideal for us. Furthermore, I don't think it's even feasible in the first place because our JS objects can become "C++ Objects" when exposed via COM (and even though it seems unlikely, it's possible in theory and we have tests for it already, I don't think we should aim for a wrong upstream approach). And so they need a proper refcount, which gecko's JS objects don't have. Note that the fake refcount I mentioned only applies to what we tell gecko, it's not user visible nor something exposed to the COM world. There's also the fact we rely on refcounting in our jscript engine, and we definitely need it for standalone jscript (without mshtml, or even pre IE9). I haven't got too deep into the details of how gecko's GC works (not the CC), nor its API. I don't even know if it's available to us since it's not XPCOM (and it's internal only). I don't think it's something we can, or should, use. But let me know if you have some ideas to pursue. I did not mean to suggest removing reference counting from JS objects. My point was that Gecko faces the same problem: objects without an associated `nsCycleCollectingAutoRefCnt` can still be part of a cycle. I was wondering what the intended solution is in that case, that is, how the cycle collector authors expected this to be handled. From your description, it sounds like GC is supposed to be used for that instead of the CC? I do not think Gecko's GC is directly useful for us, but in theory, whatever strategy they use there could be replicated in our jscript GC.
In your implementation, what you are doing in `describe_node` is not really how `nsCycleCollectingAutoRefCnt` is meant to be used. This might happen to work for full CC runs, but using a temporary stack-based ref is still a hack, right? And to make that hack work, you end up needing more hacks in places like QI, which now depends on CC state? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045#note_129246
On Mon Feb 9 18:43:44 2026 +0000, Jacek Caban wrote:
I did not mean to suggest removing reference counting from JS objects. My point was that Gecko faces the same problem: objects without an associated `nsCycleCollectingAutoRefCnt` can still be part of a cycle. I was wondering what the intended solution is in that case, that is, how the cycle collector authors expected this to be handled. From your description, it sounds like GC is supposed to be used for that instead of the CC? I do not think Gecko's GC is directly useful for us, but in theory, whatever strategy they use there could be replicated in our jscript GC. In your implementation, what you are doing in `describe_node` is not really how `nsCycleCollectingAutoRefCnt` is meant to be used. This might happen to work for full CC runs, but using a temporary stack-based ref is still a hack, right? And to make that hack work, you end up needing more hacks in places like QI, which now depends on CC state? Yeah, but the stack reference is not actually held, since it's only used during the purple buffer, and we don't use it. That's why I set it to NULL so it would otherwise crash (if it was ever used, which it shouldn't be), sort of like an assertion. (I can actually add a function that asserts instead, but it adds a bit of complexity)
And yes that's the reason for the QI. Note that this QI is "special" already (it doesn't follow COM rules) like some other gecko's stuff, so making it conditional isn't *that* much of a hack, because it's not meant to be cached nor treated as a normal interface to the object. You can see it doesn't even add a ref to the output, so it's already a hack by gecko to begin with. I investigated a bit more, I *think* I understood now how the CC and GC cooperate, it's iterative/several step approach. I mean for a mixed cycle like: A->B->J->A (J is the only JS object, rest are XPCOM objects) CC can't break this normally, it treats JS objects as special and has special wrappers for them. Since JS objects have no refcount it doesn't even use refcounts to see if a cycle can be broken/unlinked, it just keeps the cycle alive. GC has to run first and mark the JS object as unreachable (assuming it's not reachable from a JS root obviously, then it can't be collected). Unreachable doesn't instantly mean removed though, cause they're still linked by the XPCOM objects. Unreachable or "gray" JS objects are treated specially by the CC, they don't participate in keeping the cycle alive when traversing their edges. So it finds out it can unlink to break the cycle using CC rules. This only cleans up A and B though. The next GC run will finally release J since it's not reachable *and* it has no refs from an XPCOM object. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045#note_129255
On Mon Feb 9 20:53:12 2026 +0000, Gabriel Ivăncescu wrote:
Yeah, but the stack reference is not actually held, since it's only used during the purple buffer, and we don't use it. That's why I set it to NULL so it would otherwise crash (if it was ever used, which it shouldn't be), sort of like an assertion. (I can actually add a function that asserts instead, but it adds a bit of complexity) And yes that's the reason for the QI. Note that this QI is "special" already (it doesn't follow COM rules) like some other gecko's stuff, so making it conditional isn't *that* much of a hack, because it's not meant to be cached nor treated as a normal interface to the object. You can see it doesn't even add a ref to the output, so it's already a hack by gecko to begin with. I investigated a bit more, I *think* I understood now how the CC and GC cooperate, it's iterative/several step approach. I mean for a mixed cycle like: A->B->J->A (J is the only JS object, rest are XPCOM objects) CC can't break this normally, it treats JS objects as special and has special wrappers for them. Since JS objects have no refcount it doesn't even use refcounts to see if a cycle can be broken/unlinked, it just keeps the cycle alive. GC has to run first and mark the JS object as unreachable (assuming it's not reachable from a JS root obviously, then it can't be collected). Unreachable doesn't instantly mean removed though, cause they're still linked by the XPCOM objects. Unreachable or "gray" JS objects are treated specially by the CC, they don't participate in keeping the cycle alive when traversing their edges. So it finds out it can unlink to break the cycle using CC rules. This only cleans up A and B though. The next GC run will finally release J since it's not reachable *and* it has no refs from an XPCOM object. I do not see how we can know that J may be treated that way. Consider another case: A -> J -> B -> J, where A has an external reference, so it can't be collected. According to your description, J is not a root (or is it?). The GC would mark it as gray, and if J does not participate as alive in the CC, the CC might release B, which is obviously incorrect.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045#note_129777
On Mon Feb 16 21:53:33 2026 +0000, Jacek Caban wrote:
I do not see how we can know that J may be treated that way. Consider another case: A -> J -> B -> J, where A has an external reference, so it can't be collected. According to your description, J is not a root (or is it?). The GC would mark it as gray, and if J does not participate as alive in the CC, the CC might release B, which is obviously incorrect. J doesn't have to be a root, it has to be "reachable" from a root (i.e. it traverses from the root and marks them somehow, rest of JS objects are marked gray). But let's focus where J is not reachable from any root. Even if J is not reachable from any root and the GC consequently marks it as gray, B can't be collected because A isn't.
In other words, J is basically a "wrapped" XPCOM object that: 1) is treated as having no external refs to it *if* it is marked Gray. 2) is treated as having external refs otherwise. But now in your example it's not J that keeps it alive (because it has no external refs), it's A, because A has external refs to it, and reaches to B via J. Does that make sense? Let me know if I need to expand on it. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045#note_129806
On Tue Feb 17 15:26:06 2026 +0000, Gabriel Ivăncescu wrote:
J doesn't have to be a root, it has to be "reachable" from a root (i.e. it traverses from the root and marks them somehow, rest of JS objects are marked gray). But let's focus where J is not reachable from any root. Even if J is not reachable from any root and the GC consequently marks it as gray, B can't be collected because A isn't. In other words, J is basically a "wrapped" XPCOM object that: 1) is treated as having no external refs to it *if* it is marked Gray. 2) is treated as having external refs otherwise. But now in your example it's not J that keeps it alive (because it has no external refs), it's A, because A has external refs to it, and reaches to B via J. Does that make sense? Let me know if I need to expand on it. I do not see how that explains things. Let’s modify the example so that A becomes A1 -> A2 -> ... -> A1000 -> J1 -> J2 -> ... -> J1000 -> B, and only A1 has an external reference. Only CC knows the relationship between A1 and A1000, so only it knows about those references. According to your description, only GC knows the relationship between J1 and J1000. That means that “A reaches B via J” is not something either of them can determine on its own.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045#note_130990
On Mon Mar 2 19:28:30 2026 +0000, Jacek Caban wrote:
I do not see how that explains things. Let’s modify the example so that A becomes A1 -> A2 -> ... -> A1000 -> J1 -> J2 -> ... -> J1000 -> B, and only A1 has an external reference. Only CC knows the relationship between A1 and A1000, so only it knows about those references. According to your description, only GC knows the relationship between J1 and J1000. That means that “A reaches B via J” is not something either of them can determine on its own. Oh I see the confusion. No, the GC supplies the CC with edge info so the CC knows as well, it just doesn't supply it with a real "refcount" per object (since it doesn't have one anyway). That is, all edges from the GC (all JS objects) are known to the CC, and all JS objects are treated as if they had an extra external ref to themselves (so that the CC cannot clean them up). Does that clear things up?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045#note_130999
On Mon Mar 2 21:10:11 2026 +0000, Gabriel Ivăncescu wrote:
Oh I see the confusion. No, the GC supplies the CC with edge info so the CC knows as well, it just doesn't supply it with a real "refcount" per object (since it doesn't have one anyway). That is, all edges from the GC (all JS objects) are known to the CC, and all JS objects are treated as if they had an extra external ref to themselves (so that the CC cannot clean them up). Does that clear things up? Of course this is only for reachable JS objects. If the JS object is gray (non-reachable from JS) then it's treated as having no extra refcount at all so that the CC will clean up/unlink its XPCOM objects if suitable (it doesn't clean up JS objects regardless of their color).
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045#note_131000
No, the GC supplies the CC with edge info so the CC knows as well, it just doesn't supply it with a real "refcount" per object (since it doesn't have one anyway).
Okay, that is what I expected (and I was confused by your earlier description). So, back to the main question: should we do the same? How tightly is it tied to Gecko's JS internals? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045#note_131114
On Tue Mar 3 14:36:27 2026 +0000, Jacek Caban wrote:
No, the GC supplies the CC with edge info so the CC knows as well, it just doesn't supply it with a real "refcount" per object (since it doesn't have one anyway). Okay, that is what I expected (and I was confused by your earlier description). So, back to the main question: should we do the same? How tightly is it tied to Gecko's JS internals? I'm not sure it's gonna work. For one thing, we don't track roots. But the main problem is that this integrates with gecko's JS (and root tracing), which we don't even use but may actually be used by internal gecko mechanisms (like the chrome:// stuff), so how is it supposed to work?
Not to mention our objects are properly refcounted so it seems weird in the first place (because those are collected by the CC, unsure if they can even work on the GC). I also don't think gecko's approach is ideal, as in it probably has scenarios that will leak in one way or another with such mixing-up, but it's probably the best they could come up with given their constraints. (they state this in comments in several places) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10045#note_131140
participants (3)
-
Gabriel Ivăncescu -
Gabriel Ivăncescu (@insn) -
Jacek Caban (@jacek)