-- v2: uiautomationcore: Add normalization navigation to UiaGetUpdatedCache. uiautomationcore: Implement conditional NavigateDirection_Parent navigation.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 62 +++++++++++++++++++++ dlls/uiautomationcore/uia_client.c | 10 ++++ dlls/uiautomationcore/uiautomationcore.spec | 2 +- include/uiautomationcoreapi.h | 1 + 4 files changed, 74 insertions(+), 1 deletion(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 3ec275a874c..a3153eff7ab 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -12792,6 +12792,67 @@ static void test_default_clientside_providers(void) CoUninitialize(); }
+static void test_UiaGetRootNode(void) +{ + HUIANODE node; + HRESULT hr; + VARIANT v; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + UiaRegisterProviderCallback(test_uia_provider_callback); + + /* + * UiaGetRootNode is the same as calling UiaNodeFromHandle with the + * desktop window handle. + */ + initialize_provider(&Provider_hwnd, ProviderOptions_ClientSideProvider, GetDesktopWindow(), TRUE); + initialize_provider(&Provider_nc, ProviderOptions_ClientSideProvider | ProviderOptions_NonClientAreaProvider, + GetDesktopWindow(), TRUE); + initialize_provider(&Provider_proxy, ProviderOptions_ClientSideProvider, GetDesktopWindow(), TRUE); + Provider_proxy.ignore_hwnd_prop = TRUE; + + base_hwnd_prov = &Provider_hwnd.IRawElementProviderSimple_iface; + proxy_prov = &Provider_proxy.IRawElementProviderSimple_iface; + nc_prov = &Provider_nc.IRawElementProviderSimple_iface; + + method_sequences_enabled = FALSE; + SET_EXPECT(prov_callback_base_hwnd); + SET_EXPECT(prov_callback_nonclient); + SET_EXPECT(prov_callback_proxy); + hr = UiaGetRootNode(&node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!node, "Node == NULL.\n"); + ok(Provider_proxy.ref == 2, "Unexpected refcnt %ld\n", Provider_proxy.ref); + ok(Provider_hwnd.ref == 2, "Unexpected refcnt %ld\n", Provider_hwnd.ref); + ok(Provider_nc.ref == 2, "Unexpected refcnt %ld\n", Provider_nc.ref); + CHECK_CALLED(prov_callback_base_hwnd); + CHECK_CALLED(prov_callback_nonclient); + CHECK_CALLED(prov_callback_proxy); + + hr = UiaGetPropertyValue(node, UIA_ProviderDescriptionPropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), GetDesktopWindow()); + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_proxy", TRUE); + check_node_provider_desc(V_BSTR(&v), L"Hwnd", L"Provider_hwnd", FALSE); + check_node_provider_desc(V_BSTR(&v), L"Nonclient", L"Provider_nc", FALSE); + VariantClear(&v); + + ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + ok(Provider_proxy.ref == 1, "Unexpected refcnt %ld\n", Provider_proxy.ref); + ok(Provider_hwnd.ref == 1, "Unexpected refcnt %ld\n", Provider_hwnd.ref); + ok(Provider_nc.ref == 1, "Unexpected refcnt %ld\n", Provider_nc.ref); + + initialize_provider(&Provider_hwnd, ProviderOptions_ClientSideProvider, NULL, TRUE); + initialize_provider(&Provider_nc, ProviderOptions_ClientSideProvider | ProviderOptions_NonClientAreaProvider, NULL, + TRUE); + initialize_provider(&Provider_proxy, ProviderOptions_ClientSideProvider, NULL, TRUE); + base_hwnd_prov = proxy_prov = nc_prov = NULL; + + method_sequences_enabled = TRUE; + UiaRegisterProviderCallback(NULL); + CoUninitialize(); +} + /* * Once a process returns a UI Automation provider with * UiaReturnRawElementProvider it ends up in an implicit MTA until exit. This @@ -12860,6 +12921,7 @@ START_TEST(uiautomation) test_UiaFind(); test_CUIAutomation(); test_default_clientside_providers(); + test_UiaGetRootNode(); if (uia_dll) { pUiaProviderFromIAccessible = (void *)GetProcAddress(uia_dll, "UiaProviderFromIAccessible"); diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index e3eb5cd0561..4ec12423533 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -2368,6 +2368,16 @@ HRESULT WINAPI UiaNodeFromHandle(HWND hwnd, HUIANODE *huianode) return S_OK; }
+/*********************************************************************** + * UiaGetRootNode (uiautomationcore.@) + */ +HRESULT WINAPI UiaGetRootNode(HUIANODE *huianode) +{ + TRACE("(%p)\n", huianode); + + return UiaNodeFromHandle(GetDesktopWindow(), huianode); +} + /*********************************************************************** * UiaNodeRelease (uiautomationcore.@) */ diff --git a/dlls/uiautomationcore/uiautomationcore.spec b/dlls/uiautomationcore/uiautomationcore.spec index 7168135487b..34856be6b36 100644 --- a/dlls/uiautomationcore/uiautomationcore.spec +++ b/dlls/uiautomationcore/uiautomationcore.spec @@ -65,7 +65,7 @@ @ stdcall UiaGetPropertyValue(ptr long ptr) @ stdcall UiaGetReservedMixedAttributeValue(ptr) @ stdcall UiaGetReservedNotSupportedValue(ptr) -@ stub UiaGetRootNode +@ stdcall UiaGetRootNode(ptr) @ stdcall UiaGetRuntimeId(ptr ptr) @ stdcall UiaGetUpdatedCache(ptr ptr long ptr ptr ptr) @ stub UiaHPatternObjectFromVariant diff --git a/include/uiautomationcoreapi.h b/include/uiautomationcoreapi.h index 1d78113e1fd..320bb814b9c 100644 --- a/include/uiautomationcoreapi.h +++ b/include/uiautomationcoreapi.h @@ -544,6 +544,7 @@ BOOL WINAPI UiaNodeRelease(HUIANODE huianode); HRESULT WINAPI UiaGetRuntimeId(HUIANODE huianode, SAFEARRAY **runtime_id); HRESULT WINAPI UiaHUiaNodeFromVariant(VARIANT *in_val, HUIANODE *huianode); HRESULT WINAPI UiaNodeFromHandle(HWND hwnd, HUIANODE *huianode); +HRESULT WINAPI UiaGetRootNode(HUIANODE *huianode); HRESULT WINAPI UiaDisconnectProvider(IRawElementProviderSimple *elprov); HRESULT WINAPI UiaGetUpdatedCache(HUIANODE huianode, struct UiaCacheRequest *cache_req, enum NormalizeState normalize_state, struct UiaCondition *normalize_cond, SAFEARRAY **out_req, BSTR *tree_struct);
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 60 ++++++++++++++++++++++ dlls/uiautomationcore/uia_com_client.c | 17 +++++- 2 files changed, 75 insertions(+), 2 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index a3153eff7ab..25b975fa280 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -12199,6 +12199,65 @@ static void test_CUIAutomation_TreeWalker_ifaces(IUIAutomation *uia_iface) UnregisterClassA("test_CUIAutomation_TreeWalker_ifaces class", NULL); }
+static void test_GetRootElement(IUIAutomation *uia_iface) +{ + IUIAutomationElement *element; + HRESULT hr; + VARIANT v; + + hr = IUIAutomation_GetRootElement(uia_iface, NULL); + ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + + UiaRegisterProviderCallback(test_uia_provider_callback); + + initialize_provider(&Provider_hwnd, ProviderOptions_ClientSideProvider, GetDesktopWindow(), TRUE); + initialize_provider(&Provider_nc, ProviderOptions_ClientSideProvider | ProviderOptions_NonClientAreaProvider, + GetDesktopWindow(), TRUE); + initialize_provider(&Provider_proxy, ProviderOptions_ClientSideProvider, GetDesktopWindow(), TRUE); + Provider_proxy.ignore_hwnd_prop = TRUE; + + base_hwnd_prov = &Provider_hwnd.IRawElementProviderSimple_iface; + proxy_prov = &Provider_proxy.IRawElementProviderSimple_iface; + nc_prov = &Provider_nc.IRawElementProviderSimple_iface; + + /* Retrieve an element representing the desktop HWND. */ + method_sequences_enabled = FALSE; + SET_EXPECT(prov_callback_base_hwnd); + SET_EXPECT(prov_callback_nonclient); + SET_EXPECT(prov_callback_proxy); + hr = IUIAutomation_GetRootElement(uia_iface, &element); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!element, "Node == NULL.\n"); + ok(Provider_proxy.ref == 2, "Unexpected refcnt %ld\n", Provider_proxy.ref); + ok(Provider_hwnd.ref == 2, "Unexpected refcnt %ld\n", Provider_hwnd.ref); + ok(Provider_nc.ref == 2, "Unexpected refcnt %ld\n", Provider_nc.ref); + CHECK_CALLED(prov_callback_base_hwnd); + CHECK_CALLED(prov_callback_nonclient); + CHECK_CALLED(prov_callback_proxy); + + hr = IUIAutomationElement_GetCurrentPropertyValueEx(element, UIA_ProviderDescriptionPropertyId, TRUE, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), GetDesktopWindow()); + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_proxy", TRUE); + check_node_provider_desc(V_BSTR(&v), L"Hwnd", L"Provider_hwnd", FALSE); + check_node_provider_desc(V_BSTR(&v), L"Nonclient", L"Provider_nc", FALSE); + VariantClear(&v); + + IUIAutomationElement_Release(element); + ok(Provider_proxy.ref == 1, "Unexpected refcnt %ld\n", Provider_proxy.ref); + ok(Provider_hwnd.ref == 1, "Unexpected refcnt %ld\n", Provider_hwnd.ref); + ok(Provider_nc.ref == 1, "Unexpected refcnt %ld\n", Provider_nc.ref); + + initialize_provider(&Provider_hwnd, ProviderOptions_ClientSideProvider, NULL, TRUE); + initialize_provider(&Provider_nc, ProviderOptions_ClientSideProvider | ProviderOptions_NonClientAreaProvider, NULL, + TRUE); + initialize_provider(&Provider_proxy, ProviderOptions_ClientSideProvider, NULL, TRUE); + base_hwnd_prov = proxy_prov = nc_prov = NULL; + + method_sequences_enabled = TRUE; + UiaRegisterProviderCallback(NULL); +} + struct uia_com_classes { const GUID *clsid; const GUID *iid; @@ -12305,6 +12364,7 @@ static void test_CUIAutomation(void) test_Element_GetPropertyValue(uia_iface); test_Element_cache_methods(uia_iface); test_Element_Find(uia_iface); + test_GetRootElement(uia_iface);
IUIAutomation_Release(uia_iface); CoUninitialize(); diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index f1c28e81aa8..6cba9fd46db 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -2776,8 +2776,21 @@ static HRESULT WINAPI uia_iface_CompareRuntimeIds(IUIAutomation6 *iface, SAFEARR
static HRESULT WINAPI uia_iface_GetRootElement(IUIAutomation6 *iface, IUIAutomationElement **root) { - FIXME("%p, %p: stub\n", iface, root); - return E_NOTIMPL; + struct uia_iface *uia_iface = impl_from_IUIAutomation6(iface); + HUIANODE node; + HRESULT hr; + + TRACE("%p, %p\n", iface, root); + + if (!root) + return E_POINTER; + + *root = NULL; + hr = UiaGetRootNode(&node); + if (FAILED(hr)) + return hr; + + return create_uia_element(root, uia_iface->is_cui8, node); }
static HRESULT WINAPI uia_iface_ElementFromHandle(IUIAutomation6 *iface, UIA_HWND hwnd, IUIAutomationElement **out_elem)
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 106 +++++++++++++++++++++ dlls/uiautomationcore/uia_client.c | 72 +++++++++++--- 2 files changed, 166 insertions(+), 12 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 25b975fa280..193f1470029 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -1162,6 +1162,8 @@ static struct Provider Provider_hwnd, Provider_nc, Provider_proxy, Provider_prox static void initialize_provider(struct Provider *prov, int prov_opts, HWND hwnd, BOOL initialize_nav_links); static void set_provider_prop_override(struct Provider *prov, struct Provider_prop_override *override, int count); static void set_property_override(struct Provider_prop_override *override, int prop_id, VARIANT *val); +static void initialize_provider_tree(BOOL initialize_nav_links); +static void provider_add_child(struct Provider *prov, struct Provider *child);
static const WCHAR *uia_bstr_prop_str = L"uia-string"; static const ULONG uia_i4_prop_val = 0xdeadbeef; @@ -8659,6 +8661,40 @@ static const struct prov_method_sequence nav_seq14[] = { { 0 } };
+static const struct prov_method_sequence nav_seq15[] = { + NODE_CREATE_SEQ(&Provider_child2_child_child), + { &Provider_child2_child_child, PROV_GET_PROPERTY_VALUE }, /* UIA_ProviderDescriptionPropertyId */ +}; + +static const struct prov_method_sequence nav_seq16[] = { + { &Provider_child2_child_child, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child2_child), + { &Provider_child2_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child2_child, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + /* Only done on Win10v1507 and below. */ + { &Provider, FRAG_NAVIGATE, METHOD_OPTIONAL }, /* NavigateDirection_Parent */ + { 0 } +}; + +static const struct prov_method_sequence nav_seq17[] = { + { &Provider_child2_child_child, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child2_child), + { &Provider_child2_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child2_child, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_ProviderDescriptionPropertyId */ + { 0 } +}; + static void set_provider_nav_ifaces(struct Provider *prov, struct Provider *parent, struct Provider *frag_root, struct Provider *prev_sibling, struct Provider *next_sibling, struct Provider *first_child, struct Provider *last_child) @@ -8686,8 +8722,10 @@ static void set_provider_nav_ifaces(struct Provider *prov, struct Provider *pare
static void test_UiaNavigate(void) { + struct Provider_prop_override prop_override; LONG exp_lbound[2], exp_elems[2], idx[2], i; struct node_provider_desc exp_node_desc[4]; + struct UiaPropertyCondition prop_cond; struct UiaCacheRequest cache_req; HUIANODE node, node2, node3; SAFEARRAY *out_req; @@ -9195,6 +9233,74 @@ static void test_UiaNavigate(void) prov_root = NULL; UiaRegisterProviderCallback(NULL);
+ /* + * Conditional navigation for conditions other than ConditionType_True. + */ + initialize_provider_tree(TRUE); + Provider.frag_root = &Provider.IRawElementProviderFragmentRoot_iface; + provider_add_child(&Provider, &Provider_child2); + provider_add_child(&Provider_child2, &Provider_child2_child); + provider_add_child(&Provider_child2_child, &Provider_child2_child_child); + + hr = UiaNodeFromProvider(&Provider_child2_child_child.IRawElementProviderSimple_iface, &node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child2_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child2_child_child.ref); + + hr = UiaGetPropertyValue(node, UIA_ProviderDescriptionPropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child2_child_child", TRUE); + VariantClear(&v); + + ok_method_sequence(nav_seq15, "nav_seq15"); + + /* + * Navigate from Provider_child2_child_child to a parent that has + * UIA_IsControlElementPropertyId set to FALSE. + */ + V_VT(&v) = VT_BOOL; + V_BOOL(&v) = VARIANT_FALSE; + set_property_condition(&prop_cond, UIA_IsControlElementPropertyId, &v, PropertyConditionFlags_None); + + init_node_provider_desc(&exp_node_desc[0], GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); + set_cache_request(&cache_req, NULL, TreeScope_Element, NULL, 0, NULL, 0, AutomationElementMode_Full); + tree_struct = NULL; + out_req = NULL; + hr = UiaNavigate(node, NavigateDirection_Parent, (struct UiaCondition *)&prop_cond, &cache_req, &out_req, &tree_struct); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(!out_req, "out_req != NULL\n"); + ok(!tree_struct, "tree_struct != NULL\n"); + ok_method_sequence(nav_seq16, "nav_seq16"); + + /* Provider will now return FALSE for UIA_IsControlElementPropertyId. */ + set_property_override(&prop_override, UIA_IsControlElementPropertyId, &v); + set_provider_prop_override(&Provider, &prop_override, 1); + + set_property_condition(&prop_cond, UIA_IsControlElementPropertyId, &v, PropertyConditionFlags_None); + init_node_provider_desc(&exp_node_desc[0], GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); + set_cache_request(&cache_req, NULL, TreeScope_Element, NULL, 0, NULL, 0, AutomationElementMode_Full); + tree_struct = NULL; + out_req = NULL; + hr = UiaNavigate(node, NavigateDirection_Parent, (struct UiaCondition *)&prop_cond, &cache_req, &out_req, &tree_struct); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(!!out_req, "out_req == NULL\n"); + ok(!!tree_struct, "tree_struct == NULL\n"); + ok(Provider.ref == 2, "Unexpected refcnt %ld\n", Provider.ref); + + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = exp_elems[1] = 1; + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + + ok(!wcscmp(tree_struct, L"P)"), "tree structure %s\n", debugstr_w(tree_struct)); + ok_method_sequence(nav_seq17, "nav_seq17"); + SafeArrayDestroy(out_req); + SysFreeString(tree_struct); + + ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + ok(Provider_child2_child_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child2_child_child.ref); + CoUninitialize(); DestroyWindow(hwnd); UnregisterClassA("UiaNavigate class", NULL); diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 4ec12423533..9b3d734defd 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -934,6 +934,63 @@ static HRESULT navigate_uia_node(struct uia_node *node, int nav_dir, HUIANODE *o
static HRESULT uia_condition_check(HUIANODE node, struct UiaCondition *condition); static BOOL uia_condition_matched(HRESULT hr); +static HRESULT conditional_navigate_uia_node(struct uia_node *node, int nav_dir, struct UiaCondition *cond, + HUIANODE *out_node) +{ + HRESULT hr; + + *out_node = NULL; + switch (nav_dir) + { + case NavigateDirection_Parent: + { + struct uia_node *node_data = node; + HUIANODE parent; + + IWineUiaNode_AddRef(&node_data->IWineUiaNode_iface); + while (1) + { + hr = navigate_uia_node(node_data, NavigateDirection_Parent, &parent); + if (FAILED(hr) || !parent) + break; + + hr = uia_condition_check(parent, cond); + if (FAILED(hr)) + break; + + if (uia_condition_matched(hr)) + { + *out_node = parent; + break; + } + + IWineUiaNode_Release(&node_data->IWineUiaNode_iface); + node_data = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)parent); + } + IWineUiaNode_Release(&node_data->IWineUiaNode_iface); + break; + } + + case NavigateDirection_NextSibling: + case NavigateDirection_PreviousSibling: + case NavigateDirection_FirstChild: + case NavigateDirection_LastChild: + if (cond->ConditionType != ConditionType_True) + { + FIXME("ConditionType %d based navigation for dir %d is not implemented.\n", cond->ConditionType, nav_dir); + return E_NOTIMPL; + } + + hr = navigate_uia_node(node, nav_dir, out_node); + break; + + default: + WARN("Invalid NavigateDirection %d\n", nav_dir); + return E_INVALIDARG; + } + + return hr; +}
/* * Assuming we have a tree that looks like this: @@ -3068,7 +3125,7 @@ HRESULT WINAPI UiaNavigate(HUIANODE huianode, enum NavigateDirection dir, struct struct UiaCacheRequest *cache_req, SAFEARRAY **out_req, BSTR *tree_struct) { struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode); - HUIANODE node2; + HUIANODE node2 = NULL; HRESULT hr;
TRACE("(%p, %u, %p, %p, %p, %p)\n", huianode, dir, nav_condition, cache_req, out_req, @@ -3080,17 +3137,8 @@ HRESULT WINAPI UiaNavigate(HUIANODE huianode, enum NavigateDirection dir, struct *out_req = NULL; *tree_struct = NULL;
- if (nav_condition->ConditionType != ConditionType_True) - { - FIXME("ConditionType %d based navigation is not implemented.\n", nav_condition->ConditionType); - return E_NOTIMPL; - } - - hr = navigate_uia_node(node, dir, &node2); - if (FAILED(hr)) - return hr; - - if (node2) + hr = conditional_navigate_uia_node(node, dir, nav_condition, &node2); + if (SUCCEEDED(hr) && node2) { hr = UiaGetUpdatedCache(node2, cache_req, NormalizeState_None, NULL, out_req, tree_struct); if (FAILED(hr))
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 86 ++++++++++++++++++++++ dlls/uiautomationcore/uia_client.c | 29 ++++++-- 2 files changed, 108 insertions(+), 7 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 193f1470029..40f6cdd9f38 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -7873,6 +7873,34 @@ static const struct prov_method_sequence cache_req_seq7[] = { { 0 } };
+static const struct prov_method_sequence cache_req_seq8[] = { + NODE_CREATE_SEQ(&Provider_child), + { 0 } +}; + +static const struct prov_method_sequence cache_req_seq9[] = { + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + /* Done twice on Windows, but we shouldn't need to replicate this. */ + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_IsControlElementPropertyId */ + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + /* Only done on Win10v1507 and below. */ + { &Provider, FRAG_NAVIGATE, METHOD_OPTIONAL }, /* NavigateDirection_Parent */ + { 0 } +}; + +static const struct prov_method_sequence cache_req_seq10[] = { + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + /* Done twice on Windows, but we shouldn't need to replicate this. */ + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_IsControlElementPropertyId */ + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_ProviderDescriptionPropertyId. */ + { 0 } +}; + static const struct UiaCondition UiaTrueCondition = { ConditionType_True }; static const struct UiaCondition UiaFalseCondition = { ConditionType_False }; static void test_UiaGetUpdatedCache(void) @@ -8419,7 +8447,65 @@ static void test_UiaGetUpdatedCache(void)
ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); ok(Provider.ref == 1, "Unexpected refcnt %ld\n", Provider.ref); + + /* Normalization navigation tests. */ initialize_provider(&Provider, ProviderOptions_ServerSideProvider, NULL, FALSE); + initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, FALSE); + + hr = UiaNodeFromProvider(&Provider_child.IRawElementProviderSimple_iface, &node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + ok_method_sequence(cache_req_seq8, "cache_req_seq8"); + + /* + * Neither Provider_child or Provider match this condition, return + * nothing. + */ + variant_init_bool(&v, FALSE); + set_property_condition(&prop_cond, UIA_IsControlElementPropertyId, &v, PropertyConditionFlags_None); + set_cache_request(&cache_req, (struct UiaCondition *)&prop_cond, TreeScope_Element, NULL, 0, NULL, 0, AutomationElementMode_Full); + tree_struct = NULL; out_req = NULL; + hr = UiaGetUpdatedCache(node, &cache_req, NormalizeState_View, NULL, &out_req, &tree_struct); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!out_req, "out_req != NULL\n"); + ok(!!tree_struct, "tree_struct == NULL\n"); + ok(!wcscmp(tree_struct, L""), "tree structure %s\n", debugstr_w(tree_struct)); + SysFreeString(tree_struct); + ok_method_sequence(cache_req_seq9, "cache_req_seq9"); + + /* + * Provider now matches our condition, we'll get Provider in the cache + * request. + */ + variant_init_bool(&v, FALSE); + set_property_override(&prop_override, UIA_IsControlElementPropertyId, &v); + set_provider_prop_override(&Provider, &prop_override, 1); + init_node_provider_desc(&exp_node_desc[0], GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); + + set_cache_request(&cache_req, (struct UiaCondition *)&prop_cond, TreeScope_Element, NULL, 0, NULL, 0, AutomationElementMode_Full); + tree_struct = NULL; out_req = NULL; + hr = UiaGetUpdatedCache(node, &cache_req, NormalizeState_View, NULL, &out_req, &tree_struct); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!out_req, "out_req == NULL\n"); + ok(!!tree_struct, "tree_struct == NULL\n"); + ok(Provider.ref == 2, "Unexpected refcnt %ld\n", Provider.ref); + + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = exp_elems[1] = 1; + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + ok(!wcscmp(tree_struct, L"P)"), "tree structure %s\n", debugstr_w(tree_struct)); + ok_method_sequence(cache_req_seq10, "cache_req_seq10"); + + ok(Provider.ref == 1, "Unexpected refcnt %ld\n", Provider.ref); + SafeArrayDestroy(out_req); + SysFreeString(tree_struct); + VariantClear(&v); + + ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + ok(Provider_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child.ref); + initialize_provider(&Provider, ProviderOptions_ServerSideProvider, NULL, FALSE); + initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, FALSE);
IUnknown_Release(unk_ns); CoUninitialize(); diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 9b3d734defd..eecc9955235 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -3001,6 +3001,7 @@ HRESULT WINAPI UiaGetUpdatedCache(HUIANODE huianode, struct UiaCacheRequest *cac struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode); struct UiaCondition *cond; SAFEARRAYBOUND sabound[2]; + HUIANODE ret_node; SAFEARRAY *sa; LONG idx[2]; HRESULT hr; @@ -3052,16 +3053,29 @@ HRESULT WINAPI UiaGetUpdatedCache(HUIANODE huianode, struct UiaCacheRequest *cac return E_INVALIDARG; }
+ ret_node = huianode; if (cond) { hr = uia_condition_check(huianode, cond); if (FAILED(hr)) return hr;
+ /* + * Attempt to get the nearest ancestor matching the passed in + * condition. + */ if (!uia_condition_matched(hr)) { - *tree_struct = SysAllocString(L""); - return S_OK; + ret_node = NULL; + hr = conditional_navigate_uia_node(node, NavigateDirection_Parent, cond, &ret_node); + if (FAILED(hr)) + return hr; + + if (!ret_node) + { + *tree_struct = SysAllocString(L""); + return S_OK; + } } }
@@ -3074,7 +3088,7 @@ HRESULT WINAPI UiaGetUpdatedCache(HUIANODE huianode, struct UiaCacheRequest *cac return E_FAIL; }
- get_variant_for_node(huianode, &v); + get_variant_for_node(ret_node, &v); idx[0] = idx[1] = 0;
hr = SafeArrayPutElement(sa, idx, &v); @@ -3088,7 +3102,7 @@ HRESULT WINAPI UiaGetUpdatedCache(HUIANODE huianode, struct UiaCacheRequest *cac VariantClear(&v); for (i = 0; i < cache_req->cProperties; i++) { - hr = UiaGetPropertyValue(huianode, cache_req->pProperties[i], &v); + hr = UiaGetPropertyValue(ret_node, cache_req->pProperties[i], &v); /* Don't fail on unimplemented properties. */ if (FAILED(hr) && hr != E_NOTIMPL) { @@ -3107,10 +3121,11 @@ HRESULT WINAPI UiaGetUpdatedCache(HUIANODE huianode, struct UiaCacheRequest *cac }
/* - * AddRef huianode since we're returning a reference to the same node we - * passed in, rather than creating a new one. + * If we're returning a reference to the same node we passed in, make sure + * to AddRef it. */ - IWineUiaNode_AddRef(&node->IWineUiaNode_iface); + if (ret_node == huianode) + IWineUiaNode_AddRef(&node->IWineUiaNode_iface);
*out_req = sa; *tree_struct = SysAllocString(L"P)");
V2: Return `hr` from `conditional_navigate_uia_node()` to avoid accidentally returning success upon failure.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_client.c:
} /*
* AddRef huianode since we're returning a reference to the same node we
* passed in, rather than creating a new one.
* If we're returning a reference to the same node we passed in, make sure
* to AddRef it. */
- IWineUiaNode_AddRef(&node->IWineUiaNode_iface);
- if (ret_node == huianode)
I don't like this very much. It's a bit confusing to keep track of the flow of references since it's disconnected from variable assignments.
My suggestion would be to addref when assigning to ret_node, and release if the function returns failure. (In fact, I just realized the confusing flow hid a leak where the return statements in between here and the ret_node assignment won't release ret_node.) Maybe some restructuring to avoid an unneeded addref/release of huianode would make it simpler, not sure.