-- v2: uiautomationcore: Implement IUIAutomation::RemoveAutomationEventHandler. uiautomationcore: Implement IUIAutomation::RemoveAllEventHandlers. uiautomationcore: Partially implement IUIAutomation::AddAutomationEventHandler. uiautomationcore/tests: Add IUIAutomationEventHandler tests.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 368 +++++++++++++++++++++ 1 file changed, 368 insertions(+)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index a1ca9e62697..0aff7a8ad24 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -89,6 +89,7 @@ DEFINE_EXPECT(prov_callback_nonclient); DEFINE_EXPECT(prov_callback_proxy); DEFINE_EXPECT(prov_callback_parent_proxy); DEFINE_EXPECT(uia_event_callback); +DEFINE_EXPECT(uia_com_event_callback); DEFINE_EXPECT(winproc_GETOBJECT_UiaRoot); DEFINE_EXPECT(child_winproc_GETOBJECT_UiaRoot); DEFINE_EXPECT(Accessible_accNavigate); @@ -12896,6 +12897,372 @@ static void test_GetFocusedElement(IUIAutomation *uia_iface) UnregisterClassA("test_GetFocusedElement child class", NULL); }
+static void set_uia_hwnd_expects(int proxy_cback_count, int base_hwnd_cback_count, int nc_cback_count, + int win_get_uia_obj_count, int win_get_client_obj_count) +{ + SET_EXPECT_MULTI(prov_callback_base_hwnd, base_hwnd_cback_count); + SET_EXPECT_MULTI(prov_callback_nonclient, nc_cback_count); + SET_EXPECT_MULTI(prov_callback_proxy, proxy_cback_count); + SET_EXPECT_MULTI(winproc_GETOBJECT_UiaRoot, win_get_uia_obj_count); + SET_EXPECT_MULTI(winproc_GETOBJECT_CLIENT, win_get_client_obj_count); +} + +static void check_uia_hwnd_expects(int proxy_cback_count, BOOL proxy_cback_todo, + int base_hwnd_cback_count, BOOL base_hwnd_cback_todo, int nc_cback_count, BOOL nc_cback_todo, + int win_get_uia_obj_count, BOOL win_get_uia_obj_todo, int win_get_client_obj_count, BOOL win_get_client_obj_todo) +{ + todo_wine_if(proxy_cback_todo) CHECK_CALLED_MULTI(prov_callback_proxy, proxy_cback_count); + todo_wine_if(base_hwnd_cback_todo) CHECK_CALLED_MULTI(prov_callback_base_hwnd, base_hwnd_cback_count); + todo_wine_if(nc_cback_todo) CHECK_CALLED_MULTI(prov_callback_nonclient, nc_cback_count); + todo_wine_if(win_get_uia_obj_todo) CHECK_CALLED_MULTI(winproc_GETOBJECT_UiaRoot, win_get_uia_obj_count); + if (win_get_client_obj_count) + todo_wine_if(win_get_client_obj_todo) CHECK_CALLED_MULTI(winproc_GETOBJECT_CLIENT, win_get_client_obj_count); +} + +static void check_uia_hwnd_expects_at_most(int proxy_cback_count, int base_hwnd_cback_count, int nc_cback_count, + int win_get_uia_obj_count, int win_get_client_obj_count) +{ + CHECK_CALLED_AT_MOST(prov_callback_proxy, proxy_cback_count); + CHECK_CALLED_AT_MOST(prov_callback_base_hwnd, base_hwnd_cback_count); + CHECK_CALLED_AT_MOST(prov_callback_nonclient, nc_cback_count); + CHECK_CALLED_AT_MOST(winproc_GETOBJECT_UiaRoot, win_get_uia_obj_count); + CHECK_CALLED_AT_MOST(winproc_GETOBJECT_CLIENT, win_get_client_obj_count); +} + +static struct ComEventData { + struct node_provider_desc exp_node_desc; + struct node_provider_desc exp_nested_node_desc; + + HWND event_hwnd; + DWORD last_call_tid; +} ComEventData; + +static void set_com_event_data(struct node_provider_desc *exp_node_desc) +{ + if (exp_node_desc) + { + int i; + + ComEventData.exp_node_desc = *exp_node_desc; + for (i = 0; i < exp_node_desc->prov_count; i++) + { + if (exp_node_desc->nested_desc[i]) + { + ComEventData.exp_nested_node_desc = *exp_node_desc->nested_desc[i]; + ComEventData.exp_node_desc.nested_desc[i] = &ComEventData.exp_nested_node_desc; + break; + } + } + } + else + memset(&ComEventData.exp_node_desc, 0, sizeof(ComEventData.exp_node_desc)); + ComEventData.last_call_tid = 0; + SET_EXPECT(uia_com_event_callback); +} + +#define test_com_event_data( sender ) \ + test_com_event_data_( (sender), __FILE__, __LINE__) +static void test_com_event_data_(IUIAutomationElement *sender, const char *file, int line) +{ + HRESULT hr; + VARIANT v; + + CHECK_EXPECT(uia_com_event_callback); + + VariantInit(&v); + hr = IUIAutomationElement_GetCurrentPropertyValueEx(sender, UIA_ProviderDescriptionPropertyId, TRUE, &v); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx\n", hr); + test_node_provider_desc_(&ComEventData.exp_node_desc, V_BSTR(&v), file, line); + VariantClear(&v); + + ComEventData.last_call_tid = GetCurrentThreadId(); +} + +/* + * IUIAutomationEventHandler. + */ +static struct AutomationEventHandler +{ + IUIAutomationEventHandler IUIAutomationEventHandler_iface; + LONG ref; +} AutomationEventHandler; + +static inline struct AutomationEventHandler *impl_from_AutomationEventHandler(IUIAutomationEventHandler *iface) +{ + return CONTAINING_RECORD(iface, struct AutomationEventHandler, IUIAutomationEventHandler_iface); +} + +static HRESULT WINAPI AutomationEventHandler_QueryInterface(IUIAutomationEventHandler *iface, REFIID riid, void **ppv) +{ + *ppv = NULL; + if (IsEqualIID(riid, &IID_IUIAutomationEventHandler) || IsEqualIID(riid, &IID_IUnknown)) + *ppv = iface; + else + return E_NOINTERFACE; + + IUIAutomationEventHandler_AddRef(iface); + return S_OK; +} + +static ULONG WINAPI AutomationEventHandler_AddRef(IUIAutomationEventHandler* iface) +{ + struct AutomationEventHandler *handler = impl_from_AutomationEventHandler(iface); + return InterlockedIncrement(&handler->ref); +} + +static ULONG WINAPI AutomationEventHandler_Release(IUIAutomationEventHandler* iface) +{ + struct AutomationEventHandler *handler = impl_from_AutomationEventHandler(iface); + return InterlockedDecrement(&handler->ref); +} + +static HRESULT WINAPI AutomationEventHandler_HandleAutomationEvent(IUIAutomationEventHandler *iface, + IUIAutomationElement *sender, EVENTID event_id) +{ + test_com_event_data(sender); + + return S_OK; +} + +static const IUIAutomationEventHandlerVtbl AutomationEventHandlerVtbl = { + AutomationEventHandler_QueryInterface, + AutomationEventHandler_AddRef, + AutomationEventHandler_Release, + AutomationEventHandler_HandleAutomationEvent, +}; + +static struct AutomationEventHandler AutomationEventHandler = +{ + { &AutomationEventHandlerVtbl }, + 1, +}; + +static DWORD WINAPI uia_com_event_handler_test_thread(LPVOID param) +{ + struct node_provider_desc exp_node_desc; + HRESULT hr; + + /* + * Raise an event from inside of an MTA - the event handler proxy will be + * called from the current thread because it was registered in an MTA. + */ + CoInitializeEx(NULL, COINIT_MULTITHREADED); + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc, L"Main", L"Provider2", TRUE); + set_com_event_data(&exp_node_desc); + hr = UiaRaiseAutomationEvent(&Provider2.IRawElementProviderSimple_iface, UIA_LiveRegionChangedEventId); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine CHECK_CALLED(uia_com_event_callback); + todo_wine ok(ComEventData.last_call_tid == GetCurrentThreadId(), "Event handler called on unexpected thread %ld\n", + ComEventData.last_call_tid); + CoUninitialize(); + + /* + * Raise an event from inside of an STA - an event handler proxy will be + * created, and our handler will be invoked from another thread. + */ + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + set_com_event_data(&exp_node_desc); + hr = UiaRaiseAutomationEvent(&Provider2.IRawElementProviderSimple_iface, UIA_LiveRegionChangedEventId); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine CHECK_CALLED(uia_com_event_callback); + ok(ComEventData.last_call_tid != GetCurrentThreadId(), "Event handler called on unexpected thread %ld\n", + ComEventData.last_call_tid); + CoUninitialize(); + + return 0; +} + +static void test_IUIAutomationEventHandler(IUIAutomation *uia_iface, IUIAutomationElement *elem) +{ + struct Provider_prop_override prop_override; + struct node_provider_desc exp_node_desc; + IUIAutomationElement *elem2; + HANDLE thread; + HRESULT hr; + VARIANT v; + + /* + * Invalid input argument tests. + */ + hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, NULL, TreeScope_SubTree, NULL, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + todo_wine ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + + hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, TreeScope_SubTree, NULL, + NULL); + todo_wine ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + + /* + * Passing in a NULL element to this method results in an access violation + * on Windows. + */ + if (0) + { + IUIAutomation_RemoveAutomationEventHandler(uia_iface, 1, NULL, &AutomationEventHandler.IUIAutomationEventHandler_iface); + } + + hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, 1, elem, NULL); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, UIA_AutomationFocusChangedEventId, elem, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* + * UIA_AutomationFocusChangedEventId can only be listened for with + * AddFocusChangedEventHandler. Trying to register it on a regular event + * handler returns E_INVALIDARG. + */ + hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_AutomationFocusChangedEventId, elem, TreeScope_SubTree, NULL, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + todo_wine ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + /* Windows 11 queries the HWND for the element when adding a new handler. */ + set_uia_hwnd_expects(3, 2, 2, 3, 0); + /* All other event IDs are fine, only focus events are blocked. */ + hr = IUIAutomation_AddAutomationEventHandler(uia_iface, 1, elem, TreeScope_SubTree, NULL, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + check_uia_hwnd_expects_at_most(3, 2, 2, 3, 0); + + hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, 1, elem, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + + /* + * Test event raising behavior. + */ + set_uia_hwnd_expects(3, 2, 2, 3, 0); + hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, TreeScope_SubTree, NULL, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + check_uia_hwnd_expects_at_most(3, 2, 2, 3, 0); + + /* Same behavior as HUIAEVENTs, events are matched by runtime ID. */ + initialize_provider(&Provider2, ProviderOptions_ServerSideProvider, NULL, TRUE); + Provider2.runtime_id[0] = 0x2a; + Provider2.runtime_id[1] = HandleToUlong(ComEventData.event_hwnd); + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc, L"Main", L"Provider2", TRUE); + set_com_event_data(&exp_node_desc); + hr = UiaRaiseAutomationEvent(&Provider2.IRawElementProviderSimple_iface, UIA_LiveRegionChangedEventId); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine CHECK_CALLED(uia_com_event_callback); + + /* + * If no cache request is provided by the user in + * AddAutomationEventHandler, the default cache request is used. If no + * elements match the view condition, the event handler isn't invoked. + */ + variant_init_bool(&v, FALSE); + set_property_override(&prop_override, UIA_IsControlElementPropertyId, &v); + set_provider_prop_override(&Provider2, &prop_override, 1); + hr = UiaRaiseAutomationEvent(&Provider2.IRawElementProviderSimple_iface, UIA_LiveRegionChangedEventId); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + set_provider_prop_override(&Provider2, NULL, 0); + thread = CreateThread(NULL, 0, uia_com_event_handler_test_thread, NULL, 0, NULL); + while (MsgWaitForMultipleObjects(1, &thread, FALSE, INFINITE, QS_ALLINPUT) != WAIT_OBJECT_0) + { + MSG msg; + + while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + CloseHandle(thread); + + hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + + VariantInit(&v); + initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + hr = IUIAutomationElement_GetCurrentPropertyValueEx(elem, UIA_LabeledByPropertyId, TRUE, &v); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(V_VT(&v) == VT_UNKNOWN, "Unexpected vt %d\n", V_VT(&v)); + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + + hr = IUnknown_QueryInterface(V_UNKNOWN(&v), &IID_IUIAutomationElement, (void **)&elem2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!elem2, "elem2 == NULL\n"); + VariantClear(&v); + + /* + * Register an event on an element that has no runtime ID. The only way to + * remove an individual event handler is by matching a combination of + * runtime-id, property ID, and event handler interface pointer. Without a + * runtime-id, the only way to unregister the event handler is to call + * RemoveAllEventHandlers(). + */ + hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem2, TreeScope_SubTree, NULL, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + + /* No removal will occur due to a lack of a runtime ID to match. */ + hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem2, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + + hr = IUIAutomation_RemoveAllEventHandlers(uia_iface); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + + IUIAutomationElement_Release(elem2); +} + +static void test_CUIAutomation_event_handlers(IUIAutomation *uia_iface) +{ + IUIAutomationElement *elem; + HRESULT hr; + HWND hwnd; + + ComEventData.event_hwnd = hwnd = create_test_hwnd("test_CUIAutomation_event_handlers class"); + + /* Set up providers for the desktop window and our test HWND. */ + set_clientside_providers_for_hwnd(&Provider_proxy, &Provider_nc, &Provider_hwnd, GetDesktopWindow()); + base_hwnd_prov = &Provider_hwnd.IRawElementProviderSimple_iface; + proxy_prov = &Provider_proxy.IRawElementProviderSimple_iface; + nc_prov = &Provider_nc.IRawElementProviderSimple_iface; + + set_clientside_providers_for_hwnd(NULL, &Provider_nc2, &Provider_hwnd2, hwnd); + initialize_provider(&Provider, ProviderOptions_ServerSideProvider, hwnd, TRUE); + Provider.frag_root = &Provider.IRawElementProviderFragmentRoot_iface; + Provider.ignore_hwnd_prop = TRUE; + prov_root = &Provider.IRawElementProviderSimple_iface; + + method_sequences_enabled = FALSE; + set_uia_hwnd_expects(1, 1, 1, 1, 1); + UiaRegisterProviderCallback(test_uia_provider_callback); + hr = IUIAutomation_ElementFromHandle(uia_iface, hwnd, &elem); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!elem, "elem == NULL\n"); + ok(Provider.ref == 2, "Unexpected refcnt %ld\n", Provider.ref); + ok(Provider_hwnd2.ref == 2, "Unexpected refcnt %ld\n", Provider_hwnd2.ref); + ok(Provider_nc2.ref == 2, "Unexpected refcnt %ld\n", Provider_nc2.ref); + check_uia_hwnd_expects(1, TRUE, 1, FALSE, 1, FALSE, 1, FALSE, 0, FALSE); + + test_IUIAutomationEventHandler(uia_iface, elem); + + IUIAutomationElement_Release(elem); + UiaRegisterProviderCallback(NULL); + DestroyWindow(hwnd); + UnregisterClassA("test_CUIAutomation_event_handlers class", NULL); + method_sequences_enabled = TRUE; +} + struct uia_com_classes { const GUID *clsid; const GUID *iid; @@ -13004,6 +13371,7 @@ static void test_CUIAutomation(void) test_Element_Find(uia_iface); test_GetRootElement(uia_iface); test_GetFocusedElement(uia_iface); + test_CUIAutomation_event_handlers(uia_iface);
IUIAutomation_Release(uia_iface); CoUninitialize();
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 26 ++-- dlls/uiautomationcore/uia_com_client.c | 148 ++++++++++++++++++++- 2 files changed, 159 insertions(+), 15 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 0aff7a8ad24..b71d89bba45 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -13089,11 +13089,11 @@ static void test_IUIAutomationEventHandler(IUIAutomation *uia_iface, IUIAutomati */ hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, NULL, TreeScope_SubTree, NULL, &AutomationEventHandler.IUIAutomationEventHandler_iface); - todo_wine ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);
hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, TreeScope_SubTree, NULL, NULL); - todo_wine ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr);
/* * Passing in a NULL element to this method results in an access violation @@ -13118,21 +13118,21 @@ static void test_IUIAutomationEventHandler(IUIAutomation *uia_iface, IUIAutomati */ hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_AutomationFocusChangedEventId, elem, TreeScope_SubTree, NULL, &AutomationEventHandler.IUIAutomationEventHandler_iface); - todo_wine ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);
/* Windows 11 queries the HWND for the element when adding a new handler. */ set_uia_hwnd_expects(3, 2, 2, 3, 0); /* All other event IDs are fine, only focus events are blocked. */ hr = IUIAutomation_AddAutomationEventHandler(uia_iface, 1, elem, TreeScope_SubTree, NULL, &AutomationEventHandler.IUIAutomationEventHandler_iface); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); check_uia_hwnd_expects_at_most(3, 2, 2, 3, 0);
hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, 1, elem, &AutomationEventHandler.IUIAutomationEventHandler_iface); todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + todo_wine ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref);
/* * Test event raising behavior. @@ -13140,8 +13140,8 @@ static void test_IUIAutomationEventHandler(IUIAutomation *uia_iface, IUIAutomati set_uia_hwnd_expects(3, 2, 2, 3, 0); hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, TreeScope_SubTree, NULL, &AutomationEventHandler.IUIAutomationEventHandler_iface); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); check_uia_hwnd_expects_at_most(3, 2, 2, 3, 0);
/* Same behavior as HUIAEVENTs, events are matched by runtime ID. */ @@ -13184,7 +13184,7 @@ static void test_IUIAutomationEventHandler(IUIAutomation *uia_iface, IUIAutomati hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, &AutomationEventHandler.IUIAutomationEventHandler_iface); todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + todo_wine ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref);
VariantInit(&v); initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, TRUE); @@ -13207,18 +13207,18 @@ static void test_IUIAutomationEventHandler(IUIAutomation *uia_iface, IUIAutomati */ hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem2, TreeScope_SubTree, NULL, &AutomationEventHandler.IUIAutomationEventHandler_iface); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref);
/* No removal will occur due to a lack of a runtime ID to match. */ hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem2, &AutomationEventHandler.IUIAutomationEventHandler_iface); todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref);
hr = IUIAutomation_RemoveAllEventHandlers(uia_iface); todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + todo_wine ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref);
IUIAutomationElement_Release(elem2); } diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index 919936c7acb..6616568d535 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -898,6 +898,123 @@ static HRESULT get_uia_cache_request_struct_from_iface(IUIAutomationCacheRequest return S_OK; }
+/* + * COM API UI Automation event related functions. + */ +static struct uia_com_event_handlers +{ + struct rb_tree handler_map; + + LONG handler_count; +} com_event_handlers; + +static CRITICAL_SECTION com_event_handlers_cs; +static CRITICAL_SECTION_DEBUG com_event_handlers_cs_debug = +{ + 0, 0, &com_event_handlers_cs, + { &com_event_handlers_cs_debug.ProcessLocksList, &com_event_handlers_cs_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": com_event_handlers_cs") } +}; +static CRITICAL_SECTION com_event_handlers_cs = { &com_event_handlers_cs_debug, -1, 0, 0, 0, 0 }; + +struct uia_event_handler_identifier { + IUnknown *handler_iface; + SAFEARRAY *runtime_id; + int event_id; +}; + +struct uia_event_handler_map_entry +{ + struct rb_entry entry; + + IUnknown *handler_iface; + SAFEARRAY *runtime_id; + int event_id; + + struct list handlers_list; +}; + +static int uia_com_event_handler_id_compare(const void *key, const struct rb_entry *entry) +{ + struct uia_event_handler_map_entry *map_entry = RB_ENTRY_VALUE(entry, struct uia_event_handler_map_entry, entry); + struct uia_event_handler_identifier *event_id = (struct uia_event_handler_identifier *)key; + + if (event_id->event_id != map_entry->event_id) + return (event_id->event_id > map_entry->event_id) - (event_id->event_id < map_entry->event_id); + else if (event_id->handler_iface != map_entry->handler_iface) + return (event_id->handler_iface > map_entry->handler_iface) - (event_id->handler_iface < map_entry->handler_iface); + else if (event_id->runtime_id && map_entry->runtime_id) + return uia_compare_safearrays(event_id->runtime_id, map_entry->runtime_id, UIAutomationType_IntArray); + else + return (event_id->runtime_id > map_entry->runtime_id) - (event_id->runtime_id < map_entry->runtime_id); +} + +struct uia_com_event { + IUnknown *handler_iface; + + struct list event_handler_map_list_entry; + struct uia_event_handler_map_entry *handler_map; +}; + +static HRESULT uia_event_handlers_add_handler(IUnknown *handler_iface, SAFEARRAY *runtime_id, int event_id) +{ + struct uia_event_handler_identifier event_ident = { handler_iface, runtime_id, event_id }; + struct uia_event_handler_map_entry *event_map; + struct uia_com_event *event; + struct rb_entry *rb_entry; + HRESULT hr = S_OK; + + if (!(event = heap_alloc_zero(sizeof(*event)))) + return E_OUTOFMEMORY; + + event->handler_iface = handler_iface; + IUnknown_AddRef(handler_iface); + + EnterCriticalSection(&com_event_handlers_cs); + + if (!com_event_handlers.handler_count) + rb_init(&com_event_handlers.handler_map, uia_com_event_handler_id_compare); + + if ((rb_entry = rb_get(&com_event_handlers.handler_map, &event_ident))) + event_map = RB_ENTRY_VALUE(rb_entry, struct uia_event_handler_map_entry, entry); + else + { + if (!(event_map = heap_alloc_zero(sizeof(*event_map)))) + { + hr = E_OUTOFMEMORY; + goto exit; + } + + hr = SafeArrayCopy(runtime_id, &event_map->runtime_id); + if (FAILED(hr)) + { + heap_free(event_map); + goto exit; + } + + event_map->event_id = event_id; + event_map->handler_iface = event->handler_iface; + IUnknown_AddRef(event_map->handler_iface); + + list_init(&event_map->handlers_list); + rb_put(&com_event_handlers.handler_map, &event_ident, &event_map->entry); + } + + list_add_tail(&event_map->handlers_list, &event->event_handler_map_list_entry); + event->handler_map = event_map; + com_event_handlers.handler_count++; + +exit: + LeaveCriticalSection(&com_event_handlers_cs); + if (FAILED(hr)) + { + IUnknown_Release(event->handler_iface); + heap_free(event); + } + + return hr; +} + /* * IUIAutomationElementArray interface. */ @@ -3052,8 +3169,35 @@ static HRESULT WINAPI uia_iface_AddAutomationEventHandler(IUIAutomation6 *iface, IUIAutomationElement *elem, enum TreeScope scope, IUIAutomationCacheRequest *cache_req, IUIAutomationEventHandler *handler) { - FIXME("%p, %d, %p, %#x, %p, %p: stub\n", iface, event_id, elem, scope, cache_req, handler); - return E_NOTIMPL; + struct uia_element *element; + IUnknown *handler_iface; + SAFEARRAY *runtime_id; + HRESULT hr; + + TRACE("%p, %d, %p, %#x, %p, %p\n", iface, event_id, elem, scope, cache_req, handler); + + if (!elem || !handler) + return E_POINTER; + + if (event_id == UIA_AutomationFocusChangedEventId) + return E_INVALIDARG; + + hr = IUIAutomationEventHandler_QueryInterface(handler, &IID_IUnknown, (void **)&handler_iface); + if (FAILED(hr)) + return hr; + + element = impl_from_IUIAutomationElement9((IUIAutomationElement9 *)elem); + hr = UiaGetRuntimeId(element->node, &runtime_id); + if (FAILED(hr)) + goto exit; + + hr = uia_event_handlers_add_handler(handler_iface, runtime_id, event_id); + +exit: + IUnknown_Release(handler_iface); + SafeArrayDestroy(runtime_id); + + return hr; }
static HRESULT WINAPI uia_iface_RemoveAutomationEventHandler(IUIAutomation6 *iface, EVENTID event_id,
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 4 +-- dlls/uiautomationcore/uia_com_client.c | 32 ++++++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index b71d89bba45..545fc7d0bfc 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -13217,8 +13217,8 @@ static void test_IUIAutomationEventHandler(IUIAutomation *uia_iface, IUIAutomati ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref);
hr = IUIAutomation_RemoveAllEventHandlers(uia_iface); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref);
IUIAutomationElement_Release(elem2); } diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index 6616568d535..cba1f7f68a4 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -3261,8 +3261,36 @@ static HRESULT WINAPI uia_iface_RemoveFocusChangedEventHandler(IUIAutomation6 *i
static HRESULT WINAPI uia_iface_RemoveAllEventHandlers(IUIAutomation6 *iface) { - FIXME("%p: stub\n", iface); - return E_NOTIMPL; + struct uia_event_handler_map_entry *entry, *cursor; + + TRACE("%p\n", iface); + + EnterCriticalSection(&com_event_handlers_cs); + if (!com_event_handlers.handler_count) + goto exit; + + RB_FOR_EACH_ENTRY_DESTRUCTOR(entry, cursor, &com_event_handlers.handler_map, struct uia_event_handler_map_entry, entry) + { + struct uia_com_event *event, *event2; + + LIST_FOR_EACH_ENTRY_SAFE(event, event2, &entry->handlers_list, struct uia_com_event, event_handler_map_list_entry) + { + list_remove(&event->event_handler_map_list_entry); + IUnknown_Release(event->handler_iface); + com_event_handlers.handler_count--; + heap_free(event); + } + + rb_remove(&com_event_handlers.handler_map, &entry->entry); + IUnknown_Release(entry->handler_iface); + SafeArrayDestroy(entry->runtime_id); + heap_free(entry); + } + +exit: + LeaveCriticalSection(&com_event_handlers_cs); + + return S_OK; }
static HRESULT WINAPI uia_iface_IntNativeArrayToSafeArray(IUIAutomation6 *iface, int *arr, int arr_count,
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 14 ++-- dlls/uiautomationcore/uia_com_client.c | 75 +++++++++++++++++----- 2 files changed, 66 insertions(+), 23 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 545fc7d0bfc..0dd75ce862f 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -13105,11 +13105,11 @@ static void test_IUIAutomationEventHandler(IUIAutomation *uia_iface, IUIAutomati }
hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, 1, elem, NULL); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, UIA_AutomationFocusChangedEventId, elem, &AutomationEventHandler.IUIAutomationEventHandler_iface); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
/* * UIA_AutomationFocusChangedEventId can only be listened for with @@ -13131,8 +13131,8 @@ static void test_IUIAutomationEventHandler(IUIAutomation *uia_iface, IUIAutomati
hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, 1, elem, &AutomationEventHandler.IUIAutomationEventHandler_iface); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref);
/* * Test event raising behavior. @@ -13183,8 +13183,8 @@ static void test_IUIAutomationEventHandler(IUIAutomation *uia_iface, IUIAutomati
hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, &AutomationEventHandler.IUIAutomationEventHandler_iface); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref);
VariantInit(&v); initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, TRUE); @@ -13213,7 +13213,7 @@ static void test_IUIAutomationEventHandler(IUIAutomation *uia_iface, IUIAutomati /* No removal will occur due to a lack of a runtime ID to match. */ hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem2, &AutomationEventHandler.IUIAutomationEventHandler_iface); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref);
hr = IUIAutomation_RemoveAllEventHandlers(uia_iface); diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index cba1f7f68a4..75c722771c3 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -1015,6 +1015,37 @@ exit: return hr; }
+static void uia_event_handler_map_entry_destroy(struct uia_event_handler_map_entry *entry) +{ + struct uia_com_event *event, *event2; + + LIST_FOR_EACH_ENTRY_SAFE(event, event2, &entry->handlers_list, struct uia_com_event, event_handler_map_list_entry) + { + list_remove(&event->event_handler_map_list_entry); + IUnknown_Release(event->handler_iface); + com_event_handlers.handler_count--; + heap_free(event); + } + + rb_remove(&com_event_handlers.handler_map, &entry->entry); + IUnknown_Release(entry->handler_iface); + SafeArrayDestroy(entry->runtime_id); + heap_free(entry); +} + +static void uia_event_handlers_remove_handlers(IUnknown *handler_iface, SAFEARRAY *runtime_id, int event_id) +{ + struct uia_event_handler_identifier event_ident = { handler_iface, runtime_id, event_id }; + struct rb_entry *rb_entry; + + EnterCriticalSection(&com_event_handlers_cs); + + if (com_event_handlers.handler_count && (rb_entry = rb_get(&com_event_handlers.handler_map, &event_ident))) + uia_event_handler_map_entry_destroy(RB_ENTRY_VALUE(rb_entry, struct uia_event_handler_map_entry, entry)); + + LeaveCriticalSection(&com_event_handlers_cs); +} + /* * IUIAutomationElementArray interface. */ @@ -3203,8 +3234,33 @@ exit: static HRESULT WINAPI uia_iface_RemoveAutomationEventHandler(IUIAutomation6 *iface, EVENTID event_id, IUIAutomationElement *elem, IUIAutomationEventHandler *handler) { - FIXME("%p, %d, %p, %p: stub\n", iface, event_id, elem, handler); - return E_NOTIMPL; + struct uia_element *element; + IUnknown *handler_iface; + SAFEARRAY *runtime_id; + HRESULT hr; + + TRACE("%p, %d, %p, %p\n", iface, event_id, elem, handler); + + if (!elem || !handler) + return S_OK; + + element = impl_from_IUIAutomationElement9((IUIAutomationElement9 *)elem); + hr = UiaGetRuntimeId(element->node, &runtime_id); + if (FAILED(hr) || !runtime_id) + return hr; + + hr = IUIAutomationEventHandler_QueryInterface(handler, &IID_IUnknown, (void **)&handler_iface); + if (FAILED(hr)) + { + SafeArrayDestroy(runtime_id); + return hr; + } + + uia_event_handlers_remove_handlers(handler_iface, runtime_id, event_id); + IUnknown_Release(handler_iface); + SafeArrayDestroy(runtime_id); + + return S_OK; }
static HRESULT WINAPI uia_iface_AddPropertyChangedEventHandlerNativeArray(IUIAutomation6 *iface, @@ -3271,20 +3327,7 @@ static HRESULT WINAPI uia_iface_RemoveAllEventHandlers(IUIAutomation6 *iface)
RB_FOR_EACH_ENTRY_DESTRUCTOR(entry, cursor, &com_event_handlers.handler_map, struct uia_event_handler_map_entry, entry) { - struct uia_com_event *event, *event2; - - LIST_FOR_EACH_ENTRY_SAFE(event, event2, &entry->handlers_list, struct uia_com_event, event_handler_map_list_entry) - { - list_remove(&event->event_handler_map_list_entry); - IUnknown_Release(event->handler_iface); - com_event_handlers.handler_count--; - heap_free(event); - } - - rb_remove(&com_event_handlers.handler_map, &entry->entry); - IUnknown_Release(entry->handler_iface); - SafeArrayDestroy(entry->runtime_id); - heap_free(entry); + uia_event_handler_map_entry_destroy(entry); }
exit:
On Mon Jul 17 13:27:58 2023 +0000, Connor McAdams wrote:
changed this line in [version 2 of the diff](/wine/wine/-/merge_requests/3297/diffs?diff_id=57829&start_sha=e2a933f68db57d2fc77c849e56438713303217b7#94c88683243c4ed654665c5bed46ae71a2c68159_987_977)
Changed it to only use the rbtree in the current revision.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_com_client.c:
- if (event_id->event_id != map_entry->event_id)
return (event_id->event_id > map_entry->event_id) - (event_id->event_id < map_entry->event_id);
- else if (event_id->handler_iface != map_entry->handler_iface)
return (event_id->handler_iface > map_entry->handler_iface) - (event_id->handler_iface < map_entry->handler_iface);
- else if (event_id->runtime_id && map_entry->runtime_id)
return uia_compare_safearrays(event_id->runtime_id, map_entry->runtime_id, UIAutomationType_IntArray);
- else
return (event_id->runtime_id > map_entry->runtime_id) - (event_id->runtime_id < map_entry->runtime_id);
+}
+struct uia_com_event {
- IUnknown *handler_iface;
- struct list event_handler_map_list_entry;
- struct uia_event_handler_map_entry *handler_map;
+};
Is this struct still needed? It seems like it has no new information compared to `uia_event_handler_map_entry`.
On Mon Jul 17 20:24:54 2023 +0000, Esme Povirk wrote:
Is this struct still needed? It seems like it has no new information compared to `uia_event_handler_map_entry`.
Yes, it'll eventually get the actual HUIAEVENT when we add support for invoking the COM event handler, along with a GIT cookie for this interface.
We can split that off I guess into a separate patch, but I don't know that it helps much.
This merge request was approved by Esme Povirk.