From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 673 ++++++++++++++++++++- 1 file changed, 654 insertions(+), 19 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 842525b78fa..89757e2eb84 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -391,6 +391,7 @@ DEFINE_EXPECT(prov_callback_nonclient); DEFINE_EXPECT(prov_callback_proxy); DEFINE_EXPECT(prov_callback_parent_proxy); DEFINE_EXPECT(uia_event_callback); +DEFINE_EXPECT(uia_event_callback2); DEFINE_EXPECT(uia_com_event_callback); DEFINE_EXPECT(winproc_GETOBJECT_UiaRoot); DEFINE_EXPECT(child_winproc_GETOBJECT_UiaRoot); @@ -493,6 +494,34 @@ static void test_implements_interface_(IUnknown *unk, const GUID *iid, BOOL exp_ DEFINE_ACC_METHOD_EXPECT(get_locale); \ DEFINE_ACC_METHOD_EXPECT(get_attributes) \
+static DWORD msg_wait_for_all_events(HANDLE *event_handles, int event_handle_count, DWORD timeout_val) +{ + int events_handled = 0; + DWORD wait_res; + + while ((wait_res = MsgWaitForMultipleObjects(event_handle_count, (const HANDLE *)event_handles, FALSE, timeout_val, + QS_ALLINPUT)) <= (WAIT_OBJECT_0 + event_handle_count)) + { + if (wait_res == (WAIT_OBJECT_0 + event_handle_count)) + { + MSG msg; + + while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + else + events_handled++; + + if (events_handled == event_handle_count) + break; + } + + return wait_res; +} + static struct Accessible { IAccessible IAccessible_iface; @@ -1555,6 +1584,19 @@ struct Provider_legacy_accessible_pattern_data DWORD role; };
+struct Provider_win_event_handler_data +{ + BOOL is_supported; + + DWORD exp_win_event_id; + HWND exp_win_event_hwnd; + LONG exp_win_event_obj_id; + LONG exp_win_event_child_id; + + IRawElementProviderSimple *responder_prov; + int responder_event; +}; + static struct Provider { IRawElementProviderSimple IRawElementProviderSimple_iface; @@ -1562,6 +1604,7 @@ static struct Provider IRawElementProviderFragmentRoot IRawElementProviderFragmentRoot_iface; IRawElementProviderHwndOverride IRawElementProviderHwndOverride_iface; IRawElementProviderAdviseEvents IRawElementProviderAdviseEvents_iface; + IProxyProviderWinEventHandler IProxyProviderWinEventHandler_iface; IValueProvider IValueProvider_iface; ILegacyIAccessibleProvider ILegacyIAccessibleProvider_iface; LONG ref; @@ -1591,6 +1634,7 @@ static struct Provider int embedded_frag_roots_count; int advise_events_added_event_id; int advise_events_removed_event_id; + struct Provider_win_event_handler_data win_event_handler_data; } 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); @@ -1662,6 +1706,7 @@ enum { HWND_OVERRIDE_GET_OVERRIDE_PROVIDER, ADVISE_EVENTS_EVENT_ADDED, ADVISE_EVENTS_EVENT_REMOVED, + WINEVENT_HANDLER_RESPOND_TO_WINEVENT, };
static const char *prov_method_str[] = { @@ -1678,6 +1723,7 @@ static const char *prov_method_str[] = { "GetOverrideProviderForHwnd", "AdviseEventAdded", "AdviseEventRemoved", + "RespondToWinEvent", };
static const char *get_prov_method_str(int method) @@ -2061,6 +2107,8 @@ HRESULT WINAPI ProviderSimple_QueryInterface(IRawElementProviderSimple *iface, R *ppv = &This->IValueProvider_iface; else if (IsEqualIID(riid, &IID_ILegacyIAccessibleProvider)) *ppv = &This->ILegacyIAccessibleProvider_iface; + else if (This->win_event_handler_data.is_supported && IsEqualIID(riid, &IID_IProxyProviderWinEventHandler)) + *ppv = &This->IProxyProviderWinEventHandler_iface; else return E_NOINTERFACE;
@@ -2723,6 +2771,64 @@ static const IRawElementProviderAdviseEventsVtbl ProviderAdviseEventsVtbl = { ProviderAdviseEvents_AdviseEventRemoved, };
+static inline struct Provider *impl_from_ProviderWinEventHandler(IProxyProviderWinEventHandler *iface) +{ + return CONTAINING_RECORD(iface, struct Provider, IProxyProviderWinEventHandler_iface); +} + +static HRESULT WINAPI ProviderWinEventHandler_QueryInterface(IProxyProviderWinEventHandler *iface, REFIID riid, + void **ppv) +{ + struct Provider *Provider = impl_from_ProviderWinEventHandler(iface); + return IRawElementProviderSimple_QueryInterface(&Provider->IRawElementProviderSimple_iface, riid, ppv); +} + +static ULONG WINAPI ProviderWinEventHandler_AddRef(IProxyProviderWinEventHandler *iface) +{ + struct Provider *Provider = impl_from_ProviderWinEventHandler(iface); + return IRawElementProviderSimple_AddRef(&Provider->IRawElementProviderSimple_iface); +} + +static ULONG WINAPI ProviderWinEventHandler_Release(IProxyProviderWinEventHandler *iface) +{ + struct Provider *Provider = impl_from_ProviderWinEventHandler(iface); + return IRawElementProviderSimple_Release(&Provider->IRawElementProviderSimple_iface); +} + +static HRESULT WINAPI ProviderWinEventHandler_RespondToWinEvent(IProxyProviderWinEventHandler *iface, + DWORD event_id, HWND hwnd, LONG obj_id, LONG child_id, IProxyProviderWinEventSink *event_sink) +{ + struct Provider *This = impl_from_ProviderWinEventHandler(iface); + struct Provider_win_event_handler_data *data; + HRESULT hr; + + PROV_METHOD_TRACE(This, RespondToWinEvent); + data = &This->win_event_handler_data; + if ((data->exp_win_event_id != event_id) || (data->exp_win_event_hwnd != hwnd) || (data->exp_win_event_obj_id != obj_id) || + (data->exp_win_event_child_id != child_id)) + return S_OK; + + add_method_call(This, WINEVENT_HANDLER_RESPOND_TO_WINEVENT); + if (This->expected_tid) + ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); + This->last_call_tid = GetCurrentThreadId(); + + if (data->responder_prov) + { + hr = IProxyProviderWinEventSink_AddAutomationEvent(event_sink, data->responder_prov, data->responder_event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + } + + return S_OK; +} + +static const IProxyProviderWinEventHandlerVtbl ProviderWinEventHandlerVtbl = { + ProviderWinEventHandler_QueryInterface, + ProviderWinEventHandler_AddRef, + ProviderWinEventHandler_Release, + ProviderWinEventHandler_RespondToWinEvent, +}; + static inline struct Provider *impl_from_ProviderValuePattern(IValueProvider *iface) { return CONTAINING_RECORD(iface, struct Provider, IValueProvider_iface); @@ -2921,6 +3027,7 @@ static struct Provider Provider = { &ProviderFragmentRootVtbl }, { &ProviderHwndOverrideVtbl }, { &ProviderAdviseEventsVtbl }, + { &ProviderWinEventHandlerVtbl }, { &ProviderValuePatternVtbl }, { &ProviderLegacyIAccessiblePatternVtbl }, 1, @@ -2938,6 +3045,7 @@ static struct Provider Provider2 = { &ProviderFragmentRootVtbl }, { &ProviderHwndOverrideVtbl }, { &ProviderAdviseEventsVtbl }, + { &ProviderWinEventHandlerVtbl }, { &ProviderValuePatternVtbl }, { &ProviderLegacyIAccessiblePatternVtbl }, 1, @@ -2955,6 +3063,7 @@ static struct Provider Provider_child = { &ProviderFragmentRootVtbl }, { &ProviderHwndOverrideVtbl }, { &ProviderAdviseEventsVtbl }, + { &ProviderWinEventHandlerVtbl }, { &ProviderValuePatternVtbl }, { &ProviderLegacyIAccessiblePatternVtbl }, 1, @@ -2972,6 +3081,7 @@ static struct Provider Provider_child2 = { &ProviderFragmentRootVtbl }, { &ProviderHwndOverrideVtbl }, { &ProviderAdviseEventsVtbl }, + { &ProviderWinEventHandlerVtbl }, { &ProviderValuePatternVtbl }, { &ProviderLegacyIAccessiblePatternVtbl }, 1, @@ -2989,6 +3099,7 @@ static struct Provider Provider_hwnd = { &ProviderFragmentRootVtbl }, { &ProviderHwndOverrideVtbl }, { &ProviderAdviseEventsVtbl }, + { &ProviderWinEventHandlerVtbl }, { &ProviderValuePatternVtbl }, { &ProviderLegacyIAccessiblePatternVtbl }, 1, @@ -3006,6 +3117,7 @@ static struct Provider Provider_nc = { &ProviderFragmentRootVtbl }, { &ProviderHwndOverrideVtbl }, { &ProviderAdviseEventsVtbl }, + { &ProviderWinEventHandlerVtbl }, { &ProviderValuePatternVtbl }, { &ProviderLegacyIAccessiblePatternVtbl }, 1, @@ -3024,6 +3136,7 @@ static struct Provider Provider_proxy = { &ProviderFragmentRootVtbl }, { &ProviderHwndOverrideVtbl }, { &ProviderAdviseEventsVtbl }, + { &ProviderWinEventHandlerVtbl }, { &ProviderValuePatternVtbl }, { &ProviderLegacyIAccessiblePatternVtbl }, 1, @@ -3042,6 +3155,7 @@ static struct Provider Provider_proxy2 = { &ProviderFragmentRootVtbl }, { &ProviderHwndOverrideVtbl }, { &ProviderAdviseEventsVtbl }, + { &ProviderWinEventHandlerVtbl }, { &ProviderValuePatternVtbl }, { &ProviderLegacyIAccessiblePatternVtbl }, 1, @@ -3060,6 +3174,7 @@ static struct Provider Provider_override = { &ProviderFragmentRootVtbl }, { &ProviderHwndOverrideVtbl }, { &ProviderAdviseEventsVtbl }, + { &ProviderWinEventHandlerVtbl }, { &ProviderValuePatternVtbl }, { &ProviderLegacyIAccessiblePatternVtbl }, 1, @@ -3079,6 +3194,7 @@ static struct Provider Provider_override = { &ProviderFragmentRootVtbl }, \ { &ProviderHwndOverrideVtbl }, \ { &ProviderAdviseEventsVtbl }, \ + { &ProviderWinEventHandlerVtbl }, \ { &ProviderValuePatternVtbl }, \ { &ProviderLegacyIAccessiblePatternVtbl }, \ 1, \ @@ -10793,6 +10909,37 @@ static void set_property_override(struct Provider_prop_override *override, int p override->val = *val; }
+static void set_provider_win_event_handler_respond_prov(struct Provider *prov, IRawElementProviderSimple *responder_prov, + int responder_event) +{ + struct Provider_win_event_handler_data *data = &prov->win_event_handler_data; + + data->responder_prov = responder_prov; + data->responder_event = responder_event; +} + +static void set_provider_win_event_handler_win_event_expects(struct Provider *prov, DWORD exp_win_event_id, + HWND exp_win_event_hwnd, LONG exp_win_event_obj_id, LONG exp_win_event_child_id) +{ + struct Provider_win_event_handler_data *data = &prov->win_event_handler_data; + + data->exp_win_event_id = exp_win_event_id; + data->exp_win_event_hwnd = exp_win_event_hwnd; + data->exp_win_event_obj_id = exp_win_event_obj_id; + data->exp_win_event_child_id = exp_win_event_child_id; +} + +static void set_provider_runtime_id(struct Provider *prov, int val, int val2) +{ + prov->runtime_id[0] = val; + prov->runtime_id[1] = val2; +} + +static void initialize_provider_advise_events_ids(struct Provider *prov) +{ + prov->advise_events_added_event_id = prov->advise_events_removed_event_id = 0; +} + static void initialize_provider(struct Provider *prov, int prov_opts, HWND hwnd, BOOL initialize_nav_links) { prov->prov_opts = prov_opts; @@ -10812,6 +10959,7 @@ static void initialize_provider(struct Provider *prov, int prov_opts, HWND hwnd, prov->embedded_frag_roots = NULL; 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)); if (initialize_nav_links) { prov->frag_root = NULL; @@ -11875,6 +12023,14 @@ static HWND create_child_test_hwnd(const char *class_name, HWND parent) 0, 0, 50, 50, parent, NULL, NULL, NULL); }
+static void destroy_test_hwnd(HWND hwnd, const char *class_name, const char *child_class_name) +{ + DestroyWindow(hwnd); + UnregisterClassA(class_name, NULL); + if (child_class_name) + UnregisterClassA(child_class_name, NULL); +} + static IUIAutomationElement *create_test_element_from_hwnd(IUIAutomation *uia_iface, HWND hwnd, BOOL block_hwnd_provs) { IUIAutomationElement *element; @@ -14164,6 +14320,17 @@ static void check_uia_hwnd_expects_at_most(int proxy_cback_count, int base_hwnd_ CHECK_CALLED_AT_MOST(winproc_GETOBJECT_CLIENT, win_get_client_obj_count); }
+static void check_uia_hwnd_expects_at_least(int proxy_cback_count, BOOL proxy_cback_todo, + int base_hwnd_cback_count, BOOL base_hwnd_cback_todo, int nc_cback_count, BOOL nc_cback_todo, + int win_get_uia_obj_count, BOOL win_get_uia_obj_todo, int win_get_client_obj_count, BOOL win_get_client_obj_todo) +{ + todo_wine_if(proxy_cback_todo) CHECK_CALLED_AT_LEAST(prov_callback_proxy, proxy_cback_count); + todo_wine_if(base_hwnd_cback_todo) CHECK_CALLED_AT_LEAST(prov_callback_base_hwnd, base_hwnd_cback_count); + todo_wine_if(nc_cback_todo) CHECK_CALLED_AT_LEAST(prov_callback_nonclient, nc_cback_count); + todo_wine_if(win_get_uia_obj_todo) CHECK_CALLED_AT_LEAST(winproc_GETOBJECT_UiaRoot, win_get_uia_obj_count); + todo_wine_if(win_get_client_obj_todo) CHECK_CALLED_AT_LEAST(winproc_GETOBJECT_CLIENT, win_get_client_obj_count); +} + static struct ComEventData { struct node_provider_desc exp_node_desc; struct node_provider_desc exp_nested_node_desc; @@ -15551,47 +15718,67 @@ static struct EventData {
struct node_provider_desc exp_nested_node_desc; HANDLE event_handle; -} EventData; +} EventData, EventData2;
-static void set_event_data(LONG exp_lbound0, LONG exp_lbound1, LONG exp_elems0, LONG exp_elems1, - struct node_provider_desc *exp_node_desc, const WCHAR *exp_tree_struct) +static void set_event_data_struct(struct EventData *data, LONG exp_lbound0, LONG exp_lbound1, LONG exp_elems0, + LONG exp_elems1, struct node_provider_desc *exp_node_desc, const WCHAR *exp_tree_struct) { - EventData.exp_lbound[0] = exp_lbound0; - EventData.exp_lbound[1] = exp_lbound1; - EventData.exp_elems[0] = exp_elems0; - EventData.exp_elems[1] = exp_elems1; + data->exp_lbound[0] = exp_lbound0; + data->exp_lbound[1] = exp_lbound1; + data->exp_elems[0] = exp_elems0; + data->exp_elems[1] = exp_elems1; if (exp_node_desc) { int i;
- EventData.exp_node_desc = *exp_node_desc; + data->exp_node_desc = *exp_node_desc; for (i = 0; i < exp_node_desc->prov_count; i++) { if (exp_node_desc->nested_desc[i]) { - EventData.exp_nested_node_desc = *exp_node_desc->nested_desc[i]; - EventData.exp_node_desc.nested_desc[i] = &EventData.exp_nested_node_desc; + data->exp_nested_node_desc = *exp_node_desc->nested_desc[i]; + data->exp_node_desc.nested_desc[i] = &data->exp_nested_node_desc; break; } } } else - memset(&EventData.exp_node_desc, 0, sizeof(EventData.exp_node_desc)); - EventData.exp_tree_struct = exp_tree_struct; + memset(&data->exp_node_desc, 0, sizeof(data->exp_node_desc)); + data->exp_tree_struct = exp_tree_struct; }
-static void WINAPI uia_event_callback(struct UiaEventArgs *args, SAFEARRAY *req_data, BSTR tree_struct) +static void set_event_data(LONG exp_lbound0, LONG exp_lbound1, LONG exp_elems0, LONG exp_elems1, + struct node_provider_desc *exp_node_desc, const WCHAR *exp_tree_struct) { - CHECK_EXPECT(uia_event_callback); + set_event_data_struct(&EventData, exp_lbound0, exp_lbound1, exp_elems0, exp_elems1, exp_node_desc, + exp_tree_struct); +}
- if (!EventData.exp_elems[0] && !EventData.exp_elems[1]) +#define check_event_data( data, args, req_data, tree_struct ) \ + check_event_data_( (data), (args), (req_data), (tree_struct), __FILE__, __LINE__) +static void check_event_data_(struct EventData *data, struct UiaEventArgs *args, SAFEARRAY *req_data, BSTR tree_struct, + const char *file, int line) +{ + if (!data->exp_elems[0] && !data->exp_elems[1]) ok(!req_data, "req_data != NULL\n"); else - test_cache_req_sa(req_data, EventData.exp_lbound, EventData.exp_elems, &EventData.exp_node_desc); + test_cache_req_sa_(req_data, data->exp_lbound, data->exp_elems, &data->exp_node_desc, file, line);
- ok(!wcscmp(tree_struct, EventData.exp_tree_struct), "tree structure %s\n", debugstr_w(tree_struct)); - if (EventData.event_handle) - SetEvent(EventData.event_handle); + ok(!wcscmp(tree_struct, data->exp_tree_struct), "tree structure %s\n", debugstr_w(tree_struct)); + if (data->event_handle) + SetEvent(data->event_handle); +} + +static void WINAPI uia_event_callback(struct UiaEventArgs *args, SAFEARRAY *req_data, BSTR tree_struct) +{ + CHECK_EXPECT(uia_event_callback); + check_event_data(&EventData, args, req_data, tree_struct); +} + +static void WINAPI uia_event_callback2(struct UiaEventArgs *args, SAFEARRAY *req_data, BSTR tree_struct) +{ + CHECK_EXPECT(uia_event_callback2); + check_event_data(&EventData2, args, req_data, tree_struct); }
enum { @@ -16516,6 +16703,453 @@ static void test_UiaHasServerSideProvider(void) UnregisterClassA("UiaHasServerSideProvider test class", NULL); }
+static const struct prov_method_sequence win_event_handler_seq[] = { + { &Provider_proxy, HWND_OVERRIDE_GET_OVERRIDE_PROVIDER, METHOD_TODO }, + { &Provider_hwnd2, PROV_GET_HOST_RAW_ELEMENT_PROVIDER, METHOD_OPTIONAL }, /* Only done on Win10v1809+. */ + { &Provider_nc2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + { &Provider_hwnd2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + { &Provider_nc2, WINEVENT_HANDLER_RESPOND_TO_WINEVENT }, + { &Provider_hwnd2, WINEVENT_HANDLER_RESPOND_TO_WINEVENT }, + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, FRAG_GET_RUNTIME_ID }, + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_ProviderDescriptionPropertyId */ + { 0 } +}; + +#define test_uia_event_win_event_mapping( win_event, hwnd, obj_id, child_id, event_handles, event_handle_count, \ + expect_event1, expect_event2, todo ) \ + test_uia_event_win_event_mapping_( (win_event), (hwnd), (obj_id), (child_id), (event_handles), (event_handle_count), \ + (expect_event1), (expect_event2), (todo), __FILE__, __LINE__) +static void test_uia_event_win_event_mapping_(DWORD win_event, HWND hwnd, LONG obj_id, LONG child_id, + HANDLE *event_handles, int event_handle_count, BOOL expect_event1, BOOL expect_event2, + BOOL todo, const char *file, int line) +{ + const BOOL exp_timeout = (!expect_event1 && !expect_event2); + DWORD timeout_val = exp_timeout ? 500 : 3000; + DWORD wait_res; + + SET_EXPECT_MULTI(uia_event_callback, !!expect_event1); + SET_EXPECT_MULTI(uia_event_callback2, !!expect_event2); + if (expect_event2) + SET_EXPECT(uia_event_callback2); + NotifyWinEvent(win_event, hwnd, obj_id, child_id); + + wait_res = msg_wait_for_all_events(event_handles, event_handle_count, timeout_val); + todo_wine_if(todo) ok_(file, line)((wait_res == WAIT_TIMEOUT) == exp_timeout, + "Unexpected result while waiting for event callback(s).\n"); + if (expect_event1) + todo_wine_if(todo) CHECK_CALLED(uia_event_callback); + if (expect_event2) + todo_wine_if(todo) CHECK_CALLED(uia_event_callback2); +} + +#define test_provider_event_advise_added( prov, event_id, todo) \ + test_provider_event_advise_added_( (prov), (event_id), (todo), __FILE__, __LINE__) +static void test_provider_event_advise_added_(struct Provider *prov, int event_id, BOOL todo, const char *file, int line) +{ + todo_wine_if (todo) ok_(file, line)(prov->advise_events_added_event_id == event_id, "%s: Unexpected advise event added, event ID %d.\n", + prov->prov_name, prov->advise_events_added_event_id); +} + +static DWORD WINAPI uia_proxy_provider_win_event_handler_test_thread(LPVOID param) +{ + struct UiaCacheRequest cache_req = { (struct UiaCondition *)&UiaTrueCondition, TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full }; + HWND hwnd[2] = { ((HWND *)param)[0], ((HWND *)param)[1] }; + struct node_provider_desc exp_node_desc; + HUIAEVENT event, event2; + HANDLE event_handles[2]; + HWND tmp_hwnd; + HUIANODE node; + HRESULT hr; + int i; + + method_sequences_enabled = FALSE; + CoInitializeEx(NULL, COINIT_MULTITHREADED); + for (i = 0; i < ARRAY_SIZE(event_handles); i++) + event_handles[i] = CreateEventW(NULL, FALSE, FALSE, NULL); + + set_uia_hwnd_expects(1, 1, 1, 2, 0); + hr = UiaNodeFromHandle(hwnd[0], &node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!node, "Node == NULL.\n"); + check_uia_hwnd_expects_at_most(1, 1, 1, 2, 0); + + set_uia_hwnd_expects(2, 2, 2, 4, 0); + hr = UiaAddEvent(node, UIA_AutomationFocusChangedEventId, uia_event_callback, TreeScope_Element, NULL, 0, &cache_req, + &event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + /* Windows 11 recreates HWND clientside providers for the node passed into UiaAddEvent. */ + check_uia_hwnd_expects_at_most(2, 2, 2, 4, 0); + + set_uia_hwnd_expects(2, 2, 2, 4, 0); + hr = UiaAddEvent(node, UIA_AutomationFocusChangedEventId, uia_event_callback2, TreeScope_Subtree, NULL, 0, &cache_req, + &event2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + /* Windows 11 recreates HWND clientside providers for the node passed into UiaAddEvent. */ + check_uia_hwnd_expects_at_most(2, 2, 2, 4, 0); + UiaNodeRelease(node); + + /* + * Raise EVENT_OBJECT_FOCUS. If none of our clientside providers returned + * an IProxyProviderWinEventHandler interface when being advised of events + * being listened for, nothing happens. + */ + EventData.event_handle = event_handles[0]; + EventData2.event_handle = event_handles[1]; + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[0], OBJID_WINDOW, CHILDID_SELF, event_handles, + ARRAY_SIZE(event_handles), FALSE, FALSE, FALSE); + + /* + * Return an IProxyProviderWinEventHandler interface on our clientside + * providers. WinEvents will now be listened for. If a provider returns a + * WinEvent handler interface, IRawElementProviderAdviseEvents will not be + * queried for or used. + */ + initialize_provider_advise_events_ids(&Provider_hwnd2); + initialize_provider_advise_events_ids(&Provider_nc2); + Provider_hwnd2.win_event_handler_data.is_supported = Provider_nc2.win_event_handler_data.is_supported = TRUE; + set_uia_hwnd_expects(1, 1, 1, 2, 0); + hr = UiaEventAddWindow(event, hwnd[0]); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + test_provider_event_advise_added(&Provider_hwnd2, 0, TRUE); + test_provider_event_advise_added(&Provider_nc2, 0, TRUE); + check_uia_hwnd_expects_at_least(1, TRUE, 1, FALSE, 1, FALSE, 1, FALSE, 0, FALSE); + + /* + * WinEvents will now be listened for, however if our HWND has a + * serverside provider they will be ignored. + */ + SET_EXPECT_MULTI(winproc_GETOBJECT_UiaRoot, 2); /* Only called twice on Win11. */ + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[0], OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, FALSE, FALSE, FALSE); + todo_wine CHECK_CALLED(winproc_GETOBJECT_UiaRoot); + + /* + * Get rid of our serverside provider and raise EVENT_OBJECT_FOCUS + * again. Now, our WinEvent handler interfaces will be invoked. + */ + prov_root = NULL; + set_provider_win_event_handler_win_event_expects(&Provider_hwnd2, EVENT_OBJECT_FOCUS, hwnd[0], OBJID_WINDOW, CHILDID_SELF); + set_provider_win_event_handler_win_event_expects(&Provider_nc2, EVENT_OBJECT_FOCUS, hwnd[0], OBJID_WINDOW, CHILDID_SELF); + + initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + set_provider_runtime_id(&Provider_child, UIA_RUNTIME_ID_PREFIX, HandleToUlong(hwnd[0])); + set_provider_win_event_handler_respond_prov(&Provider_hwnd2, &Provider_child.IRawElementProviderSimple_iface, + UIA_AutomationFocusChangedEventId); + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc, L"Main", L"Provider_child", TRUE); + set_event_data(0, 0, 1, 1, &exp_node_desc, L"P)"); + + method_sequences_enabled = TRUE; + set_uia_hwnd_expects(1, 1, 1, 4, 3); + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[0], OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, TRUE, FALSE, TRUE); + if (CALLED_COUNT(winproc_GETOBJECT_CLIENT)) + ok_method_sequence(win_event_handler_seq, "win_event_handler_seq"); + check_uia_hwnd_expects_at_least(1, TRUE, 1, TRUE, 1, TRUE, 1, TRUE, 1, TRUE); + method_sequences_enabled = FALSE; + + /* + * Not all WinEvents are passed to our WinEvent responder interface - + * they're filtered by HWND. + */ + Provider_hwnd.win_event_handler_data.is_supported = Provider_nc.win_event_handler_data.is_supported = TRUE; + set_provider_win_event_handler_win_event_expects(&Provider_nc, EVENT_OBJECT_FOCUS, GetDesktopWindow(), OBJID_WINDOW, CHILDID_SELF); + set_provider_win_event_handler_respond_prov(&Provider_nc, &Provider_child.IRawElementProviderSimple_iface, + UIA_AutomationFocusChangedEventId); + SET_EXPECT(uia_event_callback); + set_uia_hwnd_expects(0, 1, 1, 0, 0); + NotifyWinEvent(EVENT_OBJECT_FOCUS, GetDesktopWindow(), OBJID_WINDOW, CHILDID_SELF); + if (msg_wait_for_all_events(event_handles, 1, 3000) == WAIT_OBJECT_0) + { + win_skip("Win10v1507 and below don't filter WinEvents by HWND, skipping further tests.\n"); + + CHECK_CALLED(uia_event_callback); + check_uia_hwnd_expects(0, FALSE, 1, FALSE, 1, FALSE, 0, FALSE, 0, FALSE); + hr = UiaRemoveEvent(event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = UiaRemoveEvent(event2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + goto skip_win_event_hwnd_filter_test; + } + + /* Clear expects/called values. */ + CHECK_CALLED_MULTI(uia_event_callback, 0); + + /* + * Child HWNDs of top level HWNDs that are within our scope are listened + * to by default. + */ + child_win_prov_root = NULL; + Provider_hwnd3.win_event_handler_data.is_supported = Provider_nc3.win_event_handler_data.is_supported = TRUE; + set_provider_win_event_handler_win_event_expects(&Provider_nc3, EVENT_OBJECT_FOCUS, hwnd[1], OBJID_WINDOW, CHILDID_SELF); + set_provider_win_event_handler_win_event_expects(&Provider_hwnd3, EVENT_OBJECT_FOCUS, hwnd[1], OBJID_WINDOW, CHILDID_SELF); + set_provider_win_event_handler_respond_prov(&Provider_nc3, &Provider_child.IRawElementProviderSimple_iface, + UIA_AutomationFocusChangedEventId); + + set_uia_hwnd_expects(0, 1, 1, 2, 0); + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 4); /* Only sent 4 times on Win11. */ + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[1], OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, TRUE, FALSE, TRUE); + check_uia_hwnd_expects_at_least(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); + todo_wine CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot); + + /* + * Child HWND now has a serverside provider, WinEvent is ignored. + */ + child_win_prov_root = &Provider2.IRawElementProviderSimple_iface; + + SET_EXPECT(winproc_GETOBJECT_UiaRoot); /* Only sent on Win11. */ + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 2); /* Only sent 2 times on Win11. */ + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[1], OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, FALSE, FALSE, FALSE); + todo_wine CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot); + CHECK_CALLED_AT_MOST(winproc_GETOBJECT_UiaRoot, 1); + + /* + * HWNDs owned by a top level HWND that is within our scope are ignored. + */ + child_win_prov_root = NULL; + tmp_hwnd = CreateWindowA("ProxyProviderWinEventHandler test child class", "Test child window 2", WS_POPUP, + 0, 0, 50, 50, hwnd[0], NULL, NULL, NULL); + Provider_nc3.hwnd = Provider_hwnd3.hwnd = tmp_hwnd; + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, tmp_hwnd, OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, FALSE, FALSE, FALSE); + DestroyWindow(tmp_hwnd); + + /* + * Add our test child HWND to event2. This only puts the child HWND within + * the scope of event2, it doesn't put the parent HWND within its scope. + */ + child_win_prov_root = &Provider2.IRawElementProviderSimple_iface; + Provider_nc3.hwnd = Provider_hwnd3.hwnd = hwnd[1]; + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 2); /* Only sent 2 times on Win11. */ + set_uia_hwnd_expects(0, 1, 1, 1, 0); + hr = UiaEventAddWindow(event2, hwnd[1]); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + check_uia_hwnd_expects_at_least(0, FALSE, 1, FALSE, 1, FALSE, 1, TRUE, 0, FALSE); + CHECK_CALLED(child_winproc_GETOBJECT_UiaRoot); + + /* + * Raise a WinEvent on our top level test HWND, will not invoke the + * callback on event2. + */ + set_uia_hwnd_expects(1, 1, 1, 4, 3); + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[0], OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, TRUE, FALSE, TRUE); + check_uia_hwnd_expects_at_least(1, TRUE, 1, TRUE, 1, TRUE, 1, TRUE, 1, TRUE); + + /* Raise a WinEvent on our test child HWND, both event callbacks invoked. */ + child_win_prov_root = NULL; + set_event_data_struct(&EventData2, 0, 0, 1, 1, &exp_node_desc, L"P)"); + + set_uia_hwnd_expects(0, 2, 2, 4, 0); + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 8); /* Only sent 8 times on Win11. */ + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[1], OBJID_WINDOW, CHILDID_SELF, event_handles, + ARRAY_SIZE(event_handles), TRUE, TRUE, TRUE); + todo_wine CHECK_CALLED_AT_LEAST(child_winproc_GETOBJECT_UiaRoot, 2); + check_uia_hwnd_expects_at_least(0, FALSE, 2, TRUE, 2, TRUE, 2, TRUE, 0, FALSE); + + hr = UiaRemoveEvent(event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = UiaRemoveEvent(event2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* + * Create an event on the desktop HWND. If a WinEvent handler interface is + * returned on a provider representing the desktop HWND, all visible + * top-level HWNDs at the time of advisement will be considered within + * scope. + */ + set_uia_hwnd_expects(1, 1, 1, 0, 0); + hr = UiaGetRootNode(&node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!node, "Node == NULL.\n"); + check_uia_hwnd_expects(1, FALSE, 1, FALSE, 1, FALSE, 0, FALSE, 0, FALSE); + + Provider_proxy.win_event_handler_data.is_supported = TRUE; + Provider_hwnd.win_event_handler_data.is_supported = Provider_nc.win_event_handler_data.is_supported = TRUE; + set_provider_win_event_handler_win_event_expects(&Provider_nc, EVENT_OBJECT_FOCUS, GetDesktopWindow(), OBJID_WINDOW, CHILDID_SELF); + set_provider_win_event_handler_win_event_expects(&Provider_hwnd, EVENT_OBJECT_FOCUS, GetDesktopWindow(), OBJID_WINDOW, CHILDID_SELF); + set_provider_win_event_handler_respond_prov(&Provider_nc, &Provider_child.IRawElementProviderSimple_iface, + UIA_AutomationFocusChangedEventId); + + /* Register a focus change event handler on the desktop HWND. */ + hr = UiaAddEvent(node, UIA_AutomationFocusChangedEventId, uia_event_callback, TreeScope_Element, NULL, 0, &cache_req, + &event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + test_provider_event_advise_added(&Provider_proxy, 0, TRUE); + test_provider_event_advise_added(&Provider_hwnd, 0, TRUE); + test_provider_event_advise_added(&Provider_nc, 0, TRUE); + + /* Raise WinEvent on the desktop HWND. */ + set_provider_runtime_id(&Provider_child, UIA_RUNTIME_ID_PREFIX, HandleToUlong(GetDesktopWindow())); + set_provider_win_event_handler_respond_prov(&Provider_hwnd, &Provider_child.IRawElementProviderSimple_iface, + UIA_AutomationFocusChangedEventId); + set_uia_hwnd_expects(0, 1, 1, 0, 0); + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, GetDesktopWindow(), OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, TRUE, FALSE, TRUE); + check_uia_hwnd_expects(0, FALSE, 1, TRUE, 1, TRUE, 0, FALSE, 0, FALSE); + + /* + * Top-level HWND, a child of the desktop HWND. Will not have an event + * raised since it was not visible when the desktop providers were advised + * of an event being added. + */ + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[0], OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, FALSE, FALSE, FALSE); + + /* Test child hwnd, same deal. */ + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[1], OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, FALSE, FALSE, FALSE); + + /* + * Show window after calling UiaAddEvent(), does nothing. Window must be + * visible when provider is advised of an event being added. + */ + ShowWindow(hwnd[0], SW_SHOW); + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[0], OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, FALSE, FALSE, FALSE); + + hr = UiaRemoveEvent(event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* + * Create the event again, except this time our test HWND was visible when + * the desktop provider was advised that our event was being added. Now + * WinEvents on our test HWND will be handled. + */ + hr = UiaAddEvent(node, UIA_AutomationFocusChangedEventId, uia_event_callback, TreeScope_Element, NULL, 0, &cache_req, + &event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + test_provider_event_advise_added(&Provider_hwnd, 0, TRUE); + test_provider_event_advise_added(&Provider_nc, 0, TRUE); + test_provider_event_advise_added(&Provider_proxy, 0, TRUE); + + /* WinEvent handled. */ + set_uia_hwnd_expects(1, 1, 1, 2, 1); + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[0], OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, TRUE, FALSE, TRUE); + check_uia_hwnd_expects(1, TRUE, 1, TRUE, 1, TRUE, 2, TRUE, 1, TRUE); + + /* Child HWNDs of our test window are handled as well. */ + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 2); + set_uia_hwnd_expects(0, 1, 1, 1, 0); + test_uia_event_win_event_mapping(EVENT_OBJECT_FOCUS, hwnd[1], OBJID_WINDOW, CHILDID_SELF, event_handles, + 1, TRUE, FALSE, TRUE); + check_uia_hwnd_expects(0, FALSE, 1, TRUE, 1, TRUE, 1, TRUE, 0, FALSE); + + hr = UiaRemoveEvent(event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + UiaNodeRelease(node); + +skip_win_event_hwnd_filter_test: + /* + * Test default MSAA proxy WinEvent handler. + */ + prov_root = &Provider.IRawElementProviderSimple_iface; + set_uia_hwnd_expects(2, 1, 1, 2, 0); + hr = UiaNodeFromHandle(hwnd[0], &node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!node, "Node == NULL.\n"); + check_uia_hwnd_expects_at_most(1, 1, 1, 2, 0); + + Provider_hwnd2.win_event_handler_data.is_supported = Provider_nc2.win_event_handler_data.is_supported = TRUE; + hr = UiaAddEvent(node, UIA_SystemAlertEventId, uia_event_callback, TreeScope_Subtree, NULL, 0, &cache_req, + &event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + UiaNodeRelease(node); + + set_provider_win_event_handler_respond_prov(&Provider_hwnd2, NULL, 0); + set_provider_win_event_handler_win_event_expects(&Provider_hwnd2, 0, hwnd[0], 0, 0); + set_provider_win_event_handler_respond_prov(&Provider_nc2, NULL, 0); + set_provider_win_event_handler_win_event_expects(&Provider_nc2, 0, NULL, 0, 0); + + prov_root = NULL; + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc, L"Main", NULL, TRUE); /* MSAA proxy. */ + set_event_data(0, 0, 1, 1, &exp_node_desc, L"P)"); + + /* WinEvent handled by default MSAA proxy provider. */ + set_uia_hwnd_expects(1, 1, 1, 4, 5); + test_uia_event_win_event_mapping(EVENT_SYSTEM_ALERT, hwnd[0], OBJID_CLIENT, 2, event_handles, + 1, TRUE, FALSE, TRUE); + check_uia_hwnd_expects_at_most(1, 1, 1, 4, 5); + + hr = UiaRemoveEvent(event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + for (i = 0; i < ARRAY_SIZE(event_handles); i++) + CloseHandle(event_handles[i]); + method_sequences_enabled = TRUE; + CoUninitialize(); + return 0; +} + +static void test_uia_event_ProxyProviderWinEventHandler(void) +{ + HANDLE thread; + HWND hwnd[2]; + + /* + * Windows 7 behaves different than all other versions, just skip the + * tests. + */ + if (!UiaLookupId(AutomationIdentifierType_Property, &OptimizeForVisualContent_Property_GUID)) + { + win_skip("Skipping IProxyProviderWinEventSink tests for Win7\n"); + return; + } + + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + hwnd[0] = create_test_hwnd("ProxyProviderWinEventHandler test class"); + hwnd[1] = create_child_test_hwnd("ProxyProviderWinEventHandler test child class", hwnd[0]); + + UiaRegisterProviderCallback(test_uia_provider_callback); + + /* Set clientside providers for our test windows and the desktop. */ + set_clientside_providers_for_hwnd(&Provider_proxy, &Provider_nc, &Provider_hwnd, GetDesktopWindow()); + base_hwnd_prov = &Provider_hwnd.IRawElementProviderSimple_iface; + nc_prov = &Provider_nc.IRawElementProviderSimple_iface; + proxy_prov = &Provider_proxy.IRawElementProviderSimple_iface; + + set_clientside_providers_for_hwnd(NULL, &Provider_nc2, &Provider_hwnd2, hwnd[0]); + initialize_provider(&Provider, ProviderOptions_ServerSideProvider, hwnd[0], TRUE); + Provider.frag_root = &Provider.IRawElementProviderFragmentRoot_iface; + Provider.ignore_hwnd_prop = TRUE; + + set_clientside_providers_for_hwnd(NULL, &Provider_nc3, &Provider_hwnd3, hwnd[1]); + initialize_provider(&Provider2, ProviderOptions_ServerSideProvider, hwnd[1], TRUE); + Provider2.frag_root = &Provider2.IRawElementProviderFragmentRoot_iface; + Provider2.ignore_hwnd_prop = TRUE; + + prov_root = &Provider.IRawElementProviderSimple_iface; + child_win_prov_root = &Provider2.IRawElementProviderSimple_iface; + + thread = CreateThread(NULL, 0, uia_proxy_provider_win_event_handler_test_thread, (void *)hwnd, 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); + + CoUninitialize(); + destroy_test_hwnd(hwnd[0], "ProxyProviderWinEventHandler test class", "ProxyProviderWinEventHandler test child class"); + UiaRegisterProviderCallback(NULL); +} + /* * Once a process returns a UI Automation provider with * UiaReturnRawElementProvider it ends up in an implicit MTA until exit. This @@ -16590,6 +17224,7 @@ START_TEST(uiautomation) test_UiaNodeFromFocus(); test_UiaAddEvent(argv[0]); test_UiaHasServerSideProvider(); + test_uia_event_ProxyProviderWinEventHandler(); if (uia_dll) { pUiaProviderFromIAccessible = (void *)GetProcAddress(uia_dll, "UiaProviderFromIAccessible");