From 163e9c5b453b3a4255deb4fd1d8da73866a3c0a1 Mon Sep 17 00:00:00 2001 From: Connor McAdams Date: Wed, 25 May 2022 12:21:40 -0400 Subject: [PATCH] uiautomationcore: Implement NavigateDirection_{Previous/Next}Sibling for MSAA providers. Signed-off-by: Connor McAdams --- dlls/uiautomationcore/tests/uiautomation.c | 132 +++++++++---------- dlls/uiautomationcore/uia_provider.c | 139 +++++++++++++++++++-- 2 files changed, 194 insertions(+), 77 deletions(-) diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index da19d3668fa..15d7b4e784b 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -1112,13 +1112,13 @@ static void test_uia_prov_from_acc_navigation(void) elfrag2 = (void *)0xdeadbeef; hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2); ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ok(!elfrag2, "elfrag2 != NULL\n"); elfrag2 = (void *)0xdeadbeef; hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_PreviousSibling, &elfrag2); ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ok(!elfrag2, "elfrag2 != NULL\n"); /* @@ -1149,18 +1149,16 @@ static void test_uia_prov_from_acc_navigation(void) SET_EXPECT(Accessible_get_accChild); SET_EXPECT(Accessible_get_accState); hr = IRawElementProviderFragment_Navigate(elfrag2, NavigateDirection_NextSibling, &elfrag3); - todo_wine ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(!!elfrag3, "elfrag2 == NULL\n"); - todo_wine CHECK_CALLED(Accessible_get_accChildCount); - todo_wine CHECK_CALLED(Accessible_get_accChild); - todo_wine CHECK_CALLED(Accessible_get_accState); - if (elfrag3) - { - check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3); - IRawElementProviderFragment_Release(elfrag3); - ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref); - } + ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!elfrag3, "elfrag2 == NULL\n"); + CHECK_CALLED(Accessible_get_accChildCount); + CHECK_CALLED(Accessible_get_accChild); + CHECK_CALLED(Accessible_get_accState); + check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3); + + IRawElementProviderFragment_Release(elfrag3); + ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref); IRawElementProviderFragment_Release(elfrag2); ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref); ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref); @@ -1206,19 +1204,16 @@ static void test_uia_prov_from_acc_navigation(void) SET_EXPECT(Accessible_get_accChild); SET_EXPECT(Accessible_get_accState); hr = IRawElementProviderFragment_Navigate(elfrag2, NavigateDirection_PreviousSibling, &elfrag3); - todo_wine ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(!!elfrag3, "elfrag2 == NULL\n"); - todo_wine CHECK_CALLED(Accessible_get_accChildCount); - todo_wine CHECK_CALLED(Accessible_get_accChild); - todo_wine CHECK_CALLED(Accessible_get_accState); - if (elfrag3) - { - check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3); - IRawElementProviderFragment_Release(elfrag3); - ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref); - } + ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!elfrag3, "elfrag2 == NULL\n"); + CHECK_CALLED(Accessible_get_accChildCount); + CHECK_CALLED(Accessible_get_accChild); + CHECK_CALLED(Accessible_get_accState); + check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3); + IRawElementProviderFragment_Release(elfrag3); + ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref); IRawElementProviderFragment_Release(elfrag2); ok(Accessible_child2.ref == 1, "Unexpected refcnt %ld\n", Accessible_child2.ref); ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref); @@ -1338,21 +1333,21 @@ static void test_uia_prov_from_acc_navigation(void) SET_EXPECT(Accessible_child2_accLocation); SET_EXPECT(Accessible_child2_get_accName); hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ok(!elfrag2, "elfrag2 != NULL\n"); - todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 5); - todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 4); - todo_wine CHECK_CALLED(Accessible_child_get_accParent); - todo_wine CHECK_CALLED(Accessible_child_get_accRole); - todo_wine CHECK_CALLED(Accessible_child_get_accState); - todo_wine CHECK_CALLED(Accessible_child_get_accChildCount); - todo_wine CHECK_CALLED(Accessible_child_accLocation); - todo_wine CHECK_CALLED(Accessible_child_get_accName); - todo_wine CHECK_CALLED(Accessible_child2_get_accRole); - todo_wine CHECK_CALLED(Accessible_child2_get_accState); - todo_wine CHECK_CALLED(Accessible_child2_get_accChildCount); - todo_wine CHECK_CALLED(Accessible_child2_accLocation); - todo_wine CHECK_CALLED(Accessible_child2_get_accName); + CHECK_CALLED_MULTI(Accessible_get_accChildCount, 5); + CHECK_CALLED_MULTI(Accessible_get_accChild, 4); + CHECK_CALLED(Accessible_child_get_accParent); + CHECK_CALLED(Accessible_child_get_accRole); + CHECK_CALLED(Accessible_child_get_accState); + CHECK_CALLED(Accessible_child_get_accChildCount); + CHECK_CALLED(Accessible_child_accLocation); + CHECK_CALLED(Accessible_child_get_accName); + CHECK_CALLED(Accessible_child2_get_accRole); + CHECK_CALLED(Accessible_child2_get_accState); + CHECK_CALLED(Accessible_child2_get_accChildCount); + CHECK_CALLED(Accessible_child2_accLocation); + CHECK_CALLED(Accessible_child2_get_accName); /* Now they have a role mismatch, we can determine our position. */ set_accessible_props(&Accessible_child2, ROLE_SYSTEM_DOCUMENT, STATE_SYSTEM_FOCUSABLE, 1, @@ -1369,27 +1364,27 @@ static void test_uia_prov_from_acc_navigation(void) * Even though we didn't get a new fragment, now that we know our * position, a reference is added to the parent IAccessible. */ - todo_wine ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ok(!elfrag2, "elfrag2 != NULL\n"); - todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 6); - todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 5); - todo_wine CHECK_CALLED(Accessible_get_accState); - todo_wine CHECK_CALLED(Accessible_child_get_accParent); - todo_wine CHECK_CALLED(Accessible_child_get_accRole); - todo_wine CHECK_CALLED(Accessible_child2_get_accRole); + CHECK_CALLED_MULTI(Accessible_get_accChildCount, 6); + CHECK_CALLED_MULTI(Accessible_get_accChild, 5); + CHECK_CALLED(Accessible_get_accState); + CHECK_CALLED(Accessible_child_get_accParent); + CHECK_CALLED(Accessible_child_get_accRole); + CHECK_CALLED(Accessible_child2_get_accRole); /* Now that we know our position, no extra nav work. */ SET_EXPECT(Accessible_get_accChildCount); SET_EXPECT(Accessible_get_accChild); SET_EXPECT(Accessible_get_accState); hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2); - todo_wine ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(!!elfrag2, "elfrag2 == NULL\n"); - todo_wine CHECK_CALLED(Accessible_get_accChildCount); - todo_wine CHECK_CALLED(Accessible_get_accChild); - todo_wine CHECK_CALLED(Accessible_get_accState); + ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!elfrag2, "elfrag2 == NULL\n"); + CHECK_CALLED(Accessible_get_accChildCount); + CHECK_CALLED(Accessible_get_accChild); + CHECK_CALLED(Accessible_get_accState); if (elfrag2) { check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, 3); @@ -1449,23 +1444,20 @@ static void test_uia_prov_from_acc_navigation(void) SET_EXPECT(Accessible_child_accNavigate); SET_EXPECT(Accessible_child_get_accParent); hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2); - todo_wine ok(Accessible_child.ref == 2, "Unexpected refcnt %ld\n", Accessible_child.ref); - todo_wine ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(!!elfrag2, "elfrag2 == NULL\n"); - todo_wine CHECK_CALLED(Accessible_get_accChildCount); - todo_wine CHECK_CALLED(Accessible_get_accChild); - todo_wine CHECK_CALLED(Accessible_child_get_accState); - todo_wine CHECK_CALLED(Accessible_child_accNavigate); - todo_wine CHECK_CALLED(Accessible_child_get_accParent); - if (elfrag2) - { - check_fragment_acc(elfrag2, &Accessible_child.IAccessible_iface, CHILDID_SELF); - IRawElementProviderFragment_Release(elfrag2); - ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref); - ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref); - } + ok(Accessible_child.ref == 2, "Unexpected refcnt %ld\n", Accessible_child.ref); + ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!elfrag2, "elfrag2 == NULL\n"); + CHECK_CALLED(Accessible_get_accChildCount); + CHECK_CALLED(Accessible_get_accChild); + CHECK_CALLED(Accessible_child_get_accState); + CHECK_CALLED(Accessible_child_accNavigate); + CHECK_CALLED(Accessible_child_get_accParent); + check_fragment_acc(elfrag2, &Accessible_child.IAccessible_iface, CHILDID_SELF); + IRawElementProviderFragment_Release(elfrag2); + ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref); + ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref); IRawElementProviderFragment_Release(elfrag); IRawElementProviderSimple_Release(elprov); ok(Accessible.ref == 1, "Unexpected refcnt %ld\n", Accessible.ref); diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c index af093527a58..11c8e17b766 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -223,7 +223,7 @@ static HRESULT msaa_acc_get_parent(IAccessible *acc, IAccessible **parent) #define DIR_FORWARD 0 #define DIR_REVERSE 1 static HRESULT msaa_acc_get_next_child(IAccessible *acc, LONG start_pos, LONG direction, - IAccessible **child, LONG *child_id, LONG *child_pos) + IAccessible **child, LONG *child_id, LONG *child_pos, BOOL check_visible) { LONG child_count, cur_pos; IDispatch *disp; @@ -246,7 +246,7 @@ static HRESULT msaa_acc_get_next_child(IAccessible *acc, LONG start_pos, LONG di if (hr == S_FALSE) { - if (!msaa_check_acc_state(acc, cid, STATE_SYSTEM_INVISIBLE)) + if (!check_visible || !msaa_check_acc_state(acc, cid, STATE_SYSTEM_INVISIBLE)) { *child = acc; *child_id = *child_pos = cur_pos; @@ -264,7 +264,7 @@ static HRESULT msaa_acc_get_next_child(IAccessible *acc, LONG start_pos, LONG di break; variant_init_i4(&cid, CHILDID_SELF); - if (!msaa_check_acc_state(acc_child, cid, STATE_SYSTEM_INVISIBLE)) + if (!check_visible || !msaa_check_acc_state(acc_child, cid, STATE_SYSTEM_INVISIBLE)) { *child = acc_child; *child_id = CHILDID_SELF; @@ -287,6 +287,86 @@ static HRESULT msaa_acc_get_next_child(IAccessible *acc, LONG start_pos, LONG di return hr; } +static HRESULT msaa_acc_get_child_pos(IAccessible *acc, IAccessible **out_parent, + LONG *out_child_pos) +{ + LONG child_count, child_id, child_pos, match_pos; + IAccessible *child, *parent, *match, **children; + HRESULT hr; + int i; + + *out_parent = NULL; + *out_child_pos = 0; + hr = msaa_acc_get_parent(acc, &parent); + if (FAILED(hr) || !parent) + return hr; + + hr = IAccessible_get_accChildCount(parent, &child_count); + if (FAILED(hr) || !child_count) + { + IAccessible_Release(parent); + return hr; + } + + children = heap_alloc_zero(sizeof(*children) * child_count); + if (!children) + return E_OUTOFMEMORY; + + match = NULL; + for (i = 0; i < child_count; i++) + { + hr = msaa_acc_get_next_child(parent, i + 1, DIR_FORWARD, &child, &child_id, &child_pos, FALSE); + if (FAILED(hr) || !child) + goto exit; + + if (child != parent) + children[i] = child; + else + IAccessible_Release(child); + } + + for (i = 0; i < child_count; i++) + { + if (!children[i]) + continue; + + if (msaa_acc_compare(acc, children[i])) + { + if (!match) + { + match = children[i]; + match_pos = i + 1; + } + /* Can't have more than one IAccessible match. */ + else + { + match = NULL; + match_pos = 0; + break; + } + } + } + +exit: + if (match) + { + *out_parent = parent; + *out_child_pos = match_pos; + } + else + IAccessible_Release(parent); + + for (i = 0; i < child_count; i++) + { + if (children[i]) + IAccessible_Release(children[i]); + } + + heap_free(children); + + return hr; +} + static LONG msaa_role_to_uia_control_type(LONG role) { switch (role) @@ -649,10 +729,10 @@ static HRESULT WINAPI msaa_fragment_Navigate(IRawElementProviderFragment *iface, if (direction == NavigateDirection_FirstChild) hr = msaa_acc_get_next_child(msaa_prov->acc, 1, DIR_FORWARD, &acc, &child_id, - &child_pos); + &child_pos, TRUE); else hr = msaa_acc_get_next_child(msaa_prov->acc, child_count, DIR_REVERSE, &acc, &child_id, - &child_pos); + &child_pos, TRUE); if (FAILED(hr) || !acc) break; @@ -676,8 +756,53 @@ static HRESULT WINAPI msaa_fragment_Navigate(IRawElementProviderFragment *iface, case NavigateDirection_NextSibling: case NavigateDirection_PreviousSibling: - FIXME("Unimplemented NavigateDirection %d\n", direction); - return E_NOTIMPL; + if (msaa_check_root_acc(msaa_prov)) + break; + + if (!msaa_prov->parent) + { + if (V_I4(&msaa_prov->cid) != CHILDID_SELF) + { + msaa_prov->parent = msaa_prov->acc; + IAccessible_AddRef(msaa_prov->acc); + msaa_prov->child_pos = V_I4(&msaa_prov->cid); + } + else + { + hr = msaa_acc_get_child_pos(msaa_prov->acc, &acc, &child_pos); + if (FAILED(hr) || !acc) + break; + msaa_prov->parent = acc; + msaa_prov->child_pos = child_pos; + } + } + + if (direction == NavigateDirection_NextSibling) + hr = msaa_acc_get_next_child(msaa_prov->parent, msaa_prov->child_pos + 1, DIR_FORWARD, + &acc, &child_id, &child_pos, TRUE); + else + hr = msaa_acc_get_next_child(msaa_prov->parent, msaa_prov->child_pos - 1, DIR_REVERSE, + &acc, &child_id, &child_pos, TRUE); + + if (FAILED(hr) || !acc) + break; + + hr = UiaProviderFromIAccessible(acc, child_id, 0, &elprov); + if (SUCCEEDED(hr)) + { + struct msaa_provider *prov = impl_from_msaa_provider(elprov); + + *ret_val = &prov->IRawElementProviderFragment_iface; + prov->parent = msaa_prov->parent; + IAccessible_AddRef(msaa_prov->parent); + if (acc != msaa_prov->acc) + prov->child_pos = child_pos; + else + prov->child_pos = child_id; + } + IAccessible_Release(acc); + + break; default: FIXME("Invalid NavigateDirection %d\n", direction);