From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 169 +++++++++++++++++++++ dlls/uiautomationcore/uia_com_client.c | 74 +++++++++ dlls/uiautomationcore/uia_event.c | 6 +- dlls/uiautomationcore/uia_private.h | 3 + 4 files changed, 248 insertions(+), 4 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 300fc434505..f34f020b814 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -582,6 +582,18 @@ static struct Accessible (acc)->expect_ ## method = (acc)->called_ ## method = 0; \ }while(0)
+#define CHECK_ACC_METHOD_CALLED_AT_LEAST(acc, method, num) \ + do { \ + ok((acc)->called_ ## method >= num, "expected %s_" #method " at least %d time(s) (got %d)\n", (acc)->interface_name, num, (acc)->called_ ## method); \ + (acc)->expect_ ## method = (acc)->called_ ## method = 0; \ + }while(0) + +#define CHECK_ACC_METHOD_CALLED_AT_MOST(acc, method, num) \ + do { \ + ok((acc)->called_ ## method <= num, "expected %s_" #method " at most %d time(s) (got %d)\n", (acc)->interface_name, num, (acc)->called_ ## method); \ + (acc)->expect_ ## method = (acc)->called_ ## method = 0; \ + }while(0) + static inline struct Accessible* impl_from_Accessible(IAccessible *iface) { return CONTAINING_RECORD(iface, struct Accessible, IAccessible_iface); @@ -15774,6 +15786,163 @@ static void test_uia_com_focus_change_event_handler_win_event_handling(IUIAutoma CHECK_CALLED(uia_com_event_callback); CHECK_CALLED(uia_event_callback);
+ /* + * Windows 7 has quirky behavior around MSAA proxy creation, skip tests. + */ + if (!UiaLookupId(AutomationIdentifierType_Property, &OptimizeForVisualContent_Property_GUID)) + { + win_skip("Skipping focus MSAA proxy tests for Win7\n"); + goto exit; + } + + /* + * Creates an MSAA proxy, raises event on that. + */ + prov_root = NULL; + set_accessible_props(&Accessible, ROLE_SYSTEM_CLIENT, STATE_SYSTEM_FOCUSED, 0, L"acc_name", 0, 0, 0, 0); + Accessible.ow_hwnd = test_hwnd; + Accessible.focus_child_id = CHILDID_SELF; + acc_client = &Accessible.IAccessible_iface; + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), test_hwnd); + add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd2", FALSE); + add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc2", FALSE); + add_provider_desc(&exp_node_desc, L"Main", NULL, TRUE); /* MSAA Proxy provider. */ + + set_multi_event_data(&exp_node_desc); + set_com_event_data(&exp_node_desc); + + set_uia_hwnd_expects(0, 1, 1, 4, 4); /* Win11 sends 4 WM_GETOBJECT messages, normally only 2. */ + SET_ACC_METHOD_EXPECT_MULTI(&Accessible, QI_IAccIdentity, 3); + SET_ACC_METHOD_EXPECT_MULTI(&Accessible, get_accParent, 3); + SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus); + SET_ACC_METHOD_EXPECT(&Accessible, get_accState); + NotifyWinEvent(EVENT_OBJECT_FOCUS, test_hwnd, OBJID_CLIENT, CHILDID_SELF); + ok(msg_wait_for_all_events(event_handles, 2, 5000) != WAIT_TIMEOUT, "Wait for event_handle(s) timed out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 1, FALSE, 1, FALSE, 1, FALSE, 1, FALSE); + todo_wine CHECK_ACC_METHOD_CALLED(&Accessible, QI_IAccIdentity); + todo_wine CHECK_ACC_METHOD_CALLED(&Accessible, get_accParent); + CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus); + CHECK_ACC_METHOD_CALLED(&Accessible, get_accState); + CHECK_CALLED(uia_com_event_callback); + CHECK_CALLED(uia_event_callback); + + /* + * Return Accessible_child2 from get_accFocus. + */ + set_accessible_props(&Accessible, ROLE_SYSTEM_CLIENT, STATE_SYSTEM_FOCUSED, 0, L"acc_name", 0, 0, 0, 0); + Accessible.focus_child_id = 4; /* 4 gets us Accessible_child2. */ + set_accessible_props(&Accessible_child2, ROLE_SYSTEM_DOCUMENT, STATE_SYSTEM_FOCUSED, 0, L"acc_child2", 0, 0, 0, 0); + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc, L"Main", NULL, TRUE); /* MSAA Proxy provider. */ + + set_multi_event_data(&exp_node_desc); + set_com_event_data(&exp_node_desc); + set_uia_hwnd_expects(0, 0, 0, 2, 3); /* Win11 sends 2/3 WM_GETOBJECT messages, normally only 1/2. */ + SET_ACC_METHOD_EXPECT_MULTI(&Accessible, QI_IAccIdentity, 4); /* Only done 4 times on Win11, normally 3. */ + SET_ACC_METHOD_EXPECT_MULTI(&Accessible, get_accParent, 3); /* Only done 3 times on Win11, normally 2. */ + SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus); + SET_ACC_METHOD_EXPECT(&Accessible, get_accChild); + SET_ACC_METHOD_EXPECT(&Accessible, get_accRole); + SET_ACC_METHOD_EXPECT(&Accessible_child2, QI_IAccIdentity); + SET_ACC_METHOD_EXPECT(&Accessible_child2, get_accFocus); + SET_ACC_METHOD_EXPECT(&Accessible_child2, get_accRole); + SET_ACC_METHOD_EXPECT(&Accessible_child2, accNavigate); /* Wine only, Windows doesn't pass this through the DA wrapper. */ + SET_ACC_METHOD_EXPECT_MULTI(&Accessible_child2, get_accParent, 2); + SET_ACC_METHOD_EXPECT_MULTI(&Accessible_child2, get_accState, 2); + NotifyWinEvent(EVENT_OBJECT_FOCUS, test_hwnd, OBJID_CLIENT, CHILDID_SELF); + ok(msg_wait_for_all_events(event_handles, 2, 5000) != WAIT_TIMEOUT, "Wait for event_handle(s) timed out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 0, FALSE, 0, FALSE, 1, FALSE, 2, FALSE); + todo_wine CHECK_ACC_METHOD_CALLED_AT_LEAST(&Accessible, QI_IAccIdentity, 3); + todo_wine CHECK_ACC_METHOD_CALLED_AT_LEAST(&Accessible, get_accParent, 2); + CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus); + CHECK_ACC_METHOD_CALLED(&Accessible, get_accChild); + CHECK_ACC_METHOD_CALLED(&Accessible, get_accRole); + todo_wine CHECK_ACC_METHOD_CALLED(&Accessible_child2, QI_IAccIdentity); + CHECK_ACC_METHOD_CALLED(&Accessible_child2, get_accFocus); + CHECK_ACC_METHOD_CALLED(&Accessible_child2, get_accRole); + CHECK_ACC_METHOD_CALLED_MULTI(&Accessible_child2, get_accParent, 2); + CHECK_ACC_METHOD_CALLED_MULTI(&Accessible_child2, get_accState, 2); + CHECK_ACC_METHOD_CALLED_AT_MOST(&Accessible_child2, accNavigate, 1); + CHECK_CALLED(uia_com_event_callback); + CHECK_CALLED(uia_event_callback); + + /* + * accFocus returns Accessible_child2, however it has + * STATE_SYSTEM_INVISIBLE set. Falls back to Accessible. + */ + set_accessible_props(&Accessible_child2, ROLE_SYSTEM_DOCUMENT, STATE_SYSTEM_INVISIBLE | STATE_SYSTEM_FOCUSED, 0, L"acc_child2", 0, 0, 0, 0); + set_accessible_props(&Accessible, ROLE_SYSTEM_CLIENT, STATE_SYSTEM_FOCUSED, 0, L"acc_name", 0, 0, 0, 0); + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), test_hwnd); + add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd2", FALSE); + add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc2", FALSE); + add_provider_desc(&exp_node_desc, L"Main", NULL, TRUE); /* MSAA Proxy provider. */ + + set_multi_event_data(&exp_node_desc); + set_com_event_data(&exp_node_desc); + set_uia_hwnd_expects(0, 1, 1, 4, 4); /* Win11 sends 4 WM_GETOBJECT messages, normally only 2. */ + SET_ACC_METHOD_EXPECT_MULTI(&Accessible, QI_IAccIdentity, 4); /* Only done 4 times on Win11, normally 2. */ + SET_ACC_METHOD_EXPECT_MULTI(&Accessible, get_accParent, 3); /* Only done 3 times on Win11, normally 1. */ + SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus); + SET_ACC_METHOD_EXPECT(&Accessible, get_accChild); + SET_ACC_METHOD_EXPECT(&Accessible, get_accState); + SET_ACC_METHOD_EXPECT(&Accessible_child2, accNavigate); /* Wine only, Windows doesn't pass this through the DA wrapper. */ + SET_ACC_METHOD_EXPECT(&Accessible_child2, QI_IAccIdentity); + SET_ACC_METHOD_EXPECT(&Accessible_child2, get_accParent); + SET_ACC_METHOD_EXPECT(&Accessible_child2, get_accState); + NotifyWinEvent(EVENT_OBJECT_FOCUS, test_hwnd, OBJID_CLIENT, CHILDID_SELF); + ok(msg_wait_for_all_events(event_handles, 2, 5000) != WAIT_TIMEOUT, "Wait for event_handle(s) timed out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 1, FALSE, 1, FALSE, 1, FALSE, 1, FALSE); + todo_wine CHECK_ACC_METHOD_CALLED_AT_LEAST(&Accessible, QI_IAccIdentity, 2); + todo_wine CHECK_ACC_METHOD_CALLED(&Accessible, get_accParent); + CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus); + CHECK_ACC_METHOD_CALLED(&Accessible, get_accChild); + CHECK_ACC_METHOD_CALLED(&Accessible, get_accState); + todo_wine CHECK_ACC_METHOD_CALLED(&Accessible_child2, QI_IAccIdentity); + CHECK_ACC_METHOD_CALLED(&Accessible_child2, get_accParent); + CHECK_ACC_METHOD_CALLED(&Accessible_child2, get_accState); + CHECK_ACC_METHOD_CALLED_AT_MOST(&Accessible_child2, accNavigate, 1); + CHECK_CALLED(uia_com_event_callback); + CHECK_CALLED(uia_event_callback); + + /* + * Get Accessible_child2 by raising an event with its child ID directly. + * It will have its get_accFocus method called. + */ + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc, L"Main", NULL, TRUE); /* MSAA Proxy provider. */ + + set_multi_event_data(&exp_node_desc); + set_com_event_data(&exp_node_desc); + set_uia_hwnd_expects(0, 0, 0, 2, 2); /* Win11 sends 2/2 WM_GETOBJECT messages, normally only 1/1. */ + SET_ACC_METHOD_EXPECT_MULTI(&Accessible, QI_IAccIdentity, 2); /* Only done 2 times on Win11, normally 1. */ + SET_ACC_METHOD_EXPECT(&Accessible, get_accParent); /* Only done on Win11. */ + SET_ACC_METHOD_EXPECT(&Accessible, get_accChild); + SET_ACC_METHOD_EXPECT(&Accessible_child2, QI_IAccIdentity); + SET_ACC_METHOD_EXPECT(&Accessible_child2, get_accFocus); + SET_ACC_METHOD_EXPECT(&Accessible_child2, get_accState); + SET_ACC_METHOD_EXPECT_MULTI(&Accessible_child2, get_accParent, 2); + NotifyWinEvent(EVENT_OBJECT_FOCUS, test_hwnd, OBJID_CLIENT, 4); + ok(msg_wait_for_all_events(event_handles, 2, 5000) != WAIT_TIMEOUT, "Wait for event_handle(s) timed out.\n"); + if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n"); + check_uia_hwnd_expects_at_least(0, FALSE, 0, FALSE, 0, FALSE, 1, FALSE, 1, FALSE); + CHECK_ACC_METHOD_CALLED_AT_MOST(&Accessible, get_accParent, 1); /* Only done on Win11. */ + todo_wine CHECK_ACC_METHOD_CALLED(&Accessible, QI_IAccIdentity); + CHECK_ACC_METHOD_CALLED(&Accessible, get_accChild); + todo_wine CHECK_ACC_METHOD_CALLED(&Accessible_child2, QI_IAccIdentity); + CHECK_ACC_METHOD_CALLED(&Accessible_child2, get_accFocus); + CHECK_ACC_METHOD_CALLED(&Accessible_child2, get_accState); + CHECK_ACC_METHOD_CALLED_AT_MOST(&Accessible_child2, get_accParent, 2); + CHECK_CALLED(uia_com_event_callback); + CHECK_CALLED(uia_event_callback); + acc_client = NULL; + +exit: set_uia_hwnd_expects(0, 1, 1, 0, 0); hr = IUIAutomation_RemoveFocusChangedEventHandler(uia_iface, &FocusChangedHandler.IUIAutomationFocusChangedEventHandler_iface); diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index d78b5c5366d..d138d4ed919 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -1079,6 +1079,78 @@ static void uia_com_focus_win_event_handler(HUIANODE node, HWND hwnd, struct uia VariantClear(&v); }
+static HRESULT uia_com_focus_win_event_msaa_callback(struct uia_event *event, void *user_data) +{ + struct uia_event_args args = { { EventArgsType_Simple, UIA_AutomationFocusChangedEventId }, 0 }; + HUIANODE node = (HUIANODE)user_data; + + /* Only match desktop events. */ + if (!event->desktop_subtree_event) + return S_OK; + + return uia_event_invoke(node, NULL, &args, event); +} + +static void uia_com_focus_win_event_msaa_handler(HWND hwnd, LONG child_id) +{ + IRawElementProviderFragmentRoot *elroot; + IRawElementProviderFragment *elfrag; + IRawElementProviderSimple *elprov; + HRESULT hr; + VARIANT v; + + hr = create_msaa_provider_from_hwnd(hwnd, child_id, &elprov); + if (FAILED(hr)) + { + WARN("create_msaa_provider_from_hwnd failed with hr %#lx\n", hr); + return; + } + + hr = IRawElementProviderSimple_QueryInterface(elprov, &IID_IRawElementProviderFragmentRoot, (void **)&elroot); + if (FAILED(hr)) + goto exit; + + hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag); + IRawElementProviderFragmentRoot_Release(elroot); + if (FAILED(hr)) + goto exit; + + if (elfrag) + { + IRawElementProviderSimple *elprov2; + + hr = IRawElementProviderFragment_QueryInterface(elfrag, &IID_IRawElementProviderSimple, (void **)&elprov2); + IRawElementProviderFragment_Release(elfrag); + if (FAILED(hr)) + goto exit; + + IRawElementProviderSimple_Release(elprov); + elprov = elprov2; + } + + VariantInit(&v); + hr = IRawElementProviderSimple_GetPropertyValue(elprov, UIA_HasKeyboardFocusPropertyId, &v); + if (FAILED(hr)) + goto exit; + + if (V_VT(&v) == VT_BOOL && V_BOOL(&v) == VARIANT_TRUE) + { + HUIANODE node; + + hr = create_uia_node_from_elprov(elprov, &node, TRUE, 0); + if (SUCCEEDED(hr)) + { + hr = uia_event_for_each(UIA_AutomationFocusChangedEventId, uia_com_focus_win_event_msaa_callback, (void *)node, TRUE); + if (FAILED(hr)) + WARN("uia_event_for_each failed with hr %#lx\n", hr); + UiaNodeRelease(node); + } + } + +exit: + IRawElementProviderSimple_Release(elprov); +} + HRESULT uia_com_win_event_callback(DWORD event_id, HWND hwnd, LONG obj_id, LONG child_id, DWORD thread_id, DWORD event_time) { LONG handler_count; @@ -1168,6 +1240,8 @@ HRESULT uia_com_win_event_callback(DWORD event_id, HWND hwnd, LONG obj_id, LONG hr = create_uia_node_from_hwnd(hwnd, &node, NODE_FLAG_IGNORE_CLIENTSIDE_HWND_PROVS); if (SUCCEEDED(hr)) uia_com_focus_win_event_handler(node, hwnd, event_id_map); + else + uia_com_focus_win_event_msaa_handler(hwnd, child_id);
UiaNodeRelease(node); } diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 15b3b84d96b..0106be09999 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -587,8 +587,6 @@ static struct uia_queue_event *uia_event_queue_pop(struct list *event_queue) return queue_event; }
-static HRESULT uia_event_invoke(HUIANODE node, HUIANODE nav_start_node, struct uia_event_args *args, - struct uia_event *event); static HRESULT uia_raise_clientside_event(struct uia_queue_uia_event *event) { HUIANODE node, nav_start_node; @@ -676,7 +674,7 @@ static BOOL uia_win_event_hwnd_map_contains_ancestors(struct rb_tree *hwnd_map, return FALSE; }
-static HRESULT create_msaa_provider_from_hwnd(HWND hwnd, int in_child_id, IRawElementProviderSimple **ret_elprov) +HRESULT create_msaa_provider_from_hwnd(HWND hwnd, int in_child_id, IRawElementProviderSimple **ret_elprov) { IRawElementProviderSimple *elprov; IAccessible *acc; @@ -1733,7 +1731,7 @@ HRESULT WINAPI UiaRemoveEvent(HUIAEVENT huiaevent) return S_OK; }
-static HRESULT uia_event_invoke(HUIANODE node, HUIANODE nav_start_node, struct uia_event_args *args, struct uia_event *event) +HRESULT uia_event_invoke(HUIANODE node, HUIANODE nav_start_node, struct uia_event_args *args, struct uia_event *event) { HRESULT hr = S_OK;
diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 00ed1f8f293..a32d0c4d884 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -241,6 +241,7 @@ HRESULT uia_event_add_win_event_hwnd(struct uia_event *event, HWND hwnd) DECLSPE HRESULT uia_event_for_each(int event_id, UiaWineEventForEachCallback *callback, void *user_data, BOOL clientside_only) DECLSPEC_HIDDEN; BOOL uia_clientside_event_start_event_thread(struct uia_event *event) DECLSPEC_HIDDEN; +HRESULT create_msaa_provider_from_hwnd(HWND hwnd, int in_child_id, IRawElementProviderSimple **ret_elprov) 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; @@ -249,6 +250,8 @@ HRESULT uia_event_advise_node(struct uia_event *event, HUIANODE node) DECLSPEC_H 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_invoke(HUIANODE node, HUIANODE nav_start_node, struct uia_event_args *args, + struct uia_event *event) 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;