From 5bd409070556980edb2683c6a1d99beee7cb7cec Mon Sep 17 00:00:00 2001
From: Connor McAdams <cmcadams@codeweavers.com>
Date: Fri, 24 Mar 2023 13:14:05 -0400
Subject: [PATCH] uiautomationcore: Implement
 IUIAutomation::GetFocusedElement{BuildCacheRequest}.

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,
-- 
GitLab