-- v3: 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 the COM API. 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 | 9 +++++++++ dlls/uiautomationcore/uia_event.c | 8 ++++++-- dlls/uiautomationcore/uia_private.h | 3 +++ 3 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index 19540b19dc0..8f8d5d0170e 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -958,6 +958,12 @@ struct uia_com_event { struct uia_event_handler_map_entry *handler_map; };
+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) { @@ -3311,6 +3317,9 @@ static HRESULT uia_add_com_event_handler(IUIAutomation6 *iface, EVENTID event_id if (FAILED(hr)) goto exit;
+ if (!uia_clientside_event_start_event_thread((struct uia_event *)com_event->event)) + WARN("Failed to start event thread, WinEvents may not be delivered.\n"); + hr = uia_event_handlers_add_handler(handler_unk, runtime_id, event_id, com_event);
exit: diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 395b20e0537..0008b543808 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -78,7 +78,6 @@ static BOOL CALLBACK uia_win_event_enum_top_level_hwnds(HWND hwnd, LPARAM lparam return TRUE; }
-static BOOL uia_clientside_event_start_event_thread(struct uia_event *event); HRESULT uia_event_add_win_event_hwnd(struct uia_event *event, HWND hwnd) { if (!uia_clientside_event_start_event_thread(event)) @@ -844,6 +843,11 @@ static void uia_event_thread_process_queue(struct list *event_queue) { struct uia_queue_win_event *win_event = (struct uia_queue_win_event *)event;
+ hr = uia_com_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("uia_com_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); break; @@ -990,7 +994,7 @@ static void uia_stop_event_thread(void) LeaveCriticalSection(&event_thread_cs); }
-static BOOL uia_clientside_event_start_event_thread(struct uia_event *event) +BOOL uia_clientside_event_start_event_thread(struct uia_event *event) { if (!event->u.clientside.event_thread_started) event->u.clientside.event_thread_started = uia_start_event_thread(); diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 8a750bf3ae1..358a0c95217 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -223,10 +223,13 @@ HRESULT uia_condition_check(HUIANODE node, struct UiaCondition *condition) DECLS BOOL uia_condition_matched(HRESULT hr) DECLSPEC_HIDDEN;
/* uia_com_client.c */ +HRESULT uia_com_win_event_callback(DWORD event_id, HWND hwnd, LONG obj_id, LONG child_id, DWORD thread_id, + DWORD event_time) DECLSPEC_HIDDEN; 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; +BOOL uia_clientside_event_start_event_thread(struct uia_event *event) 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 | 68 +++++++++++++++++++------------ 1 file changed, 43 insertions(+), 25 deletions(-)
diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 0008b543808..7d3af1a9d3c 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -1734,8 +1734,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; @@ -1745,8 +1745,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); @@ -1807,35 +1807,41 @@ 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; + + if (event->event_type == EVENT_TYPE_SERVERSIDE) + assert(clientside_nav_node_out);
/* 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; @@ -1855,16 +1861,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); @@ -1873,7 +1882,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; }
@@ -1882,13 +1891,17 @@ 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; - HRESULT hr; + HUIANODE nav_node = NULL; + HRESULT hr = S_OK;
if (!event_data->node) { @@ -1905,7 +1918,12 @@ 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) + hr = uia_event_invoke(event_data->node, nav_node, event_data->args, event); + + UiaNodeRelease(nav_node); + return hr; }
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 | 72 +++++++++++++++++++++- dlls/uiautomationcore/uia_event.c | 31 ++++++---- dlls/uiautomationcore/uia_private.h | 3 + 4 files changed, 107 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 8f8d5d0170e..6d3d2216169 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -960,7 +960,77 @@ struct uia_com_event {
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); + LONG handler_count; + + TRACE("%ld, %p, %ld, %ld, %ld, %ld\n", event_id, hwnd, obj_id, child_id, thread_id, event_time); + + EnterCriticalSection(&com_event_handlers_cs); + handler_count = com_event_handlers.handler_count; + LeaveCriticalSection(&com_event_handlers_cs); + + if (!handler_count) + return S_OK; + + 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 7d3af1a9d3c..0e6cf4e000a 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; @@ -1579,13 +1580,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;
@@ -1600,15 +1615,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; @@ -1734,8 +1741,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; @@ -1812,7 +1817,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 358a0c95217..0a8f2d69dbd 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -234,9 +234,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;
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=138282
Your paranoid android.
=== w10pro64_en_AE_u8 (32 bit report) ===
uiautomationcore: uiautomation.c:8772: Test failed: unexpected call prov_callback_nonclient uiautomation.c:8749: Test failed: unexpected call prov_callback_base_hwnd uiautomation.c:14742: Test failed: Unexpected refcnt 6
On Wed Oct 4 12:09:47 2023 +0000, Connor McAdams wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/4005/diffs?diff_id=73786&start_sha=60a1e1f775064ee8445a557926a34f1ed71c54d0#14d9c4bde8659a60721a64739d545cd66adf72c2_1951_1928)
Good catch, should be fixed in the current revision.
On Tue Oct 3 19:25:06 2023 +0000, Esme Povirk wrote:
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.
Added an assert to hopefully make this clearer.
On Wed Oct 4 12:09:46 2023 +0000, Connor McAdams wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/4005/diffs?diff_id=73786&start_sha=60a1e1f775064ee8445a557926a34f1ed71c54d0#14d9c4bde8659a60721a64739d545cd66adf72c2_1031_1006)
Yeah, with a fresh set of eyes it was probably more complex than necessary. Mainly, I was trying to make sure the event thread was started so that WinEvents would be listened for, but there's really no need to start/stop the event thread separately.
I went ahead and tied the event thread starting to each HUIAEVENT that's created for a COM event handler, and got rid of the generic callback. It seems more clear this way.
This merge request was approved by Esme Povirk.