-- v5: uiautomationcore: Use EVENT_OBJECT_DESTROY to remove HWNDs from the COM API focus change HWND map. uiautomationcore: Use EVENT_OBJECT_FOCUS to advise HWND providers of focus change events in the COM API. uiautomationcore: Query EVENT_OBJECT_FOCUS HWND for a serverside provider if there is a registered focus change event handler. uiautomationcore/tests: Add tests for IUIAutomationFocusChangedEventHandler event advisement behavior.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 412 ++++++++++++++++++++- 1 file changed, 403 insertions(+), 9 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 6cdc19b739d..5519cbbc312 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -8785,6 +8785,147 @@ static SAFEARRAY WINAPI *test_uia_provider_callback(HWND hwnd, enum ProviderType return get_safearray_for_elprov(elprov); }
+/* + * Windows 11 will infinitely loop if a clientside provider isn't returned for + * an HWND in certain circumstances. In order to prevent this, we use these + * temporary clientside providers. + */ +struct ClientSideProvider +{ + IRawElementProviderSimple IRawElementProviderSimple_iface; + LONG ref; + + enum ProviderType prov_type; + HWND hwnd; +}; + +static inline struct ClientSideProvider *impl_from_ClientSideProvider(IRawElementProviderSimple *iface) +{ + return CONTAINING_RECORD(iface, struct ClientSideProvider, IRawElementProviderSimple_iface); +} + +static HRESULT WINAPI ClientSideProvider_QueryInterface(IRawElementProviderSimple *iface, REFIID riid, void **ppv) +{ + *ppv = NULL; + if (IsEqualIID(riid, &IID_IRawElementProviderSimple) || IsEqualIID(riid, &IID_IUnknown)) + *ppv = iface; + else + return E_NOINTERFACE; + + IRawElementProviderSimple_AddRef(iface); + return S_OK; +} + +static ULONG WINAPI ClientSideProvider_AddRef(IRawElementProviderSimple *iface) +{ + struct ClientSideProvider *This = impl_from_ClientSideProvider(iface); + return InterlockedIncrement(&This->ref); +} + +static ULONG WINAPI ClientSideProvider_Release(IRawElementProviderSimple *iface) +{ + struct ClientSideProvider *This = impl_from_ClientSideProvider(iface); + ULONG ref = InterlockedDecrement(&This->ref); + + if (!ref) + HeapFree(GetProcessHeap(), 0, This); + + return ref; +} + +static HRESULT WINAPI ClientSideProvider_get_ProviderOptions(IRawElementProviderSimple *iface, + enum ProviderOptions *ret_val) +{ + struct ClientSideProvider *This = impl_from_ClientSideProvider(iface); + + *ret_val = ProviderOptions_ClientSideProvider; + if (This->prov_type == ProviderType_NonClientArea) + *ret_val |= ProviderOptions_NonClientAreaProvider; + + return S_OK; +} + +static HRESULT WINAPI ClientSideProvider_GetPatternProvider(IRawElementProviderSimple *iface, + PATTERNID pattern_id, IUnknown **ret_val) +{ + *ret_val = NULL; + return S_OK; +} + +static HRESULT WINAPI ClientSideProvider_GetPropertyValue(IRawElementProviderSimple *iface, PROPERTYID prop_id, + VARIANT *ret_val) +{ + struct ClientSideProvider *This = impl_from_ClientSideProvider(iface); + + VariantInit(ret_val); + switch (prop_id) + { + case UIA_NativeWindowHandlePropertyId: + if (This->prov_type == ProviderType_BaseHwnd) + { + V_VT(ret_val) = VT_I4; + V_I4(ret_val) = HandleToULong(This->hwnd); + } + break; + + case UIA_ProviderDescriptionPropertyId: + V_VT(ret_val) = VT_BSTR; + V_BSTR(ret_val) = SysAllocString(L"ClientSideProvider"); + break; + + default: + break; + } + + return S_OK; +} + +static HRESULT WINAPI ClientSideProvider_get_HostRawElementProvider(IRawElementProviderSimple *iface, + IRawElementProviderSimple **ret_val) +{ + struct ClientSideProvider *This = impl_from_ClientSideProvider(iface); + + return UiaHostProviderFromHwnd(This->hwnd, ret_val); +} + +static IRawElementProviderSimpleVtbl ClientSideProviderVtbl = { + ClientSideProvider_QueryInterface, + ClientSideProvider_AddRef, + ClientSideProvider_Release, + ClientSideProvider_get_ProviderOptions, + ClientSideProvider_GetPatternProvider, + ClientSideProvider_GetPropertyValue, + ClientSideProvider_get_HostRawElementProvider, +}; + +static IRawElementProviderSimple *create_temporary_clientside_provider(HWND hwnd, enum ProviderType prov_type) +{ + struct ClientSideProvider *prov = HeapAlloc(GetProcessHeap(), 0, sizeof(*prov)); + + if (!prov) + { + trace("Failed to allocate memory for temporary clientside provider\n"); + return NULL; + } + + prov->IRawElementProviderSimple_iface.lpVtbl = &ClientSideProviderVtbl; + prov->ref = 1; + prov->prov_type = prov_type; + prov->hwnd = hwnd; + + return &prov->IRawElementProviderSimple_iface; +} + +static CRITICAL_SECTION clientside_provider_callback_cs; +static CRITICAL_SECTION_DEBUG clientside_provider_callback_cs_debug = +{ + 0, 0, &clientside_provider_callback_cs, + { &clientside_provider_callback_cs_debug.ProcessLocksList, &clientside_provider_callback_cs_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": clientside_provider_callback_cs") } +}; +static CRITICAL_SECTION clientside_provider_callback_cs = { &clientside_provider_callback_cs_debug, -1, 0, 0, 0, 0 }; +static CONDITION_VARIABLE *clientside_provider_callback_cv; + /* * Same deal as test_uia_provider_callback, except we only return a provider * if we can match one by HWND. This is necessary due to certain versions of @@ -8793,7 +8934,12 @@ static SAFEARRAY WINAPI *test_uia_provider_callback(HWND hwnd, enum ProviderType static SAFEARRAY WINAPI *uia_com_win_event_clientside_provider_callback(HWND hwnd, enum ProviderType prov_type) { IRawElementProviderSimple *elprov = NULL; + SAFEARRAY *sa = NULL;
+ EnterCriticalSection(&clientside_provider_callback_cs); + if (clientside_provider_callback_cv) + WakeConditionVariable(clientside_provider_callback_cv); + LeaveCriticalSection(&clientside_provider_callback_cs); switch (prov_type) { case ProviderType_BaseHwnd: @@ -8829,7 +8975,53 @@ static SAFEARRAY WINAPI *uia_com_win_event_clientside_provider_callback(HWND hwn break; }
- return get_safearray_for_elprov(elprov); + if (!elprov) + { + if (!(elprov = create_temporary_clientside_provider(hwnd, prov_type))) + return NULL; + } + else + IRawElementProviderSimple_AddRef(elprov); + + sa = get_safearray_for_elprov(elprov); + IRawElementProviderSimple_Release(elprov); + return sa; +} + +/* + * Some versions of Windows 10 query multiple unrelated HWNDs when + * winevents are fired, this function waits for these to stop before + * continuing to the next test. + */ +#define TIME_SINCE_LAST_CALLBACK_TIMEOUT 200 +static BOOL wait_for_clientside_callbacks(DWORD total_timeout) +{ + DWORD start_time = GetTickCount(); + CONDITION_VARIABLE cv; + BOOL ret = FALSE; + + InitializeConditionVariable(&cv); + EnterCriticalSection(&clientside_provider_callback_cs); + clientside_provider_callback_cv = &cv; + while (1) + { + BOOL ret_val = SleepConditionVariableCS(&cv, &clientside_provider_callback_cs, TIME_SINCE_LAST_CALLBACK_TIMEOUT); + + if ((GetTickCount() - start_time) >= total_timeout) + ret = TRUE; + else if (!ret_val && (GetLastError() == ERROR_TIMEOUT)) + ret = FALSE; + else if (!ret_val) + trace("SleepConditionVariableCS failed, last err %ld\n", GetLastError()); + else + continue; + + break; + } + + clientside_provider_callback_cv = NULL; + LeaveCriticalSection(&clientside_provider_callback_cs); + return ret; }
static void test_UiaRegisterProviderCallback(void) @@ -9393,6 +9585,14 @@ static void test_cache_req_sa_(SAFEARRAY *sa, LONG exp_lbound[2], LONG exp_elems } }
+#define test_provider_event_advise_added( prov, event_id, todo) \ + test_provider_event_advise_added_( (prov), (event_id), (todo), __FILE__, __LINE__) +static void test_provider_event_advise_added_(struct Provider *prov, int event_id, BOOL todo, const char *file, int line) +{ + todo_wine_if (todo) ok_(file, line)(prov->advise_events_added_event_id == event_id, "%s: Unexpected advise event added, event ID %d.\n", + prov->prov_name, prov->advise_events_added_event_id); +} + static const struct prov_method_sequence cache_req_seq1[] = { { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_ProviderDescriptionPropertyId. */ { 0 } @@ -14864,8 +15064,43 @@ struct com_win_event_test_thread_data HWND test_child_hwnd; };
+static void set_method_event_handle_for_providers(struct Provider *main, struct Provider *hwnd, struct Provider *nc, + HANDLE *handles, int method) +{ + if (!handles || method < 0) + { + set_provider_method_event_data(main, NULL, -1); + set_provider_method_event_data(hwnd, NULL, -1); + set_provider_method_event_data(nc, NULL, -1); + } + else + { + set_provider_method_event_data(main, handles[0], method); + set_provider_method_event_data(hwnd, handles[1], method); + set_provider_method_event_data(nc, handles[2], method); + } +} + +static void reset_event_advise_values_for_hwnd_providers(struct Provider *main, struct Provider *hwnd, struct Provider *nc) +{ + initialize_provider_advise_events_ids(main); + initialize_provider_advise_events_ids(hwnd); + initialize_provider_advise_events_ids(nc); +} + +#define test_hwnd_providers_event_advise_added( main, hwnd, nc, event_id, todo) \ + test_hwnd_providers_event_advise_added_( (main), (hwnd), (nc), (event_id), (todo), __FILE__, __LINE__) +static void test_hwnd_providers_event_advise_added_(struct Provider *main, struct Provider *hwnd, struct Provider *nc, + int event_id, BOOL todo, const char *file, int line) +{ + test_provider_event_advise_added_(main, event_id, todo, file, line); + test_provider_event_advise_added_(hwnd, event_id, todo, file, line); + test_provider_event_advise_added_(nc, event_id, todo, file, line); +} + static void test_uia_com_event_handler_event_advisement(IUIAutomation *uia_iface, HWND test_hwnd, HWND test_child_hwnd) { + GUITHREADINFO info = { .cbSize = sizeof(info) }; IUIAutomationElement *elem; HANDLE method_event[4]; int event_handle_count; @@ -15079,6 +15314,173 @@ static void test_uia_com_event_handler_event_advisement(IUIAutomation *uia_iface if (is_win11) set_provider_method_event_data(&Provider2, NULL, -1);
+ /* + * IUIAutomationFocusChangedEventHandler is treated differently than all + * other event handlers - it only advises providers of events the first + * time it encounters an EVENT_OBJECT_FOCUS WinEvent for an HWND. + */ + reset_event_advise_values_for_hwnd_providers(&Provider_proxy, &Provider_hwnd, &Provider_nc); + reset_event_advise_values_for_hwnd_providers(&Provider, &Provider_hwnd2, &Provider_nc2); + reset_event_advise_values_for_hwnd_providers(&Provider2, &Provider_hwnd3, &Provider_nc3); + + set_uia_hwnd_expects(0, 6, 6, 5, 0); + SET_EXPECT(child_winproc_GETOBJECT_UiaRoot); /* Only sent on Win11. */ + FocusChangedHandler.event_handler_added = FALSE; + hr = IUIAutomation_AddFocusChangedEventHandler(uia_iface, NULL, + &FocusChangedHandler.IUIAutomationFocusChangedEventHandler_iface); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(FocusChangedHandler.ref > 1, "Unexpected refcnt %ld\n", FocusChangedHandler.ref); + check_uia_hwnd_expects_at_most(0, 6, 6, 5, 0); + CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 1); + + /* + * Windows 10 version 1507 and below only advise the currently focused + * HWNDs providers of events being added - newer versions instead advise + * the desktop HWND's providers of events being added. We're matching the + * newer behavior, so skip the tests on older versions. + */ + if (!Provider_nc.advise_events_added_event_id && !Provider_hwnd.advise_events_added_event_id && + !Provider_proxy.advise_events_added_event_id) + { + win_skip("Win10v1507 and below advise the currently focused HWND and not the desktop HWND, skipping tests.\n"); + + test_hwnd_providers_event_advise_added(&Provider, &Provider_hwnd2, &Provider_nc2, UIA_AutomationFocusChangedEventId, FALSE); + set_uia_hwnd_expects(0, 1, 1, 0, 0); + hr = IUIAutomation_RemoveFocusChangedEventHandler(uia_iface, + &FocusChangedHandler.IUIAutomationFocusChangedEventHandler_iface); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_uia_hwnd_expects(0, FALSE, 1, FALSE, 1, FALSE, 0, FALSE, 0, FALSE); + goto exit; + } + + /* + * Currently focused HWND is not advised, it's expected that the desktop + * providers will advise it using GetEmbeddedFragmentRoots. + */ + test_provider_event_advise_added(&Provider_hwnd2, 0, FALSE); + test_provider_event_advise_added(&Provider_nc2, 0, FALSE); + test_hwnd_providers_event_advise_added(&Provider_proxy, &Provider_hwnd, &Provider_nc, UIA_AutomationFocusChangedEventId, FALSE); + + /* + * EVENT_OBJECT_SHOW doesn't advise events on anything other than the + * desktop HWND for focus changed event handlers. + */ + event_handle_count = 3; + set_uia_hwnd_expects(0, 1, 1, 2, 0); /* Win11 sends WM_GETOBJECT twice. */ + set_method_event_handle_for_providers(&Provider, &Provider_hwnd2, &Provider_nc2, method_event, ADVISE_EVENTS_EVENT_ADDED); + NotifyWinEvent(EVENT_OBJECT_SHOW, test_hwnd, OBJID_WINDOW, CHILDID_SELF); + ok(msg_wait_for_all_events(method_event, event_handle_count, 750) == WAIT_TIMEOUT, "Wait for method_event(s) didn't time out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + check_uia_hwnd_expects_at_most(0, 1, 1, 2, 0); + set_method_event_handle_for_providers(&Provider, &Provider_hwnd2, &Provider_nc2, NULL, -1); + + set_method_event_handle_for_providers(&Provider2, &Provider_hwnd3, &Provider_nc3, method_event, ADVISE_EVENTS_EVENT_ADDED); + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 2); /* Only done twice on Win11. */ + set_uia_hwnd_expects(0, 1, 1, 1, 0); + NotifyWinEvent(EVENT_OBJECT_SHOW, test_child_hwnd, OBJID_WINDOW, CHILDID_SELF); + ok(msg_wait_for_all_events(method_event, event_handle_count, 750) == WAIT_TIMEOUT, "Wait for method_event(s) didn't time out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + set_method_event_handle_for_providers(&Provider2, &Provider_hwnd3, &Provider_nc3, NULL, -1); + check_uia_hwnd_expects(0, FALSE, 1, FALSE, 1, FALSE, 1, TRUE, 0, FALSE); + CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 2); + + /* + * This will advise events. + */ + set_method_event_handle_for_providers(&Provider_proxy, &Provider_hwnd, &Provider_nc, method_event, ADVISE_EVENTS_EVENT_ADDED); + reset_event_advise_values_for_hwnd_providers(&Provider_proxy, &Provider_hwnd, &Provider_nc); + SET_EXPECT(child_winproc_GETOBJECT_UiaRoot); /* Only sent on Win11. */ + set_uia_hwnd_expects(0, 3, 3, 1, 0); /* Only Win11 sends WM_GETOBJECT. */ + + NotifyWinEvent(EVENT_OBJECT_SHOW, GetDesktopWindow(), OBJID_WINDOW, CHILDID_SELF); + ok(msg_wait_for_all_events(method_event, event_handle_count, 2000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + + test_hwnd_providers_event_advise_added(&Provider_proxy, &Provider_hwnd, &Provider_nc, UIA_AutomationFocusChangedEventId, FALSE); + set_method_event_handle_for_providers(&Provider_proxy, &Provider_hwnd, &Provider_nc, NULL, -1); + check_uia_hwnd_expects_at_most(0, 3, 3, 1, 0); + CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 1); + + /* + * The HWND that was focused when adding the event handler is ignored, + * EVENT_OBJECT_FOCUS only advises of events the first time + * EVENT_OBJECT_FOCUS is raised on an HWND, and it tracks if it has + * encountered a particular HWND before. + */ + set_method_event_handle_for_providers(&Provider, &Provider_hwnd2, &Provider_nc2, method_event, ADVISE_EVENTS_EVENT_ADDED); + set_uia_hwnd_expects(0, 1, 1, 3, 0); /* Only Win11 sends WM_GETOBJECT 3 times. */ + + NotifyWinEvent(EVENT_OBJECT_FOCUS, test_hwnd, OBJID_CLIENT, CHILDID_SELF); + ok(msg_wait_for_all_events(method_event, event_handle_count, 750) == WAIT_TIMEOUT, "Wait for method_event(s) didn't time out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + + set_method_event_handle_for_providers(&Provider, &Provider_hwnd2, &Provider_nc2, NULL, -1); + check_uia_hwnd_expects_at_most(0, 1, 1, 3, 0); + + /* Only OBJID_CLIENT is listened for, all other OBJIDs are ignored. */ + set_method_event_handle_for_providers(&Provider2, &Provider_hwnd3, &Provider_nc3, method_event, ADVISE_EVENTS_EVENT_ADDED); + + NotifyWinEvent(EVENT_OBJECT_FOCUS, test_child_hwnd, OBJID_WINDOW, CHILDID_SELF); + ok(msg_wait_for_all_events(method_event, event_handle_count, 750) == WAIT_TIMEOUT, "Wait for method_event(s) didn't time out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + + /* + * First time EVENT_OBJECT_FOCUS is fired for this HWND, it will be + * advised of UIA_AutomationFocusChangedEventId being listened for. + * But only the serverside provider. + */ + set_method_event_handle_for_providers(&Provider2, &Provider_hwnd3, &Provider_nc3, NULL, -1); + reset_event_advise_values_for_hwnd_providers(&Provider2, &Provider_hwnd3, &Provider_nc3); + set_provider_method_event_data(&Provider2, method_event[0], ADVISE_EVENTS_EVENT_ADDED); + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 3); /* Only sent 3 times on Win11. */ + set_uia_hwnd_expects(0, 1, 1, 2, 0); /* Only Win11 sends WM_GETOBJECT 2 times. */ + + NotifyWinEvent(EVENT_OBJECT_FOCUS, test_child_hwnd, OBJID_CLIENT, CHILDID_SELF); + todo_wine ok(msg_wait_for_all_events(method_event, 1, 2000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + + check_uia_hwnd_expects_at_most(0, 1, 1, 2, 0); + CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 3); + test_provider_event_advise_added(&Provider2, UIA_AutomationFocusChangedEventId, TRUE); + test_provider_event_advise_added(&Provider_hwnd3, 0, FALSE); + test_provider_event_advise_added(&Provider_nc3, 0, FALSE); + + /* Doing it again has no effect, it has already been advised. */ + reset_event_advise_values_for_hwnd_providers(&Provider2, &Provider_hwnd3, &Provider_nc3); + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 3); /* Only sent 3 times on Win11. */ + set_uia_hwnd_expects(0, 1, 1, 2, 0); /* Only Win11 sends WM_GETOBJECT 2 times. */ + + NotifyWinEvent(EVENT_OBJECT_FOCUS, test_child_hwnd, OBJID_CLIENT, CHILDID_SELF); + ok(msg_wait_for_all_events(method_event, 1, 750) == WAIT_TIMEOUT, "Wait for method_event(s) didn't time out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + + set_provider_method_event_data(&Provider2, NULL, -1); + check_uia_hwnd_expects_at_most(0, 1, 1, 2, 0); + CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 3); + test_hwnd_providers_event_advise_added(&Provider2, &Provider_hwnd3, &Provider_nc3, 0, FALSE); + + /* + * Same deal here - we've already encountered this HWND and advised it + * when the event handler was added initially, so it too is ignored. + */ + set_method_event_handle_for_providers(&Provider, &Provider_hwnd2, &Provider_nc2, method_event, ADVISE_EVENTS_EVENT_ADDED); + reset_event_advise_values_for_hwnd_providers(&Provider, &Provider_hwnd2, &Provider_nc2); + set_uia_hwnd_expects(0, 1, 1, 3, 0); /* Only Win11 sends WM_GETOBJECT 3 times. */ + + NotifyWinEvent(EVENT_OBJECT_FOCUS, test_hwnd, OBJID_CLIENT, CHILDID_SELF); + ok(msg_wait_for_all_events(method_event, event_handle_count, 750) == WAIT_TIMEOUT, "Wait for method_event(s) didn't time out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + + set_method_event_handle_for_providers(&Provider, &Provider_hwnd2, &Provider_nc2, NULL, -1); + check_uia_hwnd_expects_at_most(0, 1, 1, 3, 0); + test_hwnd_providers_event_advise_added(&Provider, &Provider_hwnd2, &Provider_nc2, 0, FALSE); + + set_uia_hwnd_expects(0, 1, 1, 0, 0); + hr = IUIAutomation_RemoveFocusChangedEventHandler(uia_iface, + &FocusChangedHandler.IUIAutomationFocusChangedEventHandler_iface); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_uia_hwnd_expects(0, FALSE, 1, FALSE, 1, FALSE, 0, FALSE, 0, FALSE); + exit: for (i = 0; i < ARRAY_SIZE(method_event); i++) CloseHandle(method_event[i]); @@ -17113,14 +17515,6 @@ static void test_uia_event_win_event_mapping_(DWORD win_event, HWND hwnd, LONG o todo_wine_if(todo) CHECK_CALLED(uia_event_callback2); }
-#define test_provider_event_advise_added( prov, event_id, todo) \ - test_provider_event_advise_added_( (prov), (event_id), (todo), __FILE__, __LINE__) -static void test_provider_event_advise_added_(struct Provider *prov, int event_id, BOOL todo, const char *file, int line) -{ - todo_wine_if (todo) ok_(file, line)(prov->advise_events_added_event_id == event_id, "%s: Unexpected advise event added, event ID %d.\n", - prov->prov_name, prov->advise_events_added_event_id); -} - static DWORD WINAPI uia_proxy_provider_win_event_handler_test_thread(LPVOID param) { struct UiaCacheRequest cache_req = { (struct UiaCondition *)&UiaTrueCondition, TreeScope_Element, NULL, 0, NULL, 0,
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_client.c | 2 +- dlls/uiautomationcore/uia_com_client.c | 87 ++++++++++++++++++++++++++ dlls/uiautomationcore/uia_private.h | 1 + 3 files changed, 89 insertions(+), 1 deletion(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index f7b602ab2ed..f57ffb4bc4c 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -2631,7 +2631,7 @@ static HRESULT uia_get_provider_from_hwnd(struct uia_node *node) return SendMessageW(client_thread.hwnd, WM_UIA_CLIENT_GET_NODE_PROV, (WPARAM)&args, (LPARAM)node); }
-static HRESULT create_uia_node_from_hwnd(HWND hwnd, HUIANODE *out_node, int node_flags) +HRESULT create_uia_node_from_hwnd(HWND hwnd, HUIANODE *out_node, int node_flags) { struct uia_node *node; HRESULT hr; diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index 6d3d2216169..01272f89064 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_event_id_map; struct rb_tree handler_map;
LONG handler_count; @@ -917,6 +918,22 @@ 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_event_id_map_entry +{ + struct rb_entry entry; + int event_id; + + struct list handlers_list; +}; + +static int uia_com_event_handler_event_id_compare(const void *key, const struct rb_entry *entry) +{ + struct uia_event_handler_event_id_map_entry *event_entry = RB_ENTRY_VALUE(entry, struct uia_event_handler_event_id_map_entry, entry); + int event_id = *((int *)key); + + return (event_entry->event_id > event_id) - (event_entry->event_id < event_id); +} + struct uia_event_handler_identifier { IUnknown *handler_iface; SAFEARRAY *runtime_id; @@ -932,6 +949,9 @@ struct uia_event_handler_map_entry int event_id;
struct list handlers_list; + + struct uia_event_handler_event_id_map_entry *handler_event_id_map; + struct list handler_event_id_map_list_entry; };
static int uia_com_event_handler_id_compare(const void *key, const struct rb_entry *entry) @@ -1027,6 +1047,32 @@ HRESULT uia_com_win_event_callback(DWORD event_id, HWND hwnd, LONG obj_id, LONG break; }
+ case EVENT_OBJECT_FOCUS: + { + static const int uia_event_id = UIA_AutomationFocusChangedEventId; + struct rb_entry *rb_entry; + HRESULT hr; + + if (obj_id != OBJID_CLIENT) + break; + + EnterCriticalSection(&com_event_handlers_cs); + + if ((rb_entry = rb_get(&com_event_handlers.handler_event_id_map, &uia_event_id))) + { + HUIANODE node = NULL; + + hr = create_uia_node_from_hwnd(hwnd, &node, NODE_FLAG_IGNORE_CLIENTSIDE_HWND_PROVS); + if (SUCCEEDED(hr)) + FIXME("EVENT_OBJECT_FOCUS event advisement currently unimplemented\n"); + + UiaNodeRelease(node); + } + + LeaveCriticalSection(&com_event_handlers_cs); + break; + } + default: break; } @@ -1034,6 +1080,29 @@ HRESULT uia_com_win_event_callback(DWORD event_id, HWND hwnd, LONG obj_id, LONG return S_OK; }
+static HRESULT uia_event_handlers_add_handler_to_event_id_map(struct uia_event_handler_map_entry *event_map) +{ + struct uia_event_handler_event_id_map_entry *event_id_map; + struct rb_entry *rb_entry; + + if ((rb_entry = rb_get(&com_event_handlers.handler_event_id_map, &event_map->event_id))) + event_id_map = RB_ENTRY_VALUE(rb_entry, struct uia_event_handler_event_id_map_entry, entry); + else + { + if (!(event_id_map = calloc(1, sizeof(*event_id_map)))) + return E_OUTOFMEMORY; + + event_id_map->event_id = event_map->event_id; + list_init(&event_id_map->handlers_list); + rb_put(&com_event_handlers.handler_event_id_map, &event_map->event_id, &event_id_map->entry); + } + + list_add_tail(&event_id_map->handlers_list, &event_map->handler_event_id_map_list_entry); + event_map->handler_event_id_map = event_id_map; + + return S_OK; +} + static HRESULT uia_event_handlers_add_handler(IUnknown *handler_iface, SAFEARRAY *runtime_id, int event_id, struct uia_com_event *event) { @@ -1045,7 +1114,10 @@ static HRESULT uia_event_handlers_add_handler(IUnknown *handler_iface, SAFEARRAY EnterCriticalSection(&com_event_handlers_cs);
if (!com_event_handlers.handler_count) + { rb_init(&com_event_handlers.handler_map, uia_com_event_handler_id_compare); + rb_init(&com_event_handlers.handler_event_id_map, uia_com_event_handler_event_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); @@ -1065,6 +1137,14 @@ static HRESULT uia_event_handlers_add_handler(IUnknown *handler_iface, SAFEARRAY }
event_map->event_id = event_id; + hr = uia_event_handlers_add_handler_to_event_id_map(event_map); + if (FAILED(hr)) + { + SafeArrayDestroy(event_map->runtime_id); + free(event_map); + goto exit; + } + event_map->handler_iface = handler_iface; IUnknown_AddRef(event_map->handler_iface);
@@ -1102,6 +1182,13 @@ static void uia_event_handler_map_entry_destroy(struct uia_event_handler_map_ent com_event_handlers.handler_count--; }
+ list_remove(&entry->handler_event_id_map_list_entry); + if (list_empty(&entry->handler_event_id_map->handlers_list)) + { + rb_remove(&com_event_handlers.handler_event_id_map, &entry->handler_event_id_map->entry); + free(entry->handler_event_id_map); + } + rb_remove(&com_event_handlers.handler_map, &entry->entry); IUnknown_Release(entry->handler_iface); SafeArrayDestroy(entry->runtime_id); diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 0a8f2d69dbd..0c4f1ee9dda 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -219,6 +219,7 @@ HRESULT navigate_uia_node(struct uia_node *node, int nav_dir, HUIANODE *out_node HRESULT create_uia_node_from_elprov(IRawElementProviderSimple *elprov, HUIANODE *out_node, BOOL get_hwnd_providers, int node_flags) DECLSPEC_HIDDEN; HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode) DECLSPEC_HIDDEN; +HRESULT create_uia_node_from_hwnd(HWND hwnd, HUIANODE *out_node, int node_flags) DECLSPEC_HIDDEN; HRESULT uia_condition_check(HUIANODE node, struct UiaCondition *condition) DECLSPEC_HIDDEN; BOOL uia_condition_matched(HRESULT hr) DECLSPEC_HIDDEN;
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 | 53 +++++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 5519cbbc312..278958e8562 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -15436,12 +15436,12 @@ static void test_uia_com_event_handler_event_advisement(IUIAutomation *uia_iface set_uia_hwnd_expects(0, 1, 1, 2, 0); /* Only Win11 sends WM_GETOBJECT 2 times. */
NotifyWinEvent(EVENT_OBJECT_FOCUS, test_child_hwnd, OBJID_CLIENT, CHILDID_SELF); - todo_wine ok(msg_wait_for_all_events(method_event, 1, 2000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + ok(msg_wait_for_all_events(method_event, 1, 2000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n");
check_uia_hwnd_expects_at_most(0, 1, 1, 2, 0); CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 3); - test_provider_event_advise_added(&Provider2, UIA_AutomationFocusChangedEventId, TRUE); + test_provider_event_advise_added(&Provider2, UIA_AutomationFocusChangedEventId, FALSE); test_provider_event_advise_added(&Provider_hwnd3, 0, FALSE); test_provider_event_advise_added(&Provider_nc3, 0, FALSE);
diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index 01272f89064..22b55dd5234 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -974,10 +974,43 @@ struct uia_com_event { HUIAEVENT event; BOOL from_cui8;
+ struct rb_tree focus_hwnd_map; struct list event_handler_map_list_entry; struct uia_event_handler_map_entry *handler_map; };
+static void uia_com_focus_handler_advise_node(struct uia_com_event *event, HUIANODE node, HWND hwnd) +{ + HRESULT hr; + + hr = uia_event_advise_node((struct uia_event *)event->event, node); + if (FAILED(hr)) + { + WARN("uia_event_advise_node failed with hr %#lx\n", hr); + return; + } + + hr = uia_hwnd_map_add_hwnd(&event->focus_hwnd_map, hwnd); + if (FAILED(hr)) + WARN("Failed to add hwnd for focus winevent, hr %#lx\n", hr); +} + +static void uia_com_focus_win_event_handler(HUIANODE node, HWND hwnd, struct uia_event_handler_event_id_map_entry *event_id_map) +{ + struct uia_event_handler_map_entry *entry; + + LIST_FOR_EACH_ENTRY(entry, &event_id_map->handlers_list, struct uia_event_handler_map_entry, handler_event_id_map_list_entry) + { + struct uia_com_event *event; + + LIST_FOR_EACH_ENTRY(event, &entry->handlers_list, struct uia_com_event, event_handler_map_list_entry) + { + if (!uia_hwnd_map_check_hwnd(&event->focus_hwnd_map, hwnd)) + uia_com_focus_handler_advise_node(event, node, hwnd); + } + } +} + HRESULT uia_com_win_event_callback(DWORD event_id, HWND hwnd, LONG obj_id, LONG child_id, DWORD thread_id, DWORD event_time) { LONG handler_count; @@ -1060,11 +1093,13 @@ HRESULT uia_com_win_event_callback(DWORD event_id, HWND hwnd, LONG obj_id, LONG
if ((rb_entry = rb_get(&com_event_handlers.handler_event_id_map, &uia_event_id))) { + struct uia_event_handler_event_id_map_entry *event_id_map; HUIANODE node = NULL;
+ event_id_map = RB_ENTRY_VALUE(rb_entry, struct uia_event_handler_event_id_map_entry, entry); hr = create_uia_node_from_hwnd(hwnd, &node, NODE_FLAG_IGNORE_CLIENTSIDE_HWND_PROVS); if (SUCCEEDED(hr)) - FIXME("EVENT_OBJECT_FOCUS event advisement currently unimplemented\n"); + uia_com_focus_win_event_handler(node, hwnd, event_id_map);
UiaNodeRelease(node); } @@ -1155,6 +1190,20 @@ static HRESULT uia_event_handlers_add_handler(IUnknown *handler_iface, SAFEARRAY list_add_tail(&event_map->handlers_list, &event->event_handler_map_list_entry); event->handler_map = event_map; com_event_handlers.handler_count++; + if (event_id == UIA_AutomationFocusChangedEventId) + { + GUITHREADINFO info = { sizeof(info) }; + + if (GetGUIThreadInfo(0, &info) && info.hwndFocus) + { + HUIANODE node = NULL; + + hr = create_uia_node_from_hwnd(info.hwndFocus, &node, NODE_FLAG_IGNORE_CLIENTSIDE_HWND_PROVS); + if (SUCCEEDED(hr)) + uia_com_focus_handler_advise_node(event, node, info.hwndFocus); + UiaNodeRelease(node); + } + }
exit: LeaveCriticalSection(&com_event_handlers_cs); @@ -1165,6 +1214,7 @@ exit: static void uia_event_handler_destroy(struct uia_com_event *event) { list_remove(&event->event_handler_map_list_entry); + uia_hwnd_map_destroy(&event->focus_hwnd_map); if (event->event) UiaRemoveEvent(event->event); if (event->git_cookie) @@ -3459,6 +3509,7 @@ static HRESULT uia_add_com_event_handler(IUIAutomation6 *iface, EVENTID event_id
com_event->from_cui8 = element->from_cui8; list_init(&com_event->event_handler_map_list_entry); + uia_hwnd_map_init(&com_event->focus_hwnd_map);
hr = IUnknown_QueryInterface(handler_unk, handler_riid, (void **)&handler_iface); if (FAILED(hr))
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 24 ++++++++++++++++ dlls/uiautomationcore/uia_com_client.c | 32 ++++++++++++++++++++++ dlls/uiautomationcore/uia_event.c | 1 + dlls/uiautomationcore/uia_private.h | 1 + dlls/uiautomationcore/uia_utils.c | 17 ++++++++++++ 5 files changed, 75 insertions(+)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 278958e8562..7790907d674 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -15475,6 +15475,30 @@ static void test_uia_com_event_handler_event_advisement(IUIAutomation *uia_iface check_uia_hwnd_expects_at_most(0, 1, 1, 3, 0); test_hwnd_providers_event_advise_added(&Provider, &Provider_hwnd2, &Provider_nc2, 0, FALSE);
+ /* HWND destruction is tracked with EVENT_OBJECT_DESTROY/OBJID_WINDOW. */ + NotifyWinEvent(EVENT_OBJECT_DESTROY, test_child_hwnd, OBJID_WINDOW, CHILDID_SELF); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + + /* + * EVENT_OBJECT_DESTROY removed this HWND, EVENT_OBJECT_FOCUS will now + * advise it again. + */ + reset_event_advise_values_for_hwnd_providers(&Provider2, &Provider_hwnd3, &Provider_nc3); + set_provider_method_event_data(&Provider2, method_event[0], ADVISE_EVENTS_EVENT_ADDED); + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 3); /* Only sent 3 times on Win11. */ + set_uia_hwnd_expects(0, 1, 1, 2, 0); /* Only Win11 sends WM_GETOBJECT 2 times. */ + + NotifyWinEvent(EVENT_OBJECT_FOCUS, test_child_hwnd, OBJID_CLIENT, CHILDID_SELF); + ok(msg_wait_for_all_events(method_event, 1, 2000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + + set_provider_method_event_data(&Provider2, NULL, -1); + check_uia_hwnd_expects_at_most(0, 1, 1, 2, 0); + CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 3); + test_provider_event_advise_added(&Provider2, UIA_AutomationFocusChangedEventId, FALSE); + test_provider_event_advise_added(&Provider_hwnd3, 0, FALSE); + test_provider_event_advise_added(&Provider_nc3, 0, FALSE); + set_uia_hwnd_expects(0, 1, 1, 0, 0); hr = IUIAutomation_RemoveFocusChangedEventHandler(uia_iface, &FocusChangedHandler.IUIAutomationFocusChangedEventHandler_iface); diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index 22b55dd5234..91013dfc4ed 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -1108,6 +1108,38 @@ HRESULT uia_com_win_event_callback(DWORD event_id, HWND hwnd, LONG obj_id, LONG break; }
+ case EVENT_OBJECT_DESTROY: + { + static const int uia_event_id = UIA_AutomationFocusChangedEventId; + struct rb_entry *rb_entry; + + if (obj_id != OBJID_WINDOW) + break; + + EnterCriticalSection(&com_event_handlers_cs); + + if ((rb_entry = rb_get(&com_event_handlers.handler_event_id_map, &uia_event_id))) + { + struct uia_event_handler_event_id_map_entry *event_id_map; + struct uia_event_handler_map_entry *entry; + + event_id_map = RB_ENTRY_VALUE(rb_entry, struct uia_event_handler_event_id_map_entry, entry); + LIST_FOR_EACH_ENTRY(entry, &event_id_map->handlers_list, struct uia_event_handler_map_entry, + handler_event_id_map_list_entry) + { + struct uia_com_event *event; + + LIST_FOR_EACH_ENTRY(event, &entry->handlers_list, struct uia_com_event, event_handler_map_list_entry) + { + uia_hwnd_map_remove_hwnd(&event->focus_hwnd_map, hwnd); + } + } + } + + LeaveCriticalSection(&com_event_handlers_cs); + break; + } + default: break; } diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 0e6cf4e000a..2cb61a2062e 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -56,6 +56,7 @@ static int win_event_to_uia_event_id(int win_event) case EVENT_OBJECT_FOCUS: return UIA_AutomationFocusChangedEventId; case EVENT_SYSTEM_ALERT: return UIA_SystemAlertEventId; case EVENT_OBJECT_SHOW: return UIA_StructureChangedEventId; + case EVENT_OBJECT_DESTROY: return UIA_StructureChangedEventId;
default: break; diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 0c4f1ee9dda..b55379b459d 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -270,5 +270,6 @@ BOOL uia_hwnd_is_visible(HWND hwnd) DECLSPEC_HIDDEN; BOOL uia_is_top_level_hwnd(HWND hwnd) DECLSPEC_HIDDEN; BOOL uia_hwnd_map_check_hwnd(struct rb_tree *hwnd_map, HWND hwnd) DECLSPEC_HIDDEN; HRESULT uia_hwnd_map_add_hwnd(struct rb_tree *hwnd_map, HWND hwnd) DECLSPEC_HIDDEN; +void uia_hwnd_map_remove_hwnd(struct rb_tree *hwnd_map, HWND hwnd) DECLSPEC_HIDDEN; void uia_hwnd_map_init(struct rb_tree *hwnd_map) DECLSPEC_HIDDEN; void uia_hwnd_map_destroy(struct rb_tree *hwnd_map) DECLSPEC_HIDDEN; diff --git a/dlls/uiautomationcore/uia_utils.c b/dlls/uiautomationcore/uia_utils.c index 866358f97c8..f62f85d5883 100644 --- a/dlls/uiautomationcore/uia_utils.c +++ b/dlls/uiautomationcore/uia_utils.c @@ -459,6 +459,23 @@ HRESULT uia_hwnd_map_add_hwnd(struct rb_tree *hwnd_map, HWND hwnd) return S_OK; }
+void uia_hwnd_map_remove_hwnd(struct rb_tree *hwnd_map, HWND hwnd) +{ + struct rb_entry *rb_entry = rb_get(hwnd_map, hwnd); + struct uia_hwnd_map_entry *entry; + + if (!rb_entry) + { + TRACE("hwnd %p not in map %p, nothing to remove.\n", hwnd, hwnd_map); + return; + } + + TRACE("Removing hwnd %p from map %p\n", hwnd, hwnd_map); + entry = RB_ENTRY_VALUE(rb_entry, struct uia_hwnd_map_entry, entry); + rb_remove(hwnd_map, &entry->entry); + free(entry); +} + void uia_hwnd_map_init(struct rb_tree *hwnd_map) { rb_init(hwnd_map, uia_hwnd_map_hwnd_compare);
This merge request was approved by Esme Povirk.