-- v3: uiautomationcore: Retrieve runtime ID on UiaReturnRawElementProvider thread to prevent a deadlock. uiautomationcore: Implement IUIAutomation::GetFocusedElement{BuildCacheRequest}. uiautomationcore: Implement UiaNodeFromFocus.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 264 ++++++++++++++++++-- dlls/uiautomationcore/uia_client.c | 9 + dlls/uiautomationcore/uiautomationcore.spec | 2 +- include/uiautomationcoreapi.h | 1 + 4 files changed, 259 insertions(+), 17 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 40f6cdd9f38..3fb8426a7f5 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -1157,6 +1157,7 @@ static struct Provider struct UiaRect bounds_rect; struct Provider_value_pattern_data value_pattern_data; struct Provider_legacy_accessible_pattern_data legacy_acc_pattern_data; + IRawElementProviderFragment *focus_prov; } Provider, Provider2, Provider_child, Provider_child2; static struct Provider Provider_hwnd, Provider_nc, Provider_proxy, Provider_proxy2, Provider_override; static void initialize_provider(struct Provider *prov, int prov_opts, HWND hwnd, BOOL initialize_nav_links); @@ -1223,6 +1224,7 @@ enum { FRAG_GET_RUNTIME_ID, FRAG_GET_FRAGMENT_ROOT, FRAG_GET_BOUNDING_RECT, + FRAG_ROOT_GET_FOCUS, HWND_OVERRIDE_GET_OVERRIDE_PROVIDER, };
@@ -1235,6 +1237,7 @@ static const char *prov_method_str[] = { "GetRuntimeId", "get_FragmentRoot", "get_BoundingRectangle", + "GetFocus", "GetOverrideProviderForHwnd", };
@@ -2090,8 +2093,21 @@ static HRESULT WINAPI ProviderFragmentRoot_ElementProviderFromPoint(IRawElementP static HRESULT WINAPI ProviderFragmentRoot_GetFocus(IRawElementProviderFragmentRoot *iface, IRawElementProviderFragment **ret_val) { - ok(0, "unexpected call\n"); - return E_NOTIMPL; + struct Provider *Provider = impl_from_ProviderFragmentRoot(iface); + + add_method_call(Provider, FRAG_ROOT_GET_FOCUS); + if (Provider->expected_tid) + ok(Provider->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); + Provider->last_call_tid = GetCurrentThreadId(); + + *ret_val = NULL; + if (Provider->focus_prov) + { + *ret_val = Provider->focus_prov; + IRawElementProviderFragment_AddRef(*ret_val); + } + + return S_OK; }
static const IRawElementProviderFragmentRootVtbl ProviderFragmentRootVtbl = { @@ -2514,6 +2530,10 @@ DEFINE_PROVIDER(child_child); DEFINE_PROVIDER(child_child2); DEFINE_PROVIDER(child2_child); DEFINE_PROVIDER(child2_child_child); +DEFINE_PROVIDER(hwnd2); +DEFINE_PROVIDER(nc2); +DEFINE_PROVIDER(hwnd3); +DEFINE_PROVIDER(nc3);
static IAccessible *acc_client; static IRawElementProviderSimple *prov_root; @@ -7233,7 +7253,12 @@ static SAFEARRAY WINAPI *test_uia_provider_callback(HWND hwnd, enum ProviderType { case ProviderType_BaseHwnd: CHECK_EXPECT(prov_callback_base_hwnd); - elprov = base_hwnd_prov; + if (hwnd == Provider_hwnd3.hwnd) + elprov = &Provider_hwnd3.IRawElementProviderSimple_iface; + else if (hwnd == Provider_hwnd2.hwnd) + elprov = &Provider_hwnd2.IRawElementProviderSimple_iface; + else + elprov = base_hwnd_prov; break;
case ProviderType_Proxy: @@ -7251,7 +7276,12 @@ static SAFEARRAY WINAPI *test_uia_provider_callback(HWND hwnd, enum ProviderType
case ProviderType_NonClientArea: CHECK_EXPECT(prov_callback_nonclient); - elprov = nc_prov; + if (hwnd == Provider_nc3.hwnd) + elprov = &Provider_nc3.IRawElementProviderSimple_iface; + else if (hwnd == Provider_nc2.hwnd) + elprov = &Provider_nc2.IRawElementProviderSimple_iface; + else + elprov = nc_prov; break;
default: @@ -9428,6 +9458,7 @@ static void initialize_provider(struct Provider *prov, int prov_opts, HWND hwnd, memset(&prov->bounds_rect, 0, sizeof(prov->bounds_rect)); memset(&prov->value_pattern_data, 0, sizeof(prov->value_pattern_data)); memset(&prov->legacy_acc_pattern_data, 0, sizeof(prov->legacy_acc_pattern_data)); + prov->focus_prov = NULL; if (initialize_nav_links) { prov->frag_root = NULL; @@ -12391,6 +12422,23 @@ static void test_CUIAutomation_TreeWalker_ifaces(IUIAutomation *uia_iface) UnregisterClassA("test_CUIAutomation_TreeWalker_ifaces class", NULL); }
+static void set_clientside_providers_for_hwnd(struct Provider *proxy_prov, struct Provider *nc_prov, + struct Provider *hwnd_prov, HWND hwnd) +{ + if (proxy_prov) + { + initialize_provider(proxy_prov, ProviderOptions_ClientSideProvider, hwnd, TRUE); + proxy_prov->frag_root = &proxy_prov->IRawElementProviderFragmentRoot_iface; + proxy_prov->ignore_hwnd_prop = TRUE; + } + + initialize_provider(hwnd_prov, ProviderOptions_ClientSideProvider, hwnd, TRUE); + initialize_provider(nc_prov, ProviderOptions_NonClientAreaProvider | ProviderOptions_ClientSideProvider, hwnd, TRUE); + hwnd_prov->frag_root = &hwnd_prov->IRawElementProviderFragmentRoot_iface; + nc_prov->frag_root = &nc_prov->IRawElementProviderFragmentRoot_iface; + nc_prov->ignore_hwnd_prop = TRUE; +} + static void test_GetRootElement(IUIAutomation *uia_iface) { IUIAutomationElement *element; @@ -12402,12 +12450,7 @@ static void test_GetRootElement(IUIAutomation *uia_iface)
UiaRegisterProviderCallback(test_uia_provider_callback);
- initialize_provider(&Provider_hwnd, ProviderOptions_ClientSideProvider, GetDesktopWindow(), TRUE); - initialize_provider(&Provider_nc, ProviderOptions_ClientSideProvider | ProviderOptions_NonClientAreaProvider, - GetDesktopWindow(), TRUE); - initialize_provider(&Provider_proxy, ProviderOptions_ClientSideProvider, GetDesktopWindow(), TRUE); - Provider_proxy.ignore_hwnd_prop = TRUE; - + set_clientside_providers_for_hwnd(&Provider_proxy, &Provider_nc, &Provider_hwnd, GetDesktopWindow()); base_hwnd_prov = &Provider_hwnd.IRawElementProviderSimple_iface; proxy_prov = &Provider_proxy.IRawElementProviderSimple_iface; nc_prov = &Provider_nc.IRawElementProviderSimple_iface; @@ -13057,12 +13100,7 @@ static void test_UiaGetRootNode(void) * UiaGetRootNode is the same as calling UiaNodeFromHandle with the * desktop window handle. */ - initialize_provider(&Provider_hwnd, ProviderOptions_ClientSideProvider, GetDesktopWindow(), TRUE); - initialize_provider(&Provider_nc, ProviderOptions_ClientSideProvider | ProviderOptions_NonClientAreaProvider, - GetDesktopWindow(), TRUE); - initialize_provider(&Provider_proxy, ProviderOptions_ClientSideProvider, GetDesktopWindow(), TRUE); - Provider_proxy.ignore_hwnd_prop = TRUE; - + set_clientside_providers_for_hwnd(&Provider_proxy, &Provider_nc, &Provider_hwnd, GetDesktopWindow()); base_hwnd_prov = &Provider_hwnd.IRawElementProviderSimple_iface; proxy_prov = &Provider_proxy.IRawElementProviderSimple_iface; nc_prov = &Provider_nc.IRawElementProviderSimple_iface; @@ -13105,6 +13143,199 @@ static void test_UiaGetRootNode(void) CoUninitialize(); }
+#define test_node_from_focus( cache_req, exp_node_desc, proxy_cback_count, base_hwnd_cback_count, nc_cback_count, \ + win_get_obj_count, child_win_get_obj_count, proxy_cback_todo, base_hwnd_cback_todo, \ + nc_cback_todo, win_get_obj_todo, child_win_get_obj_todo) \ + test_node_from_focus_( (cache_req), (exp_node_desc), (proxy_cback_count), (base_hwnd_cback_count), (nc_cback_count), \ + (win_get_obj_count), (child_win_get_obj_count), (proxy_cback_todo), (base_hwnd_cback_todo), \ + (nc_cback_todo), (win_get_obj_todo), (child_win_get_obj_todo), __FILE__, __LINE__) +static void test_node_from_focus_(struct UiaCacheRequest *cache_req, struct node_provider_desc *exp_node_desc, + int proxy_cback_count, int base_hwnd_cback_count, int nc_cback_count, int win_get_obj_count, + int child_win_get_obj_count, BOOL proxy_cback_todo, BOOL base_hwnd_cback_todo, BOOL nc_cback_todo, + BOOL win_get_obj_todo, BOOL child_win_get_obj_todo, const char *file, int line) +{ + const WCHAR *exp_tree_struct = exp_node_desc->prov_count ? L"P)" : L""; + LONG exp_lbound[2], exp_elems[2]; + SAFEARRAY *out_req = NULL; + BSTR tree_struct = NULL; + HRESULT hr; + + SET_EXPECT_MULTI(prov_callback_base_hwnd, base_hwnd_cback_count); + SET_EXPECT_MULTI(prov_callback_nonclient, nc_cback_count); + SET_EXPECT_MULTI(prov_callback_proxy, proxy_cback_count); + SET_EXPECT_MULTI(winproc_GETOBJECT_UiaRoot, win_get_obj_count); + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, child_win_get_obj_count); + hr = UiaNodeFromFocus(cache_req, &out_req, &tree_struct); + todo_wine ok_(file, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (exp_node_desc->prov_count) + todo_wine ok_(file, line)(!!out_req, "out_req == NULL\n"); + else + ok_(file, line)(!out_req, "out_req != NULL\n"); + todo_wine ok_(file, line)(!!tree_struct, "tree_struct == NULL\n"); + todo_wine_if(base_hwnd_cback_todo) CHECK_CALLED_MULTI(prov_callback_base_hwnd, base_hwnd_cback_count); + todo_wine_if(proxy_cback_todo) CHECK_CALLED_MULTI(prov_callback_proxy, proxy_cback_count); + todo_wine_if(nc_cback_todo) CHECK_CALLED_MULTI(prov_callback_nonclient, nc_cback_count); + todo_wine_if(win_get_obj_todo) CHECK_CALLED_MULTI(winproc_GETOBJECT_UiaRoot, win_get_obj_count); + todo_wine_if(child_win_get_obj_todo) CHECK_CALLED_MULTI(child_winproc_GETOBJECT_UiaRoot, child_win_get_obj_count); + + if (tree_struct) + ok_(file, line)(!wcscmp(tree_struct, exp_tree_struct), "unexpected tree structure %s\n", debugstr_w(tree_struct)); + if (exp_node_desc->prov_count && SUCCEEDED(hr)) + { + exp_lbound[0] = exp_lbound[1] = 0; + exp_elems[0] = 1; + exp_elems[1] = 1 + cache_req->cProperties; + test_cache_req_sa_(out_req, exp_lbound, exp_elems, exp_node_desc, file, line); + } + + SafeArrayDestroy(out_req); + SysFreeString(tree_struct); +} + +static void test_UiaNodeFromFocus(void) +{ + struct Provider_prop_override prop_override; + struct node_provider_desc exp_node_desc; + struct UiaPropertyCondition prop_cond; + struct UiaCacheRequest cache_req; + struct UiaNotCondition not_cond; + HWND hwnd, hwnd_child; + int cache_prop; + VARIANT v; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + + hwnd = create_test_hwnd("UiaNodeFromFocus class"); + hwnd_child = create_child_test_hwnd("UiaNodeFromFocus child class", hwnd); + + UiaRegisterProviderCallback(test_uia_provider_callback); + + /* Set clientside providers for our test windows and the desktop. */ + set_clientside_providers_for_hwnd(&Provider_proxy, &Provider_nc, &Provider_hwnd, GetDesktopWindow()); + base_hwnd_prov = &Provider_hwnd.IRawElementProviderSimple_iface; + nc_prov = &Provider_nc.IRawElementProviderSimple_iface; + proxy_prov = &Provider_proxy.IRawElementProviderSimple_iface; + + set_clientside_providers_for_hwnd(NULL, &Provider_nc2, &Provider_hwnd2, hwnd); + initialize_provider(&Provider, ProviderOptions_ServerSideProvider, hwnd, TRUE); + Provider.frag_root = &Provider.IRawElementProviderFragmentRoot_iface; + prov_root = &Provider.IRawElementProviderSimple_iface; + Provider.ignore_hwnd_prop = TRUE; + + set_clientside_providers_for_hwnd(NULL, &Provider_nc3, &Provider_hwnd3, hwnd_child); + initialize_provider(&Provider2, ProviderOptions_ServerSideProvider, hwnd_child, TRUE); + Provider2.frag_root = &Provider2.IRawElementProviderFragmentRoot_iface; + child_win_prov_root = &Provider2.IRawElementProviderSimple_iface; + Provider2.ignore_hwnd_prop = TRUE; + + /* + * Nodes are normalized against the cache request view condition. Here, + * we're setting it to the same as the default ControlView. + */ + variant_init_bool(&v, FALSE); + set_property_condition(&prop_cond, UIA_IsControlElementPropertyId, &v, PropertyConditionFlags_None); + set_not_condition(¬_cond, (struct UiaCondition *)&prop_cond); + cache_prop = UIA_RuntimeIdPropertyId; + set_cache_request(&cache_req, (struct UiaCondition *)¬_cond, TreeScope_Element, &cache_prop, 1, NULL, 0, + AutomationElementMode_Full); + + /* + * None of the providers for the desktop node return a provider from + * IRawElementProviderFragmentRoot::GetFocus, so we just get the + * desktop node. + */ + method_sequences_enabled = FALSE; + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), GetDesktopWindow()); + add_provider_desc(&exp_node_desc, L"Main", L"Provider_proxy", TRUE); + add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc", FALSE); + add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd", FALSE); + + test_node_from_focus(&cache_req, &exp_node_desc, 1, 1, 1, 0, 0, TRUE, TRUE, TRUE, FALSE, FALSE); + + /* Provider_hwnd returns Provider_hwnd2 from GetFocus. */ + Provider_hwnd.focus_prov = &Provider_hwnd2.IRawElementProviderFragment_iface; + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), hwnd); + add_provider_desc(&exp_node_desc, L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc2", FALSE); + add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd2", FALSE); + + test_node_from_focus(&cache_req, &exp_node_desc, 2, 1, 2, 1, 0, TRUE, TRUE, TRUE, TRUE, FALSE); + + /* + * Provider_proxy returns Provider from GetFocus. The provider that + * creates the node will not have GetFocus called on it to avoid returning + * the same provider twice. Similarly, on nodes other than the desktop + * node, the HWND provider will not have GetFocus called on it. + */ + Provider_hwnd.focus_prov = NULL; + Provider_proxy.focus_prov = &Provider.IRawElementProviderFragment_iface; + Provider.focus_prov = Provider_hwnd2.focus_prov = &Provider2.IRawElementProviderFragment_iface; + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), hwnd); + add_provider_desc(&exp_node_desc, L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc2", FALSE); + add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd2", FALSE); + + test_node_from_focus(&cache_req, &exp_node_desc, 2, 2, 2, 1, 0, TRUE, TRUE, TRUE, TRUE, FALSE); + + /* + * Provider_nc returns Provider_nc2 from GetFocus, Provider returns + * Provider2, Provider_nc3 returns Provider_child. + */ + initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + Provider_proxy.focus_prov = Provider_hwnd.focus_prov = Provider_hwnd2.focus_prov = NULL; + Provider_nc.focus_prov = &Provider_nc2.IRawElementProviderFragment_iface; + Provider.focus_prov = &Provider2.IRawElementProviderFragment_iface; + Provider_nc3.focus_prov = &Provider_child.IRawElementProviderFragment_iface; + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc, L"Main", L"Provider_child", TRUE); + + test_node_from_focus(&cache_req, &exp_node_desc, 2, 3, 2, 2, 1, TRUE, TRUE, TRUE, TRUE, TRUE); + + /* + * Provider_proxy returns Provider_child_child from GetFocus. The focus + * provider is normalized against the cache request view condition. + * Provider_child_child and its ancestors don't match the cache request + * view condition, so we'll get no provider. + */ + initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + initialize_provider(&Provider_child_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + provider_add_child(&Provider, &Provider_child); + provider_add_child(&Provider_child, &Provider_child_child); + Provider_proxy.focus_prov = &Provider_child_child.IRawElementProviderFragment_iface; + Provider_nc.focus_prov = Provider_hwnd.focus_prov = NULL; + + variant_init_bool(&v, FALSE); + set_property_override(&prop_override, UIA_IsControlElementPropertyId, &v); + set_provider_prop_override(&Provider_child_child, &prop_override, 1); + set_provider_prop_override(&Provider_child, &prop_override, 1); + set_provider_prop_override(&Provider, &prop_override, 1); + + init_node_provider_desc(&exp_node_desc, 0, NULL); + test_node_from_focus(&cache_req, &exp_node_desc, 2, 2, 2, 1, 0, TRUE, TRUE, TRUE, TRUE, FALSE); + + /* This time, Provider_child matches our view condition. */ + set_provider_prop_override(&Provider_child, NULL, 0); + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc, L"Main", L"Provider_child", TRUE); + + test_node_from_focus(&cache_req, &exp_node_desc, 1, 1, 1, 0, 0, TRUE, TRUE, TRUE, FALSE, FALSE); + + method_sequences_enabled = TRUE; + initialize_provider(&Provider, ProviderOptions_ServerSideProvider, NULL, TRUE); + initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + initialize_provider(&Provider_child_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + + CoUninitialize(); + UiaRegisterProviderCallback(NULL); + DestroyWindow(hwnd); + UnregisterClassA("UiaNodeFromFocus class", NULL); + UnregisterClassA("UiaNodeFromFocus child class", NULL); +} + /* * Once a process returns a UI Automation provider with * UiaReturnRawElementProvider it ends up in an implicit MTA until exit. This @@ -13174,6 +13405,7 @@ START_TEST(uiautomation) test_CUIAutomation(); test_default_clientside_providers(); test_UiaGetRootNode(); + test_UiaNodeFromFocus(); if (uia_dll) { pUiaProviderFromIAccessible = (void *)GetProcAddress(uia_dll, "UiaProviderFromIAccessible"); diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 64fd8b3b1cb..1329cc43db0 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -2435,6 +2435,15 @@ HRESULT WINAPI UiaGetRootNode(HUIANODE *huianode) return UiaNodeFromHandle(GetDesktopWindow(), huianode); }
+/*********************************************************************** + * UiaNodeFromFocus (uiautomationcore.@) + */ +HRESULT WINAPI UiaNodeFromFocus(struct UiaCacheRequest *cache_req, SAFEARRAY **out_req, BSTR *tree_struct) +{ + FIXME("(%p, %p, %p): stub\n", cache_req, out_req, tree_struct); + return E_NOTIMPL; +} + /*********************************************************************** * UiaNodeRelease (uiautomationcore.@) */ diff --git a/dlls/uiautomationcore/uiautomationcore.spec b/dlls/uiautomationcore/uiautomationcore.spec index 34856be6b36..686594debba 100644 --- a/dlls/uiautomationcore/uiautomationcore.spec +++ b/dlls/uiautomationcore/uiautomationcore.spec @@ -76,7 +76,7 @@ #@ stub UiaIAccessibleFromProvider @ stdcall UiaLookupId(long ptr) @ stdcall UiaNavigate(ptr long ptr ptr ptr ptr) -@ stub UiaNodeFromFocus +@ stdcall UiaNodeFromFocus(ptr ptr ptr) @ stdcall UiaNodeFromHandle(long ptr) @ stub UiaNodeFromPoint @ stdcall UiaNodeFromProvider(ptr ptr) diff --git a/include/uiautomationcoreapi.h b/include/uiautomationcoreapi.h index 320bb814b9c..9d131abc6cc 100644 --- a/include/uiautomationcoreapi.h +++ b/include/uiautomationcoreapi.h @@ -545,6 +545,7 @@ HRESULT WINAPI UiaGetRuntimeId(HUIANODE huianode, SAFEARRAY **runtime_id); HRESULT WINAPI UiaHUiaNodeFromVariant(VARIANT *in_val, HUIANODE *huianode); HRESULT WINAPI UiaNodeFromHandle(HWND hwnd, HUIANODE *huianode); HRESULT WINAPI UiaGetRootNode(HUIANODE *huianode); +HRESULT WINAPI UiaNodeFromFocus(struct UiaCacheRequest *cache_req, SAFEARRAY **out_req, BSTR *tree_struct); HRESULT WINAPI UiaDisconnectProvider(IRawElementProviderSimple *elprov); HRESULT WINAPI UiaGetUpdatedCache(HUIANODE huianode, struct UiaCacheRequest *cache_req, enum NormalizeState normalize_state, struct UiaCondition *normalize_cond, SAFEARRAY **out_req, BSTR *tree_struct);
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 23 ++-- dlls/uiautomationcore/uia_classes.idl | 1 + dlls/uiautomationcore/uia_client.c | 148 ++++++++++++++++++++- 3 files changed, 158 insertions(+), 14 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 3fb8426a7f5..f64188b0842 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -13166,21 +13166,20 @@ static void test_node_from_focus_(struct UiaCacheRequest *cache_req, struct node SET_EXPECT_MULTI(winproc_GETOBJECT_UiaRoot, win_get_obj_count); SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, child_win_get_obj_count); hr = UiaNodeFromFocus(cache_req, &out_req, &tree_struct); - todo_wine ok_(file, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); if (exp_node_desc->prov_count) - todo_wine ok_(file, line)(!!out_req, "out_req == NULL\n"); + ok_(file, line)(!!out_req, "out_req == NULL\n"); else ok_(file, line)(!out_req, "out_req != NULL\n"); - todo_wine ok_(file, line)(!!tree_struct, "tree_struct == NULL\n"); + ok_(file, line)(!!tree_struct, "tree_struct == NULL\n"); todo_wine_if(base_hwnd_cback_todo) CHECK_CALLED_MULTI(prov_callback_base_hwnd, base_hwnd_cback_count); todo_wine_if(proxy_cback_todo) CHECK_CALLED_MULTI(prov_callback_proxy, proxy_cback_count); todo_wine_if(nc_cback_todo) CHECK_CALLED_MULTI(prov_callback_nonclient, nc_cback_count); todo_wine_if(win_get_obj_todo) CHECK_CALLED_MULTI(winproc_GETOBJECT_UiaRoot, win_get_obj_count); todo_wine_if(child_win_get_obj_todo) CHECK_CALLED_MULTI(child_winproc_GETOBJECT_UiaRoot, child_win_get_obj_count);
- if (tree_struct) - ok_(file, line)(!wcscmp(tree_struct, exp_tree_struct), "unexpected tree structure %s\n", debugstr_w(tree_struct)); - if (exp_node_desc->prov_count && SUCCEEDED(hr)) + ok_(file, line)(!wcscmp(tree_struct, exp_tree_struct), "unexpected tree structure %s\n", debugstr_w(tree_struct)); + if (exp_node_desc->prov_count) { exp_lbound[0] = exp_lbound[1] = 0; exp_elems[0] = 1; @@ -13250,7 +13249,7 @@ static void test_UiaNodeFromFocus(void) add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc", FALSE); add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd", FALSE);
- test_node_from_focus(&cache_req, &exp_node_desc, 1, 1, 1, 0, 0, TRUE, TRUE, TRUE, FALSE, FALSE); + test_node_from_focus(&cache_req, &exp_node_desc, 1, 1, 1, 0, 0, FALSE, FALSE, FALSE, FALSE, FALSE);
/* Provider_hwnd returns Provider_hwnd2 from GetFocus. */ Provider_hwnd.focus_prov = &Provider_hwnd2.IRawElementProviderFragment_iface; @@ -13260,7 +13259,7 @@ static void test_UiaNodeFromFocus(void) add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc2", FALSE); add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd2", FALSE);
- test_node_from_focus(&cache_req, &exp_node_desc, 2, 1, 2, 1, 0, TRUE, TRUE, TRUE, TRUE, FALSE); + test_node_from_focus(&cache_req, &exp_node_desc, 2, 1, 2, 1, 0, TRUE, FALSE, FALSE, FALSE, FALSE);
/* * Provider_proxy returns Provider from GetFocus. The provider that @@ -13277,7 +13276,7 @@ static void test_UiaNodeFromFocus(void) add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc2", FALSE); add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd2", FALSE);
- test_node_from_focus(&cache_req, &exp_node_desc, 2, 2, 2, 1, 0, TRUE, TRUE, TRUE, TRUE, FALSE); + test_node_from_focus(&cache_req, &exp_node_desc, 2, 2, 2, 1, 0, TRUE, FALSE, FALSE, FALSE, FALSE);
/* * Provider_nc returns Provider_nc2 from GetFocus, Provider returns @@ -13292,7 +13291,7 @@ static void test_UiaNodeFromFocus(void) init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); add_provider_desc(&exp_node_desc, L"Main", L"Provider_child", TRUE);
- test_node_from_focus(&cache_req, &exp_node_desc, 2, 3, 2, 2, 1, TRUE, TRUE, TRUE, TRUE, TRUE); + test_node_from_focus(&cache_req, &exp_node_desc, 2, 3, 2, 2, 1, TRUE, FALSE, FALSE, TRUE, FALSE);
/* * Provider_proxy returns Provider_child_child from GetFocus. The focus @@ -13314,7 +13313,7 @@ static void test_UiaNodeFromFocus(void) set_provider_prop_override(&Provider, &prop_override, 1);
init_node_provider_desc(&exp_node_desc, 0, NULL); - test_node_from_focus(&cache_req, &exp_node_desc, 2, 2, 2, 1, 0, TRUE, TRUE, TRUE, TRUE, FALSE); + test_node_from_focus(&cache_req, &exp_node_desc, 2, 2, 2, 1, 0, TRUE, FALSE, FALSE, FALSE, FALSE);
/* This time, Provider_child matches our view condition. */ set_provider_prop_override(&Provider_child, NULL, 0); @@ -13322,7 +13321,7 @@ static void test_UiaNodeFromFocus(void) init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); add_provider_desc(&exp_node_desc, L"Main", L"Provider_child", TRUE);
- test_node_from_focus(&cache_req, &exp_node_desc, 1, 1, 1, 0, 0, TRUE, TRUE, TRUE, FALSE, FALSE); + test_node_from_focus(&cache_req, &exp_node_desc, 1, 1, 1, 0, 0, FALSE, FALSE, FALSE, FALSE, FALSE);
method_sequences_enabled = TRUE; initialize_provider(&Provider, ProviderOptions_ServerSideProvider, NULL, TRUE); diff --git a/dlls/uiautomationcore/uia_classes.idl b/dlls/uiautomationcore/uia_classes.idl index 7272a09bfd6..b727184ff37 100644 --- a/dlls/uiautomationcore/uia_classes.idl +++ b/dlls/uiautomationcore/uia_classes.idl @@ -66,6 +66,7 @@ library UIA_wine_private HRESULT get_prov_opts([out, retval]int *out_opts); HRESULT has_parent([out, retval]BOOL *out_val); HRESULT navigate([in]int nav_dir, [out, retval]VARIANT *ret_val); + HRESULT get_focus([out, retval]VARIANT *ret_val); }
[ diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 1329cc43db0..b62bb00b458 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -446,6 +446,22 @@ static HRESULT get_navigate_from_node_provider(IWineUiaNode *node, int idx, int return hr; }
+static HRESULT get_focus_from_node_provider(IWineUiaNode *node, int idx, VARIANT *ret_val) +{ + IWineUiaProvider *prov; + HRESULT hr; + + VariantInit(ret_val); + hr = IWineUiaNode_get_provider(node, idx, &prov); + if (FAILED(hr)) + return hr; + + hr = IWineUiaProvider_get_focus(prov, ret_val); + IWineUiaProvider_Release(prov); + + return hr; +} + /* * IWineUiaNode interface. */ @@ -1762,6 +1778,38 @@ static HRESULT WINAPI uia_provider_navigate(IWineUiaProvider *iface, int nav_dir return S_OK; }
+static HRESULT WINAPI uia_provider_get_focus(IWineUiaProvider *iface, VARIANT *out_val) +{ + struct uia_provider *prov = impl_from_IWineUiaProvider(iface); + IRawElementProviderFragmentRoot *elroot; + IRawElementProviderFragment *elfrag; + IRawElementProviderSimple *elprov; + HRESULT hr; + + TRACE("%p, %p\n", iface, out_val); + + VariantInit(out_val); + hr = IRawElementProviderSimple_QueryInterface(prov->elprov, &IID_IRawElementProviderFragmentRoot, (void **)&elroot); + if (FAILED(hr)) + return S_OK; + + hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag); + IRawElementProviderFragmentRoot_Release(elroot); + if (FAILED(hr) || !elfrag) + return hr; + + hr = IRawElementProviderFragment_QueryInterface(elfrag, &IID_IRawElementProviderSimple, (void **)&elprov); + IRawElementProviderFragment_Release(elfrag); + if (SUCCEEDED(hr)) + { + hr = get_variant_for_elprov_node(elprov, prov->return_nested_node, out_val); + if (FAILED(hr)) + VariantClear(out_val); + } + + return hr; +} + static const IWineUiaProviderVtbl uia_provider_vtbl = { uia_provider_QueryInterface, uia_provider_AddRef, @@ -1770,6 +1818,7 @@ static const IWineUiaProviderVtbl uia_provider_vtbl = { uia_provider_get_prov_opts, uia_provider_has_parent, uia_provider_navigate, + uia_provider_get_focus, };
static HRESULT create_wine_uia_provider(struct uia_node *node, IRawElementProviderSimple *elprov, @@ -2170,6 +2219,30 @@ static HRESULT WINAPI uia_nested_node_provider_navigate(IWineUiaProvider *iface, return S_OK; }
+static HRESULT WINAPI uia_nested_node_provider_get_focus(IWineUiaProvider *iface, VARIANT *out_val) +{ + struct uia_nested_node_provider *prov = impl_from_nested_node_IWineUiaProvider(iface); + HUIANODE node; + HRESULT hr; + VARIANT v; + + TRACE("%p, %p\n", iface, out_val); + + VariantInit(out_val); + hr = get_focus_from_node_provider(prov->nested_node, 0, &v); + if (FAILED(hr) || V_VT(&v) == VT_EMPTY) + return hr; + + hr = uia_node_from_lresult((LRESULT)V_I4(&v), &node); + if (FAILED(hr)) + return hr; + + get_variant_for_node(node, out_val); + VariantClear(&v); + + return S_OK; +} + static const IWineUiaProviderVtbl uia_nested_node_provider_vtbl = { uia_nested_node_provider_QueryInterface, uia_nested_node_provider_AddRef, @@ -2178,6 +2251,7 @@ static const IWineUiaProviderVtbl uia_nested_node_provider_vtbl = { uia_nested_node_provider_get_prov_opts, uia_nested_node_provider_has_parent, uia_nested_node_provider_navigate, + uia_nested_node_provider_get_focus, };
static BOOL is_nested_node_provider(IWineUiaProvider *iface) @@ -2435,13 +2509,83 @@ HRESULT WINAPI UiaGetRootNode(HUIANODE *huianode) return UiaNodeFromHandle(GetDesktopWindow(), huianode); }
+static HRESULT get_focused_uia_node(HUIANODE in_node, HUIANODE *out_node) +{ + struct uia_node *node = unsafe_impl_from_IWineUiaNode((IWineUiaNode *)in_node); + const BOOL desktop_node = (node->hwnd == GetDesktopWindow()); + HRESULT hr = S_OK; + VARIANT v; + int i; + + *out_node = NULL; + VariantInit(&v); + for (i = 0; i < node->prov_count; i++) + { + /* + * When getting focus from nodes other than the desktop, we ignore + * both the node's creator provider and its HWND provider. This avoids + * the problem of returning the same provider twice from GetFocus. + */ + if (!desktop_node && ((i == node->creator_prov_idx) || + (get_node_provider_type_at_idx(node, i) == PROV_TYPE_HWND))) + continue; + + hr = get_focus_from_node_provider(&node->IWineUiaNode_iface, i, &v); + if (FAILED(hr)) + break; + + if (V_VT(&v) != VT_EMPTY) + { + hr = UiaHUiaNodeFromVariant(&v, out_node); + if (FAILED(hr)) + *out_node = NULL; + break; + } + } + + return hr; +} + /*********************************************************************** * UiaNodeFromFocus (uiautomationcore.@) */ HRESULT WINAPI UiaNodeFromFocus(struct UiaCacheRequest *cache_req, SAFEARRAY **out_req, BSTR *tree_struct) { - FIXME("(%p, %p, %p): stub\n", cache_req, out_req, tree_struct); - return E_NOTIMPL; + HUIANODE node, node2; + HRESULT hr; + + TRACE("(%p, %p, %p)\n", cache_req, out_req, tree_struct); + + if (!cache_req || !out_req || !tree_struct) + return E_INVALIDARG; + + *out_req = NULL; + *tree_struct = NULL; + + hr = UiaGetRootNode(&node); + if (FAILED(hr)) + return hr; + + while (1) + { + hr = get_focused_uia_node(node, &node2); + if (FAILED(hr)) + goto exit; + + if (!node2) + break; + + UiaNodeRelease(node); + node = node2; + } + + hr = UiaGetUpdatedCache(node, cache_req, NormalizeState_View, NULL, out_req, tree_struct); + if (FAILED(hr)) + WARN("UiaGetUpdatedCache failed with hr %#lx\n", hr); +exit: + UiaNodeRelease(node); + + return hr; }
/***********************************************************************
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 208 +++++++++++++++++++++ dlls/uiautomationcore/uia_com_client.c | 60 +++++- 2 files changed, 264 insertions(+), 4 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index f64188b0842..cf680b6a733 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -12493,6 +12493,213 @@ static void test_GetRootElement(IUIAutomation *uia_iface) UiaRegisterProviderCallback(NULL); }
+#define test_get_focused_elem( uia_iface, cache_req, exp_hr, exp_node_desc, proxy_cback_count, base_hwnd_cback_count, \ + nc_cback_count, win_get_obj_count, child_win_get_obj_count, proxy_cback_todo, \ + base_hwnd_cback_todo, nc_cback_todo, win_get_obj_todo, child_win_get_obj_todo ) \ + test_get_focused_elem_( (uia_iface), (cache_req), (exp_hr), (exp_node_desc), (proxy_cback_count), (base_hwnd_cback_count), \ + (nc_cback_count), (win_get_obj_count), (child_win_get_obj_count), (proxy_cback_todo), \ + (base_hwnd_cback_todo), (nc_cback_todo), (win_get_obj_todo), (child_win_get_obj_todo), __FILE__, __LINE__) +static void test_get_focused_elem_(IUIAutomation *uia_iface, IUIAutomationCacheRequest *cache_req, HRESULT exp_hr, + struct node_provider_desc *exp_node_desc, int proxy_cback_count, int base_hwnd_cback_count, int nc_cback_count, + int win_get_obj_count, int child_win_get_obj_count, BOOL proxy_cback_todo, BOOL base_hwnd_cback_todo, + BOOL nc_cback_todo, BOOL win_get_obj_todo, BOOL child_win_get_obj_todo, const char *file, int line) +{ + IUIAutomationElement *element = NULL; + HRESULT hr; + VARIANT v; + + SET_EXPECT_MULTI(prov_callback_base_hwnd, base_hwnd_cback_count); + SET_EXPECT_MULTI(prov_callback_nonclient, nc_cback_count); + SET_EXPECT_MULTI(prov_callback_proxy, proxy_cback_count); + SET_EXPECT_MULTI(winproc_GETOBJECT_UiaRoot, win_get_obj_count); + SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, child_win_get_obj_count); + if (cache_req) + hr = IUIAutomation_GetFocusedElementBuildCache(uia_iface, cache_req, &element); + else + hr = IUIAutomation_GetFocusedElement(uia_iface, &element); + ok_(file, line)(hr == exp_hr, "Unexpected hr %#lx.\n", hr); + todo_wine_if(base_hwnd_cback_todo) CHECK_CALLED_MULTI(prov_callback_base_hwnd, base_hwnd_cback_count); + todo_wine_if(proxy_cback_todo) CHECK_CALLED_MULTI(prov_callback_proxy, proxy_cback_count); + todo_wine_if(nc_cback_todo) CHECK_CALLED_MULTI(prov_callback_nonclient, nc_cback_count); + todo_wine_if(win_get_obj_todo) CHECK_CALLED_MULTI(winproc_GETOBJECT_UiaRoot, win_get_obj_count); + todo_wine_if(child_win_get_obj_todo) CHECK_CALLED_MULTI(child_winproc_GETOBJECT_UiaRoot, child_win_get_obj_count); + + if (exp_node_desc->prov_count) + { + ok_(file, line)(!!element, "element == NULL\n"); + + hr = IUIAutomationElement_GetCurrentPropertyValueEx(element, UIA_ProviderDescriptionPropertyId, TRUE, &v); + ok_(file, line)(hr == S_OK, "Unexpected hr %#lx\n", hr); + test_node_provider_desc_(exp_node_desc, V_BSTR(&v), file, line); + VariantClear(&v); + + IUIAutomationElement_Release(element); + } + else + ok_(file, line)(!element, "element != NULL\n"); +} + +static void test_GetFocusedElement(IUIAutomation *uia_iface) +{ + struct Provider_prop_override prop_override; + struct node_provider_desc exp_node_desc; + IUIAutomationCacheRequest *cache_req; + IUIAutomationElement *element; + HWND hwnd, hwnd_child; + HRESULT hr; + VARIANT v; + + hwnd = create_test_hwnd("test_GetFocusedElement class"); + hwnd_child = create_child_test_hwnd("test_GetFocusedElement child class", hwnd); + UiaRegisterProviderCallback(test_uia_provider_callback); + + cache_req = NULL; + hr = IUIAutomation_CreateCacheRequest(uia_iface, &cache_req); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!cache_req, "cache_req == NULL\n"); + + /* + * Set clientside providers for our test windows and the desktop. Same + * tests as UiaNodeFromFocus, just with COM methods. + */ + set_clientside_providers_for_hwnd(&Provider_proxy, &Provider_nc, &Provider_hwnd, GetDesktopWindow()); + base_hwnd_prov = &Provider_hwnd.IRawElementProviderSimple_iface; + nc_prov = &Provider_nc.IRawElementProviderSimple_iface; + proxy_prov = &Provider_proxy.IRawElementProviderSimple_iface; + + set_clientside_providers_for_hwnd(NULL, &Provider_nc2, &Provider_hwnd2, hwnd); + initialize_provider(&Provider, ProviderOptions_ServerSideProvider, hwnd, TRUE); + Provider.frag_root = &Provider.IRawElementProviderFragmentRoot_iface; + prov_root = &Provider.IRawElementProviderSimple_iface; + Provider.ignore_hwnd_prop = TRUE; + + set_clientside_providers_for_hwnd(NULL, &Provider_nc3, &Provider_hwnd3, hwnd_child); + initialize_provider(&Provider2, ProviderOptions_ServerSideProvider, hwnd_child, TRUE); + Provider2.frag_root = &Provider2.IRawElementProviderFragmentRoot_iface; + child_win_prov_root = &Provider2.IRawElementProviderSimple_iface; + Provider2.ignore_hwnd_prop = TRUE; + + /* NULL input argument tests. */ + hr = IUIAutomation_GetFocusedElement(uia_iface, NULL); + ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + + hr = IUIAutomation_GetFocusedElementBuildCache(uia_iface, cache_req, NULL); + ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + + element = (void *)0xdeadbeef; + hr = IUIAutomation_GetFocusedElementBuildCache(uia_iface, NULL, &element); + ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + ok(!element, "element != NULL\n"); + + /* + * None of the providers for the desktop node return a provider from + * IRawElementProviderFragmentRoot::GetFocus, so we just get the + * desktop node. + */ + method_sequences_enabled = FALSE; + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), GetDesktopWindow()); + add_provider_desc(&exp_node_desc, L"Main", L"Provider_proxy", TRUE); + add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc", FALSE); + add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd", FALSE); + + test_get_focused_elem(uia_iface, NULL, S_OK, &exp_node_desc, 1, 1, 1, 0, 0, FALSE, FALSE, FALSE, FALSE, FALSE); + test_get_focused_elem(uia_iface, cache_req, S_OK, &exp_node_desc, 1, 1, 1, 0, 0, FALSE, FALSE, FALSE, FALSE, FALSE); + + /* Provider_hwnd returns Provider_hwnd2 from GetFocus. */ + Provider_hwnd.focus_prov = &Provider_hwnd2.IRawElementProviderFragment_iface; + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), hwnd); + add_provider_desc(&exp_node_desc, L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc2", FALSE); + add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd2", FALSE); + + test_get_focused_elem(uia_iface, NULL, S_OK, &exp_node_desc, 2, 1, 2, 1, 0, TRUE, FALSE, FALSE, FALSE, FALSE); + test_get_focused_elem(uia_iface, cache_req, S_OK, &exp_node_desc, 2, 1, 2, 1, 0, TRUE, FALSE, FALSE, FALSE, FALSE); + + /* + * Provider_proxy returns Provider from GetFocus. The provider that + * creates the node will not have GetFocus called on it to avoid returning + * the same provider twice. Similarly, on nodes other than the desktop + * node, the HWND provider will not have GetFocus called on it. + */ + Provider_hwnd.focus_prov = NULL; + Provider_proxy.focus_prov = &Provider.IRawElementProviderFragment_iface; + Provider.focus_prov = Provider_hwnd2.focus_prov = &Provider2.IRawElementProviderFragment_iface; + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), hwnd); + add_provider_desc(&exp_node_desc, L"Main", L"Provider", TRUE); + add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc2", FALSE); + add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd2", FALSE); + + test_get_focused_elem(uia_iface, NULL, S_OK, &exp_node_desc, 2, 2, 2, 1, 0, TRUE, FALSE, FALSE, FALSE, FALSE); + test_get_focused_elem(uia_iface, cache_req, S_OK, &exp_node_desc, 2, 2, 2, 1, 0, TRUE, FALSE, FALSE, FALSE, FALSE); + + /* + * Provider_nc returns Provider_nc2 from GetFocus, Provider returns + * Provider2, Provider_nc3 returns Provider_child. + */ + initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + Provider_proxy.focus_prov = Provider_hwnd.focus_prov = NULL; + Provider_nc.focus_prov = &Provider_nc2.IRawElementProviderFragment_iface; + Provider.focus_prov = &Provider2.IRawElementProviderFragment_iface; + Provider_nc3.focus_prov = &Provider_child.IRawElementProviderFragment_iface; + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc, L"Main", L"Provider_child", TRUE); + + test_get_focused_elem(uia_iface, NULL, S_OK, &exp_node_desc, 2, 3, 2, 2, 1, TRUE, FALSE, FALSE, TRUE, FALSE); + test_get_focused_elem(uia_iface, cache_req, S_OK, &exp_node_desc, 2, 3, 2, 2, 1, TRUE, FALSE, FALSE, TRUE, FALSE); + + /* + * Provider_proxy returns Provider_child_child from GetFocus. The focus + * provider is normalized against the cache request view condition. + * Provider_child_child and its ancestors don't match the cache request + * view condition, so we'll get no provider. + */ + initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + initialize_provider(&Provider_child_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + provider_add_child(&Provider, &Provider_child); + provider_add_child(&Provider_child, &Provider_child_child); + Provider_proxy.focus_prov = &Provider_child_child.IRawElementProviderFragment_iface; + Provider_nc.focus_prov = Provider_hwnd.focus_prov = NULL; + + variant_init_bool(&v, FALSE); + set_property_override(&prop_override, UIA_IsControlElementPropertyId, &v); + set_provider_prop_override(&Provider_child_child, &prop_override, 1); + set_provider_prop_override(&Provider_child, &prop_override, 1); + set_provider_prop_override(&Provider, &prop_override, 1); + + /* + * GetFocusedElement returns UIA_E_ELEMENTNOTAVAILABLE when no provider + * matches our view condition, GetFocusedElementBuildCache returns E_FAIL. + */ + init_node_provider_desc(&exp_node_desc, 0, NULL); + test_get_focused_elem(uia_iface, NULL, UIA_E_ELEMENTNOTAVAILABLE, + &exp_node_desc, 2, 2, 2, 1, 0, TRUE, FALSE, FALSE, FALSE, FALSE); + test_get_focused_elem(uia_iface, cache_req, E_FAIL, + &exp_node_desc, 2, 2, 2, 1, 0, TRUE, FALSE, FALSE, FALSE, FALSE); + + /* This time, Provider_child matches our view condition. */ + set_provider_prop_override(&Provider_child, NULL, 0); + + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc, L"Main", L"Provider_child", TRUE); + + test_get_focused_elem(uia_iface, NULL, S_OK, &exp_node_desc, 1, 1, 1, 0, 0, FALSE, FALSE, FALSE, FALSE, FALSE); + + method_sequences_enabled = TRUE; + initialize_provider(&Provider, ProviderOptions_ServerSideProvider, NULL, TRUE); + initialize_provider(&Provider_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + initialize_provider(&Provider_child_child, ProviderOptions_ServerSideProvider, NULL, TRUE); + + base_hwnd_prov = nc_prov = proxy_prov = prov_root = NULL; + IUIAutomationCacheRequest_Release(cache_req); + UiaRegisterProviderCallback(NULL); + DestroyWindow(hwnd); + UnregisterClassA("test_GetFocusedElement class", NULL); + UnregisterClassA("test_GetFocusedElement child class", NULL); +} + struct uia_com_classes { const GUID *clsid; const GUID *iid; @@ -12600,6 +12807,7 @@ static void test_CUIAutomation(void) test_Element_cache_methods(uia_iface); test_Element_Find(uia_iface); test_GetRootElement(uia_iface); + test_GetFocusedElement(uia_iface);
IUIAutomation_Release(uia_iface); CoUninitialize(); diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c index 6cba9fd46db..919936c7acb 100644 --- a/dlls/uiautomationcore/uia_com_client.c +++ b/dlls/uiautomationcore/uia_com_client.c @@ -2814,10 +2814,61 @@ static HRESULT WINAPI uia_iface_ElementFromPoint(IUIAutomation6 *iface, POINT pt return E_NOTIMPL; }
+static HRESULT uia_get_focused_element(IUIAutomation6 *iface, IUIAutomationCacheRequest *cache_req, + BOOL use_default_cache_req, IUIAutomationElement **out_elem) +{ + struct uia_iface *uia_iface = impl_from_IUIAutomation6(iface); + struct UiaCacheRequest *cache_req_struct; + BSTR tree_struct; + SAFEARRAY *sa; + HRESULT hr; + + if (!out_elem) + return E_POINTER; + + *out_elem = NULL; + if (use_default_cache_req) + { + hr = create_uia_cache_request_iface(&cache_req); + if (FAILED(hr)) + return hr; + } + + hr = get_uia_cache_request_struct_from_iface(cache_req, &cache_req_struct); + if (FAILED(hr)) + goto exit; + + hr = UiaNodeFromFocus(cache_req_struct, &sa, &tree_struct); + if (SUCCEEDED(hr)) + { + if (!sa) + { + /* + * Failure to get a focused element returns E_FAIL from the BuildCache + * method, but UIA_E_ELEMENTNOTAVAILABLE from the default cache + * request method. + */ + hr = use_default_cache_req ? UIA_E_ELEMENTNOTAVAILABLE : E_FAIL; + SysFreeString(tree_struct); + goto exit; + } + + hr = create_uia_element_from_cache_req(out_elem, uia_iface->is_cui8, cache_req_struct, 0, sa, tree_struct); + SafeArrayDestroy(sa); + } + +exit: + if (use_default_cache_req) + IUIAutomationCacheRequest_Release(cache_req); + + return hr; +} + static HRESULT WINAPI uia_iface_GetFocusedElement(IUIAutomation6 *iface, IUIAutomationElement **out_elem) { - FIXME("%p, %p: stub\n", iface, out_elem); - return E_NOTIMPL; + TRACE("%p, %p\n", iface, out_elem); + + return uia_get_focused_element(iface, NULL, TRUE, out_elem); }
static HRESULT WINAPI uia_iface_GetRootElementBuildCache(IUIAutomation6 *iface, IUIAutomationCacheRequest *cache_req, @@ -2844,8 +2895,9 @@ static HRESULT WINAPI uia_iface_ElementFromPointBuildCache(IUIAutomation6 *iface static HRESULT WINAPI uia_iface_GetFocusedElementBuildCache(IUIAutomation6 *iface, IUIAutomationCacheRequest *cache_req, IUIAutomationElement **out_elem) { - FIXME("%p, %p, %p: stub\n", iface, cache_req, out_elem); - return E_NOTIMPL; + TRACE("%p, %p, %p\n", iface, cache_req, out_elem); + + return uia_get_focused_element(iface, cache_req, FALSE, out_elem); }
static HRESULT WINAPI uia_iface_CreateTreeWalker(IUIAutomation6 *iface, IUIAutomationCondition *cond,
From: Connor McAdams cmcadams@codeweavers.com
If we pass a node to the provider thread that contains a provider that was created in an STA with the ProviderOptions_UseComThreading flag set, we can deadlock when attempting to get a runtime ID from the proxy due to the message queue not being pumped. To avoid this, retrieve the runtime ID before passing the node to the provider thread.
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 32 +++++++++++ dlls/uiautomationcore/uia_provider.c | 64 ++++++++++++---------- 2 files changed, 66 insertions(+), 30 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index cf680b6a733..35f2f18ffcf 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -6305,6 +6305,16 @@ static const struct prov_method_sequence node_from_hwnd9[] = { { 0 } };
+static const struct prov_method_sequence node_from_hwnd10[] = { + NODE_CREATE_SEQ(&Provider), + /* Next two only done on Windows 8+. */ + { &Provider, FRAG_GET_RUNTIME_ID, METHOD_OPTIONAL }, + { &Provider, FRAG_GET_RUNTIME_ID, METHOD_OPTIONAL }, + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + { &Provider, FRAG_GET_RUNTIME_ID, METHOD_OPTIONAL }, /* Only done on Win11+. */ + { 0 } +}; + static const struct prov_method_sequence disconnect_prov1[] = { { &Provider_child, PROV_GET_PROVIDER_OPTIONS }, /* Win10v1507 and below call this. */ @@ -6576,6 +6586,28 @@ static DWORD WINAPI uia_node_from_handle_test_thread(LPVOID param) Sleep(50); ok(Provider.ref == 1, "Unexpected refcnt %ld\n", Provider.ref);
+ /* ProviderOptions_UseComThreading test from a separate thread. */ + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + /* Only sent on Win7. */ + SET_EXPECT(winproc_GETOBJECT_CLIENT); + prov_root = &Provider.IRawElementProviderSimple_iface; + initialize_provider(&Provider, ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading, NULL, FALSE); + Provider.frag_root = NULL; + Provider.runtime_id[0] = Provider.runtime_id[1] = 0xdeadbeef; + hr = UiaNodeFromHandle(hwnd, &node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider.ref == 2, "Unexpected refcnt %ld\n", Provider.ref); + CHECK_CALLED(winproc_GETOBJECT_UiaRoot); + called_winproc_GETOBJECT_CLIENT = expect_winproc_GETOBJECT_CLIENT = 0; + + ok_method_sequence(node_from_hwnd10, "node_from_hwnd10"); + + ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + /* Win10v1809 can be slow to call Release on Provider. */ + if (Provider.ref != 1) + Sleep(50); + ok(Provider.ref == 1, "Unexpected refcnt %ld\n", Provider.ref); + if (!pUiaDisconnectProvider) { win_skip("UiaDisconnectProvider not exported by uiautomationcore.dll\n"); diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c index 76b5c03537a..6adbeba5739 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -1744,19 +1744,15 @@ exit: LeaveCriticalSection(&provider_thread_cs); }
-static HRESULT uia_provider_thread_add_node(HUIANODE node) +static HRESULT uia_provider_thread_add_node(HUIANODE node, SAFEARRAY *rt_id) { struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); int prov_type = get_node_provider_type_at_idx(node_data, 0); struct uia_provider *prov_data; - SAFEARRAY *sa; - HRESULT hr; + HRESULT hr = S_OK;
prov_data = impl_from_IWineUiaProvider(node_data->prov[prov_type]); node_data->nested_node = prov_data->return_nested_node = TRUE; - hr = UiaGetRuntimeId(node, &sa); - if (FAILED(hr)) - return hr;
TRACE("Adding node %p\n", node);
@@ -1764,38 +1760,40 @@ static HRESULT uia_provider_thread_add_node(HUIANODE node) list_add_tail(&provider_thread.nodes_list, &node_data->prov_thread_list_entry);
/* If we have a runtime ID, create an entry in the rb tree. */ - if (sa) + if (rt_id) { struct uia_provider_thread_map_entry *prov_map; struct rb_entry *rb_entry;
- if ((rb_entry = rb_get(&provider_thread.node_map, sa))) - { + if ((rb_entry = rb_get(&provider_thread.node_map, rt_id))) prov_map = RB_ENTRY_VALUE(rb_entry, struct uia_provider_thread_map_entry, entry); - SafeArrayDestroy(sa); - } else { prov_map = heap_alloc_zero(sizeof(*prov_map)); if (!prov_map) { - SafeArrayDestroy(sa); - LeaveCriticalSection(&provider_thread_cs); - return E_OUTOFMEMORY; + hr = E_OUTOFMEMORY; + goto exit; }
- prov_map->runtime_id = sa; + hr = SafeArrayCopy(rt_id, &prov_map->runtime_id); + if (FAILED(hr)) + { + heap_free(prov_map); + goto exit; + } list_init(&prov_map->nodes_list); - rb_put(&provider_thread.node_map, sa, &prov_map->entry); + rb_put(&provider_thread.node_map, prov_map->runtime_id, &prov_map->entry); }
list_add_tail(&prov_map->nodes_list, &node_data->node_map_list_entry); node_data->map = prov_map; }
+exit: LeaveCriticalSection(&provider_thread_cs);
- return S_OK; + return hr; }
#define WM_GET_OBJECT_UIA_NODE (WM_USER + 1) @@ -1807,13 +1805,13 @@ static LRESULT CALLBACK uia_provider_thread_msg_proc(HWND hwnd, UINT msg, WPARAM { case WM_GET_OBJECT_UIA_NODE: { + SAFEARRAY *rt_id = (SAFEARRAY *)wparam; HUIANODE node = (HUIANODE)lparam; LRESULT lr;
- if (FAILED(uia_provider_thread_add_node(node))) + if (FAILED(uia_provider_thread_add_node(node, rt_id))) { WARN("Failed to add node %p to provider thread list.\n", node); - UiaNodeRelease(node); return 0; }
@@ -1828,11 +1826,6 @@ static LRESULT CALLBACK uia_provider_thread_msg_proc(HWND hwnd, UINT msg, WPARAM lr = 0; }
- /* - * LresultFromObject increases refcnt by 1. If LresultFromObject - * failed, this is expected to release the node. - */ - UiaNodeRelease(node); return lr; }
@@ -1948,13 +1941,24 @@ void uia_stop_provider_thread(void) */ LRESULT uia_lresult_from_node(HUIANODE huianode) { - if (!uia_start_provider_thread()) - { - UiaNodeRelease(huianode); - return 0; - } + SAFEARRAY *rt_id; + LRESULT lr = 0; + HRESULT hr; + + hr = UiaGetRuntimeId(huianode, &rt_id); + if (SUCCEEDED(hr) && uia_start_provider_thread()) + lr = SendMessageW(provider_thread.hwnd, WM_GET_OBJECT_UIA_NODE, (WPARAM)rt_id, (LPARAM)huianode); + + if (FAILED(hr)) + WARN("UiaGetRuntimeId failed with hr %#lx\n", hr);
- return SendMessageW(provider_thread.hwnd, WM_GET_OBJECT_UIA_NODE, 0, (LPARAM)huianode); + /* + * LresultFromObject increases refcnt by 1. If LresultFromObject + * failed or wasn't called, this is expected to release the node. + */ + UiaNodeRelease(huianode); + SafeArrayDestroy(rt_id); + return lr; }
/***********************************************************************
This merge request was approved by Esme Povirk.