-- v2: uiautomationcore: Retrieve runtime ID on UiaReturnRawElementProvider thread to prevent a deadlock. uiautomationcore: Implement IUIAutomation::GetFocusedElement{BuildCacheRequest}. uiautomationcore: Implement UiaNodeFromFocus. uiautomationcore: Add UiaNodeFromFocus stub.
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..d9c294ba814 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; }
/***********************************************************************
On Fri Apr 21 20:36:22 2023 +0000, Esme Povirk wrote:
I'm not sure about inserting a message loop into a public function. Applications may not expect some messages to be handled there.
Should be fixed now in v2, we get the runtime ID before passing the node to the provider thread instead.
V2:
- Remove method sequences from UiaNodeFromFocus tests and add/remove some tests. - Change method of avoiding deadlock when retrieving Runtime ID.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_client.c:
* 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;
I'm guessing you meant to assign to `*out_node` here?
On Fri Apr 28 18:02:29 2023 +0000, Esme Povirk wrote:
I'm guessing you meant to assign to `*out_node` here?
Yep, good catch. :)