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 | 72 +++++++++++++++++++++- 2 files changed, 83 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..63abc3cb931 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -898,6 +898,57 @@ 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 list handler_list; + + 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_com_event { + IUnknown *handler_iface; + int event_id; + + struct list event_handler_list_entry; +}; + +static HRESULT uia_event_handlers_add_handler(IUnknown *handler_iface, int event_id) +{ + struct uia_com_event *event; + HRESULT hr = S_OK; + + if (!(event = heap_alloc_zero(sizeof(*event)))) + return E_OUTOFMEMORY; + + event->handler_iface = handler_iface; + IUnknown_AddRef(handler_iface); + event->event_id = event_id; + + EnterCriticalSection(&com_event_handlers_cs); + + if (!com_event_handlers.handler_count) + list_init(&com_event_handlers.handler_list); + + list_add_tail(&com_event_handlers.handler_list, &event->event_handler_list_entry); + com_event_handlers.handler_count++; + + LeaveCriticalSection(&com_event_handlers_cs); + + return hr; +} + /* * IUIAutomationElementArray interface. */ @@ -3052,8 +3103,25 @@ 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; + IUnknown *handler_iface; + 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; + + hr = uia_event_handlers_add_handler(handler_iface, event_id); + IUnknown_Release(handler_iface); + + 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 | 29 ++++++++++++++++++++-- 2 files changed, 29 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 63abc3cb931..55abda5e86d 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -949,6 +949,13 @@ static HRESULT uia_event_handlers_add_handler(IUnknown *handler_iface, int event return hr; }
+static void uia_event_handlers_destroy_handler(struct uia_com_event *event) +{ + list_remove(&event->event_handler_list_entry); + IUnknown_Release(event->handler_iface); + heap_free(event); +} + /* * IUIAutomationElementArray interface. */ @@ -3185,8 +3192,26 @@ 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 list *cursor, *cursor2; + + TRACE("%p\n", iface); + + EnterCriticalSection(&com_event_handlers_cs); + if (!com_event_handlers.handler_count) + goto exit; + + LIST_FOR_EACH_SAFE(cursor, cursor2, &com_event_handlers.handler_list) + { + struct uia_com_event *event = LIST_ENTRY(cursor, struct uia_com_event, event_handler_list_entry); + + uia_event_handlers_destroy_handler(event); + com_event_handlers.handler_count--; + } + +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 | 175 ++++++++++++++++++++- 2 files changed, 178 insertions(+), 11 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 55abda5e86d..d5bc353ffd1 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -903,6 +903,7 @@ static HRESULT get_uia_cache_request_struct_from_iface(IUIAutomationCacheRequest */ static struct uia_com_event_handlers { + struct rb_tree handler_map; struct list handler_list;
LONG handler_count; @@ -917,14 +918,48 @@ static CRITICAL_SECTION_DEBUG com_event_handlers_cs_debug = }; 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 + return uia_compare_safearrays(event_id->runtime_id, map_entry->runtime_id, UIAutomationType_IntArray); +} + struct uia_com_event { IUnknown *handler_iface; + SAFEARRAY *runtime_id; int event_id;
struct list event_handler_list_entry; + + 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, int event_id) +static HRESULT uia_event_handlers_add_handler(IUnknown *handler_iface, SAFEARRAY *runtime_id, int event_id) { struct uia_com_event *event; HRESULT hr = S_OK; @@ -932,6 +967,13 @@ static HRESULT uia_event_handlers_add_handler(IUnknown *handler_iface, int event if (!(event = heap_alloc_zero(sizeof(*event)))) return E_OUTOFMEMORY;
+ if (runtime_id) + { + hr = SafeArrayCopy(runtime_id, &event->runtime_id); + if (FAILED(hr)) + return hr; + } + event->handler_iface = handler_iface; IUnknown_AddRef(handler_iface); event->event_id = event_id; @@ -939,23 +981,113 @@ static HRESULT uia_event_handlers_add_handler(IUnknown *handler_iface, int event EnterCriticalSection(&com_event_handlers_cs);
if (!com_event_handlers.handler_count) + { list_init(&com_event_handlers.handler_list); + rb_init(&com_event_handlers.handler_map, uia_com_event_handler_id_compare); + } + + if (runtime_id) + { + struct uia_event_handler_identifier event_ident = { handler_iface, runtime_id, event_id }; + struct uia_event_handler_map_entry *event_map; + struct rb_entry *rb_entry; + + 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(event->runtime_id, &event_map->runtime_id); + if (FAILED(hr)) + { + heap_free(event_map); + goto exit; + } + + event_map->event_id = event->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; + }
list_add_tail(&com_event_handlers.handler_list, &event->event_handler_list_entry); com_event_handlers.handler_count++;
+exit: LeaveCriticalSection(&com_event_handlers_cs);
+ if (FAILED(hr)) + { + IUnknown_Release(event->handler_iface); + SafeArrayDestroy(event->runtime_id); + heap_free(event); + } + return hr; }
+static void uia_event_handler_map_entry_destroy(struct uia_event_handler_map_entry *event_map) +{ + rb_remove(&com_event_handlers.handler_map, &event_map->entry); + IUnknown_Release(event_map->handler_iface); + SafeArrayDestroy(event_map->runtime_id); + heap_free(event_map); +} + static void uia_event_handlers_destroy_handler(struct uia_com_event *event) { list_remove(&event->event_handler_list_entry); IUnknown_Release(event->handler_iface); + SafeArrayDestroy(event->runtime_id); + if (event->handler_map) + { + list_remove(&event->event_handler_map_list_entry); + if (list_empty(&event->handler_map->handlers_list)) + uia_event_handler_map_entry_destroy(event->handler_map); + } heap_free(event); }
+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))) + { + struct uia_event_handler_map_entry *event_map = RB_ENTRY_VALUE(rb_entry, struct uia_event_handler_map_entry, entry); + struct list *cursor, *cursor2; + + LIST_FOR_EACH_SAFE(cursor, cursor2, &event_map->handlers_list) + { + struct uia_com_event *event = LIST_ENTRY(cursor, struct uia_com_event, event_handler_map_list_entry); + + list_remove(cursor); + /* Set to NULL to prevent destruction in uia_event_handlers_destroy_handler. */ + event->handler_map = NULL; + uia_event_handlers_destroy_handler(event); + com_event_handlers.handler_count--; + } + + uia_event_handler_map_entry_destroy(event_map); + } + + LeaveCriticalSection(&com_event_handlers_cs); +} + /* * IUIAutomationElementArray interface. */ @@ -3110,7 +3242,9 @@ static HRESULT WINAPI uia_iface_AddAutomationEventHandler(IUIAutomation6 *iface, IUIAutomationElement *elem, enum TreeScope scope, IUIAutomationCacheRequest *cache_req, IUIAutomationEventHandler *handler) { + 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); @@ -3125,8 +3259,16 @@ static HRESULT WINAPI uia_iface_AddAutomationEventHandler(IUIAutomation6 *iface, if (FAILED(hr)) return hr;
- hr = uia_event_handlers_add_handler(handler_iface, event_id); + 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; } @@ -3134,8 +3276,33 @@ static HRESULT WINAPI uia_iface_AddAutomationEventHandler(IUIAutomation6 *iface, 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,
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_com_client.c:
EnterCriticalSection(&com_event_handlers_cs); if (!com_event_handlers.handler_count)
- { list_init(&com_event_handlers.handler_list);
rb_init(&com_event_handlers.handler_map, uia_com_event_handler_id_compare);
- }
I think this would be simpler with just the rbtree, so we don't have 2 different data structures that need to be kept consistent. Unless the order matters for some reason, the rbtree should be able to hold all the information.
On Sat Jul 15 19:08:44 2023 +0000, Esme Povirk wrote:
I think this would be simpler with just the rbtree, so we don't have 2 different data structures that need to be kept consistent. Unless the order matters for some reason, the rbtree should be able to hold all the information.
This was mainly done to have a simpler implementation first prior to introducing the rbtree which feels a bit more complicated, but I see your point. I'll do that then.
On Mon Jul 17 11:39:06 2023 +0000, Connor McAdams wrote:
This was mainly done to have a simpler implementation first prior to introducing the rbtree which feels a bit more complicated, but I see your point. I'll do that then.
oh wait. We can't do an rbtree lookup on event handlers that don't have a runtime ID associated with their element. In that case, we do still need to store them somewhere. I guess we could have those on a separate list?