From f26d470920bf9291a0b8861de66c1a262cdf0b03 Mon Sep 17 00:00:00 2001
From: Connor McAdams <cmcadams@codeweavers.com>
Date: Mon, 18 Sep 2023 10:54:55 -0400
Subject: [PATCH] uiautomationcore: Potentially raise focus event on the
 currently focused serverside provider in response to EVENT_OBJECT_FOCUS.

Signed-off-by: Connor McAdams <cmcadams@codeweavers.com>
---
 dlls/uiautomationcore/tests/uiautomation.c | 78 ++++++++++++++++++++++
 dlls/uiautomationcore/uia_classes.idl      |  2 +-
 dlls/uiautomationcore/uia_client.c         | 37 ++++++----
 dlls/uiautomationcore/uia_com_client.c     | 29 ++++++--
 dlls/uiautomationcore/uia_event.c          |  4 +-
 dlls/uiautomationcore/uia_private.h        |  3 +-
 6 files changed, 131 insertions(+), 22 deletions(-)

diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c
index b6f8b3d75bb..300fc434505 100644
--- a/dlls/uiautomationcore/tests/uiautomation.c
+++ b/dlls/uiautomationcore/tests/uiautomation.c
@@ -14681,6 +14681,12 @@ static void set_com_event_data(struct node_provider_desc *exp_node_desc)
     SET_EXPECT(uia_com_event_callback);
 }
 
+static void push_expected_com_event(struct node_provider_desc *exp_node_desc)
+{
+    push_event_queue_event(&ComEventData.exp_events, exp_node_desc);
+    SET_EXPECT_MULTI(uia_com_event_callback, ComEventData.exp_events.exp_event_count);
+}
+
 #define test_com_event_data( sender ) \
         test_com_event_data_( (sender), __FILE__, __LINE__)
 static void test_com_event_data_(IUIAutomationElement *sender, const char *file, int line)
@@ -15560,6 +15566,12 @@ static void set_multi_event_data(struct node_provider_desc *exp_node_desc)
     SET_EXPECT(uia_event_callback);
 }
 
+static void push_expected_event(struct node_provider_desc *exp_node_desc)
+{
+    push_event_queue_event(&MultiEventData.exp_events, exp_node_desc);
+    SET_EXPECT_MULTI(uia_event_callback, MultiEventData.exp_events.exp_event_count);
+}
+
 static void WINAPI multi_uia_event_callback(struct UiaEventArgs *args, SAFEARRAY *req_data, BSTR tree_struct)
 {
     struct node_provider_desc *exp_desc;
@@ -15696,6 +15708,72 @@ static void test_uia_com_focus_change_event_handler_win_event_handling(IUIAutoma
     CHECK_CALLED(winproc_GETOBJECT_UiaRoot);
     set_provider_prop_override(&Provider, NULL, 0);
 
+    /*
+     * The first time EVENT_OBJECT_FOCUS is raised for an HWND with a
+     * serverside provider UIA will query for the currently focused provider
+     * and raise a focus change event for it, alongside advising the root
+     * provider of focus change events being listened for. All subsequent
+     * EVENT_OBJECT_FOCUS events on the same HWND only query the root
+     * provider.
+     */
+    initialize_provider(&Provider_child2, ProviderOptions_ServerSideProvider, NULL, TRUE);
+    Provider_child2.parent = &Provider2.IRawElementProviderFragment_iface;
+    Provider_child2.frag_root = &Provider2.IRawElementProviderFragmentRoot_iface;
+    Provider2.focus_prov = &Provider_child2.IRawElementProviderFragment_iface;
+    set_provider_runtime_id(&Provider_child2, UiaAppendRuntimeId, 2);
+    initialize_provider_advise_events_ids(&Provider2);
+
+    init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL);
+    add_provider_desc(&exp_node_desc, L"Main", L"Provider_child2", TRUE);
+
+    set_multi_event_data(&exp_node_desc);
+    set_com_event_data(&exp_node_desc);
+
+    /* Second event. */
+    init_node_provider_desc(&exp_nested_node_desc, GetCurrentProcessId(), test_child_hwnd);
+    add_provider_desc(&exp_nested_node_desc, L"Main", L"Provider2", TRUE);
+
+    init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), test_child_hwnd);
+    add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd3", TRUE);
+    add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc3", FALSE);
+    add_nested_provider_desc(&exp_node_desc, L"Main", NULL, FALSE, &exp_nested_node_desc);
+
+    push_expected_event(&exp_node_desc);
+    push_expected_com_event(&exp_node_desc);
+    set_uia_hwnd_expects(0, 2, 2, 3, 0); /* Win11 sends 3 WM_GETOBJECT messages, normally only 2. */
+    SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 4); /* Win11 sends 4 WM_GETOBJECT messages, normally only 3. */
+    NotifyWinEvent(EVENT_OBJECT_FOCUS, test_child_hwnd, OBJID_CLIENT, CHILDID_SELF);
+    ok(msg_wait_for_all_events(event_handles, 2, 5000) != WAIT_TIMEOUT, "Wait for event_handle(s) timed out.\n");
+    if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n");
+    check_uia_hwnd_expects_at_least(0, FALSE, 2, FALSE, 2, FALSE, 2, TRUE, 0, FALSE);
+    CHECK_CALLED_AT_LEAST(child_winproc_GETOBJECT_UiaRoot, 3);
+    CHECK_CALLED_MULTI(uia_com_event_callback, 2);
+    CHECK_CALLED_MULTI(uia_event_callback, 2);
+
+    /*
+     * Second time EVENT_OBJECT_FOCUS is raised for this HWND, only the root
+     * provider will have an event raised.
+     */
+    init_node_provider_desc(&exp_nested_node_desc, GetCurrentProcessId(), test_child_hwnd);
+    add_provider_desc(&exp_nested_node_desc, L"Main", L"Provider2", TRUE);
+
+    init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), test_child_hwnd);
+    add_provider_desc(&exp_node_desc, L"Hwnd", L"Provider_hwnd3", TRUE);
+    add_provider_desc(&exp_node_desc, L"Nonclient", L"Provider_nc3", FALSE);
+    add_nested_provider_desc(&exp_node_desc, L"Main", NULL, FALSE, &exp_nested_node_desc);
+
+    set_multi_event_data(&exp_node_desc);
+    set_com_event_data(&exp_node_desc);
+    set_uia_hwnd_expects(0, 2, 2, 3, 0); /* Win11 sends 3 WM_GETOBJECT messages, normally only 2. */
+    SET_EXPECT_MULTI(child_winproc_GETOBJECT_UiaRoot, 4); /* Win11 sends 4 WM_GETOBJECT messages, normally only 3. */
+    NotifyWinEvent(EVENT_OBJECT_FOCUS, test_child_hwnd, OBJID_CLIENT, CHILDID_SELF);
+    ok(msg_wait_for_all_events(event_handles, 2, 5000) != WAIT_TIMEOUT, "Wait for event_handle(s) timed out.\n");
+    if (wait_for_clientside_callbacks(2000)) trace("Kept getting callbacks up until timeout\n");
+    check_uia_hwnd_expects_at_least(0, FALSE, 2, FALSE, 2, FALSE, 2, TRUE, 0, FALSE);
+    CHECK_CALLED_AT_LEAST(child_winproc_GETOBJECT_UiaRoot, 3);
+    CHECK_CALLED(uia_com_event_callback);
+    CHECK_CALLED(uia_event_callback);
+
     set_uia_hwnd_expects(0, 1, 1, 0, 0);
     hr = IUIAutomation_RemoveFocusChangedEventHandler(uia_iface,
             &FocusChangedHandler.IUIAutomationFocusChangedEventHandler_iface);
diff --git a/dlls/uiautomationcore/uia_classes.idl b/dlls/uiautomationcore/uia_classes.idl
index ed1a1aa3f04..7b3de05268f 100644
--- a/dlls/uiautomationcore/uia_classes.idl
+++ b/dlls/uiautomationcore/uia_classes.idl
@@ -91,7 +91,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);
+        HRESULT get_focus([in]long flags, [out, retval]VARIANT *ret_val);
         HRESULT attach_event([in]LONG_PTR huiaevent);
         HRESULT respond_to_win_event([in]DWORD win_event, [in]ULONG hwnd, [in]LONG obj_id, [in]LONG child_id,
                                     [in]IProxyProviderWinEventSink *sink);
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c
index a7ea3998ff6..22cce1324e8 100644
--- a/dlls/uiautomationcore/uia_client.c
+++ b/dlls/uiautomationcore/uia_client.c
@@ -347,7 +347,7 @@ 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)
+HRESULT get_focus_from_node_provider(IWineUiaNode *node, int idx, LONG flags, VARIANT *ret_val)
 {
     IWineUiaProvider *prov;
     HRESULT hr;
@@ -357,7 +357,7 @@ static HRESULT get_focus_from_node_provider(IWineUiaNode *node, int idx, VARIANT
     if (FAILED(hr))
         return hr;
 
-    hr = IWineUiaProvider_get_focus(prov, ret_val);
+    hr = IWineUiaProvider_get_focus(prov, flags, ret_val);
     IWineUiaProvider_Release(prov);
 
     return hr;
@@ -1836,7 +1836,7 @@ 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)
+static HRESULT WINAPI uia_provider_get_focus(IWineUiaProvider *iface, LONG flags, VARIANT *out_val)
 {
     struct uia_provider *prov = impl_from_IWineUiaProvider(iface);
     IRawElementProviderFragmentRoot *elroot;
@@ -1844,7 +1844,10 @@ static HRESULT WINAPI uia_provider_get_focus(IWineUiaProvider *iface, VARIANT *o
     IRawElementProviderSimple *elprov;
     HRESULT hr;
 
-    TRACE("%p, %p\n", iface, out_val);
+    TRACE("%p, %#lx, %p\n", iface, flags, out_val);
+
+    if (flags & PROV_METHOD_FLAG_RETURN_NODE_LRES)
+        FIXME("PROV_METHOD_FLAG_RETURN_NODE_LRES ignored for normal providers.\n");
 
     VariantInit(out_val);
     hr = IRawElementProviderSimple_QueryInterface(prov->elprov, &IID_IRawElementProviderFragmentRoot, (void **)&elroot);
@@ -2357,7 +2360,7 @@ static HRESULT WINAPI uia_nested_node_provider_get_prop_val(IWineUiaProvider *if
     {
         HUIANODE node;
 
-        hr = uia_node_from_lresult((LRESULT)V_I4(&v), &node);
+        hr = uia_node_from_lresult((LRESULT)V_I4(&v), &node, 0);
         if (FAILED(hr))
             return hr;
 
@@ -2406,7 +2409,7 @@ static HRESULT WINAPI uia_nested_node_provider_navigate(IWineUiaProvider *iface,
     if (FAILED(hr) || V_VT(&v) == VT_EMPTY)
         return hr;
 
-    hr = uia_node_from_lresult((LRESULT)V_I4(&v), &node);
+    hr = uia_node_from_lresult((LRESULT)V_I4(&v), &node, 0);
     if (FAILED(hr))
         return hr;
 
@@ -2416,21 +2419,27 @@ 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)
+static HRESULT WINAPI uia_nested_node_provider_get_focus(IWineUiaProvider *iface, LONG flags, 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);
+    TRACE("%p, %#lx, %p\n", iface, flags, out_val);
 
     VariantInit(out_val);
-    hr = get_focus_from_node_provider(prov->nested_node, 0, &v);
+    hr = get_focus_from_node_provider(prov->nested_node, 0, 0, &v);
     if (FAILED(hr) || V_VT(&v) == VT_EMPTY)
         return hr;
 
-    hr = uia_node_from_lresult((LRESULT)V_I4(&v), &node);
+    if (flags & PROV_METHOD_FLAG_RETURN_NODE_LRES)
+    {
+        *out_val = v;
+        return S_OK;
+    }
+
+    hr = uia_node_from_lresult((LRESULT)V_I4(&v), &node, 0);
     if (FAILED(hr))
         return hr;
 
@@ -2488,7 +2497,7 @@ static HRESULT WINAPI uia_nested_node_provider_create_node_from_prov(IWineUiaPro
         return S_OK;
     }
 
-    hr = uia_node_from_lresult((LRESULT)V_I4(&v), &node);
+    hr = uia_node_from_lresult((LRESULT)V_I4(&v), &node, 0);
     if (FAILED(hr))
         return hr;
 
@@ -2631,14 +2640,14 @@ static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESU
     return S_OK;
 }
 
-HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode)
+HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode, int node_flags)
 {
     struct uia_node *node;
     HRESULT hr;
 
     *huianode = NULL;
 
-    hr = create_uia_node(&node, 0);
+    hr = create_uia_node(&node, node_flags);
     if (FAILED(hr))
     {
         uia_node_lresult_release(lr);
@@ -2804,7 +2813,7 @@ static HRESULT get_focused_uia_node(HUIANODE in_node, HUIANODE *out_node)
                     (get_node_provider_type_at_idx(node, i) == PROV_TYPE_HWND)))
             continue;
 
-        hr = get_focus_from_node_provider(&node->IWineUiaNode_iface, i, &v);
+        hr = get_focus_from_node_provider(&node->IWineUiaNode_iface, i, 0, &v);
         if (FAILED(hr))
             break;
 
diff --git a/dlls/uiautomationcore/uia_com_client.c b/dlls/uiautomationcore/uia_com_client.c
index e2fd8f5b5b5..d78b5c5366d 100644
--- a/dlls/uiautomationcore/uia_com_client.c
+++ b/dlls/uiautomationcore/uia_com_client.c
@@ -1013,7 +1013,7 @@ static HRESULT uia_com_focus_win_event_callback(struct uia_event *event, void *u
     return hr;
 }
 
-static void uia_com_focus_handler_advise_node(struct uia_com_event *event, HUIANODE node, HWND hwnd)
+static BOOL uia_com_focus_handler_advise_node(struct uia_com_event *event, HUIANODE node, HWND hwnd)
 {
     HRESULT hr;
 
@@ -1021,12 +1021,15 @@ static void uia_com_focus_handler_advise_node(struct uia_com_event *event, HUIAN
     if (FAILED(hr))
     {
         WARN("uia_event_advise_node failed with hr %#lx\n", hr);
-        return;
+        goto exit;
     }
 
     hr = uia_hwnd_map_add_hwnd(&event->focus_hwnd_map, hwnd);
     if (FAILED(hr))
         WARN("Failed to add hwnd for focus winevent, hr %#lx\n", hr);
+
+exit:
+    return SUCCEEDED(hr);
 }
 
 static void uia_com_focus_win_event_handler(HUIANODE node, HWND hwnd, struct uia_event_handler_event_id_map_entry *event_id_map)
@@ -1041,8 +1044,26 @@ static void uia_com_focus_win_event_handler(HUIANODE node, HWND hwnd, struct uia
 
         LIST_FOR_EACH_ENTRY(event, &entry->handlers_list, struct uia_com_event, event_handler_map_list_entry)
         {
-            if (!uia_hwnd_map_check_hwnd(&event->focus_hwnd_map, hwnd))
-                uia_com_focus_handler_advise_node(event, node, hwnd);
+            if (uia_hwnd_map_check_hwnd(&event->focus_hwnd_map, hwnd) ||
+                    !uia_com_focus_handler_advise_node(event, node, hwnd))
+                continue;
+
+            hr = get_focus_from_node_provider((IWineUiaNode *)node, 0, PROV_METHOD_FLAG_RETURN_NODE_LRES, &v);
+            if (V_VT(&v) == VT_I4)
+            {
+                HUIANODE focus_node = NULL;
+
+                hr = uia_node_from_lresult(V_I4(&v), &focus_node, NODE_FLAG_IGNORE_CLIENTSIDE_HWND_PROVS);
+                if (SUCCEEDED(hr))
+                {
+                    hr = uia_event_for_each(UIA_AutomationFocusChangedEventId, uia_com_focus_win_event_callback,
+                            (void *)focus_node, TRUE);
+                    if (FAILED(hr))
+                        WARN("uia_event_for_each on focus_node failed with hr %#lx\n", hr);
+                }
+                UiaNodeRelease(focus_node);
+            }
+            VariantClear(&v);
         }
     }
 
diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c
index e85bcf2e825..15b3b84d96b 100644
--- a/dlls/uiautomationcore/uia_event.c
+++ b/dlls/uiautomationcore/uia_event.c
@@ -595,7 +595,7 @@ static HRESULT uia_raise_clientside_event(struct uia_queue_uia_event *event)
     HRESULT hr;
 
     node = nav_start_node = NULL;
-    hr = uia_node_from_lresult(event->u.clientside.node, &node);
+    hr = uia_node_from_lresult(event->u.clientside.node, &node, 0);
     if (FAILED(hr))
     {
         WARN("Failed to create node from lresult, hr %#lx\n", hr);
@@ -605,7 +605,7 @@ static HRESULT uia_raise_clientside_event(struct uia_queue_uia_event *event)
 
     if (event->u.clientside.nav_start_node)
     {
-        hr = uia_node_from_lresult(event->u.clientside.nav_start_node, &nav_start_node);
+        hr = uia_node_from_lresult(event->u.clientside.nav_start_node, &nav_start_node, 0);
         if (FAILED(hr))
         {
             WARN("Failed to create nav_start_node from lresult, hr %#lx\n", hr);
diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h
index b7b379cdb87..00ed1f8f293 100644
--- a/dlls/uiautomationcore/uia_private.h
+++ b/dlls/uiautomationcore/uia_private.h
@@ -216,6 +216,7 @@ enum provider_method_flags {
 
 /* uia_client.c */
 int get_node_provider_type_at_idx(struct uia_node *node, int idx) DECLSPEC_HIDDEN;
+HRESULT get_focus_from_node_provider(IWineUiaNode *node, int idx, LONG flags, VARIANT *ret_val) DECLSPEC_HIDDEN;
 HRESULT respond_to_win_event_on_node_provider(IWineUiaNode *node, int idx, DWORD win_event, HWND hwnd, LONG obj_id,
         LONG child_id, IProxyProviderWinEventSink *sink) DECLSPEC_HIDDEN;
 HRESULT create_node_from_node_provider(IWineUiaNode *node, int idx, LONG flags, VARIANT *ret_val) DECLSPEC_HIDDEN;
@@ -224,7 +225,7 @@ HRESULT clone_uia_node(HUIANODE in_node, HUIANODE *out_node) DECLSPEC_HIDDEN;
 HRESULT navigate_uia_node(struct uia_node *node, int nav_dir, HUIANODE *out_node) DECLSPEC_HIDDEN;
 HRESULT create_uia_node_from_elprov(IRawElementProviderSimple *elprov, HUIANODE *out_node,
         BOOL get_hwnd_providers, int node_flags) DECLSPEC_HIDDEN;
-HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode) DECLSPEC_HIDDEN;
+HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode, int node_flags) DECLSPEC_HIDDEN;
 void uia_node_lresult_release(LRESULT lr) DECLSPEC_HIDDEN;
 HRESULT create_uia_node_from_hwnd(HWND hwnd, HUIANODE *out_node, int node_flags) DECLSPEC_HIDDEN;
 HRESULT uia_condition_check(HUIANODE node, struct UiaCondition *condition) DECLSPEC_HIDDEN;
-- 
GitLab