-- v2: uiautomationcore: Use EVENT_OBJECT_SHOW to advise providers of events being listened for in the COM API. uiautomationcore: Separate checking if a node is within the scope of a registered event from event invocation. uiautomationcore: Add support for passing WinEvents to a custom callback function. uiautomationcore/tests: Add tests for COM event handler event advisement.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 382 ++++++++++++++++++++- 1 file changed, 372 insertions(+), 10 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index e386db246a3..ae2758c39bf 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -1640,6 +1640,8 @@ static struct Provider int advise_events_added_event_id; int advise_events_removed_event_id; struct Provider_win_event_handler_data win_event_handler_data; + HANDLE method_call_event_handle; + int method_call_event_method_id; } Provider, Provider2, Provider_child, Provider_child2; static struct Provider Provider_hwnd, Provider_nc, Provider_proxy, Provider_proxy2, Provider_override; static void initialize_provider(struct Provider *prov, int prov_opts, HWND hwnd, BOOL initialize_nav_links); @@ -1910,6 +1912,12 @@ static void ok_method_sequence_(const struct prov_method_sequence *expected_list flush_method_sequence(); }
+static void check_for_method_call_event(struct Provider *prov, int method) +{ + if (prov->method_call_event_handle && (prov->method_call_event_method_id == method)) + SetEvent(prov->method_call_event_handle); +} + /* * Parsing the string returned by UIA_ProviderDescriptionPropertyId is * the only way to know what an HUIANODE represents internally. It @@ -2142,6 +2150,7 @@ HRESULT WINAPI ProviderSimple_get_ProviderOptions(IRawElementProviderSimple *ifa if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); This->last_call_tid = GetCurrentThreadId(); + check_for_method_call_event(This, PROV_GET_PROVIDER_OPTIONS); PROV_METHOD_TRACE(This, get_ProviderOptions);
*ret_val = 0; @@ -2185,6 +2194,7 @@ HRESULT WINAPI ProviderSimple_GetPatternProvider(IRawElementProviderSimple *ifac if (*ret_val) IUnknown_AddRef(*ret_val);
+ check_for_method_call_event(This, PROV_GET_PATTERN_PROV); return S_OK; }
@@ -2197,6 +2207,7 @@ HRESULT WINAPI ProviderSimple_GetPropertyValue(IRawElementProviderSimple *iface, if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); This->last_call_tid = GetCurrentThreadId(); + check_for_method_call_event(This, PROV_GET_PROPERTY_VALUE); PROV_METHOD_TRACE2(This, GetPropertyValue, prop_id, uia_prop_id_strs);
if (This->prop_override && This->prop_override_count) @@ -2405,6 +2416,7 @@ HRESULT WINAPI ProviderSimple_get_HostRawElementProvider(IRawElementProviderSimp if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); This->last_call_tid = GetCurrentThreadId(); + check_for_method_call_event(This, PROV_GET_HOST_RAW_ELEMENT_PROVIDER); PROV_METHOD_TRACE(This, get_HostRawElementProvider);
*ret_val = NULL; @@ -2490,6 +2502,7 @@ static HRESULT WINAPI ProviderFragment_Navigate(IRawElementProviderFragment *ifa if (*ret_val) IRawElementProviderFragment_AddRef(*ret_val);
+ check_for_method_call_event(This, FRAG_NAVIGATE); return S_OK; }
@@ -2502,6 +2515,7 @@ static HRESULT WINAPI ProviderFragment_GetRuntimeId(IRawElementProviderFragment if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); This->last_call_tid = GetCurrentThreadId(); + check_for_method_call_event(This, FRAG_GET_RUNTIME_ID); PROV_METHOD_TRACE(This, GetRuntimeId);
*ret_val = NULL; @@ -2531,6 +2545,7 @@ static HRESULT WINAPI ProviderFragment_get_BoundingRectangle(IRawElementProvider if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); This->last_call_tid = GetCurrentThreadId(); + check_for_method_call_event(This, FRAG_GET_BOUNDING_RECT); PROV_METHOD_TRACE(This, get_BoundingRectangle);
*ret_val = This->bounds_rect; @@ -2546,6 +2561,7 @@ static HRESULT WINAPI ProviderFragment_GetEmbeddedFragmentRoots(IRawElementProvi if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); This->last_call_tid = GetCurrentThreadId(); + check_for_method_call_event(This, FRAG_GET_EMBEDDED_FRAGMENT_ROOTS); PROV_METHOD_TRACE(This, GetEmbeddedFragmentRoots);
*ret_val = NULL; @@ -2590,6 +2606,7 @@ static HRESULT WINAPI ProviderFragment_get_FragmentRoot(IRawElementProviderFragm IRawElementProviderFragmentRoot_AddRef(This->frag_root); }
+ check_for_method_call_event(This, FRAG_GET_FRAGMENT_ROOT); return S_OK; }
@@ -2654,6 +2671,7 @@ static HRESULT WINAPI ProviderFragmentRoot_GetFocus(IRawElementProviderFragmentR IRawElementProviderFragment_AddRef(*ret_val); }
+ check_for_method_call_event(Provider, FRAG_ROOT_GET_FOCUS); return S_OK; }
@@ -2695,6 +2713,7 @@ static HRESULT WINAPI ProviderHwndOverride_GetOverrideProviderForHwnd(IRawElemen struct Provider *This = impl_from_ProviderHwndOverride(iface);
add_method_call(This, HWND_OVERRIDE_GET_OVERRIDE_PROVIDER); + check_for_method_call_event(This, HWND_OVERRIDE_GET_OVERRIDE_PROVIDER); PROV_METHOD_TRACE(This, GetOverrideProviderForHwnd);
*ret_val = NULL; @@ -2750,6 +2769,7 @@ static HRESULT WINAPI ProviderAdviseEvents_AdviseEventAdded(IRawElementProviderA This->advise_events_added_event_id = event_id; PROV_METHOD_TRACE2(This, AdviseEventAdded, event_id, uia_event_id_strs);
+ check_for_method_call_event(This, ADVISE_EVENTS_EVENT_ADDED); return S_OK; }
@@ -2765,6 +2785,7 @@ static HRESULT WINAPI ProviderAdviseEvents_AdviseEventRemoved(IRawElementProvide This->advise_events_removed_event_id = event_id; PROV_METHOD_TRACE2(This, AdviseEventRemoved, event_id, uia_event_id_strs);
+ check_for_method_call_event(This, ADVISE_EVENTS_EVENT_REMOVED); return S_OK; }
@@ -2829,6 +2850,7 @@ static HRESULT WINAPI ProviderWinEventHandler_RespondToWinEvent(IProxyProviderWi ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); }
+ check_for_method_call_event(This, WINEVENT_HANDLER_RESPOND_TO_WINEVENT); return S_OK; }
@@ -8701,6 +8723,21 @@ static const struct prov_method_sequence reg_prov_cb13[] = { { 0 } };
+static SAFEARRAY *get_safearray_for_elprov(IRawElementProviderSimple *elprov) +{ + SAFEARRAY *sa = NULL; + LONG idx = 0; + + if (elprov) + { + sa = SafeArrayCreateVector(VT_UNKNOWN, 0, 1); + if (sa) + SafeArrayPutElement(sa, &idx, (void *)elprov); + } + + return sa; +} + static IRawElementProviderSimple *base_hwnd_prov, *proxy_prov, *parent_proxy_prov, *nc_prov; static SAFEARRAY WINAPI *test_uia_provider_callback(HWND hwnd, enum ProviderType prov_type) { @@ -8745,19 +8782,54 @@ static SAFEARRAY WINAPI *test_uia_provider_callback(HWND hwnd, enum ProviderType break; }
- if (elprov) + return get_safearray_for_elprov(elprov); +} + +/* + * 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 + * Windows 10 unpredictably attempting to create elements in the background. + */ +static SAFEARRAY WINAPI *uia_com_win_event_clientside_provider_callback(HWND hwnd, enum ProviderType prov_type) +{ + IRawElementProviderSimple *elprov = NULL; + + switch (prov_type) { - SAFEARRAY *sa; - LONG idx = 0; + case ProviderType_BaseHwnd: + if (hwnd == Provider_hwnd3.hwnd) + elprov = &Provider_hwnd3.IRawElementProviderSimple_iface; + else if (hwnd == Provider_hwnd2.hwnd) + elprov = &Provider_hwnd2.IRawElementProviderSimple_iface; + else if (hwnd == Provider_hwnd.hwnd) + elprov = &Provider_hwnd.IRawElementProviderSimple_iface;
- sa = SafeArrayCreateVector(VT_UNKNOWN, 0, 1); - if (sa) - SafeArrayPutElement(sa, &idx, (void *)elprov); + if (elprov) + CHECK_EXPECT(prov_callback_base_hwnd); + break; + + case ProviderType_Proxy: + if (Provider_proxy.hwnd == hwnd) + elprov = proxy_prov; + break; + + case ProviderType_NonClientArea: + if (hwnd == Provider_nc3.hwnd) + elprov = &Provider_nc3.IRawElementProviderSimple_iface; + else if (hwnd == Provider_nc2.hwnd) + elprov = &Provider_nc2.IRawElementProviderSimple_iface; + else if (hwnd == Provider_nc.hwnd) + elprov = &Provider_nc.IRawElementProviderSimple_iface;
- return sa; + if (elprov) + CHECK_EXPECT(prov_callback_nonclient); + break; + + default: + break; }
- return NULL; + return get_safearray_for_elprov(elprov); }
static void test_UiaRegisterProviderCallback(void) @@ -10945,6 +11017,12 @@ static void set_provider_runtime_id(struct Provider *prov, int val, int val2) prov->runtime_id[1] = val2; }
+static void set_provider_method_event_data(struct Provider *prov, HANDLE event_handle, int method_id) +{ + prov->method_call_event_handle = event_handle; + prov->method_call_event_method_id = method_id; +} + static void initialize_provider_advise_events_ids(struct Provider *prov) { prov->advise_events_added_event_id = prov->advise_events_removed_event_id = 0; @@ -10970,6 +11048,8 @@ static void initialize_provider(struct Provider *prov, int prov_opts, HWND hwnd, prov->embedded_frag_roots_count = 0; prov->advise_events_added_event_id = prov->advise_events_removed_event_id = 0; memset(&prov->win_event_handler_data, 0, sizeof(prov->win_event_handler_data)); + prov->method_call_event_handle = NULL; + prov->method_call_event_method_id = -1; if (initialize_nav_links) { prov->frag_root = NULL; @@ -14777,13 +14857,258 @@ static void test_IUIAutomationFocusChangedEventHandler(IUIAutomation *uia_iface) IUIAutomationElement_Release(elem); }
+struct com_win_event_test_thread_data +{ + IUIAutomation *uia_iface; + HWND test_hwnd; + HWND test_child_hwnd; +}; + +static void test_uia_com_event_handler_event_advisement(IUIAutomation *uia_iface, HWND test_hwnd, HWND test_child_hwnd) +{ + IUIAutomationElement *elem; + HANDLE method_event[4]; + int event_handle_count; + DWORD wait_res; + BOOL is_win11; + HRESULT hr; + int i; + + for (i = 0; i < ARRAY_SIZE(method_event); i++) + method_event[i] = CreateEventW(NULL, FALSE, FALSE, NULL); + + /* Only sends WM_GETOBJECT twice on Win11. */ + set_uia_hwnd_expects(0, 1, 1, 2, 0); + hr = IUIAutomation_ElementFromHandle(uia_iface, test_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_at_least(0, FALSE, 1, FALSE, 1, FALSE, 1, FALSE, 0, FALSE); + + /* + * The COM API has no equivalent to UiaEventAddWindow, which means all + * event advisement has to be done by the COM API itself. It does this by + * using EVENT_OBJECT_SHOW as a way to find HWNDs that need to be advised. + */ + set_uia_hwnd_expects(0, 1, 1, 4, 0); /* Only done on Win11. */ + hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, TreeScope_Subtree, NULL, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + is_win11 = !!CALLED_COUNT(prov_callback_base_hwnd); + check_uia_hwnd_expects_at_most(0, 1, 1, 4, 0); + + set_provider_method_event_data(&Provider_hwnd2, method_event[0], ADVISE_EVENTS_EVENT_ADDED); + set_provider_method_event_data(&Provider_nc2, method_event[1], ADVISE_EVENTS_EVENT_ADDED); + set_provider_method_event_data(&Provider, method_event[2], ADVISE_EVENTS_EVENT_ADDED); + event_handle_count = 3; + + /* + * Raise EVENT_OBJECT_SHOW on a non-visible HWND. Its providers will not + * be advised of events being listened for. + */ + ok(!IsWindowVisible(test_hwnd), "Test HWND is visible\n"); + NotifyWinEvent(EVENT_OBJECT_SHOW, test_hwnd, OBJID_WINDOW, CHILDID_SELF); + ok(msg_wait_for_all_events(method_event, event_handle_count, 500) == WAIT_TIMEOUT, "Wait for method_event(s) didn't timeout.\n"); + + /* + * This fires off EVENT_OBJECT_SHOW, our providers will be advised of + * events. + */ + initialize_provider_advise_events_ids(&Provider); + initialize_provider_advise_events_ids(&Provider_nc2); + initialize_provider_advise_events_ids(&Provider_hwnd2); + + set_uia_hwnd_expects(0, 2, 2, 6, 0); /* Only done more than one of each on Win11. */ + ShowWindow(test_hwnd, SW_SHOW); + wait_res = msg_wait_for_all_events(method_event, event_handle_count, 3000); + if ((wait_res == WAIT_TIMEOUT) && (Provider_nc2.advise_events_added_event_id && Provider_hwnd2.advise_events_added_event_id) && + !Provider.advise_events_added_event_id) + { + /* + * Windows 7 won't advise a nested node provider from the current + * process of events being listened for. + */ + win_skip("Windows 7 only advises clientside providers of events, skipping further tests.\n"); + hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref == 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + IUIAutomationElement_Release(elem); + + set_provider_method_event_data(&Provider_hwnd2, NULL, -1); + set_provider_method_event_data(&Provider_nc2, NULL, -1); + set_provider_method_event_data(&Provider, NULL, -1); + goto exit; + } + todo_wine ok(wait_res != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); + + /* + * Manually fire off EVENT_OBJECT_SHOW, providers will be advised of + * events being added again. + */ + set_uia_hwnd_expects(0, 2, 2, 6, 0); /* Only done more than one of each on Win11. */ + NotifyWinEvent(EVENT_OBJECT_SHOW, test_hwnd, OBJID_WINDOW, CHILDID_SELF); + todo_wine ok(msg_wait_for_all_events(method_event, event_handle_count, 3000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); + + /* + * Providers are only advised of events being listened for if an event is + * raised with an objid of OBJID_WINDOW. + */ + NotifyWinEvent(EVENT_OBJECT_SHOW, test_hwnd, OBJID_CLIENT, CHILDID_SELF); + ok(msg_wait_for_all_events(method_event, event_handle_count, 500) == WAIT_TIMEOUT, "Wait for method_event(s) didn't timeout.\n"); + + set_provider_method_event_data(&Provider_hwnd2, NULL, -1); + set_provider_method_event_data(&Provider_nc2, NULL, -1); + set_provider_method_event_data(&Provider, NULL, -1); + + /* + * Show our child HWND. Navigation is done to confirm it is within the + * scope of our event handler, and it is only advised if it is. + */ + set_provider_method_event_data(&Provider_hwnd3, method_event[0], ADVISE_EVENTS_EVENT_ADDED); + set_provider_method_event_data(&Provider_nc3, method_event[1], ADVISE_EVENTS_EVENT_ADDED); + set_provider_method_event_data(&Provider2, method_event[2], ADVISE_EVENTS_EVENT_ADDED); + + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 6); /* Only done more than once on Win11. */ + set_uia_hwnd_expects(0, 2, 3, 5, 0); /* Only done more than one of each on Win11. */ + ShowWindow(test_child_hwnd, SW_SHOW); + todo_wine ok(msg_wait_for_all_events(method_event, event_handle_count, 3000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); + todo_wine CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot); + + /* Same deal as before, it will advise multiple times. */ + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 6); /* Only done more than once on Win11. */ + set_uia_hwnd_expects(0, 2, 3, 5, 0); /* Only done more than one of each on Win11. */ + NotifyWinEvent(EVENT_OBJECT_SHOW, test_child_hwnd, OBJID_WINDOW, CHILDID_SELF); + todo_wine ok(msg_wait_for_all_events(method_event, event_handle_count, 3000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); + todo_wine CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot); + + /* Break navigation chain, can't reach our test element so no advisement. */ + Provider_hwnd3.parent = NULL; + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 2); /* Only done more than once 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, 2000) == WAIT_TIMEOUT, "Wait for method_event(s) didn't timeout.\n"); + check_uia_hwnd_expects(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); + todo_wine CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot); + + set_provider_method_event_data(&Provider_hwnd3, NULL, -1); + set_provider_method_event_data(&Provider_nc3, NULL, -1); + set_provider_method_event_data(&Provider2, NULL, -1); + + hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IUIAutomationElement_Release(elem); + + /* + * Register event handler on desktop element with a scope of + * TreeScope_Subtree. All EVENT_OBJECT_SHOW events will result in event + * advisement regardless of navigation. + */ + set_uia_hwnd_expects(0, 1, 1, 0, 0); + hr = IUIAutomation_GetRootElement(uia_iface, &elem); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_uia_hwnd_expects(0, FALSE, 1, FALSE, 1, FALSE, 0, FALSE, 0, FALSE); + + SET_EXPECT(child_winproc_GETOBJECT_UiaRoot); /* Only done on Win11. */ + set_uia_hwnd_expects(0, 2, 2, 3, 0); /* Only done on Win11. */ + hr = IUIAutomation_AddAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, TreeScope_Subtree, NULL, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(AutomationEventHandler.ref > 1, "Unexpected refcnt %ld\n", AutomationEventHandler.ref); + CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 1); + check_uia_hwnd_expects_at_most(0, 2, 2, 3, 0); + + set_provider_method_event_data(&Provider_hwnd, method_event[0], ADVISE_EVENTS_EVENT_ADDED); + set_provider_method_event_data(&Provider_nc, method_event[1], ADVISE_EVENTS_EVENT_ADDED); + set_provider_method_event_data(&Provider_proxy, method_event[2], ADVISE_EVENTS_EVENT_ADDED); + + /* + * Windows 11 always advises all HWNDs on the desktop, so we wait for our + * child window provider to be advised as well. + */ + if (is_win11) + { + set_provider_method_event_data(&Provider2, method_event[3], ADVISE_EVENTS_EVENT_ADDED); + event_handle_count++; + } + + SET_EXPECT(child_winproc_GETOBJECT_UiaRoot); /* Only done on Win11. */ + set_uia_hwnd_expects(0, 3, 3, 1, 0); /* Only done more than once on Win11. */ + NotifyWinEvent(EVENT_OBJECT_SHOW, GetDesktopWindow(), OBJID_WINDOW, CHILDID_SELF); + todo_wine ok(msg_wait_for_all_events(method_event, event_handle_count, 2000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + CHECK_CALLED_AT_MOST(winproc_GETOBJECT_UiaRoot, 1); + CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 1); + check_uia_hwnd_expects_at_least(0, FALSE, 1, TRUE, 1, TRUE, 0, FALSE, 0, FALSE); + + set_provider_method_event_data(&Provider_hwnd, NULL, -1); + set_provider_method_event_data(&Provider_nc, NULL, -1); + set_provider_method_event_data(&Provider_proxy, NULL, -1); + + /* + * Test window isn't connected to desktop element through navigation, but + * still gets advised of events on a desktop HWND event. + */ + set_provider_method_event_data(&Provider_hwnd2, method_event[0], ADVISE_EVENTS_EVENT_ADDED); + set_provider_method_event_data(&Provider_nc2, method_event[1], ADVISE_EVENTS_EVENT_ADDED); + set_provider_method_event_data(&Provider, method_event[2], ADVISE_EVENTS_EVENT_ADDED); + SET_EXPECT(child_winproc_GETOBJECT_UiaRoot); /* Only done on Win11. */ + set_uia_hwnd_expects(0, 2, 2, 7, 0); /* Only done more than once on Win11. */ + NotifyWinEvent(EVENT_OBJECT_SHOW, test_hwnd, OBJID_WINDOW, CHILDID_SELF); + todo_wine ok(msg_wait_for_all_events(method_event, event_handle_count, 2000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 1); + check_uia_hwnd_expects_at_most(0, 2, 2, 7, 0); + + hr = IUIAutomation_RemoveAutomationEventHandler(uia_iface, UIA_LiveRegionChangedEventId, elem, + &AutomationEventHandler.IUIAutomationEventHandler_iface); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IUIAutomationElement_Release(elem); + + set_provider_method_event_data(&Provider_hwnd2, NULL, -1); + set_provider_method_event_data(&Provider_nc2, NULL, -1); + set_provider_method_event_data(&Provider, NULL, -1); + if (is_win11) + set_provider_method_event_data(&Provider2, NULL, -1); + +exit: + for (i = 0; i < ARRAY_SIZE(method_event); i++) + CloseHandle(method_event[i]); +} + +static DWORD WINAPI uia_com_event_handler_win_event_test_thread(LPVOID param) +{ + struct com_win_event_test_thread_data *test_data = (struct com_win_event_test_thread_data *)param; + IUIAutomation *uia_iface = test_data->uia_iface; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + UiaRegisterProviderCallback(uia_com_win_event_clientside_provider_callback); + + test_uia_com_event_handler_event_advisement(uia_iface, test_data->test_hwnd, test_data->test_child_hwnd); + + UiaRegisterProviderCallback(NULL); + CoUninitialize(); + + return 0; +} + static void test_CUIAutomation_event_handlers(IUIAutomation *uia_iface) { + struct com_win_event_test_thread_data test_data = { uia_iface, NULL }; IUIAutomationElement *elem; + HANDLE thread; HRESULT hr; HWND hwnd;
- ComEventData.event_hwnd = hwnd = create_test_hwnd("test_CUIAutomation_event_handlers class"); + test_data.test_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()); @@ -14810,11 +15135,48 @@ static void test_CUIAutomation_event_handlers(IUIAutomation *uia_iface)
test_IUIAutomationEventHandler(uia_iface, elem); test_IUIAutomationFocusChangedEventHandler(uia_iface); - IUIAutomationElement_Release(elem); + + /* Create a test child window. */ + test_data.test_child_hwnd = create_child_test_hwnd("test_CUIAutomation_event_handlers child class", hwnd); + set_clientside_providers_for_hwnd(NULL, &Provider_nc3, &Provider_hwnd3, test_data.test_child_hwnd); + initialize_provider(&Provider2, ProviderOptions_ServerSideProvider, test_data.test_child_hwnd, TRUE); + Provider2.frag_root = &Provider2.IRawElementProviderFragmentRoot_iface; + Provider2.ignore_hwnd_prop = TRUE; + Provider_hwnd3.parent = &Provider_hwnd2.IRawElementProviderFragment_iface; + child_win_prov_root = &Provider2.IRawElementProviderSimple_iface; + + hr = IUIAutomation_RemoveAllEventHandlers(uia_iface); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* + * Particular versions of Windows 7 trigger access violations when doing + * WinEvent tests, just skip them for Windows 7. + */ + if (!UiaLookupId(AutomationIdentifierType_Property, &OptimizeForVisualContent_Property_GUID)) + { + win_skip("Skipping COM API WinEvent translation tests for Win7\n"); + goto exit; + } + + thread = CreateThread(NULL, 0, uia_com_event_handler_win_event_test_thread, (void *)&test_data, 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); + +exit: UiaRegisterProviderCallback(NULL); DestroyWindow(hwnd); UnregisterClassA("test_CUIAutomation_event_handlers class", NULL); + UnregisterClassA("test_CUIAutomation_event_handlers child class", NULL); method_sequences_enabled = TRUE; }
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_com_client.c | 19 ++++++++++++++++ dlls/uiautomationcore/uia_event.c | 30 ++++++++++++++++++++++++++ dlls/uiautomationcore/uia_private.h | 2 ++ 3 files changed, 51 insertions(+)
diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index 19540b19dc0..0980b2d0b37 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -958,6 +958,13 @@ struct uia_com_event { struct uia_event_handler_map_entry *handler_map; };
+static HRESULT uia_com_win_event_callback(DWORD event_id, HWND hwnd, LONG obj_id, LONG child_id, DWORD thread_id, + DWORD event_time) +{ + FIXME("%ld, %p, %ld, %ld, %ld, %ld: stub\n", event_id, hwnd, obj_id, child_id, thread_id, event_time); + return S_OK; +} + static HRESULT uia_event_handlers_add_handler(IUnknown *handler_iface, SAFEARRAY *runtime_id, int event_id, struct uia_com_event *event) { @@ -998,6 +1005,14 @@ 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; + + /* + * Add our WinEvent callback function to the event thread upon adding the + * first COM event handler. + */ + if (!com_event_handlers.handler_count) + uia_event_thread_set_win_event_callback(uia_com_win_event_callback); + com_event_handlers.handler_count++;
exit: @@ -1042,6 +1057,10 @@ static void uia_event_handlers_remove_handlers(IUnknown *handler_iface, SAFEARRA 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));
+ /* No more COM event handlers registered, remove our WinEvent callback. */ + if (!com_event_handlers.handler_count) + uia_event_thread_set_win_event_callback(NULL); + LeaveCriticalSection(&com_event_handlers_cs); }
diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 395b20e0537..cf1a38a0f02 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -488,6 +488,7 @@ struct uia_event_thread HWND hwnd; LONG ref;
+ UiaWineWinEventCallback *win_event_callback; struct list *event_queue; HWINEVENTHOOK hook; }; @@ -843,6 +844,19 @@ static void uia_event_thread_process_queue(struct list *event_queue) case QUEUE_EVENT_TYPE_WIN_EVENT: { struct uia_queue_win_event *win_event = (struct uia_queue_win_event *)event; + UiaWineWinEventCallback *win_event_callback; + + EnterCriticalSection(&event_thread_cs); + win_event_callback = event_thread.win_event_callback; + LeaveCriticalSection(&event_thread_cs); + + if (win_event_callback) + { + hr = win_event_callback(win_event->event_id, win_event->hwnd, win_event->obj_id, win_event->child_id, + win_event->thread_id, win_event->event_time); + if (FAILED(hr)) + WARN("win_event_callback failed with hr %#lx\n", hr); + }
hr = uia_event_for_each(win_event_to_uia_event_id(win_event->event_id), uia_win_event_for_each_callback, (void *)win_event, TRUE); @@ -998,6 +1012,22 @@ static BOOL uia_clientside_event_start_event_thread(struct uia_event *event) return event->u.clientside.event_thread_started; }
+void uia_event_thread_set_win_event_callback(UiaWineWinEventCallback *cback) +{ + if (cback && !uia_start_event_thread()) + { + WARN("Failed to start UIA event thread\n"); + return; + } + + EnterCriticalSection(&event_thread_cs); + event_thread.win_event_callback = cback; + LeaveCriticalSection(&event_thread_cs); + + if (!cback) + uia_stop_event_thread(); +} + /* * IWineUiaEvent interface. */ diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 8a750bf3ae1..4e6aec9cffa 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -158,6 +158,7 @@ struct uia_event };
typedef HRESULT UiaWineEventCallback(struct uia_event *, struct uia_event_args *, SAFEARRAY *, BSTR); +typedef HRESULT UiaWineWinEventCallback(DWORD, HWND, LONG, LONG, DWORD, DWORD);
static inline void variant_init_bool(VARIANT *v, BOOL val) { @@ -227,6 +228,7 @@ HRESULT create_uia_iface(IUnknown **iface, BOOL is_cui8) DECLSPEC_HIDDEN;
/* uia_event.c */ HRESULT uia_event_add_win_event_hwnd(struct uia_event *event, HWND hwnd) DECLSPEC_HIDDEN; +void uia_event_thread_set_win_event_callback(UiaWineWinEventCallback *cback) DECLSPEC_HIDDEN; HRESULT create_serverside_uia_event(struct uia_event **out_event, LONG process_id, LONG event_cookie) DECLSPEC_HIDDEN; HRESULT uia_event_add_provider_event_adviser(IRawElementProviderAdviseEvents *advise_events, struct uia_event *event) DECLSPEC_HIDDEN;
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_event.c | 62 +++++++++++++++++++------------ 1 file changed, 38 insertions(+), 24 deletions(-)
diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index cf1a38a0f02..0e6d13c6f52 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -1760,8 +1760,8 @@ HRESULT WINAPI UiaRemoveEvent(HUIAEVENT huiaevent) return S_OK; }
-static HRESULT uia_event_check_match(HUIANODE node, HUIANODE nav_start_node, SAFEARRAY *rt_id, - struct uia_event_args *args, struct uia_event *event); +static HRESULT uia_event_check_node_within_event_scope(struct uia_event *event, HUIANODE node, SAFEARRAY *rt_id, + HUIANODE *clientside_nav_node_out); static HRESULT uia_event_invoke(HUIANODE node, HUIANODE nav_start_node, struct uia_event_args *args, struct uia_event *event) { HRESULT hr = S_OK; @@ -1771,8 +1771,8 @@ static HRESULT uia_event_invoke(HUIANODE node, HUIANODE nav_start_node, struct u SAFEARRAY *out_req; BSTR tree_struct;
- if (nav_start_node) - return uia_event_check_match(node, nav_start_node, NULL, args, event); + if (nav_start_node && (hr = uia_event_check_node_within_event_scope(event, nav_start_node, NULL, NULL)) != S_OK) + return hr;
hr = UiaGetUpdatedCache(node, &event->u.clientside.cache_req, NormalizeState_View, NULL, &out_req, &tree_struct); @@ -1833,35 +1833,38 @@ static void set_refuse_hwnd_providers(struct uia_node *node, BOOL refuse_hwnd_pr }
/* - * Check if the provider that raised the event matches this particular event. + * Check if a node is within the scope of a registered event. + * If it is, return S_OK. + * If it isn't, return S_FALSE. + * Upon failure, return a failure HR. */ -static HRESULT uia_event_check_match(HUIANODE node, HUIANODE nav_start_node, SAFEARRAY *rt_id, - struct uia_event_args *args, struct uia_event *event) +static HRESULT uia_event_check_node_within_event_scope(struct uia_event *event, HUIANODE node, SAFEARRAY *rt_id, + HUIANODE *clientside_nav_node_out) { struct UiaPropertyCondition prop_cond = { ConditionType_Property, UIA_RuntimeIdPropertyId }; - struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)nav_start_node); - HRESULT hr = S_OK; + struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); + BOOL in_scope = FALSE; + HRESULT hr = S_FALSE; + + if (clientside_nav_node_out) + *clientside_nav_node_out = NULL;
/* Event is no longer valid. */ if (InterlockedCompareExchange(&event->event_defunct, 0, 0) != 0) - return S_OK; + return S_FALSE;
/* Can't match an event that doesn't have a runtime ID, early out. */ if (!event->runtime_id) - return S_OK; + return S_FALSE;
if (event->desktop_subtree_event) - return uia_event_invoke(node, NULL, args, event); + return S_OK;
if (rt_id && !uia_compare_safearrays(rt_id, event->runtime_id, UIAutomationType_IntArray)) - { - if (event->scope & TreeScope_Element) - hr = uia_event_invoke(node, NULL, args, event); - return hr; - } + return (event->scope & TreeScope_Element) ? S_OK : S_FALSE;
if (!(event->scope & (TreeScope_Descendants | TreeScope_Children))) - return S_OK; + return S_FALSE;
V_VT(&prop_cond.Value) = VT_I4 | VT_ARRAY; V_ARRAY(&prop_cond.Value) = event->runtime_id; @@ -1881,16 +1884,19 @@ static HRESULT uia_event_check_match(HUIANODE node, HUIANODE nav_start_node, SAF { if (node_data->hwnd) { - hr = uia_event_invoke(node, (HUIANODE)&node_data->IWineUiaNode_iface, args, event); + *clientside_nav_node_out = (HUIANODE)&node_data->IWineUiaNode_iface; + IWineUiaNode_AddRef(&node_data->IWineUiaNode_iface); + in_scope = TRUE; break; } set_refuse_hwnd_providers(node_data, TRUE); }
hr = navigate_uia_node(node_data, NavigateDirection_Parent, &node2); - IWineUiaNode_Release(&node_data->IWineUiaNode_iface); if (FAILED(hr) || !node2) - return hr; + break; + + IWineUiaNode_Release(&node_data->IWineUiaNode_iface);
node_data = impl_from_IWineUiaNode((IWineUiaNode *)node2); hr = uia_condition_check(node2, (struct UiaCondition *)&prop_cond); @@ -1899,7 +1905,7 @@ static HRESULT uia_event_check_match(HUIANODE node, HUIANODE nav_start_node, SAF
if (uia_condition_matched(hr)) { - hr = uia_event_invoke(node, NULL, args, event); + in_scope = TRUE; break; }
@@ -1908,12 +1914,16 @@ static HRESULT uia_event_check_match(HUIANODE node, HUIANODE nav_start_node, SAF } IWineUiaNode_Release(&node_data->IWineUiaNode_iface);
- return hr; + if (FAILED(hr)) + return hr; + + return in_scope ? S_OK : S_FALSE; }
static HRESULT uia_raise_elprov_event_callback(struct uia_event *event, void *data) { struct uia_elprov_event_data *event_data = (struct uia_elprov_event_data *)data; + HUIANODE nav_node = NULL; HRESULT hr;
if (!event_data->node) @@ -1931,7 +1941,11 @@ static HRESULT uia_raise_elprov_event_callback(struct uia_event *event, void *da return hr; }
- return uia_event_check_match(event_data->node, event_data->node, event_data->rt_id, event_data->args, event); + hr = uia_event_check_node_within_event_scope(event, event_data->node, event_data->rt_id, &nav_node); + if (hr == S_OK) + return uia_event_invoke(event_data->node, nav_node, event_data->args, event); + + return S_OK; }
static HRESULT uia_raise_elprov_event(IRawElementProviderSimple *elprov, struct uia_event_args *args)
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 30 +++++------ dlls/uiautomationcore/uia_com_client.c | 63 +++++++++++++++++++++- dlls/uiautomationcore/uia_event.c | 31 ++++++----- dlls/uiautomationcore/uia_private.h | 3 ++ 4 files changed, 98 insertions(+), 29 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index ae2758c39bf..6cdc19b739d 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -14943,8 +14943,8 @@ static void test_uia_com_event_handler_event_advisement(IUIAutomation *uia_iface set_provider_method_event_data(&Provider, NULL, -1); goto exit; } - todo_wine ok(wait_res != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); - check_uia_hwnd_expects_at_least(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); + ok(wait_res != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 1, FALSE, 1, FALSE, 1, FALSE, 0, FALSE);
/* * Manually fire off EVENT_OBJECT_SHOW, providers will be advised of @@ -14952,8 +14952,8 @@ static void test_uia_com_event_handler_event_advisement(IUIAutomation *uia_iface */ set_uia_hwnd_expects(0, 2, 2, 6, 0); /* Only done more than one of each on Win11. */ NotifyWinEvent(EVENT_OBJECT_SHOW, test_hwnd, OBJID_WINDOW, CHILDID_SELF); - todo_wine ok(msg_wait_for_all_events(method_event, event_handle_count, 3000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); - check_uia_hwnd_expects_at_least(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); + ok(msg_wait_for_all_events(method_event, event_handle_count, 3000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 1, FALSE, 1, FALSE, 1, FALSE, 0, FALSE);
/* * Providers are only advised of events being listened for if an event is @@ -14977,17 +14977,17 @@ static void test_uia_com_event_handler_event_advisement(IUIAutomation *uia_iface SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 6); /* Only done more than once on Win11. */ set_uia_hwnd_expects(0, 2, 3, 5, 0); /* Only done more than one of each on Win11. */ ShowWindow(test_child_hwnd, SW_SHOW); - todo_wine ok(msg_wait_for_all_events(method_event, event_handle_count, 3000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); - check_uia_hwnd_expects_at_least(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); - todo_wine CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot); + ok(msg_wait_for_all_events(method_event, event_handle_count, 3000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 1, FALSE, 1, FALSE, 1, FALSE, 0, FALSE); + CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot);
/* Same deal as before, it will advise multiple times. */ SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 6); /* Only done more than once on Win11. */ set_uia_hwnd_expects(0, 2, 3, 5, 0); /* Only done more than one of each on Win11. */ NotifyWinEvent(EVENT_OBJECT_SHOW, test_child_hwnd, OBJID_WINDOW, CHILDID_SELF); - todo_wine ok(msg_wait_for_all_events(method_event, event_handle_count, 3000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); - check_uia_hwnd_expects_at_least(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); - todo_wine CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot); + ok(msg_wait_for_all_events(method_event, event_handle_count, 3000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 1, FALSE, 1, FALSE, 1, FALSE, 0, FALSE); + CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot);
/* Break navigation chain, can't reach our test element so no advisement. */ Provider_hwnd3.parent = NULL; @@ -14995,8 +14995,8 @@ static void test_uia_com_event_handler_event_advisement(IUIAutomation *uia_iface 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, 2000) == WAIT_TIMEOUT, "Wait for method_event(s) didn't timeout.\n"); - check_uia_hwnd_expects(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); - todo_wine CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot); + check_uia_hwnd_expects(0, FALSE, 1, FALSE, 1, FALSE, 1, TRUE, 0, FALSE); + CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot);
set_provider_method_event_data(&Provider_hwnd3, NULL, -1); set_provider_method_event_data(&Provider_nc3, NULL, -1); @@ -15044,10 +15044,10 @@ static void test_uia_com_event_handler_event_advisement(IUIAutomation *uia_iface SET_EXPECT(child_winproc_GETOBJECT_UiaRoot); /* Only done on Win11. */ set_uia_hwnd_expects(0, 3, 3, 1, 0); /* Only done more than once on Win11. */ NotifyWinEvent(EVENT_OBJECT_SHOW, GetDesktopWindow(), OBJID_WINDOW, CHILDID_SELF); - todo_wine ok(msg_wait_for_all_events(method_event, event_handle_count, 2000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + ok(msg_wait_for_all_events(method_event, event_handle_count, 2000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); CHECK_CALLED_AT_MOST(winproc_GETOBJECT_UiaRoot, 1); CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 1); - check_uia_hwnd_expects_at_least(0, FALSE, 1, TRUE, 1, TRUE, 0, FALSE, 0, FALSE); + check_uia_hwnd_expects_at_least(0, FALSE, 1, FALSE, 1, FALSE, 0, FALSE, 0, FALSE);
set_provider_method_event_data(&Provider_hwnd, NULL, -1); set_provider_method_event_data(&Provider_nc, NULL, -1); @@ -15063,7 +15063,7 @@ static void test_uia_com_event_handler_event_advisement(IUIAutomation *uia_iface SET_EXPECT(child_winproc_GETOBJECT_UiaRoot); /* Only done on Win11. */ set_uia_hwnd_expects(0, 2, 2, 7, 0); /* Only done more than once on Win11. */ NotifyWinEvent(EVENT_OBJECT_SHOW, test_hwnd, OBJID_WINDOW, CHILDID_SELF); - todo_wine ok(msg_wait_for_all_events(method_event, event_handle_count, 2000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); + ok(msg_wait_for_all_events(method_event, event_handle_count, 2000) != WAIT_TIMEOUT, "Wait for method_event(s) timed out.\n"); CHECK_CALLED_AT_MOST(child_winproc_GETOBJECT_UiaRoot, 1); check_uia_hwnd_expects_at_most(0, 2, 2, 7, 0);
diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index 0980b2d0b37..9fe6090459d 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -961,7 +961,68 @@ struct uia_com_event { static HRESULT uia_com_win_event_callback(DWORD event_id, HWND hwnd, LONG obj_id, LONG child_id, DWORD thread_id, DWORD event_time) { - FIXME("%ld, %p, %ld, %ld, %ld, %ld: stub\n", event_id, hwnd, obj_id, child_id, thread_id, event_time); + TRACE("%ld, %p, %ld, %ld, %ld, %ld\n", event_id, hwnd, obj_id, child_id, thread_id, event_time); + + switch (event_id) + { + case EVENT_OBJECT_SHOW: + { + struct uia_event_handler_map_entry *entry; + SAFEARRAY *rt_id = NULL; + HUIANODE node; + HRESULT hr; + + if (obj_id != OBJID_WINDOW || !uia_hwnd_is_visible(hwnd)) + break; + + hr = UiaNodeFromHandle(hwnd, &node); + if (FAILED(hr)) + return hr; + + hr = UiaGetRuntimeId(node, &rt_id); + if (FAILED(hr)) + { + UiaNodeRelease(node); + return hr; + } + + EnterCriticalSection(&com_event_handlers_cs); + + RB_FOR_EACH_ENTRY(entry, &com_event_handlers.handler_map, struct uia_event_handler_map_entry, entry) + { + struct uia_com_event *event; + + /* + * Focus change event handlers only listen for EVENT_OBJECT_SHOW + * on the desktop HWND. + */ + if ((entry->event_id == UIA_AutomationFocusChangedEventId) && (hwnd != GetDesktopWindow())) + continue; + + LIST_FOR_EACH_ENTRY(event, &entry->handlers_list, struct uia_com_event, event_handler_map_list_entry) + { + hr = uia_event_check_node_within_event_scope((struct uia_event *)event->event, node, rt_id, NULL); + if (FAILED(hr)) + WARN("uia_event_check_node_within_scope failed with hr %#lx\n", hr); + else if (hr == S_OK) + { + 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); + } + } + } + + LeaveCriticalSection(&com_event_handlers_cs); + + UiaNodeRelease(node); + break; + } + + default: + break; + } + return S_OK; }
diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 0e6d13c6f52..805daa5978a 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -55,6 +55,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;
default: break; @@ -1605,13 +1606,27 @@ static HRESULT uia_event_advise(struct uia_event *event, BOOL advise_added, LONG return hr; }
+HRESULT uia_event_advise_node(struct uia_event *event, HUIANODE node) +{ + int old_event_advisers_count = event->event_advisers_count; + HRESULT hr; + + hr = attach_event_to_uia_node(node, event); + if (FAILED(hr)) + return hr; + + if (event->event_advisers_count != old_event_advisers_count) + hr = uia_event_advise(event, TRUE, old_event_advisers_count); + + return hr; +} + /*********************************************************************** * UiaEventAddWindow (uiautomationcore.@) */ HRESULT WINAPI UiaEventAddWindow(HUIAEVENT huiaevent, HWND hwnd) { struct uia_event *event = unsafe_impl_from_IWineUiaEvent((IWineUiaEvent *)huiaevent); - int old_event_advisers_count; HUIANODE node; HRESULT hr;
@@ -1626,15 +1641,7 @@ HRESULT WINAPI UiaEventAddWindow(HUIAEVENT huiaevent, HWND hwnd) if (FAILED(hr)) return hr;
- old_event_advisers_count = event->event_advisers_count; - hr = attach_event_to_uia_node(node, event); - if (FAILED(hr)) - goto exit; - - if (event->event_advisers_count != old_event_advisers_count) - hr = uia_event_advise(event, TRUE, old_event_advisers_count); - -exit: + hr = uia_event_advise_node(event, node); UiaNodeRelease(node);
return hr; @@ -1760,8 +1767,6 @@ HRESULT WINAPI UiaRemoveEvent(HUIAEVENT huiaevent) return S_OK; }
-static HRESULT uia_event_check_node_within_event_scope(struct uia_event *event, HUIANODE node, SAFEARRAY *rt_id, - HUIANODE *clientside_nav_node_out); static HRESULT uia_event_invoke(HUIANODE node, HUIANODE nav_start_node, struct uia_event_args *args, struct uia_event *event) { HRESULT hr = S_OK; @@ -1838,7 +1843,7 @@ static void set_refuse_hwnd_providers(struct uia_node *node, BOOL refuse_hwnd_pr * If it isn't, return S_FALSE. * Upon failure, return a failure HR. */ -static HRESULT uia_event_check_node_within_event_scope(struct uia_event *event, HUIANODE node, SAFEARRAY *rt_id, +HRESULT uia_event_check_node_within_event_scope(struct uia_event *event, HUIANODE node, SAFEARRAY *rt_id, HUIANODE *clientside_nav_node_out) { struct UiaPropertyCondition prop_cond = { ConditionType_Property, UIA_RuntimeIdPropertyId }; diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 4e6aec9cffa..dbc92d0ed98 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -233,9 +233,12 @@ HRESULT create_serverside_uia_event(struct uia_event **out_event, LONG process_i HRESULT uia_event_add_provider_event_adviser(IRawElementProviderAdviseEvents *advise_events, struct uia_event *event) DECLSPEC_HIDDEN; HRESULT uia_event_add_serverside_event_adviser(IWineUiaEvent *serverside_event, struct uia_event *event) DECLSPEC_HIDDEN; +HRESULT uia_event_advise_node(struct uia_event *event, HUIANODE node) DECLSPEC_HIDDEN; HRESULT uia_add_clientside_event(HUIANODE huianode, EVENTID event_id, enum TreeScope scope, PROPERTYID *prop_ids, int prop_ids_count, struct UiaCacheRequest *cache_req, SAFEARRAY *rt_id, UiaWineEventCallback *cback, void *cback_data, HUIAEVENT *huiaevent) DECLSPEC_HIDDEN; +HRESULT uia_event_check_node_within_event_scope(struct uia_event *event, HUIANODE node, SAFEARRAY *rt_id, + HUIANODE *clientside_nav_node_out) DECLSPEC_HIDDEN;
/* uia_ids.c */ const struct uia_prop_info *uia_prop_info_from_id(PROPERTYID prop_id) DECLSPEC_HIDDEN;
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_event.c:
+void uia_event_thread_set_win_event_callback(UiaWineWinEventCallback *cback) +{
- if (cback && !uia_start_event_thread())
- {
WARN("Failed to start UIA event thread\n");
return;
- }
- EnterCriticalSection(&event_thread_cs);
- event_thread.win_event_callback = cback;
- LeaveCriticalSection(&event_thread_cs);
- if (!cback)
uia_stop_event_thread();
+}
This seems either broken or unnecessarily complicated to me.
If this will only ever be used for that one specific function in the com client code, it seems to me it'd be simpler to always call that function and let it return immediately if it has nothing to do.
In the case of multiple users: If this is ever changed from non-NULL to a different non-NULL value, or from NULL to NULL, the reference counting will be wrong. It's also not possible to two callbacks to exist simultaneously.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_event.c:
{ if (node_data->hwnd) {
hr = uia_event_invoke(node, (HUIANODE)&node_data->IWineUiaNode_iface, args, event);
*clientside_nav_node_out = (HUIANODE)&node_data->IWineUiaNode_iface;
IWineUiaNode_AddRef(&node_data->IWineUiaNode_iface);
in_scope = TRUE; break; }
Earlier, clientside_nav_node_out is checked for NULL, but not here. I guess the idea is that clientside_nav_node_out will always be non-NULL for serverside events, which makes sense, but it might be good to enforce this with an assert earlier in the function.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_event.c:
return hr; }
- return uia_event_check_match(event_data->node, event_data->node, event_data->rt_id, event_data->args, event);
- hr = uia_event_check_node_within_event_scope(event, event_data->node, event_data->rt_id, &nav_node);
- if (hr == S_OK)
return uia_event_invoke(event_data->node, nav_node, event_data->args, event);
I think this leaks nav_node?