-- v2: uiautomationcore: Add support for returning multiple HUIANODEs from UiaFind. uiautomationcore: Store traverse_uia_node_tree() results in a dynamic array. uiautomationcore: Add support for ExcludeRoot parameter in UiaFind. uiautomationcore: Partially implement UiaFind. uiautomationcore: Add UiaFind stub.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 999 ++++++++++++++++++++ dlls/uiautomationcore/uia_client.c | 10 + dlls/uiautomationcore/uiautomationcore.spec | 2 +- include/uiautomationcoreapi.h | 9 + 4 files changed, 1019 insertions(+), 1 deletion(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 827c23e26ef..6457a660b00 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -2184,6 +2184,10 @@ DEFINE_PROVIDER(hwnd_child); DEFINE_PROVIDER(hwnd_child2); DEFINE_PROVIDER(nc_child); DEFINE_PROVIDER(nc_child2); +DEFINE_PROVIDER(child_child); +DEFINE_PROVIDER(child_child2); +DEFINE_PROVIDER(child2_child); +DEFINE_PROVIDER(child2_child_child);
static IAccessible *acc_client; static IRawElementProviderSimple *prov_root; @@ -8203,6 +8207,1000 @@ static void test_UiaNavigate(void) UnregisterClassA("UiaNavigate class", NULL); }
+static void set_find_params(struct UiaFindParams *params, int max_depth, BOOL find_first, BOOL exclude_root, + struct UiaCondition *find_cond) +{ + params->MaxDepth = max_depth; + params->FindFirst = find_first; + params->ExcludeRoot = exclude_root; + params->pFindCondition = find_cond; +} + +static void set_provider_prop_override(struct Provider *prov, struct Provider_prop_override *override, int count) +{ + prov->prop_override = override; + prov->prop_override_count = count; +} + +static void set_property_override(struct Provider_prop_override *override, int prop_id, VARIANT *val) +{ + override->prop_id = prop_id; + override->val = *val; +} + +static void initialize_provider(struct Provider *prov, int prov_opts, HWND hwnd) +{ + prov->prov_opts = prov_opts; + prov->hwnd = hwnd; + prov->ret_invalid_prop_type = FALSE; + prov->expected_tid = 0; + prov->runtime_id[0] = prov->runtime_id[1] = 0; + prov->last_call_tid = 0; + prov->ignore_hwnd_prop = FALSE; + prov->override_hwnd = NULL; + prov->prop_override = NULL; + prov->prop_override_count = 0; +} + +static void initialize_provider_tree(void) +{ + initialize_provider(&Provider, ProviderOptions_ServerSideProvider, NULL); + initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL); + initialize_provider(&Provider_child_child, ProviderOptions_ServerSideProvider, NULL); + initialize_provider(&Provider_child_child2, ProviderOptions_ServerSideProvider, NULL); + initialize_provider(&Provider_child2, ProviderOptions_ServerSideProvider, NULL); + initialize_provider(&Provider_child2_child, ProviderOptions_ServerSideProvider, NULL); + initialize_provider(&Provider_child2_child_child, ProviderOptions_ServerSideProvider, NULL); +} + +#define test_find_sa_results( tree_structs, offsets, exp_elems, exp_tree_struct, exp_offsets ) \ + test_find_sa_results_( (tree_structs), (offsets), (exp_elems), (exp_tree_struct), (exp_offsets), __FILE__, __LINE__) +static void test_find_sa_results_(SAFEARRAY *tree_structs, SAFEARRAY *offsets, LONG exp_elems, + const WCHAR **exp_tree_struct, int *exp_offset, const char *file, int line) +{ + LONG lbound, ubound, elems; + HRESULT hr; + VARTYPE vt; + UINT dims; + LONG i; + + /* Tree structures SA. */ + hr = SafeArrayGetVartype(tree_structs, &vt); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok_(file, line)(vt == VT_BSTR, "Unexpected tree structures sa vt %d\n", vt); + dims = SafeArrayGetDim(tree_structs); + ok_(file, line)(dims == 1, "Unexpected tree structures sa dims %d\n", dims); + + lbound = ubound = elems = 0; + hr = SafeArrayGetLBound(tree_structs, 1, &lbound); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx for SafeArrayGetLBound\n", hr); + ok_(file, line)(!lbound, "Unexpected lbound %ld\n", lbound); + + hr = SafeArrayGetUBound(tree_structs, 1, &ubound); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx for SafeArrayGetUBound\n", hr); + + elems = (ubound - lbound) + 1; + ok_(file, line)(exp_elems == elems, "Unexpected elems %ld\n", elems); + + /* Offsets SA. */ + hr = SafeArrayGetVartype(offsets, &vt); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok_(file, line)(vt == VT_I4, "Unexpected offsets sa vt %d\n", vt); + dims = SafeArrayGetDim(offsets); + ok_(file, line)(dims == 1, "Unexpected offsets sa dims %d\n", dims); + + lbound = ubound = elems = 0; + hr = SafeArrayGetLBound(offsets, 1, &lbound); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx for SafeArrayGetLBound\n", hr); + ok_(file, line)(!lbound, "Unexpected lbound %ld\n", lbound); + + hr = SafeArrayGetUBound(offsets, 1, &ubound); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx for SafeArrayGetUBound\n", hr); + + elems = (ubound - lbound) + 1; + ok_(file, line)(exp_elems == elems, "Unexpected elems %ld\n", elems); + + for (i = 0; i < exp_elems; i++) + { + BSTR tree_struct; + int offset; + + hr = SafeArrayGetElement(tree_structs, &i, &tree_struct); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok_(file, line)(!wcscmp(tree_struct, exp_tree_struct[i]), "Unexpected tree structure %s\n", debugstr_w(tree_struct)); + SysFreeString(tree_struct); + + hr = SafeArrayGetElement(offsets, &i, &offset); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok_(file, line)(exp_offset[i] == offset, "Unexpected offset %d\n", offset); + } +} + +static const struct prov_method_sequence find_seq1[] = { + { &Provider, FRAG_GET_RUNTIME_ID }, + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child_child), + { &Provider_child_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + { &Provider_child_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child_child2), + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child2_child), + { &Provider_child2_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child2_child_child), + { &Provider_child2_child_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + { &Provider_child2_child_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2_child_child, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child2_child), + { &Provider_child2_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2_child, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + /* Only done on Win10v1507 and below. */ + { &Provider, FRAG_NAVIGATE, METHOD_OPTIONAL }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child2_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child2_child_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 }, +}; + +static const struct prov_method_sequence find_seq2[] = { + { &Provider, FRAG_GET_RUNTIME_ID }, + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + /* Only done on Win10v1507 and below. */ + { &Provider, FRAG_NAVIGATE, METHOD_OPTIONAL }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 }, +}; + +static const struct prov_method_sequence find_seq3[] = { + { &Provider, FRAG_GET_RUNTIME_ID }, + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + /* Only done on Win10v1507 and below. */ + { &Provider, FRAG_NAVIGATE, METHOD_OPTIONAL }, /* NavigateDirection_Parent */ + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 }, +}; + +static const struct prov_method_sequence find_seq4[] = { + { &Provider, FRAG_GET_RUNTIME_ID }, + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 }, +}; + +static const struct prov_method_sequence find_seq5[] = { + { &Provider_child, FRAG_GET_RUNTIME_ID }, + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 }, +}; + +static const struct prov_method_sequence find_seq6[] = { + { &Provider_child, FRAG_GET_RUNTIME_ID }, + { &Provider_child, FRAG_GET_RUNTIME_ID }, + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 }, +}; + +static const struct prov_method_sequence find_seq7[] = { + { &Provider, FRAG_GET_RUNTIME_ID }, + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child_child), + { &Provider_child_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child_child2), + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + /* Only done on Win10v1507 and below. */ + { &Provider, FRAG_NAVIGATE, METHOD_OPTIONAL }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 }, +}; + +static const struct prov_method_sequence find_seq8[] = { + { &Provider, FRAG_GET_RUNTIME_ID }, + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child_child), + { &Provider_child_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child_child, FRAG_GET_RUNTIME_ID }, + { &Provider_child_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child_child2), + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child_child2, FRAG_GET_RUNTIME_ID }, + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, FRAG_GET_RUNTIME_ID }, + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child2, FRAG_GET_RUNTIME_ID }, + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider, FRAG_GET_RUNTIME_ID }, + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 }, +}; + +static const struct prov_method_sequence find_seq9[] = { + { &Provider_child_child2, FRAG_GET_RUNTIME_ID }, + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child2_child), + { &Provider_child2_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child2_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child2_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2_child, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + /* Only done on Win10v1507 and below. */ + { &Provider, FRAG_NAVIGATE, METHOD_OPTIONAL }, /* NavigateDirection_Parent */ + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child2_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 }, +}; + +static const struct prov_method_sequence find_seq10[] = { + { &Provider, FRAG_GET_RUNTIME_ID }, + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child_child), + { &Provider_child_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + { &Provider_child_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child_child2), + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child2_child), + { &Provider_child2_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child2_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child2_child_child), + { &Provider_child2_child_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child2_child_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + { &Provider_child2_child_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2_child_child, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child2_child), + { &Provider_child2_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2_child, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider_child2), + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider_child2, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + NODE_CREATE_SEQ(&Provider), + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_Parent */ + /* Only done on Win10v1507 and below. */ + { &Provider, FRAG_NAVIGATE, METHOD_OPTIONAL }, /* NavigateDirection_Parent */ + { &Provider_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 }, +}; + +static const struct prov_method_sequence find_seq11[] = { + { &Provider, FRAG_GET_RUNTIME_ID }, + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child), + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + NODE_CREATE_SEQ(&Provider_child_child), + { &Provider_child_child, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child_child, FRAG_NAVIGATE }, /* NavigateDirection_FirstChild */ + { &Provider_child_child, FRAG_NAVIGATE }, /* NavigateDirection_NextSibling */ + NODE_CREATE_SEQ(&Provider_child_child2), + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsContentElementPropertyId */ + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE }, /* UIA_IsControlElementPropertyId */ + { &Provider_child_child2, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 }, +}; + +static void test_UiaFind(void) +{ + struct Provider_prop_override prop_override; + LONG exp_lbound[2], exp_elems[2], idx[2], i; + SAFEARRAY *out_req, *offsets, *tree_structs; + struct node_provider_desc exp_node_desc[7]; + struct UiaPropertyCondition prop_cond[2]; + struct UiaCacheRequest cache_req; + struct UiaFindParams find_params; + const WCHAR *exp_tree_struct[7]; + HUIANODE node, node2; + int exp_offset[6]; + HRESULT hr; + VARIANT v; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + + /* + * Tree looks like: + * -Provider + * ---Provider_child + * -----Provider_child_child + * -----Provider_child_child2 + * ---Provider_child2 + * -----Provider_child2_child + * -------Provider_child2_child_child + */ + set_provider_nav_ifaces(&Provider, NULL, NULL, NULL, NULL, &Provider_child, &Provider_child2); + + set_provider_nav_ifaces(&Provider_child, &Provider, &Provider, NULL, &Provider_child2, + &Provider_child_child, &Provider_child_child2); + set_provider_nav_ifaces(&Provider_child_child, &Provider_child, &Provider, NULL, &Provider_child_child2, NULL, NULL); + set_provider_nav_ifaces(&Provider_child_child2, &Provider_child, &Provider, &Provider_child_child, NULL, NULL, NULL); + + set_provider_nav_ifaces(&Provider_child2, &Provider, &Provider, &Provider_child, NULL, &Provider_child2_child, + &Provider_child2_child); + set_provider_nav_ifaces(&Provider_child2_child, &Provider_child2, &Provider, NULL, NULL, &Provider_child2_child_child, + &Provider_child2_child_child); + set_provider_nav_ifaces(&Provider_child2_child_child, &Provider_child2_child, &Provider, NULL, NULL, NULL, NULL); + + initialize_provider_tree(); + + for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) + init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL); + + hr = UiaNodeFromProvider(&Provider.IRawElementProviderSimple_iface, &node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider.ref == 2, "Unexpected refcnt %ld\n", Provider.ref); + + hr = UiaGetPropertyValue(node, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider", TRUE); + VariantClear(&v); + } + + ok_method_sequence(node_from_prov2, NULL); + + /* + * Maximum find depth of -1, find first is FALSE, exclude root is FALSE. A + * maximum depth of -1 will search the entire tree. + */ + out_req = offsets = tree_structs = NULL; + set_cache_request(&cache_req, (struct UiaCondition *)&UiaTrueCondition, TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + set_find_params(&find_params, -1, FALSE, FALSE, (struct UiaCondition *)&UiaTrueCondition); + hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + todo_wine ok(Provider_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child.ref); + todo_wine ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref); + todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + todo_wine ok(Provider_child2_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child2_child.ref); + todo_wine ok(Provider_child2_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child2_child_child.ref); + + if (SUCCEEDED(hr)) + { + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child", TRUE); + add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child_child", TRUE); + add_provider_desc(&exp_node_desc[3], L"Main", L"Provider_child_child2", TRUE); + add_provider_desc(&exp_node_desc[4], L"Main", L"Provider_child2", TRUE); + add_provider_desc(&exp_node_desc[5], L"Main", L"Provider_child2_child", TRUE); + add_provider_desc(&exp_node_desc[6], L"Main", L"Provider_child2_child_child", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 7; + exp_elems[1] = 1; + + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; + } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq1, "find_seq1"); + } + + SafeArrayDestroy(out_req); + SafeArrayDestroy(offsets); + SafeArrayDestroy(tree_structs); + + for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) + init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL); + + /* + * Maximum find depth of 1, find first is FALSE, exclude root is FALSE. + */ + set_cache_request(&cache_req, (struct UiaCondition *)&UiaTrueCondition, TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + set_find_params(&find_params, 1, FALSE, FALSE, (struct UiaCondition *)&UiaTrueCondition); + hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + + if (SUCCEEDED(hr)) + { + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child", TRUE); + add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 3; + exp_elems[1] = 1; + + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; + } + + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq2, "find_seq2"); + } + + SafeArrayDestroy(out_req); + SafeArrayDestroy(offsets); + SafeArrayDestroy(tree_structs); + + for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) + init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL); + + /* + * Maximum find depth of 1, find first is FALSE, exclude root is TRUE. + */ + set_cache_request(&cache_req, (struct UiaCondition *)&UiaTrueCondition, TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + set_find_params(&find_params, 1, FALSE, TRUE, (struct UiaCondition *)&UiaTrueCondition); + hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + + if (SUCCEEDED(hr)) + { + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 2; + exp_elems[1] = 1; + + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; + } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq3, "find_seq3"); + } + + SafeArrayDestroy(out_req); + SafeArrayDestroy(offsets); + SafeArrayDestroy(tree_structs); + + for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) + init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL); + + /* + * Maximum find depth of 1, find first is TRUE, exclude root is TRUE. Will + * retrieve only Provider_child. + */ + set_cache_request(&cache_req, (struct UiaCondition *)&UiaTrueCondition, TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + set_find_params(&find_params, 1, TRUE, TRUE, (struct UiaCondition *)&UiaTrueCondition); + hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + + node2 = NULL; + if (SUCCEEDED(hr)) + { + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = exp_elems[1] = 1; + + idx[0] = idx[1] = 0; + hr = SafeArrayGetElement(out_req, idx, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + + /* node2 is now set as Provider_child. */ + hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + IUnknown_AddRef((IUnknown *)node2); + + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; + } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq4, "find_seq4"); + } + + SafeArrayDestroy(out_req); + SafeArrayDestroy(offsets); + SafeArrayDestroy(tree_structs); + + for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) + init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL); + + /* + * Maximum find depth of 0, find first is FALSE, exclude root is FALSE. + * Provider_child doesn't have a runtime id for UI Automation to use as a + * way to check if it has navigated back to the node that began the + * search, so it will get siblings. + */ + set_cache_request(&cache_req, (struct UiaCondition *)&UiaTrueCondition, TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + set_find_params(&find_params, 0, FALSE, FALSE, (struct UiaCondition *)&UiaTrueCondition); + hr = UiaFind(node2, &find_params, &cache_req, &out_req, &offsets, &tree_structs); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + + if (SUCCEEDED(hr)) + { + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 2; + exp_elems[1] = 1; + + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; + } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq5, "find_seq5"); + } + + SafeArrayDestroy(out_req); + SafeArrayDestroy(offsets); + SafeArrayDestroy(tree_structs); + + for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) + init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL); + + /* + * Maximum find depth of 0, find first is FALSE, exclude root is FALSE. + * Provider_child has a runtime id for UI Automation to use as a + * way to check if it has navigated back to the node that began the + * search, so it will stop at Provider_child. + */ + Provider_child.runtime_id[0] = Provider_child.runtime_id[1] = 0xdeadbeef; + set_cache_request(&cache_req, (struct UiaCondition *)&UiaTrueCondition, TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + set_find_params(&find_params, 0, FALSE, FALSE, (struct UiaCondition *)&UiaTrueCondition); + hr = UiaFind(node2, &find_params, &cache_req, &out_req, &offsets, &tree_structs); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + if (SUCCEEDED(hr)) + { + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); + 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); + + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; + } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq6, "find_seq6"); + } + + SafeArrayDestroy(out_req); + SafeArrayDestroy(offsets); + SafeArrayDestroy(tree_structs); + + todo_wine ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); + ok(Provider_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child.ref); + + initialize_provider_tree(); + for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) + init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL); + + /* + * Maximum find depth of 1, find first is FALSE, exclude root is FALSE. + * The cache request view condition is used to determine tree depth, if an + * element matches the cache request view condition, depth is incremented. + * Since Provider_child does not, Provider_child_child, Provider_child_child2, + * and Provider_child2 are all considered to be at depth 1. + */ + V_VT(&v) = VT_BOOL; + V_BOOL(&v) = VARIANT_FALSE; + set_property_condition(&prop_cond[0], UIA_IsContentElementPropertyId, &v, 0); + set_property_override(&prop_override, UIA_IsContentElementPropertyId, &v); + + V_VT(&v) = VT_BOOL; + V_BOOL(&v) = VARIANT_TRUE; + set_property_condition(&prop_cond[1], UIA_IsControlElementPropertyId, &v, 0); + + set_provider_prop_override(&Provider, &prop_override, 1); + set_provider_prop_override(&Provider_child2, &prop_override, 1); + set_provider_prop_override(&Provider_child_child, &prop_override, 1); + set_provider_prop_override(&Provider_child_child2, &prop_override, 1); + + set_cache_request(&cache_req, (struct UiaCondition *)&prop_cond[0], TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + set_find_params(&find_params, 1, FALSE, FALSE, (struct UiaCondition *)&prop_cond[1]); + hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(Provider_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child.ref); + todo_wine ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref); + todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + + if (SUCCEEDED(hr)) + { + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child_child", TRUE); + add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child_child2", TRUE); + add_provider_desc(&exp_node_desc[3], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 4; + exp_elems[1] = 1; + + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; + } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq7, "find_seq7"); + } + + SafeArrayDestroy(out_req); + SafeArrayDestroy(offsets); + SafeArrayDestroy(tree_structs); + + initialize_provider_tree(); + for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) + init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL); + + /* + * Same test as before, except Provider has a runtime id. + */ + Provider.runtime_id[0] = Provider.runtime_id[1] = 0xdeadbeef; + V_VT(&v) = VT_BOOL; + V_BOOL(&v) = VARIANT_FALSE; + set_property_condition(&prop_cond[0], UIA_IsContentElementPropertyId, &v, 0); + set_property_override(&prop_override, UIA_IsContentElementPropertyId, &v); + + V_VT(&v) = VT_BOOL; + V_BOOL(&v) = VARIANT_TRUE; + set_property_condition(&prop_cond[1], UIA_IsControlElementPropertyId, &v, 0); + + set_provider_prop_override(&Provider, &prop_override, 1); + set_provider_prop_override(&Provider_child2, &prop_override, 1); + set_provider_prop_override(&Provider_child_child, &prop_override, 1); + set_provider_prop_override(&Provider_child_child2, &prop_override, 1); + + set_cache_request(&cache_req, (struct UiaCondition *)&prop_cond[0], TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + set_find_params(&find_params, 1, FALSE, FALSE, (struct UiaCondition *)&prop_cond[1]); + hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(Provider_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child.ref); + todo_wine ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref); + todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + + if (SUCCEEDED(hr)) + { + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child_child", TRUE); + add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child_child2", TRUE); + add_provider_desc(&exp_node_desc[3], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 4; + exp_elems[1] = 1; + + idx[0] = 2; + idx[1] = 0; + hr = SafeArrayGetElement(out_req, idx, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + + /* node2 is now set as Provider_child_child2. */ + hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + IUnknown_AddRef((IUnknown *)node2); + + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; + } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq8, "find_seq8"); + } + + SafeArrayDestroy(out_req); + SafeArrayDestroy(offsets); + SafeArrayDestroy(tree_structs); + + initialize_provider_tree(); + for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) + init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL); + + /* + * Maximum find depth of 1, find first is FALSE, exclude root is FALSE. + * Starting at Provider_child_child2, find will be able to retrieve + * Provider_child2_child by navigating to the parent of the starting node, + * and if it doesn't match the view condition, we can navigate to its + * sibling since it isn't considered a part of the tree and therefore any + * siblings it has are at the same depth level. + */ + V_VT(&v) = VT_BOOL; + V_BOOL(&v) = VARIANT_FALSE; + set_property_condition(&prop_cond[0], UIA_IsContentElementPropertyId, &v, 0); + + V_VT(&v) = VT_BOOL; + V_BOOL(&v) = VARIANT_TRUE; + set_property_condition(&prop_cond[1], UIA_IsControlElementPropertyId, &v, 0); + + prop_override.prop_id = UIA_IsContentElementPropertyId; + V_VT(&prop_override.val) = VT_BOOL; + V_BOOL(&prop_override.val) = VARIANT_FALSE; + set_provider_prop_override(&Provider_child_child2, &prop_override, 1); + set_provider_prop_override(&Provider_child2, &prop_override, 1); + set_provider_prop_override(&Provider_child2_child, &prop_override, 1); + + set_cache_request(&cache_req, (struct UiaCondition *)&prop_cond[0], TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + set_find_params(&find_params, 1, FALSE, FALSE, (struct UiaCondition *)&prop_cond[1]); + hr = UiaFind(node2, &find_params, &cache_req, &out_req, &offsets, &tree_structs); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + todo_wine ok(Provider_child2_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child2_child.ref); + + if (SUCCEEDED(hr)) + { + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child_child2", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child2", TRUE); + add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child2_child", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 3; + exp_elems[1] = 1; + + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; + } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq9, "find_seq9"); + } + + SafeArrayDestroy(out_req); + SafeArrayDestroy(offsets); + SafeArrayDestroy(tree_structs); + + initialize_provider_tree(); + for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) + init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL); + + todo_wine ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); + ok(Provider_child_child2.ref == 1, "Unexpected refcnt %ld\n", Provider_child_child2.ref); + + /* + * Maximum find depth of 1, find first is FALSE, exclude root is TRUE. + * Exclude root applies to the first node that matches the view + * condition, and not the node that is passed into UiaFind(). Since + * Provider doesn't match our view condition here, Provider_child will be + * excluded. + */ + V_VT(&v) = VT_BOOL; + V_BOOL(&v) = VARIANT_FALSE; + set_property_condition(&prop_cond[0], UIA_IsContentElementPropertyId, &v, 0); + set_property_override(&prop_override, UIA_IsContentElementPropertyId, &v); + + V_VT(&v) = VT_BOOL; + V_BOOL(&v) = VARIANT_TRUE; + set_property_condition(&prop_cond[1], UIA_IsControlElementPropertyId, &v, 0); + + set_provider_prop_override(&Provider_child, &prop_override, 1); + set_provider_prop_override(&Provider_child2, &prop_override, 1); + + set_cache_request(&cache_req, (struct UiaCondition *)&prop_cond[0], TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + set_find_params(&find_params, 1, FALSE, TRUE, (struct UiaCondition *)&prop_cond[1]); + hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + + if (SUCCEEDED(hr)) + { + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 1; + exp_elems[1] = 1; + + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; + } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + + ok_method_sequence(find_seq10, "find_seq10"); + } + + SafeArrayDestroy(out_req); + SafeArrayDestroy(offsets); + SafeArrayDestroy(tree_structs); + + initialize_provider_tree(); + for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) + init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL); + + /* + * Maximum find depth of -1, find first is TRUE, exclude root is FALSE. + * Provider_child_child2 is the only element in the tree to match our + * condition. + */ + V_VT(&v) = VT_BOOL; + V_BOOL(&v) = VARIANT_FALSE; + set_property_condition(&prop_cond[0], UIA_IsContentElementPropertyId, &v, 0); + set_property_override(&prop_override, UIA_IsContentElementPropertyId, &v); + + V_VT(&v) = VT_BOOL; + V_BOOL(&v) = VARIANT_TRUE; + set_property_condition(&prop_cond[1], UIA_IsControlElementPropertyId, &v, 0); + + set_provider_prop_override(&Provider_child_child2, &prop_override, 1); + + set_cache_request(&cache_req, (struct UiaCondition *)&prop_cond[0], TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + set_find_params(&find_params, -1, TRUE, FALSE, (struct UiaCondition *)&prop_cond[1]); + hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref); + + if (SUCCEEDED(hr)) + { + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 1; + exp_elems[1] = 1; + + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; + } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq11, "find_seq11"); + } + + SafeArrayDestroy(out_req); + SafeArrayDestroy(offsets); + SafeArrayDestroy(tree_structs); + + initialize_provider_tree(); + + ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + ok(Provider.ref == 1, "Unexpected refcnt %ld\n", Provider.ref); + + CoUninitialize(); +} + /* * Once a process returns a UI Automation provider with * UiaReturnRawElementProvider it ends up in an implicit MTA until exit. This @@ -8268,6 +9266,7 @@ START_TEST(uiautomation) launch_test_process(argv[0], "UiaRegisterProviderCallback"); test_UiaGetUpdatedCache(); test_UiaNavigate(); + test_UiaFind(); if (uia_dll) { pUiaProviderFromIAccessible = (void *)GetProcAddress(uia_dll, "UiaProviderFromIAccessible"); diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index dfacebc456f..1025b40446c 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -2559,3 +2559,13 @@ HRESULT WINAPI UiaNavigate(HUIANODE huianode, enum NavigateDirection dir, struct
return hr; } + +/*********************************************************************** + * UiaFind (uiautomationcore.@) + */ +HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, struct UiaCacheRequest *cache_req, + SAFEARRAY **out_req, SAFEARRAY **out_offsets, SAFEARRAY **out_tree_structs) +{ + FIXME("(%p, %p, %p, %p, %p, %p): stub\n", huianode, find_params, cache_req, out_req, out_offsets, out_tree_structs); + return E_NOTIMPL; +} diff --git a/dlls/uiautomationcore/uiautomationcore.spec b/dlls/uiautomationcore/uiautomationcore.spec index c14b457fd47..0ad0cb6f437 100644 --- a/dlls/uiautomationcore/uiautomationcore.spec +++ b/dlls/uiautomationcore/uiautomationcore.spec @@ -59,7 +59,7 @@ @ stdcall UiaDisconnectProvider(ptr) @ stub UiaEventAddWindow @ stub UiaEventRemoveWindow -@ stub UiaFind +@ stdcall UiaFind(ptr ptr ptr ptr ptr ptr) @ stub UiaGetErrorDescription @ stub UiaGetPatternProvider @ stdcall UiaGetPropertyValue(ptr long ptr) diff --git a/include/uiautomationcoreapi.h b/include/uiautomationcoreapi.h index 7025392cbbc..efb5eb35d06 100644 --- a/include/uiautomationcoreapi.h +++ b/include/uiautomationcoreapi.h @@ -385,6 +385,13 @@ struct UiaWindowClosedEventArgs { int cRuntimeIdLen; };
+struct UiaFindParams { + int MaxDepth; + BOOL FindFirst; + BOOL ExcludeRoot; + struct UiaCondition *pFindCondition; +}; + typedef SAFEARRAY * WINAPI UiaProviderCallback(HWND hwnd,enum ProviderType providerType);
HRESULT WINAPI UiaGetReservedMixedAttributeValue(IUnknown **value); @@ -409,6 +416,8 @@ HRESULT WINAPI UiaGetUpdatedCache(HUIANODE huianode, struct UiaCacheRequest *cac struct UiaCondition *normalize_cond, SAFEARRAY **out_req, BSTR *tree_struct); HRESULT WINAPI UiaNavigate(HUIANODE huianode, enum NavigateDirection dir, struct UiaCondition *nav_condition, struct UiaCacheRequest *cache_req, SAFEARRAY **out_req, BSTR *tree_struct); +HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, struct UiaCacheRequest *cache_req, SAFEARRAY **out_req, + SAFEARRAY **out_offsets, SAFEARRAY **out_tree_structs);
#ifdef __cplusplus }
From: Connor McAdams cmcadams@codeweavers.com
Implement UiaFind for single node searches.
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 29 ++- dlls/uiautomationcore/uia_client.c | 243 ++++++++++++++++++++- 2 files changed, 254 insertions(+), 18 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 6457a660b00..f9e232d6fc3 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -9168,26 +9168,23 @@ static void test_UiaFind(void) AutomationElementMode_Full); set_find_params(&find_params, -1, TRUE, FALSE, (struct UiaCondition *)&prop_cond[1]); hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref);
- if (SUCCEEDED(hr)) - { - add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child_child2", TRUE); - exp_lbound[0] = exp_lbound[1] = 0; - exp_elems[0] = 1; - exp_elems[1] = 1; + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 1; + exp_elems[1] = 1;
- test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc);
- for (i = 0; i < exp_elems[0]; i++) - { - exp_offset[i] = i; - exp_tree_struct[i] = L"P)"; - } - test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); - ok_method_sequence(find_seq11, "find_seq11"); + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq11, "find_seq11");
SafeArrayDestroy(out_req); SafeArrayDestroy(offsets); diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 1025b40446c..e57678c8ba3 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -23,6 +23,8 @@
WINE_DEFAULT_DEBUG_CHANNEL(uiautomation);
+static const struct UiaCondition UiaFalseCondition = { ConditionType_False }; + static HRESULT get_safearray_bounds(SAFEARRAY *sa, LONG *lbound, LONG *elems) { LONG ubound; @@ -873,6 +875,153 @@ static HRESULT navigate_uia_node(struct uia_node *node, int nav_dir, HUIANODE *o return S_OK; }
+static HRESULT uia_condition_check(HUIANODE node, struct UiaCondition *condition); +static BOOL uia_condition_matched(HRESULT hr); + +static HRESULT get_sibling_node_for_tree(HUIANODE huianode, struct UiaCondition *view_cond, int dir, + BOOL at_root_level, HUIANODE *out_node) +{ + struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode); + HUIANODE node2 = NULL; + HRESULT hr; + + *out_node = NULL; + + IWineUiaNode_AddRef(&node->IWineUiaNode_iface); + while (1) + { + hr = navigate_uia_node(node, dir, &node2); + if (FAILED(hr) || node2 || !at_root_level) + goto exit; + + /* Node has no actual sibling, attempt to find one within the tree. */ + hr = navigate_uia_node(node, NavigateDirection_Parent, &node2); + if (FAILED(hr) || !node2) + goto exit; + + /* + * If we've reached a node that is in the tree, there are no more + * nodes left that could children of this node (and therefore, no + * nodes left that could be siblings of our starting node). + */ + hr = uia_condition_check(node2, view_cond); + if (FAILED(hr) || uia_condition_matched(hr)) + { + UiaNodeRelease(node2); + node2 = NULL; + goto exit; + } + + IWineUiaNode_Release(&node->IWineUiaNode_iface); + node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)node2); + } + +exit: + IWineUiaNode_Release(&node->IWineUiaNode_iface); + *out_node = node2; + + return hr; +} + +/* + * This function is used to traverse the 'raw' tree of HUIANODEs using a + * depth-first search. As each node in the tree is visited, it is checked + * against two conditions. + * + * The first is the view condition, which is a filter for what is considered to + * be a valid node in the current tree 'view'. The view condition essentially + * creates a sort of 'virtual' tree. UI Automation provides three default tree + * views: + * -The 'raw' view has the view condition as ConditionType_True, so the tree + * is completely unfiltered. All nodes are valid. + * -The 'control' view contains only nodes that do not return VARIANT_FALSE + * for UIA_IsControlElementPropertyId. + * -The 'content' view contains only nodes that do not return VARIANT_FALSE + * for both UIA_IsControlElementPropertyId and UIA_IsContentElementPropertyId. + * + * If the currently visited node is considered to be valid within our view + * condition, it is then checked against the search condition to determine if + * it should be returned. + */ +static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *view_cond, + struct UiaCondition *search_cond, struct UiaCondition *sibling_stop_cond, int traversal_opts, + BOOL at_root_level, int max_depth, int *cur_depth, HUIANODE *out_node) +{ + struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode); + BOOL incr_depth = FALSE; + HUIANODE node2; + HRESULT hr; + + hr = uia_condition_check(huianode, view_cond); + if (FAILED(hr)) + goto exit; + + if (uia_condition_matched(hr)) + { + /* + * If this is a valid node within our treeview, we need to increment + * our current depth within the tree. + */ + incr_depth = TRUE; + hr = uia_condition_check(huianode, search_cond); + if (FAILED(hr)) + goto exit; + + if (uia_condition_matched(hr)) + { + IWineUiaNode_AddRef((IWineUiaNode *)huianode); + *out_node = huianode; + hr = S_FALSE; + goto exit; + } + } + + if (incr_depth) + (*cur_depth)++; + + /* If we haven't exceeded our maximum traversal depth, visit children of this node. */ + if (max_depth < 0 || (*cur_depth) <= max_depth) + { + if (traversal_opts & TreeTraversalOptions_LastToFirstOrder) + hr = navigate_uia_node(node, NavigateDirection_LastChild, &node2); + else + hr = navigate_uia_node(node, NavigateDirection_FirstChild, &node2); + + if (SUCCEEDED(hr) && node2) + hr = traverse_uia_node_tree(node2, view_cond, search_cond, sibling_stop_cond, traversal_opts, FALSE, + max_depth, cur_depth, out_node); + + if (FAILED(hr) || hr == S_FALSE) + goto exit; + } + + if (incr_depth) + (*cur_depth)--; + + /* + * Before attempting to visit any siblings of huianode, make sure + * huianode doesn't match our stop condition. + */ + hr = uia_condition_check(huianode, sibling_stop_cond); + if (FAILED(hr) || uia_condition_matched(hr)) + goto exit; + + /* Now, visit any siblings. */ + if (traversal_opts & TreeTraversalOptions_LastToFirstOrder) + hr = get_sibling_node_for_tree(huianode, view_cond, NavigateDirection_PreviousSibling, at_root_level, &node2); + else + hr = get_sibling_node_for_tree(huianode, view_cond, NavigateDirection_NextSibling, at_root_level, &node2); + + if (SUCCEEDED(hr) && node2) + hr = traverse_uia_node_tree(node2, view_cond, search_cond, sibling_stop_cond, traversal_opts, at_root_level, + max_depth, cur_depth, out_node); + +exit: + UiaNodeRelease(huianode); + + return hr; +} + /* * IWineUiaProvider interface. */ @@ -2566,6 +2715,96 @@ HRESULT WINAPI UiaNavigate(HUIANODE huianode, enum NavigateDirection dir, struct HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, struct UiaCacheRequest *cache_req, SAFEARRAY **out_req, SAFEARRAY **out_offsets, SAFEARRAY **out_tree_structs) { - FIXME("(%p, %p, %p, %p, %p, %p): stub\n", huianode, find_params, cache_req, out_req, out_offsets, out_tree_structs); - return E_NOTIMPL; + struct UiaPropertyCondition prop_cond = { ConditionType_Property, UIA_RuntimeIdPropertyId }; + struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode); + SAFEARRAY *runtime_id, *req, *offsets, *tree_structs; + struct UiaCondition *sibling_stop_cond; + int cur_depth = 0; + BSTR tree_struct; + HUIANODE node2; + HRESULT hr; + LONG idx; + + TRACE("(%p, %p, %p, %p, %p, %p)\n", huianode, find_params, cache_req, out_req, out_offsets, out_tree_structs); + + if (!node || !find_params || !cache_req || !out_req || !out_offsets || !out_tree_structs) + return E_INVALIDARG; + + *out_tree_structs = *out_offsets = *out_req = tree_structs = offsets = req = NULL; + + if (!find_params->FindFirst) + { + FIXME("Finding more than one HUIANODE is currently unimplemented.\n"); + return E_NOTIMPL; + } + + if (find_params->ExcludeRoot) + { + FIXME("ExcludeRoot currently unimplemented.\n"); + return E_NOTIMPL; + } + + /* + * If the initial node has a runtime ID, we'll use it as a stop + * condition. + */ + hr = UiaGetRuntimeId(huianode, &runtime_id); + if (SUCCEEDED(hr) && runtime_id) + { + V_VT(&prop_cond.Value) = VT_I4 | VT_ARRAY; + V_ARRAY(&prop_cond.Value) = runtime_id; + sibling_stop_cond = (struct UiaCondition *)&prop_cond; + } + else + sibling_stop_cond = (struct UiaCondition *)&UiaFalseCondition; + + node2 = NULL; + IWineUiaNode_AddRef(&node->IWineUiaNode_iface); + hr = traverse_uia_node_tree(huianode, cache_req->pViewCondition, find_params->pFindCondition, sibling_stop_cond, + TreeTraversalOptions_Default, TRUE, find_params->MaxDepth, &cur_depth, &node2); + if (FAILED(hr) || !node2) + goto exit; + + if (!(offsets = SafeArrayCreateVector(VT_I4, 0, 1))) + { + hr = E_FAIL; + goto exit; + } + + if (!(tree_structs = SafeArrayCreateVector(VT_BSTR, 0, 1))) + { + hr = E_FAIL; + goto exit; + } + + hr = UiaGetUpdatedCache(node2, cache_req, NormalizeState_None, NULL, &req, &tree_struct); + UiaNodeRelease(node2); + if (FAILED(hr)) + goto exit; + + idx = 0; + hr = SafeArrayPutElement(tree_structs, &idx, tree_struct); + SysFreeString(tree_struct); + if (FAILED(hr)) + goto exit; + + hr = SafeArrayPutElement(offsets, &idx, &idx); + if (FAILED(hr)) + goto exit; + + *out_tree_structs = tree_structs; + *out_offsets = offsets; + *out_req = req; + +exit: + VariantClear(&prop_cond.Value); + + if (FAILED(hr)) + { + SafeArrayDestroy(tree_structs); + SafeArrayDestroy(offsets); + SafeArrayDestroy(req); + } + + return hr; }
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 44 ++++++++++------------ dlls/uiautomationcore/uia_client.c | 37 +++++++++--------- 2 files changed, 39 insertions(+), 42 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index f9e232d6fc3..5c15c9344c9 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -8791,35 +8791,31 @@ static void test_UiaFind(void) AutomationElementMode_Full); set_find_params(&find_params, 1, TRUE, TRUE, (struct UiaCondition *)&UiaTrueCondition); hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref);
- node2 = NULL; - if (SUCCEEDED(hr)) - { - add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); - exp_lbound[0] = exp_lbound[1] = 0; - exp_elems[0] = exp_elems[1] = 1; + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = exp_elems[1] = 1;
- idx[0] = idx[1] = 0; - hr = SafeArrayGetElement(out_req, idx, &v); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + idx[0] = idx[1] = 0; + hr = SafeArrayGetElement(out_req, idx, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr);
- /* node2 is now set as Provider_child. */ - hr = UiaHUiaNodeFromVariant(&v, &node2); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - IUnknown_AddRef((IUnknown *)node2); + /* node2 is now set as Provider_child. */ + hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + IUnknown_AddRef((IUnknown *)node2);
- test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc);
- for (i = 0; i < exp_elems[0]; i++) - { - exp_offset[i] = i; - exp_tree_struct[i] = L"P)"; - } - test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); - ok_method_sequence(find_seq4, "find_seq4"); + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq4, "find_seq4");
SafeArrayDestroy(out_req); SafeArrayDestroy(offsets); @@ -8901,7 +8897,7 @@ static void test_UiaFind(void) SafeArrayDestroy(offsets); SafeArrayDestroy(tree_structs);
- todo_wine ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); + ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); ok(Provider_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child.ref);
initialize_provider_tree(); diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index e57678c8ba3..42795e7218b 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -945,7 +945,7 @@ exit: */ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *view_cond, struct UiaCondition *search_cond, struct UiaCondition *sibling_stop_cond, int traversal_opts, - BOOL at_root_level, int max_depth, int *cur_depth, HUIANODE *out_node) + BOOL at_root_level, BOOL exclude_root, BOOL *root_found, int max_depth, int *cur_depth, HUIANODE *out_node) { struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode); BOOL incr_depth = FALSE; @@ -963,17 +963,22 @@ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *vi * our current depth within the tree. */ incr_depth = TRUE; - hr = uia_condition_check(huianode, search_cond); - if (FAILED(hr)) - goto exit;
- if (uia_condition_matched(hr)) + if (!(exclude_root && !(*root_found))) { - IWineUiaNode_AddRef((IWineUiaNode *)huianode); - *out_node = huianode; - hr = S_FALSE; - goto exit; + hr = uia_condition_check(huianode, search_cond); + if (FAILED(hr)) + goto exit; + + if (uia_condition_matched(hr)) + { + IWineUiaNode_AddRef((IWineUiaNode *)huianode); + *out_node = huianode; + hr = S_FALSE; + goto exit; + } } + *root_found = TRUE; }
if (incr_depth) @@ -989,7 +994,7 @@ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *vi
if (SUCCEEDED(hr) && node2) hr = traverse_uia_node_tree(node2, view_cond, search_cond, sibling_stop_cond, traversal_opts, FALSE, - max_depth, cur_depth, out_node); + exclude_root, root_found, max_depth, cur_depth, out_node);
if (FAILED(hr) || hr == S_FALSE) goto exit; @@ -1014,7 +1019,7 @@ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *vi
if (SUCCEEDED(hr) && node2) hr = traverse_uia_node_tree(node2, view_cond, search_cond, sibling_stop_cond, traversal_opts, at_root_level, - max_depth, cur_depth, out_node); + exclude_root, root_found, max_depth, cur_depth, out_node);
exit: UiaNodeRelease(huianode); @@ -2719,6 +2724,7 @@ HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, str struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode); SAFEARRAY *runtime_id, *req, *offsets, *tree_structs; struct UiaCondition *sibling_stop_cond; + BOOL root_found = FALSE; int cur_depth = 0; BSTR tree_struct; HUIANODE node2; @@ -2738,12 +2744,6 @@ HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, str return E_NOTIMPL; }
- if (find_params->ExcludeRoot) - { - FIXME("ExcludeRoot currently unimplemented.\n"); - return E_NOTIMPL; - } - /* * If the initial node has a runtime ID, we'll use it as a stop * condition. @@ -2761,7 +2761,8 @@ HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, str node2 = NULL; IWineUiaNode_AddRef(&node->IWineUiaNode_iface); hr = traverse_uia_node_tree(huianode, cache_req->pViewCondition, find_params->pFindCondition, sibling_stop_cond, - TreeTraversalOptions_Default, TRUE, find_params->MaxDepth, &cur_depth, &node2); + TreeTraversalOptions_Default, TRUE, find_params->ExcludeRoot, &root_found, find_params->MaxDepth, + &cur_depth, &node2); if (FAILED(hr) || !node2) goto exit;
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_client.c | 85 ++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 11 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 42795e7218b..715d8b27fbd 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -25,6 +25,67 @@ WINE_DEFAULT_DEBUG_CHANNEL(uiautomation);
static const struct UiaCondition UiaFalseCondition = { ConditionType_False };
+static BOOL uia_array_reserve(void **elements, SIZE_T *capacity, SIZE_T element_count, SIZE_T element_size) +{ + SIZE_T new_capacity, max_capacity; + void *new_elements; + + if (element_count <= *capacity) + return TRUE; + + max_capacity = ~(SIZE_T)0 / element_size; + if (max_capacity < element_count) + return FALSE; + + new_capacity = max(*capacity, 4); + while (new_capacity < element_count && new_capacity <= max_capacity / 2) + new_capacity *= 2; + + if (new_capacity < element_count) + new_capacity = max_capacity; + + if (!(new_elements = heap_realloc(*elements, new_capacity * element_size))) + return FALSE; + + *elements = new_elements; + *capacity = new_capacity; + return TRUE; +} + +struct uia_node_array { + HUIANODE *nodes; + int node_count; + SIZE_T node_arr_size; +}; + +static void clear_node_array(struct uia_node_array *nodes) +{ + if (nodes->nodes) + { + int i; + + for (i = 0; i < nodes->node_count; i++) + UiaNodeRelease(nodes->nodes[i]); + + heap_free(nodes->nodes); + } + + memset(nodes, 0, sizeof(*nodes)); +} + +static HRESULT add_node_to_node_array(struct uia_node_array *out_nodes, HUIANODE node) +{ + if (!uia_array_reserve((void **)&out_nodes->nodes, &out_nodes->node_arr_size, out_nodes->node_count + 1, + sizeof(node))) + return E_OUTOFMEMORY; + + IWineUiaNode_AddRef((IWineUiaNode *)node); + out_nodes->nodes[out_nodes->node_count] = node; + out_nodes->node_count++; + + return S_OK; +} + static HRESULT get_safearray_bounds(SAFEARRAY *sa, LONG *lbound, LONG *elems) { LONG ubound; @@ -945,7 +1006,8 @@ exit: */ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *view_cond, struct UiaCondition *search_cond, struct UiaCondition *sibling_stop_cond, int traversal_opts, - BOOL at_root_level, BOOL exclude_root, BOOL *root_found, int max_depth, int *cur_depth, HUIANODE *out_node) + BOOL at_root_level, BOOL exclude_root, BOOL *root_found, int max_depth, int *cur_depth, + struct uia_node_array *out_nodes) { struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode); BOOL incr_depth = FALSE; @@ -972,8 +1034,10 @@ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *vi
if (uia_condition_matched(hr)) { - IWineUiaNode_AddRef((IWineUiaNode *)huianode); - *out_node = huianode; + hr = add_node_to_node_array(out_nodes, huianode); + if (FAILED(hr)) + goto exit; + hr = S_FALSE; goto exit; } @@ -994,7 +1058,7 @@ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *vi
if (SUCCEEDED(hr) && node2) hr = traverse_uia_node_tree(node2, view_cond, search_cond, sibling_stop_cond, traversal_opts, FALSE, - exclude_root, root_found, max_depth, cur_depth, out_node); + exclude_root, root_found, max_depth, cur_depth, out_nodes);
if (FAILED(hr) || hr == S_FALSE) goto exit; @@ -1019,7 +1083,7 @@ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *vi
if (SUCCEEDED(hr) && node2) hr = traverse_uia_node_tree(node2, view_cond, search_cond, sibling_stop_cond, traversal_opts, at_root_level, - exclude_root, root_found, max_depth, cur_depth, out_node); + exclude_root, root_found, max_depth, cur_depth, out_nodes);
exit: UiaNodeRelease(huianode); @@ -2724,10 +2788,10 @@ HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, str struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode); SAFEARRAY *runtime_id, *req, *offsets, *tree_structs; struct UiaCondition *sibling_stop_cond; + struct uia_node_array nodes = { 0 }; BOOL root_found = FALSE; int cur_depth = 0; BSTR tree_struct; - HUIANODE node2; HRESULT hr; LONG idx;
@@ -2758,12 +2822,11 @@ HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, str else sibling_stop_cond = (struct UiaCondition *)&UiaFalseCondition;
- node2 = NULL; IWineUiaNode_AddRef(&node->IWineUiaNode_iface); hr = traverse_uia_node_tree(huianode, cache_req->pViewCondition, find_params->pFindCondition, sibling_stop_cond, TreeTraversalOptions_Default, TRUE, find_params->ExcludeRoot, &root_found, find_params->MaxDepth, - &cur_depth, &node2); - if (FAILED(hr) || !node2) + &cur_depth, &nodes); + if (FAILED(hr) || !nodes.node_count) goto exit;
if (!(offsets = SafeArrayCreateVector(VT_I4, 0, 1))) @@ -2778,8 +2841,7 @@ HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, str goto exit; }
- hr = UiaGetUpdatedCache(node2, cache_req, NormalizeState_None, NULL, &req, &tree_struct); - UiaNodeRelease(node2); + hr = UiaGetUpdatedCache(nodes.nodes[0], cache_req, NormalizeState_None, NULL, &req, &tree_struct); if (FAILED(hr)) goto exit;
@@ -2799,6 +2861,7 @@ HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, str
exit: VariantClear(&prop_cond.Value); + clear_node_array(&nodes);
if (FAILED(hr)) {
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 339 ++++++++++----------- dlls/uiautomationcore/uia_client.c | 180 ++++++++--- 2 files changed, 297 insertions(+), 222 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 5c15c9344c9..75782b0f5e0 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -8668,37 +8668,34 @@ static void test_UiaFind(void) AutomationElementMode_Full); set_find_params(&find_params, -1, FALSE, FALSE, (struct UiaCondition *)&UiaTrueCondition); hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); - todo_wine ok(Provider_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child.ref); - todo_wine ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref); - todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); - todo_wine ok(Provider_child2_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child2_child.ref); - todo_wine ok(Provider_child2_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child2_child_child.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + ok(Provider_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child.ref); + ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref); + ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + ok(Provider_child2_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child2_child.ref); + ok(Provider_child2_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child2_child_child.ref);
- if (SUCCEEDED(hr)) - { - add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); - add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child", TRUE); - add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child_child", TRUE); - add_provider_desc(&exp_node_desc[3], L"Main", L"Provider_child_child2", TRUE); - add_provider_desc(&exp_node_desc[4], L"Main", L"Provider_child2", TRUE); - add_provider_desc(&exp_node_desc[5], L"Main", L"Provider_child2_child", TRUE); - add_provider_desc(&exp_node_desc[6], L"Main", L"Provider_child2_child_child", TRUE); - exp_lbound[0] = exp_lbound[1] = 0; - exp_elems[0] = 7; - exp_elems[1] = 1; + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child", TRUE); + add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child_child", TRUE); + add_provider_desc(&exp_node_desc[3], L"Main", L"Provider_child_child2", TRUE); + add_provider_desc(&exp_node_desc[4], L"Main", L"Provider_child2", TRUE); + add_provider_desc(&exp_node_desc[5], L"Main", L"Provider_child2_child", TRUE); + add_provider_desc(&exp_node_desc[6], L"Main", L"Provider_child2_child_child", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 7; + exp_elems[1] = 1;
- test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc);
- for (i = 0; i < exp_elems[0]; i++) - { - exp_offset[i] = i; - exp_tree_struct[i] = L"P)"; - } - test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); - ok_method_sequence(find_seq1, "find_seq1"); + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq1, "find_seq1");
SafeArrayDestroy(out_req); SafeArrayDestroy(offsets); @@ -8714,31 +8711,28 @@ static void test_UiaFind(void) AutomationElementMode_Full); set_find_params(&find_params, 1, FALSE, FALSE, (struct UiaCondition *)&UiaTrueCondition); hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); - todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); - - if (SUCCEEDED(hr)) - { - add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); - add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child", TRUE); - add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child2", TRUE); - exp_lbound[0] = exp_lbound[1] = 0; - exp_elems[0] = 3; - exp_elems[1] = 1; + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref);
- test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child", TRUE); + add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 3; + exp_elems[1] = 1;
- for (i = 0; i < exp_elems[0]; i++) - { - exp_offset[i] = i; - exp_tree_struct[i] = L"P)"; - } + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc);
- test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); - ok_method_sequence(find_seq2, "find_seq2"); + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; }
+ test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq2, "find_seq2"); + SafeArrayDestroy(out_req); SafeArrayDestroy(offsets); SafeArrayDestroy(tree_structs); @@ -8753,28 +8747,25 @@ static void test_UiaFind(void) AutomationElementMode_Full); set_find_params(&find_params, 1, FALSE, TRUE, (struct UiaCondition *)&UiaTrueCondition); hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); - todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref);
- if (SUCCEEDED(hr)) - { - add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); - add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child2", TRUE); - exp_lbound[0] = exp_lbound[1] = 0; - exp_elems[0] = 2; - exp_elems[1] = 1; + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 2; + exp_elems[1] = 1;
- test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc);
- for (i = 0; i < exp_elems[0]; i++) - { - exp_offset[i] = i; - exp_tree_struct[i] = L"P)"; - } - test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); - ok_method_sequence(find_seq3, "find_seq3"); + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq3, "find_seq3");
SafeArrayDestroy(out_req); SafeArrayDestroy(offsets); @@ -8834,27 +8825,24 @@ static void test_UiaFind(void) AutomationElementMode_Full); set_find_params(&find_params, 0, FALSE, FALSE, (struct UiaCondition *)&UiaTrueCondition); hr = UiaFind(node2, &find_params, &cache_req, &out_req, &offsets, &tree_structs); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref);
- if (SUCCEEDED(hr)) - { - add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); - add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child2", TRUE); - exp_lbound[0] = exp_lbound[1] = 0; - exp_elems[0] = 2; - exp_elems[1] = 1; + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 2; + exp_elems[1] = 1;
- test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc);
- for (i = 0; i < exp_elems[0]; i++) - { - exp_offset[i] = i; - exp_tree_struct[i] = L"P)"; - } - test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); - ok_method_sequence(find_seq5, "find_seq5"); + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq5, "find_seq5");
SafeArrayDestroy(out_req); SafeArrayDestroy(offsets); @@ -8874,24 +8862,21 @@ static void test_UiaFind(void) AutomationElementMode_Full); set_find_params(&find_params, 0, FALSE, FALSE, (struct UiaCondition *)&UiaTrueCondition); hr = UiaFind(node2, &find_params, &cache_req, &out_req, &offsets, &tree_structs); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- if (SUCCEEDED(hr)) - { - add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); - exp_lbound[0] = exp_lbound[1] = 0; - exp_elems[0] = exp_elems[1] = 1; + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child", TRUE); + 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); + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc);
- for (i = 0; i < exp_elems[0]; i++) - { - exp_offset[i] = i; - exp_tree_struct[i] = L"P)"; - } - test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); - ok_method_sequence(find_seq6, "find_seq6"); + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq6, "find_seq6");
SafeArrayDestroy(out_req); SafeArrayDestroy(offsets); @@ -8929,31 +8914,28 @@ static void test_UiaFind(void) AutomationElementMode_Full); set_find_params(&find_params, 1, FALSE, FALSE, (struct UiaCondition *)&prop_cond[1]); hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(Provider_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child.ref); - todo_wine ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref); - todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child.ref); + ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref); + ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref);
- if (SUCCEEDED(hr)) - { - add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); - add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child_child", TRUE); - add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child_child2", TRUE); - add_provider_desc(&exp_node_desc[3], L"Main", L"Provider_child2", TRUE); - exp_lbound[0] = exp_lbound[1] = 0; - exp_elems[0] = 4; - exp_elems[1] = 1; + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child_child", TRUE); + add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child_child2", TRUE); + add_provider_desc(&exp_node_desc[3], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 4; + exp_elems[1] = 1;
- test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc);
- for (i = 0; i < exp_elems[0]; i++) - { - exp_offset[i] = i; - exp_tree_struct[i] = L"P)"; - } - test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); - ok_method_sequence(find_seq7, "find_seq7"); + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq7, "find_seq7");
SafeArrayDestroy(out_req); SafeArrayDestroy(offsets); @@ -8985,41 +8967,38 @@ static void test_UiaFind(void) AutomationElementMode_Full); set_find_params(&find_params, 1, FALSE, FALSE, (struct UiaCondition *)&prop_cond[1]); hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(Provider_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child.ref); - todo_wine ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref); - todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child.ref); + ok(Provider_child_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child_child2.ref); + ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref);
- if (SUCCEEDED(hr)) - { - add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); - add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child_child", TRUE); - add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child_child2", TRUE); - add_provider_desc(&exp_node_desc[3], L"Main", L"Provider_child2", TRUE); - exp_lbound[0] = exp_lbound[1] = 0; - exp_elems[0] = 4; - exp_elems[1] = 1; + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child_child", TRUE); + add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child_child2", TRUE); + add_provider_desc(&exp_node_desc[3], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 4; + exp_elems[1] = 1;
- idx[0] = 2; - idx[1] = 0; - hr = SafeArrayGetElement(out_req, idx, &v); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + idx[0] = 2; + idx[1] = 0; + hr = SafeArrayGetElement(out_req, idx, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr);
- /* node2 is now set as Provider_child_child2. */ - hr = UiaHUiaNodeFromVariant(&v, &node2); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - IUnknown_AddRef((IUnknown *)node2); + /* node2 is now set as Provider_child_child2. */ + hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + IUnknown_AddRef((IUnknown *)node2);
- test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc);
- for (i = 0; i < exp_elems[0]; i++) - { - exp_offset[i] = i; - exp_tree_struct[i] = L"P)"; - } - test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); - ok_method_sequence(find_seq8, "find_seq8"); + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq8, "find_seq8");
SafeArrayDestroy(out_req); SafeArrayDestroy(offsets); @@ -9056,29 +9035,26 @@ static void test_UiaFind(void) AutomationElementMode_Full); set_find_params(&find_params, 1, FALSE, FALSE, (struct UiaCondition *)&prop_cond[1]); hr = UiaFind(node2, &find_params, &cache_req, &out_req, &offsets, &tree_structs); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); - todo_wine ok(Provider_child2_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child2_child.ref); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); + ok(Provider_child2_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child2_child.ref);
- if (SUCCEEDED(hr)) - { - add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child_child2", TRUE); - add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child2", TRUE); - add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child2_child", TRUE); - exp_lbound[0] = exp_lbound[1] = 0; - exp_elems[0] = 3; - exp_elems[1] = 1; + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child_child2", TRUE); + add_provider_desc(&exp_node_desc[1], L"Main", L"Provider_child2", TRUE); + add_provider_desc(&exp_node_desc[2], L"Main", L"Provider_child2_child", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 3; + exp_elems[1] = 1;
- test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc);
- for (i = 0; i < exp_elems[0]; i++) - { - exp_offset[i] = i; - exp_tree_struct[i] = L"P)"; - } - test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); - ok_method_sequence(find_seq9, "find_seq9"); + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + ok_method_sequence(find_seq9, "find_seq9");
SafeArrayDestroy(out_req); SafeArrayDestroy(offsets); @@ -9088,7 +9064,7 @@ static void test_UiaFind(void) for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++) init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL);
- todo_wine ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); + ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); ok(Provider_child_child2.ref == 1, "Unexpected refcnt %ld\n", Provider_child_child2.ref);
/* @@ -9114,27 +9090,24 @@ static void test_UiaFind(void) AutomationElementMode_Full); set_find_params(&find_params, 1, FALSE, TRUE, (struct UiaCondition *)&prop_cond[1]); hr = UiaFind(node, &find_params, &cache_req, &out_req, &offsets, &tree_structs); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref); - - if (SUCCEEDED(hr)) - { - add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child2", TRUE); - exp_lbound[0] = exp_lbound[1] = 0; - exp_elems[0] = 1; - exp_elems[1] = 1; + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider_child2.ref == 2, "Unexpected refcnt %ld\n", Provider_child2.ref);
- test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc); + add_provider_desc(&exp_node_desc[0], L"Main", L"Provider_child2", TRUE); + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 1; + exp_elems[1] = 1;
- for (i = 0; i < exp_elems[0]; i++) - { - exp_offset[i] = i; - exp_tree_struct[i] = L"P)"; - } - test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + test_cache_req_sa(out_req, exp_lbound, exp_elems, exp_node_desc);
- ok_method_sequence(find_seq10, "find_seq10"); + for (i = 0; i < exp_elems[0]; i++) + { + exp_offset[i] = i; + exp_tree_struct[i] = L"P)"; } + test_find_sa_results(tree_structs, offsets, exp_elems[0], exp_tree_struct, exp_offset); + + ok_method_sequence(find_seq10, "find_seq10");
SafeArrayDestroy(out_req); SafeArrayDestroy(offsets); diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 715d8b27fbd..130d8b9c3d8 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -86,25 +86,17 @@ static HRESULT add_node_to_node_array(struct uia_node_array *out_nodes, HUIANODE return S_OK; }
-static HRESULT get_safearray_bounds(SAFEARRAY *sa, LONG *lbound, LONG *elems) +static HRESULT get_safearray_dim_bounds(SAFEARRAY *sa, UINT dim, LONG *lbound, LONG *elems) { LONG ubound; HRESULT hr; - UINT dims;
*lbound = *elems = 0; - dims = SafeArrayGetDim(sa); - if (dims != 1) - { - WARN("Invalid dimensions %d for safearray.\n", dims); - return E_FAIL; - } - - hr = SafeArrayGetLBound(sa, 1, lbound); + hr = SafeArrayGetLBound(sa, dim, lbound); if (FAILED(hr)) return hr;
- hr = SafeArrayGetUBound(sa, 1, &ubound); + hr = SafeArrayGetUBound(sa, dim, &ubound); if (FAILED(hr)) return hr;
@@ -112,6 +104,21 @@ static HRESULT get_safearray_bounds(SAFEARRAY *sa, LONG *lbound, LONG *elems) return S_OK; }
+static HRESULT get_safearray_bounds(SAFEARRAY *sa, LONG *lbound, LONG *elems) +{ + UINT dims; + + *lbound = *elems = 0; + dims = SafeArrayGetDim(sa); + if (dims != 1) + { + WARN("Invalid dimensions %d for safearray.\n", dims); + return E_FAIL; + } + + return get_safearray_dim_bounds(sa, 1, lbound, elems); +} + int uia_compare_safearrays(SAFEARRAY *sa1, SAFEARRAY *sa2, int prop_type) { LONG i, idx, lbound[2], elems[2]; @@ -1006,7 +1013,7 @@ exit: */ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *view_cond, struct UiaCondition *search_cond, struct UiaCondition *sibling_stop_cond, int traversal_opts, - BOOL at_root_level, BOOL exclude_root, BOOL *root_found, int max_depth, int *cur_depth, + BOOL at_root_level, BOOL exclude_root, BOOL *root_found, BOOL find_first, int max_depth, int *cur_depth, struct uia_node_array *out_nodes) { struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode); @@ -1038,8 +1045,11 @@ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *vi if (FAILED(hr)) goto exit;
- hr = S_FALSE; - goto exit; + if (find_first) + { + hr = S_FALSE; + goto exit; + } } } *root_found = TRUE; @@ -1058,7 +1068,7 @@ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *vi
if (SUCCEEDED(hr) && node2) hr = traverse_uia_node_tree(node2, view_cond, search_cond, sibling_stop_cond, traversal_opts, FALSE, - exclude_root, root_found, max_depth, cur_depth, out_nodes); + exclude_root, root_found, find_first, max_depth, cur_depth, out_nodes);
if (FAILED(hr) || hr == S_FALSE) goto exit; @@ -1083,7 +1093,7 @@ static HRESULT traverse_uia_node_tree(HUIANODE huianode, struct UiaCondition *vi
if (SUCCEEDED(hr) && node2) hr = traverse_uia_node_tree(node2, view_cond, search_cond, sibling_stop_cond, traversal_opts, at_root_level, - exclude_root, root_found, max_depth, cur_depth, out_nodes); + exclude_root, root_found, find_first, max_depth, cur_depth, out_nodes);
exit: UiaNodeRelease(huianode); @@ -2778,6 +2788,50 @@ HRESULT WINAPI UiaNavigate(HUIANODE huianode, enum NavigateDirection dir, struct return hr; }
+/* Combine multiple cache requests into a single SAFEARRAY. */ +static HRESULT uia_cache_request_combine(SAFEARRAY **reqs, int reqs_count, SAFEARRAY *out_req) +{ + LONG idx[2], lbound[2], elems[2], cur_offset; + int i, x, y; + HRESULT hr; + VARIANT v; + + for (i = cur_offset = 0; i < reqs_count; i++) + { + if (!reqs[i]) + continue; + + for (x = 0; x < 2; x++) + { + hr = get_safearray_dim_bounds(reqs[i], 1 + x, &lbound[x], &elems[x]); + if (FAILED(hr)) + return hr; + } + + for (x = 0; x < elems[0]; x++) + { + for (y = 0; y < elems[1]; y++) + { + idx[0] = x + lbound[0]; + idx[1] = y + lbound[1]; + hr = SafeArrayGetElement(reqs[i], idx, &v); + if (FAILED(hr)) + return hr; + + idx[0] = x + cur_offset; + idx[1] = y; + hr = SafeArrayPutElement(out_req, idx, &v); + if (FAILED(hr)) + return hr; + } + } + + cur_offset += elems[0]; + } + + return S_OK; +} + /*********************************************************************** * UiaFind (uiautomationcore.@) */ @@ -2786,14 +2840,15 @@ HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, str { struct UiaPropertyCondition prop_cond = { ConditionType_Property, UIA_RuntimeIdPropertyId }; struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode); - SAFEARRAY *runtime_id, *req, *offsets, *tree_structs; + SAFEARRAY *runtime_id, *req, *offsets, *tree_structs, **tmp_reqs; struct UiaCondition *sibling_stop_cond; struct uia_node_array nodes = { 0 }; + LONG idx, lbound, elems, cur_offset; + SAFEARRAYBOUND sabound[2]; BOOL root_found = FALSE; - int cur_depth = 0; + int i, cur_depth = 0; BSTR tree_struct; HRESULT hr; - LONG idx;
TRACE("(%p, %p, %p, %p, %p, %p)\n", huianode, find_params, cache_req, out_req, out_offsets, out_tree_structs);
@@ -2801,12 +2856,7 @@ HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, str return E_INVALIDARG;
*out_tree_structs = *out_offsets = *out_req = tree_structs = offsets = req = NULL; - - if (!find_params->FindFirst) - { - FIXME("Finding more than one HUIANODE is currently unimplemented.\n"); - return E_NOTIMPL; - } + tmp_reqs = NULL;
/* * If the initial node has a runtime ID, we'll use it as a stop @@ -2824,36 +2874,81 @@ HRESULT WINAPI UiaFind(HUIANODE huianode, struct UiaFindParams *find_params, str
IWineUiaNode_AddRef(&node->IWineUiaNode_iface); hr = traverse_uia_node_tree(huianode, cache_req->pViewCondition, find_params->pFindCondition, sibling_stop_cond, - TreeTraversalOptions_Default, TRUE, find_params->ExcludeRoot, &root_found, find_params->MaxDepth, - &cur_depth, &nodes); + TreeTraversalOptions_Default, TRUE, find_params->ExcludeRoot, &root_found, find_params->FindFirst, + find_params->MaxDepth, &cur_depth, &nodes); if (FAILED(hr) || !nodes.node_count) goto exit;
- if (!(offsets = SafeArrayCreateVector(VT_I4, 0, 1))) + if (!(offsets = SafeArrayCreateVector(VT_I4, 0, nodes.node_count))) { hr = E_FAIL; goto exit; }
- if (!(tree_structs = SafeArrayCreateVector(VT_BSTR, 0, 1))) + if (!(tree_structs = SafeArrayCreateVector(VT_BSTR, 0, nodes.node_count))) { hr = E_FAIL; goto exit; }
- hr = UiaGetUpdatedCache(nodes.nodes[0], cache_req, NormalizeState_None, NULL, &req, &tree_struct); - if (FAILED(hr)) + if (!(tmp_reqs = heap_alloc_zero(sizeof(*tmp_reqs) * nodes.node_count))) + { + hr = E_OUTOFMEMORY; goto exit; + }
- idx = 0; - hr = SafeArrayPutElement(tree_structs, &idx, tree_struct); - SysFreeString(tree_struct); - if (FAILED(hr)) - goto exit; + /* + * Get a count of how many total nodes we'll need to return, as well as + * set the tree structure strings and cache request offsets for our final + * combined SAFEARRAY. + */ + for (i = cur_offset = 0; i < nodes.node_count; i++) + { + hr = UiaGetUpdatedCache(nodes.nodes[i], cache_req, NormalizeState_None, NULL, &tmp_reqs[i], &tree_struct); + if (FAILED(hr)) + goto exit;
- hr = SafeArrayPutElement(offsets, &idx, &idx); - if (FAILED(hr)) - goto exit; + idx = i; + hr = SafeArrayPutElement(tree_structs, &idx, tree_struct); + SysFreeString(tree_struct); + if (FAILED(hr)) + goto exit; + + hr = SafeArrayPutElement(offsets, &idx, &cur_offset); + if (FAILED(hr)) + goto exit; + + if (!tmp_reqs[i]) + continue; + + hr = get_safearray_dim_bounds(tmp_reqs[i], 1, &lbound, &elems); + if (FAILED(hr)) + goto exit; + + cur_offset += elems; + } + + if (nodes.node_count == 1) + { + req = tmp_reqs[0]; + heap_free(tmp_reqs); + tmp_reqs = NULL; + } + else + { + sabound[0].lLbound = sabound[1].lLbound = 0; + sabound[0].cElements = cur_offset; + sabound[1].cElements = 1 + cache_req->cProperties + cache_req->cPatterns; + if (!(req = SafeArrayCreate(VT_VARIANT, 2, sabound))) + { + hr = E_FAIL; + goto exit; + } + + hr = uia_cache_request_combine(tmp_reqs, nodes.node_count, req); + if (FAILED(hr)) + goto exit; + }
*out_tree_structs = tree_structs; *out_offsets = offsets; @@ -2863,6 +2958,13 @@ exit: VariantClear(&prop_cond.Value); clear_node_array(&nodes);
+ if (tmp_reqs) + { + for (i = 0; i < nodes.node_count; i++) + SafeArrayDestroy(tmp_reqs[i]); + heap_free(tmp_reqs); + } + if (FAILED(hr)) { SafeArrayDestroy(tree_structs);
V2: Clarify some comments, and rename some functions to make behavior more clear.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/tests/uiautomation.c:
- {
BSTR tree_struct;
int offset;
hr = SafeArrayGetElement(tree_structs, &i, &tree_struct);
ok_(file, line)(hr == S_OK, "Unexpected hr %#lx\n", hr);
ok_(file, line)(!wcscmp(tree_struct, exp_tree_struct[i]), "Unexpected tree structure %s\n", debugstr_w(tree_struct));
SysFreeString(tree_struct);
hr = SafeArrayGetElement(offsets, &i, &offset);
ok_(file, line)(hr == S_OK, "Unexpected hr %#lx\n", hr);
ok_(file, line)(exp_offset[i] == offset, "Unexpected offset %d\n", offset);
- }
+}
+static const struct prov_method_sequence find_seq1[] = {
I'm starting to think that maybe you don't need to test a method call sequence for everything.
I suspect you could also get away with testing fewer things generally, if the same things are tested elsewhere or the details do not impact the function of the implementation.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/tests/uiautomation.c:
- HRESULT hr;
- VARIANT v;
- CoInitializeEx(NULL, COINIT_MULTITHREADED);
- /*
* Tree looks like:
* -Provider
* ---Provider_child
* -----Provider_child_child
* -----Provider_child_child2
* ---Provider_child2
* -----Provider_child2_child
* -------Provider_child2_child_child
*/
- set_provider_nav_ifaces(&Provider, NULL, NULL, NULL, NULL, &Provider_child, &Provider_child2);
Is there some way you could have in the code only a description of the tree? It's a bit much specifying every individual link, and leaves open the possibility of accidental inconsistencies.
For example, you could write an "add to parent" function that sets these links automatically.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/tests/uiautomation.c:
- initialize_provider_tree();
- for (i = 0; i < ARRAY_SIZE(exp_node_desc); i++)
init_node_provider_desc(&exp_node_desc[i], GetCurrentProcessId(), NULL);
- hr = UiaNodeFromProvider(&Provider.IRawElementProviderSimple_iface, &node);
- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- ok(Provider.ref == 2, "Unexpected refcnt %ld\n", Provider.ref);
- hr = UiaGetPropertyValue(node, UIA_ProviderDescriptionPropertyId, &v);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr);
- if (SUCCEEDED(hr))
- {
check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL);
check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider", TRUE);
This is supposed to be a test for the find function, do we really need to check this here?
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_client.c:
+static HRESULT uia_condition_check(HUIANODE node, struct UiaCondition *condition); +static BOOL uia_condition_matched(HRESULT hr);
+static HRESULT get_sibling_node_for_tree(HUIANODE huianode, struct UiaCondition *view_cond, int dir,
BOOL at_root_level, HUIANODE *out_node)
+{
- struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)huianode);
- HUIANODE node2 = NULL;
- HRESULT hr;
- *out_node = NULL;
- IWineUiaNode_AddRef(&node->IWineUiaNode_iface);
- while (1)
- {
hr = navigate_uia_node(node, dir, &node2);
I'm confused why it's not necessary to check the condition if we get a non-NULL node2 here.
Only reviewed the first 2 commits for now because my time is limited and I wanted to get the feedback out sooner.
On Mon Nov 7 23:00:28 2022 +0000, Esme Povirk wrote:
This is supposed to be a test for the find function, do we really need to check this here?
This is largely a leftover from trying to re-use the `node_from_prov2` sequence. It isn't strictly necessary I guess, but it's a good way of knowing a node represents the exact provider that I expect it to, and that it's classified as the proper provider for the node before working with it. I guess we can reasonably assume it will always be that way, so we could create a new simpler sequence that excludes that for clarity.
On Mon Nov 7 23:00:27 2022 +0000, Esme Povirk wrote:
I'm starting to think that maybe you don't need to test a method call sequence for everything. I suspect you could also get away with testing fewer things generally, if the same things are tested elsewhere or the details do not impact the function of the implementation.
I think testing less things generally can make sense, and we could probably cut a few of the tests out that might be redundant, but I think method call sequences are important here because they get at the order of how things work. I.e, what order nodes in the tree are visited, what order they're returned in, when passed in UiaConditions are checked. Without having the method sequences, we won't end up testing those behaviors and confirming that we're matching them.
On Mon Nov 7 23:00:28 2022 +0000, Esme Povirk wrote:
Is there some way you could have in the code only a description of the tree? It's a bit much specifying every individual link, and leaves open the possibility of accidental inconsistencies. For example, you could write an "add to parent" function that sets these links automatically.
I see what you mean here, I will try to improve this.
On Mon Nov 7 23:00:29 2022 +0000, Esme Povirk wrote:
I'm confused why it's not necessary to check the condition if we get a non-NULL node2 here.
In `get_sibling_node_for_tree()`, we're not concerned with checking the condition on the sibling, we're only concerned with retrieving it. The sibling returned from `get_sibling_node_for_tree()`, if there is one, gets passed into `traverse_uia_node_tree()` which has its condition checked as it's 'visited'.
On Tue Nov 8 15:44:13 2022 +0000, Connor McAdams wrote:
In `get_sibling_node_for_tree()`, we're not concerned with checking the condition on the sibling, we're only concerned with retrieving it. The sibling returned from `get_sibling_node_for_tree()`, if there is one, gets passed into `traverse_uia_node_tree()` which has its condition checked as it's 'visited'.
I had assumed the function was meant to find the next or previous sibling within the virtual tree. Can you explain what the function is supposed to do, without mentioning `traverse_uia_node_tree`?
On Tue Nov 8 13:39:59 2022 +0000, Connor McAdams wrote:
This is largely a leftover from trying to re-use the `node_from_prov2` sequence. It isn't strictly necessary I guess, but it's a good way of knowing a node represents the exact provider that I expect it to, and that it's classified as the proper provider for the node before working with it. I guess we can reasonably assume it will always be that way, so we could create a new simpler sequence that excludes that for clarity.
I see. I guess it's not worth removing this if it means having to create another sequence.
On Wed Nov 9 22:45:39 2022 +0000, Esme Povirk wrote:
I had assumed the function was meant to find the next or previous sibling within the virtual tree. Can you explain what the function is supposed to do, without mentioning `traverse_uia_node_tree`?
It takes a passed in node, and does the following:
1. Attempt to navigate to the nodes sibling. 2. If no sibling is retrieved, and we're not at the root level, navigate to the parent. 3. Check if the parent is a part of the current virtual tree. If it is, stop and return no sibling. 4. If it's not, loop back to 1.
It first attempts to find an actual sibling from the node, and if it doesn't get one, it goes upwards and to the right looking for a sibling.
Maybe it would help clear things up if we removed the attempt to navigate to the sibling of the first node passed in, and just started at navigating towards the parent.
On Wed Nov 9 22:59:19 2022 +0000, Connor McAdams wrote:
It takes a passed in node, and does the following:
- Attempt to navigate to the nodes sibling.
- If no sibling is retrieved, and we're not at the root level, navigate
to the parent. 3. Check if the parent is a part of the current virtual tree. If it is, stop and return no sibling. 4. If it's not, loop back to 1. It first attempts to find an actual sibling from the node, and if it doesn't get one, it goes upwards and to the right looking for a sibling. Maybe it would help clear things up if we removed the attempt to navigate to the sibling of the first node passed in, and just started at navigating towards the parent.
That sounds like you're describing the implementation. At a higher level, what is it supposed to do? I need a simple enough answer to keep in my head, so that I can verify that it's correct in isolation, and that it's being used correctly.
On Wed Nov 9 23:02:18 2022 +0000, Esme Povirk wrote:
That sounds like you're describing the implementation. At a higher level, what is it supposed to do? I need a simple enough answer to keep in my head, so that I can verify that it's correct in isolation, and that it's being used correctly.
![3955d6b06868548df116124fdc4ce119255524a1-1](/uploads/b8b3e4df363352983130fe522b255632/3955d6b06868548df116124fdc4ce119255524a1-1.jpeg)
For a visual example. Let's say you start at node 5, and your current view condition is the control view. In the control view, 3 is a sibling of 5. So, to get 5's sibling:
- Navigate upwards. - Confirm that the parent doesn't match the view condition, or else it'd be the parent of node 5 and therefore node 5 would have no more siblings. - If it doesn't match the view condition, get it's sibling and return.
On Wed Nov 9 23:12:17 2022 +0000, Connor McAdams wrote:
![3955d6b06868548df116124fdc4ce119255524a1-1](/uploads/b8b3e4df363352983130fe522b255632/3955d6b06868548df116124fdc4ce119255524a1-1.jpeg) For a visual example. Let's say you start at node 5, and your current view condition is the control view. In the control view, 3 is a sibling of 5. So, to get 5's sibling:
- Navigate upwards.
- Confirm that the parent doesn't match the view condition, or else it'd
be the parent of node 5 and therefore node 5 would have no more siblings.
- If it doesn't match the view condition, get its sibling and return.
The actual view condition for this function is only relevant to make sure that the parent we navigate to isn't a parent to the node we're trying to get a sibling for.
On Wed Nov 9 23:13:35 2022 +0000, Connor McAdams wrote:
The actual view condition for this function is only relevant to make sure that the parent we navigate to isn't a parent to the node we're trying to get a sibling for.
I just don't see what operation you're trying to implement by doing all of that.
On Wed Nov 9 23:15:01 2022 +0000, Esme Povirk wrote:
I just don't see what operation you're trying to implement by doing all of that.
I will sleep on this and try to see if I can come up with a better answer tomorrow...
On Wed Nov 9 23:19:35 2022 +0000, Connor McAdams wrote:
I will sleep on this and try to see if I can come up with a better answer tomorrow...
What I'm trying to get at here is: I think you might have 2 different functions that are logically coupled, meaning you have to look at both functions to evaluate the correctness of either one. That makes the code harder to understand and maintain.
On Wed Nov 9 23:19:58 2022 +0000, Esme Povirk wrote:
What I'm trying to get at here is: I think you might have 2 different functions that are logically coupled, meaning you have to look at both functions to evaluate the correctness of either one. That makes the code harder to understand and maintain.
The only part of `get_sibling_node_for_tree` that is logically coupled to `traverse_uia_node_tree` is the `at_root_level` argument. The operation that `get_sibling_node_for_tree` implements also gets used by `UiaNavigate` when navigating with `NavigateDirection_{Previous/Next}Sibling`. An example:
``` +------------------------------------+-----------------------------------+ | 'Raw' (ConditionType_True) | UIA_IsControlElementPropertyId | | tree view | equals TRUE tree view | +------------------------------------+-----------------------------------+ | | | | +---+---+ | +---+---+ | | | | | | | | | | 1 + | | 1 + | | | | | | | | | +---+---+ | +---+---+ | | | | | | | +-----------+-----------+ | +-----------+-----------+ | | | | | | | | | | +---+---+ +---+---+ +---+---+ | +---+---+ +---+---+ | | | | | | | | | | | | | | | | 2 +---| 3 +---| 4 + | | 6 +---------------| 4 + | | | | | | | | | | | | | | | +---+---+ +---+---+ +---+---+ | +---+---+ +---+---+ | | | | | | +---+---+ | | | | | | | | | 5 | | | | | | | | | +---+---+ | | | | | | | +---+---+ | | | | | | | | | 6 | | | | | | | | | +-------+ | | | | | +------------------------------------+-----------------------------------+
```
If our view condition here is `UIA_IsControlElementPropertyId` being set to TRUE, and only nodes 6 and 4 return TRUE for that property, how do we navigate from node 6 to its sibling node 4 in the treeview?
First, we need to do what `get_sibling_node_for_tree` does: - Check if node 6 has a sibling. It doesn't. - Navigate to node 5 from node 6. - Check if node 5 matches our view condition, and is therefore a parent of 6 in this treeview. It doesn't. - Check if node 5 has any siblings. It doesn't. - Navigate to node 2 from node 5. - Check if node 2 matches our view condition, and is therefore a parent of 6 in this treeview. It doesn't. - Check if node 2 has any siblings. It does. Return its sibling, node 3 from `get_sibling_node_for_tree`.
Now, we're back to whatever function called `get_sibling_node_for_tree` (either `UiaNavigate` or `traverse_uia_node_tree`) and can continue doing whatever we need to do to get from node 3 to node 4.
On Thu Nov 10 17:22:53 2022 +0000, Connor McAdams wrote:
The only part of `get_sibling_node_for_tree` that is logically coupled to `traverse_uia_node_tree` is the `at_root_level` argument. The operation that `get_sibling_node_for_tree` implements also gets used by `UiaNavigate` when navigating with `NavigateDirection_{Previous/Next}Sibling`. An example:
+------------------------------------+-----------------------------------+ | 'Raw' (ConditionType_True) | UIA_IsControlElementPropertyId | | tree view | equals TRUE tree view | +------------------------------------+-----------------------------------+ | | | | +---+---+ | +---+---+ | | | | | | | | | | 1 + | | 1 + | | | | | | | | | +---+---+ | +---+---+ | | | | | | | +-----------+-----------+ | +-----------+-----------+ | | | | | | | | | | +---+---+ +---+---+ +---+---+ | +---+---+ +---+---+ | | | | | | | | | | | | | | | | 2 +---| 3 +---| 4 + | | 6 +---------------| 4 + | | | | | | | | | | | | | | | +---+---+ +---+---+ +---+---+ | +---+---+ +---+---+ | | | | | | +---+---+ | | | | | | | | | 5 | | | | | | | | | +---+---+ | | | | | | | +---+---+ | | | | | | | | | 6 | | | | | | | | | +-------+ | | | | | +------------------------------------+-----------------------------------+
If our view condition here is `UIA_IsControlElementPropertyId` being set to TRUE, and only nodes 6 and 4 return TRUE for that property, how do we navigate from node 6 to its sibling node 4 in the treeview? First, we need to do what `get_sibling_node_for_tree` does:
- Check if node 6 has a sibling. It doesn't.
- Navigate to node 5 from node 6.
- Check if node 5 matches our view condition, and is therefore a parent
of 6 in this treeview. It doesn't.
- Check if node 5 has any siblings. It doesn't.
- Navigate to node 2 from node 5.
- Check if node 2 matches our view condition, and is therefore a parent
of 6 in this treeview. It doesn't.
- Check if node 2 has any siblings. It does. Return its sibling, node 3
from `get_sibling_node_for_tree`. Now, we're back to whatever function called `get_sibling_node_for_tree` (either `UiaNavigate` or `traverse_uia_node_tree`) and can continue doing whatever we need to do to get from node 3 to node 4. In the case of `UiaNavigate`:
- Check if node 3 matches our view condition. It doesn't.
- Get node 3's sibling, node 4.
- Check if node 4 matches our view condition. It does. Return node 4.
In the case of `traverse_uia_node`, we just pass the node we received into another call to `traverse_uia_node`, like any other sibling.
What is the relationship between node 6 and node 3 in this example? It's easy for me to understand the relationship between node 6 and node 4, but I'm confused about this one.
On Thu Nov 10 22:09:13 2022 +0000, Esme Povirk wrote:
What is the relationship between node 6 and node 3 in this example? It's easy for me to understand the relationship between node 6 and node 4, but I'm confused about this one.
I understand the confusion, and the best way I can answer that is that node 3 needs to be reached/visited in order to reach node 4. Maybe this isn't an adequate explanation.
On Fri Nov 11 01:15:42 2022 +0000, Connor McAdams wrote:
I understand the confusion, and the best way I can answer that is that node 3 needs to be reached/visited in order to reach node 4. Maybe this isn't an adequate explanation.
Just an update for anyone following here: we had an in-depth discussion. I think it makes sense to me now, and we have a plan for how to explain it.