From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 6 +- dlls/uiautomationcore/uia_classes.idl | 2 + dlls/uiautomationcore/uia_client.c | 49 +++++- dlls/uiautomationcore/uia_event.c | 184 +++++++++++++++++++-- dlls/uiautomationcore/uia_private.h | 32 +++- 5 files changed, 252 insertions(+), 21 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 7003d368df5..6d0e6f7c557 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -14027,8 +14027,8 @@ static void test_UiaAddEvent_client_proc(void) SET_EXPECT_MULTI(prov_callback_proxy, 3); hr = UiaAddEvent(node, UIA_AutomationFocusChangedEventId, uia_event_callback, TreeScope_Descendants, NULL, 0, &cache_req, &event); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(!!event, "event == NULL\n"); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!event, "event == NULL\n"); ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); CHECK_CALLED_AT_MOST(prov_callback_base_hwnd, 2); CHECK_CALLED_AT_MOST(prov_callback_nonclient, 3); @@ -14070,7 +14070,7 @@ static void test_UiaAddEvent_client_proc(void) todo_wine CHECK_CALLED(uia_event_callback);
hr = UiaRemoveEvent(event); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); post_event_message(hwnd, WM_UIA_TEST_CHECK_EVENT_ADVISE_REMOVED, UIA_AutomationFocusChangedEventId, PROVIDER_ID, TRUE);
/* diff --git a/dlls/uiautomationcore/uia_classes.idl b/dlls/uiautomationcore/uia_classes.idl index 79bf8556326..3f8e17d3d72 100644 --- a/dlls/uiautomationcore/uia_classes.idl +++ b/dlls/uiautomationcore/uia_classes.idl @@ -68,6 +68,7 @@ library UIA_wine_private object, uuid(5e60162c-ab0e-4e22-a61d-3a3acd442aba), pointer_default(unique), + oleautomation, ] interface IWineUiaEvent : IUnknown { @@ -102,5 +103,6 @@ library UIA_wine_private HRESULT get_prop_val([in]const GUID *prop_guid, [out, retval]VARIANT *ret_val); HRESULT disconnect(); HRESULT get_hwnd([out, retval]ULONG *out_hwnd); + HRESULT attach_event([out, retval]IWineUiaEvent **ret_event); } } diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index e1dd7d0bd23..5fe548bff18 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -539,6 +539,37 @@ static HRESULT WINAPI uia_node_get_hwnd(IWineUiaNode *iface, ULONG *out_hwnd) return S_OK; }
+static HRESULT WINAPI uia_node_attach_event(IWineUiaNode *iface, IWineUiaEvent **ret_event) +{ + struct uia_node *node = impl_from_IWineUiaNode(iface); + struct uia_event *event = NULL; + HRESULT hr; + + TRACE("%p, %p\n", node, ret_event); + + *ret_event = NULL; + hr = create_serverside_uia_event(&event); + if (FAILED(hr)) + return hr; + + hr = attach_event_to_node_provider(iface, 0, (HUIAEVENT)event); + if (FAILED(hr)) + { + IWineUiaEvent_Release(&event->IWineUiaEvent_iface); + return hr; + } + + /* + * Attach this nested node to the serverside event to keep the provider + * thread alive. + */ + IWineUiaNode_AddRef(iface); + event->u.serverside.node = iface; + *ret_event = &event->IWineUiaEvent_iface; + + return hr; +} + static const IWineUiaNodeVtbl uia_node_vtbl = { uia_node_QueryInterface, uia_node_AddRef, @@ -547,6 +578,7 @@ static const IWineUiaNodeVtbl uia_node_vtbl = { uia_node_get_prop_val, uia_node_disconnect, uia_node_get_hwnd, + uia_node_attach_event, };
static struct uia_node *unsafe_impl_from_IWineUiaNode(IWineUiaNode *iface) @@ -2243,8 +2275,21 @@ static HRESULT WINAPI uia_nested_node_provider_get_focus(IWineUiaProvider *iface
static HRESULT WINAPI uia_nested_node_provider_attach_event(IWineUiaProvider *iface, LONG_PTR huiaevent) { - FIXME("%p, %#Ix: stub\n", iface, huiaevent); - return E_NOTIMPL; + struct uia_nested_node_provider *prov = impl_from_nested_node_IWineUiaProvider(iface); + struct uia_event *event = (struct uia_event *)huiaevent; + IWineUiaEvent *remote_event = NULL; + HRESULT hr; + + TRACE("%p, %#Ix\n", iface, huiaevent); + + hr = IWineUiaNode_attach_event(prov->nested_node, &remote_event); + if (FAILED(hr) || !remote_event) + return hr; + + hr = uia_event_add_serverside_event_adviser(remote_event, event); + IWineUiaEvent_Release(remote_event); + + return hr; }
static const IWineUiaProviderVtbl uia_nested_node_provider_vtbl = { diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index a0b4ced1aa9..d87d2c87a03 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -225,7 +225,15 @@ static ULONG WINAPI uia_event_Release(IWineUiaEvent *iface) assert(!event->event_map_entry);
SafeArrayDestroy(event->runtime_id); - uia_cache_request_destroy(&event->cache_req); + if (event->event_type == EVENT_TYPE_CLIENTSIDE) + { + uia_cache_request_destroy(&event->u.clientside.cache_req); + if (event->u.clientside.mta_cookie) + CoDecrementMTAUsage(event->u.clientside.mta_cookie); + } + else if (event->u.serverside.node) + IWineUiaNode_Release(event->u.serverside.node); + for (i = 0; i < event->event_advisers_count; i++) IWineUiaEventAdviser_Release(event->event_advisers[i]); heap_free(event->event_advisers); @@ -298,12 +306,42 @@ static HRESULT create_uia_event(struct uia_event **out_event, int event_id, int event->runtime_id = runtime_id; event->event_id = event_id; event->scope = scope; - event->cback = cback; + event->u.clientside.cback = cback; + event->event_type = EVENT_TYPE_CLIENTSIDE;
*out_event = event; return S_OK; }
+HRESULT create_serverside_uia_event(struct uia_event **out_event) +{ + struct uia_event *event = heap_alloc_zero(sizeof(*event)); + + *out_event = NULL; + if (!event) + return E_OUTOFMEMORY; + + event->IWineUiaEvent_iface.lpVtbl = &uia_event_vtbl; + event->ref = 1; + event->event_type = EVENT_TYPE_SERVERSIDE; + + *out_event = event; + return S_OK; +} + +static HRESULT uia_event_add_event_adviser(IWineUiaEventAdviser *adviser, struct uia_event *event) +{ + if (!uia_array_reserve((void **)&event->event_advisers, &event->event_advisers_arr_size, + event->event_advisers_count + 1, sizeof(*event->event_advisers))) + return E_OUTOFMEMORY; + + event->event_advisers[event->event_advisers_count] = adviser; + IWineUiaEventAdviser_AddRef(adviser); + event->event_advisers_count++; + + return S_OK; +} + /* * IWineUiaEventAdviser interface. */ @@ -438,19 +476,113 @@ HRESULT uia_event_add_provider_event_adviser(IRawElementProviderAdviseEvents *ad adv_events->advise_events = advise_events; IRawElementProviderAdviseEvents_AddRef(advise_events);
- if (!uia_array_reserve((void **)&event->event_advisers, &event->event_advisers_arr_size, - event->event_advisers_count + 1, sizeof(*event->event_advisers))) + hr = uia_event_add_event_adviser(&adv_events->IWineUiaEventAdviser_iface, event); + IWineUiaEventAdviser_Release(&adv_events->IWineUiaEventAdviser_iface); + + return hr; +} + +/* + * IWineUiaEventAdviser interface for serverside events. + */ +struct uia_serverside_event_adviser { + IWineUiaEventAdviser IWineUiaEventAdviser_iface; + LONG ref; + + IWineUiaEvent *event_iface; +}; + +static inline struct uia_serverside_event_adviser *impl_from_serverside_IWineUiaEventAdviser(IWineUiaEventAdviser *iface) +{ + return CONTAINING_RECORD(iface, struct uia_serverside_event_adviser, IWineUiaEventAdviser_iface); +} + +static HRESULT WINAPI uia_serverside_event_adviser_QueryInterface(IWineUiaEventAdviser *iface, REFIID riid, void **ppv) +{ + *ppv = NULL; + if (IsEqualIID(riid, &IID_IWineUiaEventAdviser) || IsEqualIID(riid, &IID_IUnknown)) + *ppv = iface; + else + return E_NOINTERFACE; + + IWineUiaEventAdviser_AddRef(iface); + return S_OK; +} + +static ULONG WINAPI uia_serverside_event_adviser_AddRef(IWineUiaEventAdviser *iface) +{ + struct uia_serverside_event_adviser *adv_events = impl_from_serverside_IWineUiaEventAdviser(iface); + ULONG ref = InterlockedIncrement(&adv_events->ref); + + TRACE("%p, refcount %ld\n", adv_events, ref); + return ref; +} + +static ULONG WINAPI uia_serverside_event_adviser_Release(IWineUiaEventAdviser *iface) +{ + struct uia_serverside_event_adviser *adv_events = impl_from_serverside_IWineUiaEventAdviser(iface); + ULONG ref = InterlockedDecrement(&adv_events->ref); + + TRACE("%p, refcount %ld\n", adv_events, ref); + if (!ref) { - IWineUiaEventAdviser_Release(&adv_events->IWineUiaEventAdviser_iface); - return E_OUTOFMEMORY; + IWineUiaEvent_Release(adv_events->event_iface); + heap_free(adv_events); } + return ref; +}
- event->event_advisers[event->event_advisers_count] = &adv_events->IWineUiaEventAdviser_iface; - event->event_advisers_count++; - +static HRESULT WINAPI uia_serverside_event_adviser_advise(IWineUiaEventAdviser *iface, BOOL advise_added, LONG_PTR huiaevent) +{ + FIXME("%p, %d, %#Ix: stub\n", iface, advise_added, huiaevent); return S_OK; }
+static const IWineUiaEventAdviserVtbl uia_serverside_event_adviser_vtbl = { + uia_serverside_event_adviser_QueryInterface, + uia_serverside_event_adviser_AddRef, + uia_serverside_event_adviser_Release, + uia_serverside_event_adviser_advise, +}; + +HRESULT uia_event_add_serverside_event_adviser(IWineUiaEvent *serverside_event, struct uia_event *event) +{ + struct uia_serverside_event_adviser *adv_events; + HRESULT hr; + + /* + * Need to create a proxy IWineUiaEvent for our clientside event to use + * this serverside IWineUiaEvent proxy from the appropriate apartment. + */ + if (!event->u.clientside.git_cookie) + { + hr = CoIncrementMTAUsage(&event->u.clientside.mta_cookie); + if (FAILED(hr)) + return hr; + + hr = register_interface_in_git((IUnknown *)&event->IWineUiaEvent_iface, &IID_IWineUiaEvent, + &event->u.clientside.git_cookie); + if (FAILED(hr)) + { + CoDecrementMTAUsage(event->u.clientside.mta_cookie); + return hr; + } + } + + if (!(adv_events = heap_alloc_zero(sizeof(*adv_events)))) + return E_OUTOFMEMORY; + + adv_events->IWineUiaEventAdviser_iface.lpVtbl = &uia_serverside_event_adviser_vtbl; + adv_events->ref = 1; + adv_events->event_iface = serverside_event; + IWineUiaEvent_AddRef(serverside_event); + + hr = uia_event_add_event_adviser(&adv_events->IWineUiaEventAdviser_iface, event); + IWineUiaEventAdviser_Release(&adv_events->IWineUiaEventAdviser_iface); + + return hr; +} + /*********************************************************************** * UiaAddEvent (uiautomationcore.@) */ @@ -489,7 +621,7 @@ HRESULT WINAPI UiaAddEvent(HUIANODE huianode, EVENTID event_id, UiaEventCallback return hr; }
- hr = uia_cache_request_clone(&event->cache_req, cache_req); + hr = uia_cache_request_clone(&event->u.clientside.cache_req, cache_req); if (FAILED(hr)) goto exit;
@@ -520,6 +652,7 @@ exit: HRESULT WINAPI UiaRemoveEvent(HUIAEVENT huiaevent) { struct uia_event *event = unsafe_impl_from_IWineUiaEvent((IWineUiaEvent *)huiaevent); + IWineUiaEvent *event_iface; HRESULT hr;
TRACE("(%p)\n", event); @@ -527,8 +660,31 @@ HRESULT WINAPI UiaRemoveEvent(HUIAEVENT huiaevent) if (!event) return E_INVALIDARG;
- hr = IWineUiaEvent_advise_events(&event->IWineUiaEvent_iface, FALSE); - IWineUiaEvent_Release(&event->IWineUiaEvent_iface); + assert(event->event_type == EVENT_TYPE_CLIENTSIDE); + if (event->u.clientside.git_cookie) + { + hr = get_interface_in_git(&IID_IWineUiaEvent, event->u.clientside.git_cookie, (IUnknown **)&event_iface); + if (FAILED(hr)) + return hr; + + hr = unregister_interface_in_git(event->u.clientside.git_cookie); + if (FAILED(hr)) + { + IWineUiaEvent_Release(event_iface); + return hr; + } + + /* + * We want the release of the event_iface proxy to set the reference + * count to 0, so we release our reference here. + */ + IWineUiaEvent_Release(&event->IWineUiaEvent_iface); + } + else + event_iface = &event->IWineUiaEvent_iface; + + hr = IWineUiaEvent_advise_events(event_iface, FALSE); + IWineUiaEvent_Release(event_iface); if (FAILED(hr)) WARN("advise_events failed with hr %#lx\n", hr);
@@ -541,11 +697,11 @@ static HRESULT uia_event_invoke(HUIANODE node, struct UiaEventArgs *args, struct BSTR tree_struct; HRESULT hr;
- hr = UiaGetUpdatedCache(node, &event->cache_req, NormalizeState_View, NULL, &out_req, + hr = UiaGetUpdatedCache(node, &event->u.clientside.cache_req, NormalizeState_View, NULL, &out_req, &tree_struct); if (SUCCEEDED(hr)) { - event->cback(args, out_req, tree_struct); + event->u.clientside.cback(args, out_req, tree_struct); SafeArrayDestroy(out_req); SysFreeString(tree_struct); } diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 18b97b74e65..4bd9281d486 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -93,6 +93,11 @@ static inline struct uia_provider *impl_from_IWineUiaProvider(IWineUiaProvider * return CONTAINING_RECORD(iface, struct uia_provider, IWineUiaProvider_iface); }
+enum uia_event_type { + EVENT_TYPE_CLIENTSIDE, + EVENT_TYPE_SERVERSIDE, +}; + struct uia_event { IWineUiaEvent IWineUiaEvent_iface; @@ -111,8 +116,29 @@ struct uia_event struct uia_event_map_entry *event_map_entry; LONG event_defunct;
- struct UiaCacheRequest cache_req; - UiaEventCallback *cback; + int event_type; + union + { + struct { + struct UiaCacheRequest cache_req; + UiaEventCallback *cback; + + /* + * This is temporarily used to keep the MTA alive prior to our + * introduction of a dedicated event thread. + */ + CO_MTA_USAGE_COOKIE mta_cookie; + DWORD git_cookie; + } clientside; + struct { + /* + * Similar to the client MTA cookie, used to keep the provider + * thread alive as a temporary measure before introducing the + * event thread. + */ + IWineUiaNode *node; + } serverside; + } u; };
static inline void variant_init_bool(VARIANT *v, BOOL val) @@ -170,8 +196,10 @@ BOOL uia_condition_matched(HRESULT hr) DECLSPEC_HIDDEN; HRESULT create_uia_iface(IUnknown **iface, BOOL is_cui8) DECLSPEC_HIDDEN;
/* uia_event.c */ +HRESULT create_serverside_uia_event(struct uia_event **out_event) DECLSPEC_HIDDEN; HRESULT uia_event_add_provider_event_adviser(IRawElementProviderAdviseEvents *advise_events, struct uia_event *event) DECLSPEC_HIDDEN; +HRESULT uia_event_add_serverside_event_adviser(IWineUiaEvent *serverside_event, struct uia_event *event) DECLSPEC_HIDDEN;
/* uia_ids.c */ const struct uia_prop_info *uia_prop_info_from_id(PROPERTYID prop_id) DECLSPEC_HIDDEN;