Add the ability to disconnect nested node providers, and return element properties for nested nodes.
-- v2: uiautomationcore: Add UIAutomationType_Element property support for nested node providers. uiautomationcore: Implement UiaDisconnectProvider. uiautomationcore: Track all HUIANODEs returned from the provider thread.
From: Connor McAdams cmcadams@codeweavers.com
When a client requests an HUIANODE for an HWND that belongs to the same thread, use an IRawElementProvider based provider rather than an IWineUiaNode based provider.
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 35 ++++++++--------- dlls/uiautomationcore/uia_client.c | 44 ++++++++++++++++++---- 2 files changed, 53 insertions(+), 26 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index ff711ccb325..aa5a9afab7f 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -5484,30 +5484,27 @@ static void test_UiaNodeFromHandle(const char *name) Provider_child.prov_opts = ProviderOptions_ServerSideProvider; Provider_child.hwnd = NULL; hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr);
+ hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); if (SUCCEEDED(hr)) { - hr = UiaHUiaNodeFromVariant(&v, &node2); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - if (SUCCEEDED(hr)) - { - check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); - check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child", TRUE); - VariantClear(&v); - } + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child", TRUE); + VariantClear(&v); + }
- Provider_child.expected_tid = GetCurrentThreadId(); - hr = UiaGetPropertyValue(node2, UIA_ControlTypePropertyId, &v); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - ok(V_VT(&v) == VT_I4, "Unexpected VT %d\n", V_VT(&v)); - ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); - ok_method_sequence(node_from_hwnd4, "node_from_hwnd4"); + Provider_child.expected_tid = GetCurrentThreadId(); + hr = UiaGetPropertyValue(node2, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(V_VT(&v) == VT_I4, "Unexpected VT %d\n", V_VT(&v)); + ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); + ok_method_sequence(node_from_hwnd4, "node_from_hwnd4");
- ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); - } + ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n");
Provider.expected_tid = Provider_child.expected_tid = 0; ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 1d6360682c2..d29071aea23 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -787,6 +787,11 @@ struct uia_client_thread LONG ref; };
+struct uia_get_node_prov_args { + LRESULT lr; + BOOL unwrap; +}; + static struct uia_client_thread client_thread; static CRITICAL_SECTION client_thread_cs; static CRITICAL_SECTION_DEBUG client_thread_cs_debug = @@ -799,14 +804,17 @@ static CRITICAL_SECTION client_thread_cs = { &client_thread_cs_debug, -1, 0, 0,
#define WM_UIA_CLIENT_GET_NODE_PROV (WM_USER + 1) #define WM_UIA_CLIENT_THREAD_STOP (WM_USER + 2) -static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESULT lr); +static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESULT lr, BOOL unwrap); static LRESULT CALLBACK uia_client_thread_msg_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { case WM_UIA_CLIENT_GET_NODE_PROV: - return create_wine_uia_nested_node_provider((struct uia_node *)lparam, (LRESULT)wparam); + { + struct uia_get_node_prov_args *args = (struct uia_get_node_prov_args *)wparam; + return create_wine_uia_nested_node_provider((struct uia_node *)lparam, args->lr, args->unwrap); + }
default: break; @@ -924,7 +932,8 @@ static void uia_stop_client_thread(void) LeaveCriticalSection(&client_thread_cs); }
-static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESULT lr) +static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESULT lr, + BOOL unwrap) { IGlobalInterfaceTable *git; struct uia_provider *prov; @@ -938,6 +947,26 @@ static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESU return hr; }
+ /* + * If we're retrieving a node from an HWND that belongs to the same thread + * as the client making the request, return a normal provider instead of a + * nested node provider. + */ + if (unwrap) + { + struct uia_node *node_data = unsafe_impl_from_IWineUiaNode(nested_node); + + node->prov = node_data->prov; + IWineUiaProvider_AddRef(node->prov); + node->git_cookie = node_data->git_cookie; + + node_data->git_cookie = 0; + IWineUiaNode_Release(&node_data->IWineUiaNode_iface); + + uia_stop_client_thread(); + return S_OK; + } + prov = heap_alloc_zero(sizeof(*prov)); if (!prov) return E_OUTOFMEMORY; @@ -971,27 +1000,28 @@ exit: */ static HRESULT uia_get_provider_from_hwnd(struct uia_node *node) { - LRESULT lr; + struct uia_get_node_prov_args args;
if (!uia_start_client_thread()) return E_FAIL;
SetLastError(NOERROR); - lr = SendMessageW(node->hwnd, WM_GETOBJECT, 0, UiaRootObjectId); + args.lr = SendMessageW(node->hwnd, WM_GETOBJECT, 0, UiaRootObjectId); if (GetLastError() == ERROR_INVALID_WINDOW_HANDLE) { uia_stop_client_thread(); return UIA_E_ELEMENTNOTAVAILABLE; }
- if (!lr) + if (!args.lr) { FIXME("No native UIA provider for hwnd %p, MSAA proxy currently unimplemented.\n", node->hwnd); uia_stop_client_thread(); return E_NOTIMPL; }
- return SendMessageW(client_thread.hwnd, WM_UIA_CLIENT_GET_NODE_PROV, (WPARAM)lr, (LPARAM)node); + args.unwrap = GetCurrentThreadId() == GetWindowThreadProcessId(node->hwnd, NULL); + return SendMessageW(client_thread.hwnd, WM_UIA_CLIENT_GET_NODE_PROV, (WPARAM)&args, (LPARAM)node); }
/***********************************************************************
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_client.c | 2 + dlls/uiautomationcore/uia_private.h | 2 + dlls/uiautomationcore/uia_provider.c | 71 ++++++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 3 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index d29071aea23..fd93694faa5 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -313,6 +313,8 @@ static ULONG WINAPI uia_node_Release(IWineUiaNode *iface) }
IWineUiaProvider_Release(node->prov); + if (node->in_provider_thread_list) + uia_provider_thread_remove_node((HUIANODE)iface); if (node->nested_node) uia_stop_provider_thread();
diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 1efc8fbd1e4..e0207c59e13 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -38,6 +38,7 @@ struct uia_node {
HWND hwnd; BOOL nested_node; + BOOL in_provider_thread_list; };
static inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) @@ -50,3 +51,4 @@ const struct uia_prop_info *uia_prop_info_from_id(PROPERTYID prop_id) DECLSPEC_H
/* uia_provider.c */ void uia_stop_provider_thread(void) DECLSPEC_HIDDEN; +void uia_provider_thread_remove_node(HUIANODE node) DECLSPEC_HIDDEN; diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c index 7b4a6be4042..4d93d3f3387 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -21,6 +21,7 @@
#include "wine/debug.h" #include "wine/heap.h" +#include "wine/list.h" #include "initguid.h" #include "wine/iaccessible2.h"
@@ -1130,6 +1131,7 @@ HRESULT WINAPI UiaProviderFromIAccessible(IAccessible *acc, long child_id, DWORD */ struct uia_provider_thread { + struct list nodes_list; HANDLE hthread; HWND hwnd; LONG ref; @@ -1145,6 +1147,63 @@ static CRITICAL_SECTION_DEBUG provider_thread_cs_debug = }; static CRITICAL_SECTION provider_thread_cs = { &provider_thread_cs_debug, -1, 0, 0, 0, 0 };
+struct uia_provider_thread_list_entry +{ + struct list entry; + + HUIANODE node; +}; + +void uia_provider_thread_remove_node(HUIANODE node) +{ + struct uia_provider_thread_list_entry *prov; + struct list *cursor, *cursor2; + struct uia_node *node_data; + BOOL found = FALSE; + + TRACE("Removing node %p\n", node); + EnterCriticalSection(&provider_thread_cs); + LIST_FOR_EACH_SAFE(cursor, cursor2, &provider_thread.nodes_list) + { + prov = LIST_ENTRY(cursor, struct uia_provider_thread_list_entry, entry); + if (prov->node == node) + { + list_remove(cursor); + node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); + node_data->in_provider_thread_list = FALSE; + found = TRUE; + heap_free(prov); + goto exit; + } + } + +exit: + LeaveCriticalSection(&provider_thread_cs); + if (!found) + ERR("Failed to find node %p in provider list\n", node); +} + +static HRESULT uia_provider_thread_add_node(HUIANODE node) +{ + struct uia_provider_thread_list_entry *prov = heap_alloc_zero(sizeof(*prov)); + struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); + + node_data->nested_node = TRUE; + + if (!prov) + return E_OUTOFMEMORY; + + TRACE("Adding node %p\n", node); + prov->node = node; + + EnterCriticalSection(&provider_thread_cs); + list_add_tail(&provider_thread.nodes_list, &prov->entry); + node_data->in_provider_thread_list = TRUE; + LeaveCriticalSection(&provider_thread_cs); + + return S_OK; +} + #define WM_GET_OBJECT_UIA_NODE (WM_USER + 1) #define WM_UIA_PROVIDER_THREAD_STOP (WM_USER + 2) static LRESULT CALLBACK uia_provider_thread_msg_proc(HWND hwnd, UINT msg, WPARAM wparam, @@ -1155,11 +1214,14 @@ static LRESULT CALLBACK uia_provider_thread_msg_proc(HWND hwnd, UINT msg, WPARAM case WM_GET_OBJECT_UIA_NODE: { HUIANODE node = (HUIANODE)lparam; - struct uia_node *node_data; LRESULT lr;
- node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); - node_data->nested_node = TRUE; + if (FAILED(uia_provider_thread_add_node(node))) + { + WARN("Failed to add node %p to provider thread list.\n", node); + UiaNodeRelease(node); + return 0; + }
/* * LresultFromObject returns an index into the global atom string table, @@ -1239,6 +1301,7 @@ static BOOL uia_start_provider_thread(void) GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (const WCHAR *)uia_start_provider_thread, &hmodule);
+ list_init(&provider_thread.nodes_list); events[0] = ready_event = CreateEventW(NULL, FALSE, FALSE, NULL); if (!(provider_thread.hthread = CreateThread(NULL, 0, uia_provider_thread_proc, ready_event, 0, NULL))) @@ -1276,6 +1339,8 @@ void uia_stop_provider_thread(void) { PostMessageW(provider_thread.hwnd, WM_UIA_PROVIDER_THREAD_STOP, 0, 0); CloseHandle(provider_thread.hthread); + if (!list_empty(&provider_thread.nodes_list)) + WARN("Provider thread shutdown with nodes still in the list\n"); memset(&provider_thread, 0, sizeof(provider_thread)); } LeaveCriticalSection(&provider_thread_cs);
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 277 ++++++++++++++++++++- dlls/uiautomationcore/uia_classes.idl | 1 + dlls/uiautomationcore/uia_client.c | 87 ++++++- dlls/uiautomationcore/uia_main.c | 6 - dlls/uiautomationcore/uia_private.h | 4 + dlls/uiautomationcore/uia_provider.c | 70 ++++++ include/uiautomationcoreapi.h | 1 + 7 files changed, 438 insertions(+), 8 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index aa5a9afab7f..7982bd5fa8b 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -29,6 +29,7 @@ #include "wine/test.h"
static HRESULT (WINAPI *pUiaProviderFromIAccessible)(IAccessible *, long, DWORD, IRawElementProviderSimple **); +static HRESULT (WINAPI *pUiaDisconnectProvider)(IRawElementProviderSimple *);
#define DEFINE_EXPECT(func) \ static int expect_ ## func = 0, called_ ## func = 0 @@ -5083,6 +5084,74 @@ static const struct prov_method_sequence node_from_hwnd8[] = { { 0 } };
+static const struct prov_method_sequence node_from_hwnd9[] = { + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + /* Win10v1507 and below call this. */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + /* Only done in Windows 8+. */ + { &Provider, FRAG_GET_RUNTIME_ID, METHOD_OPTIONAL }, + { &Provider, FRAG_GET_FRAGMENT_ROOT, METHOD_OPTIONAL }, + /* These three are only done on Windows 7. */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_OPTIONAL }, + { &Provider, FRAG_NAVIGATE, METHOD_OPTIONAL }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_ProviderDescriptionPropertyId */ + { 0 } +}; + +static const struct prov_method_sequence disconnect_prov1[] = { + { &Provider_child, PROV_GET_PROVIDER_OPTIONS }, + /* Win10v1507 and below call this. */ + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider_child, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider_child, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider_child, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider_child, FRAG_GET_RUNTIME_ID }, + { &Provider_child, FRAG_GET_FRAGMENT_ROOT }, + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + { 0 } +}; + +static const struct prov_method_sequence disconnect_prov2[] = { + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + /* Win10v1507 and below call this. */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider, FRAG_GET_RUNTIME_ID }, + { &Provider, FRAG_GET_FRAGMENT_ROOT }, + { 0 } +}; + +static const struct prov_method_sequence disconnect_prov3[] = { + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + /* Win10v1507 and below call this. */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider, FRAG_GET_RUNTIME_ID }, + { 0 } +}; + +static const struct prov_method_sequence disconnect_prov4[] = { + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + /* Win10v1507 and below call this. */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { 0 } +}; + static void test_UiaNodeFromHandle_client_proc(void) { APTTYPEQUALIFIER apt_qualifier; @@ -5157,8 +5226,8 @@ static void test_UiaNodeFromHandle_client_proc(void)
static DWORD WINAPI uia_node_from_handle_test_thread(LPVOID param) { + HUIANODE node, node2, node3; HWND hwnd = (HWND)param; - HUIANODE node, node2; WCHAR buf[2048]; HRESULT hr; VARIANT v; @@ -5329,6 +5398,160 @@ static DWORD WINAPI uia_node_from_handle_test_thread(LPVOID param) Sleep(50); ok(Provider.ref == 1, "Unexpected refcnt %ld\n", Provider.ref);
+ if (!pUiaDisconnectProvider) + { + win_skip("UiaDisconnectProvider not exported by uiautomationcore.dll\n"); + goto exit; + } + + /* + * UiaDisconnectProvider tests. + */ + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + prov_root = &Provider.IRawElementProviderSimple_iface; + Provider.prov_opts = ProviderOptions_ServerSideProvider; + Provider.hwnd = hwnd; + Provider.runtime_id[0] = Provider.runtime_id[1] = 0; + Provider.frag_root = NULL; + Provider.prov_opts = ProviderOptions_ServerSideProvider; + 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); + + hr = UiaGetPropertyValue(node, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + memset(buf, 0, sizeof(buf)); + + ok(get_nested_provider_desc(V_BSTR(&v), L"Main", FALSE, buf), "Failed to get nested provider description\n"); + check_node_provider_desc_prefix(buf, GetCurrentProcessId(), hwnd); + /* Win10v1507 and below have the nested provider as 'Hwnd'. */ + if (get_provider_desc(buf, L"Hwnd(parent link):", NULL)) + check_node_provider_desc(buf, L"Hwnd", L"Provider", TRUE); + else + check_node_provider_desc(buf, L"Main", L"Provider", TRUE); + + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), hwnd); + check_node_provider_desc(V_BSTR(&v), L"Nonclient", NULL, FALSE); + check_node_provider_desc(V_BSTR(&v), L"Hwnd", NULL, TRUE); + VariantClear(&v); + } + + ok_method_sequence(node_from_hwnd3, "node_from_hwnd3"); + + hr = UiaGetPropertyValue(node, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + check_uia_prop_val(UIA_ControlTypePropertyId, UIAutomationType_Int, &v); + + /* Nodes returned from a nested node will be tracked and disconnectable. */ + Provider_child.prov_opts = ProviderOptions_ServerSideProvider; + Provider_child.runtime_id[0] = UiaAppendRuntimeId; + Provider_child.runtime_id[1] = 2; + Provider_child.hwnd = NULL; + Provider_child.frag_root = &Provider.IRawElementProviderFragmentRoot_iface; + hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + + if (SUCCEEDED(hr)) + { + hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child", TRUE); + VariantClear(&v); + } + + hr = UiaGetPropertyValue(node2, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(V_VT(&v) == VT_I4, "Unexpected VT %d\n", V_VT(&v)); + ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); + ok_method_sequence(node_from_hwnd6, "node_from_hwnd6"); + + /* Get a new node for the same provider. */ + hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(Provider_child.ref == 3, "Unexpected refcnt %ld\n", Provider_child.ref); + + hr = UiaHUiaNodeFromVariant(&v, &node3); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node3, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child", TRUE); + VariantClear(&v); + } + + hr = UiaGetPropertyValue(node3, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(V_VT(&v) == VT_I4, "Unexpected VT %d\n", V_VT(&v)); + ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); + ok_method_sequence(node_from_hwnd6, "node_from_hwnd6"); + + /* + * Both node2 and node3 represent Provider_child, one call to + * UiaDisconnectProvider disconnects both. + */ + hr = pUiaDisconnectProvider(&Provider_child.IRawElementProviderSimple_iface); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(Provider_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child.ref); + + hr = UiaGetPropertyValue(node2, UIA_ControlTypePropertyId, &v); + ok(hr == UIA_E_ELEMENTNOTAVAILABLE, "Unexpected hr %#lx\n", hr); + + hr = UiaGetPropertyValue(node3, UIA_ControlTypePropertyId, &v); + ok(hr == UIA_E_ELEMENTNOTAVAILABLE, "Unexpected hr %#lx\n", hr); + ok_method_sequence(disconnect_prov1, "disconnect_prov1"); + + ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); + ok(UiaNodeRelease(node3), "UiaNodeRelease returned FALSE\n"); + } + + /* + * Returns same failure code as UiaGetRuntimeId when we fail to get a + * fragment root for AppendRuntimeId. + */ + Provider.hwnd = NULL; + Provider.runtime_id[0] = UiaAppendRuntimeId; + Provider.runtime_id[1] = 2; + Provider.frag_root = NULL; + hr = pUiaDisconnectProvider(&Provider.IRawElementProviderSimple_iface); + ok(hr == E_FAIL, "Unexpected hr %#lx\n", hr); + ok_method_sequence(disconnect_prov2, "disconnect_prov2"); + + /* + * Comparisons for disconnection are only based on RuntimeId comparisons, + * not interface pointer values. If an interface returns a NULL RuntimeId, + * no disconnection will occur. + */ + Provider.runtime_id[0] = Provider.runtime_id[1] = 0; + hr = pUiaDisconnectProvider(&Provider.IRawElementProviderSimple_iface); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx\n", hr); + ok_method_sequence(disconnect_prov3, "disconnect_prov3"); + + hr = UiaGetPropertyValue(node, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + check_uia_prop_val(UIA_ControlTypePropertyId, UIAutomationType_Int, &v); + + /* Finally, disconnect node. */ + Provider.hwnd = hwnd; + hr = pUiaDisconnectProvider(&Provider.IRawElementProviderSimple_iface); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + + hr = UiaGetPropertyValue(node, UIA_ControlTypePropertyId, &v); + ok(hr == UIA_E_ELEMENTNOTAVAILABLE, "Unexpected hr %#lx\n", hr); + ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + ok_method_sequence(disconnect_prov4, "disconnect_prov4"); + +exit: CoUninitialize();
return 0; @@ -5438,6 +5661,55 @@ static void test_UiaNodeFromHandle(const char *name) ok_method_sequence(node_from_hwnd1, "node_from_hwnd1");
todo_wine ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + + /* + * COM initialized, but provider passed into UiaReturnRawElementProvider + * fails to get a valid RuntimeId due to a lack of a fragment root. Win8+ + * will fall back to MSAA proxy. + */ + Provider.prov_opts = ProviderOptions_ServerSideProvider; + Provider.hwnd = NULL; + prov_root = &Provider.IRawElementProviderSimple_iface; + node = (void *)0xdeadbeef; + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + SET_EXPECT_MULTI(winproc_GETOBJECT_CLIENT, 2); + Provider.frag_root = NULL; + Provider.runtime_id[0] = UiaAppendRuntimeId; + Provider.runtime_id[1] = 1; + hr = UiaNodeFromHandle(hwnd, &node); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider.ref == 1 || broken(Provider.ref == 2), "Unexpected refcnt %ld\n", Provider.ref); + CHECK_CALLED(winproc_GETOBJECT_UiaRoot); + todo_wine CHECK_CALLED(winproc_GETOBJECT_CLIENT); + + hr = UiaGetPropertyValue(node, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), hwnd); + + if (get_provider_desc(V_BSTR(&v), L"Annotation:", NULL)) + { + check_node_provider_desc(V_BSTR(&v), L"Annotation", NULL, FALSE); + check_node_provider_desc(V_BSTR(&v), L"Main", NULL, FALSE); + } + else + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider", FALSE); + + check_node_provider_desc(V_BSTR(&v), L"Nonclient", NULL, FALSE); + check_node_provider_desc(V_BSTR(&v), L"Hwnd", NULL, TRUE); + VariantClear(&v); + } + ok_method_sequence(node_from_hwnd9, "node_from_hwnd9"); + todo_wine ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + /* + * Bug on Windows 8 through Win10v1709 - if we have a RuntimeId failure, + * refcount doesn't get decremented. + */ + ok(Provider.ref == 1 || broken(Provider.ref == 2), "Unexpected refcnt %ld\n", Provider.ref); + if (Provider.ref == 2) + IRawElementProviderSimple_Release(&Provider.IRawElementProviderSimple_iface); + CoUninitialize();
/* @@ -5617,6 +5889,9 @@ START_TEST(uiautomation) pImmDisableIME = NULL; FreeLibrary(hModuleImm32);
+ if (uia_dll) + pUiaDisconnectProvider = (void *)GetProcAddress(uia_dll, "UiaDisconnectProvider"); + argc = winetest_get_mainargs(&argv); if (argc == 3) { diff --git a/dlls/uiautomationcore/uia_classes.idl b/dlls/uiautomationcore/uia_classes.idl index e2889901fbb..2fb0e88086b 100644 --- a/dlls/uiautomationcore/uia_classes.idl +++ b/dlls/uiautomationcore/uia_classes.idl @@ -56,5 +56,6 @@ library UIA_wine_private { HRESULT get_provider([out, retval]IWineUiaProvider **out_prov); HRESULT get_prop_val([in]const GUID *prop_guid, [out, retval]VARIANT *ret_val); + HRESULT disconnect(); } } diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index fd93694faa5..90e13d56d3b 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -49,6 +49,42 @@ static HRESULT get_safearray_bounds(SAFEARRAY *sa, LONG *lbound, LONG *elems) return S_OK; }
+BOOL uia_compare_runtime_ids(SAFEARRAY *sa1, SAFEARRAY *sa2) +{ + LONG i, idx, lbound[2], elems[2]; + int val[2]; + HRESULT hr; + + hr = get_safearray_bounds(sa1, &lbound[0], &elems[0]); + if (FAILED(hr)) + return FALSE; + + hr = get_safearray_bounds(sa2, &lbound[1], &elems[1]); + if (FAILED(hr)) + return FALSE; + + if (elems[0] != elems[1]) + return FALSE; + + for (i = 0; i < elems[0]; i++) + { + idx = lbound[0] + i; + hr = SafeArrayGetElement(sa1, &idx, &val[0]); + if (FAILED(hr)) + return FALSE; + + idx = lbound[1] + i; + hr = SafeArrayGetElement(sa2, &idx, &val[1]); + if (FAILED(hr)) + return FALSE; + + if (val[0] != val[1]) + return FALSE; + } + + return TRUE; +} + static void clear_uia_node_ptr_safearray(SAFEARRAY *sa, LONG elems) { HUIANODE node; @@ -312,7 +348,8 @@ static ULONG WINAPI uia_node_Release(IWineUiaNode *iface) } }
- IWineUiaProvider_Release(node->prov); + if (node->prov) + IWineUiaProvider_Release(node->prov); if (node->in_provider_thread_list) uia_provider_thread_remove_node((HUIANODE)iface); if (node->nested_node) @@ -328,6 +365,12 @@ static HRESULT WINAPI uia_node_get_provider(IWineUiaNode *iface, IWineUiaProvide { struct uia_node *node = impl_from_IWineUiaNode(iface);
+ if (node->disconnected) + { + *out_prov = NULL; + return UIA_E_ELEMENTNOTAVAILABLE; + } + if (node->git_cookie) { IGlobalInterfaceTable *git; @@ -360,11 +403,18 @@ static HRESULT WINAPI uia_node_get_prop_val(IWineUiaNode *iface, const GUID *pro VARIANT *ret_val) { int prop_id = UiaLookupId(AutomationIdentifierType_Property, prop_guid); + struct uia_node *node = impl_from_IWineUiaNode(iface); HRESULT hr; VARIANT v;
TRACE("%p, %s, %p\n", iface, debugstr_guid(prop_guid), ret_val);
+ if (node->disconnected) + { + VariantInit(ret_val); + return UIA_E_ELEMENTNOTAVAILABLE; + } + hr = UiaGetPropertyValue((HUIANODE)iface, prop_id, &v);
/* VT_UNKNOWN is UiaGetReservedNotSupported value, no need to marshal it. */ @@ -376,12 +426,47 @@ static HRESULT WINAPI uia_node_get_prop_val(IWineUiaNode *iface, const GUID *pro return hr; }
+static HRESULT WINAPI uia_node_disconnect(IWineUiaNode *iface) +{ + struct uia_node *node = impl_from_IWineUiaNode(iface); + + TRACE("%p\n", node); + + if (node->disconnected) + { + ERR("Attempted to disconnect node which was already disconnected.\n"); + return E_FAIL; + } + + if (node->git_cookie) + { + IGlobalInterfaceTable *git; + HRESULT hr; + + hr = get_global_interface_table(&git); + if (SUCCEEDED(hr)) + { + hr = IGlobalInterfaceTable_RevokeInterfaceFromGlobal(git, node->git_cookie); + if (FAILED(hr)) + WARN("Failed to get revoke provider interface from Global Interface Table, hr %#lx\n", hr); + } + node->git_cookie = 0; + } + + IWineUiaProvider_Release(node->prov); + node->prov = NULL; + node->disconnected = TRUE; + + return S_OK; +} + static const IWineUiaNodeVtbl uia_node_vtbl = { uia_node_QueryInterface, uia_node_AddRef, uia_node_Release, uia_node_get_provider, uia_node_get_prop_val, + uia_node_disconnect, };
static struct uia_node *unsafe_impl_from_IWineUiaNode(IWineUiaNode *iface) diff --git a/dlls/uiautomationcore/uia_main.c b/dlls/uiautomationcore/uia_main.c index f141b4ca716..c888b51a019 100644 --- a/dlls/uiautomationcore/uia_main.c +++ b/dlls/uiautomationcore/uia_main.c @@ -346,12 +346,6 @@ HRESULT WINAPI UiaHostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **pr return S_OK; }
-HRESULT WINAPI UiaDisconnectProvider(IRawElementProviderSimple *provider) -{ - FIXME("(%p): stub\n", provider); - return E_NOTIMPL; -} - /*********************************************************************** * DllMain (uiautomationcore.@) */ diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index e0207c59e13..22f5efd3875 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -39,6 +39,7 @@ struct uia_node { HWND hwnd; BOOL nested_node; BOOL in_provider_thread_list; + BOOL disconnected; };
static inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) @@ -46,6 +47,9 @@ static inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) return CONTAINING_RECORD(iface, struct uia_node, IWineUiaNode_iface); }
+/* uia_client.c */ +BOOL uia_compare_runtime_ids(SAFEARRAY *sa1, SAFEARRAY *sa2) DECLSPEC_HIDDEN; + /* uia_ids.c */ const struct uia_prop_info *uia_prop_info_from_id(PROPERTYID prop_id) DECLSPEC_HIDDEN;
diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c index 4d93d3f3387..2e1eaa671e9 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -1152,6 +1152,7 @@ struct uia_provider_thread_list_entry struct list entry;
HUIANODE node; + SAFEARRAY *runtime_id; };
void uia_provider_thread_remove_node(HUIANODE node) @@ -1172,6 +1173,7 @@ void uia_provider_thread_remove_node(HUIANODE node) node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); node_data->in_provider_thread_list = FALSE; found = TRUE; + SafeArrayDestroy(prov->runtime_id); heap_free(prov); goto exit; } @@ -1183,10 +1185,42 @@ exit: ERR("Failed to find node %p in provider list\n", node); }
+static void uia_provider_thread_disconnect_node(SAFEARRAY *sa) +{ + struct uia_provider_thread_list_entry *prov; + struct list *cursor, *cursor2; + struct uia_node *node_data; + + EnterCriticalSection(&provider_thread_cs); + + /* Provider thread hasn't been started, no nodes to disconnect. */ + if (!provider_thread.ref) + goto exit; + + LIST_FOR_EACH_SAFE(cursor, cursor2, &provider_thread.nodes_list) + { + prov = LIST_ENTRY(cursor, struct uia_provider_thread_list_entry, entry); + + if (uia_compare_runtime_ids(sa, prov->runtime_id)) + { + list_remove(cursor); + node_data = impl_from_IWineUiaNode((IWineUiaNode *)prov->node); + node_data->in_provider_thread_list = FALSE; + IWineUiaNode_disconnect((IWineUiaNode *)prov->node); + SafeArrayDestroy(prov->runtime_id); + heap_free(prov); + } + } + +exit: + LeaveCriticalSection(&provider_thread_cs); +} + static HRESULT uia_provider_thread_add_node(HUIANODE node) { struct uia_provider_thread_list_entry *prov = heap_alloc_zero(sizeof(*prov)); struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); + HRESULT hr;
node_data->nested_node = TRUE;
@@ -1196,6 +1230,13 @@ static HRESULT uia_provider_thread_add_node(HUIANODE node) TRACE("Adding node %p\n", node); prov->node = node;
+ hr = UiaGetRuntimeId(node, &prov->runtime_id); + if (FAILED(hr)) + { + heap_free(prov); + return hr; + } + EnterCriticalSection(&provider_thread_cs); list_add_tail(&provider_thread.nodes_list, &prov->entry); node_data->in_provider_thread_list = TRUE; @@ -1394,3 +1435,32 @@ LRESULT WINAPI UiaReturnRawElementProvider(HWND hwnd, WPARAM wparam,
return uia_lresult_from_node(node); } + +/*********************************************************************** + * UiaDisconnectProvider (uiautomationcore.@) + */ +HRESULT WINAPI UiaDisconnectProvider(IRawElementProviderSimple *elprov) +{ + SAFEARRAY *sa; + HUIANODE node; + HRESULT hr; + + TRACE("(%p)\n", elprov); + + hr = UiaNodeFromProvider(elprov, &node); + if (FAILED(hr)) + return hr; + + hr = UiaGetRuntimeId(node, &sa); + UiaNodeRelease(node); + if (FAILED(hr)) + return hr; + + if (!sa) + return E_INVALIDARG; + + uia_provider_thread_disconnect_node(sa); + SafeArrayDestroy(sa); + + return S_OK; +} diff --git a/include/uiautomationcoreapi.h b/include/uiautomationcoreapi.h index b4814b9e857..025b811ba5e 100644 --- a/include/uiautomationcoreapi.h +++ b/include/uiautomationcoreapi.h @@ -398,6 +398,7 @@ BOOL WINAPI UiaNodeRelease(HUIANODE huianode); 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 UiaDisconnectProvider(IRawElementProviderSimple *elprov);
#ifdef __cplusplus }
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 214 ++++++++++----------- dlls/uiautomationcore/uia_client.c | 99 ++++++++-- dlls/uiautomationcore/uia_private.h | 1 + dlls/uiautomationcore/uia_provider.c | 2 +- 4 files changed, 191 insertions(+), 125 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 7982bd5fa8b..b500ca73b62 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -5294,57 +5294,53 @@ static DWORD WINAPI uia_node_from_handle_test_thread(LPVOID param) Provider_child.runtime_id[1] = 2; Provider_child.frag_root = NULL; hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); - todo_wine ok(hr == E_FAIL || broken(hr == S_OK), "Unexpected hr %#lx\n", hr); + ok(hr == E_FAIL || broken(hr == S_OK), "Unexpected hr %#lx\n", hr); if (SUCCEEDED(hr)) { hr = UiaHUiaNodeFromVariant(&v, &node2); ok(hr == S_OK, "Unexpected hr %#lx\n", hr); UiaNodeRelease(node2); } - if (hr != E_NOTIMPL) - ok_method_sequence(node_from_hwnd5, "node_from_hwnd5"); + + ok_method_sequence(node_from_hwnd5, "node_from_hwnd5");
/* RuntimeId check succeeds, we'll get a nested node. */ Provider_child.frag_root = &Provider.IRawElementProviderFragmentRoot_iface; hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref);
+ hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); if (SUCCEEDED(hr)) { - hr = UiaHUiaNodeFromVariant(&v, &node2); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - if (SUCCEEDED(hr)) - { - /* - * Even though this is a nested node, without any additional - * providers, it will not have the 'Nested' prefix. - */ - check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); - check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child", TRUE); - VariantClear(&v); - } - - hr = UiaGetPropertyValue(node2, UIA_ControlTypePropertyId, &v); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - ok(V_VT(&v) == VT_I4, "Unexpected VT %d\n", V_VT(&v)); - ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); - ok_method_sequence(node_from_hwnd6, "node_from_hwnd6"); - - UiaNodeRelease(node2); - /* - * There is a delay between nested nodes being released and the - * corresponding IRawElementProviderSimple release on newer Windows - * versions. + * Even though this is a nested node, without any additional + * providers, it will not have the 'Nested' prefix. */ - if (Provider_child.ref != 1) - Sleep(50); - ok(Provider_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child.ref); + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child", TRUE); + VariantClear(&v); }
+ hr = UiaGetPropertyValue(node2, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(V_VT(&v) == VT_I4, "Unexpected VT %d\n", V_VT(&v)); + ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); + ok_method_sequence(node_from_hwnd6, "node_from_hwnd6"); + + UiaNodeRelease(node2); + + /* + * There is a delay between nested nodes being released and the + * corresponding IRawElementProviderSimple release on newer Windows + * versions. + */ + if (Provider_child.ref != 1) + Sleep(50); + ok(Provider_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child.ref);
/* * Returned nested elements with an HWND will have client-side providers @@ -5355,42 +5351,39 @@ static DWORD WINAPI uia_node_from_handle_test_thread(LPVOID param) /* Only sent on Win7. */ SET_EXPECT(winproc_GETOBJECT_CLIENT); hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); todo_wine CHECK_CALLED(winproc_GETOBJECT_UiaRoot); called_winproc_GETOBJECT_CLIENT = expect_winproc_GETOBJECT_CLIENT = 0; - todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref);
+ hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); if (SUCCEEDED(hr)) { - hr = UiaHUiaNodeFromVariant(&v, &node2); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - if (SUCCEEDED(hr)) - { - memset(buf, 0, sizeof(buf)); + memset(buf, 0, sizeof(buf));
- ok(get_nested_provider_desc(V_BSTR(&v), L"Main", TRUE, buf), "Failed to get nested provider description\n"); - check_node_provider_desc_prefix(buf, GetCurrentProcessId(), hwnd); - /* Win10v1507 and below have the nested provider as 'Hwnd'. */ - if (get_provider_desc(buf, L"Hwnd(parent link):", NULL)) - check_node_provider_desc(buf, L"Hwnd", L"Provider_child", TRUE); - else - check_node_provider_desc(buf, L"Main", L"Provider_child", TRUE); + ok(get_nested_provider_desc(V_BSTR(&v), L"Main", TRUE, buf), "Failed to get nested provider description\n"); + check_node_provider_desc_prefix(buf, GetCurrentProcessId(), hwnd); + /* Win10v1507 and below have the nested provider as 'Hwnd'. */ + if (get_provider_desc(buf, L"Hwnd(parent link):", NULL)) + check_node_provider_desc(buf, L"Hwnd", L"Provider_child", TRUE); + else + check_node_provider_desc(buf, L"Main", L"Provider_child", TRUE);
- check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), hwnd); - check_node_provider_desc(V_BSTR(&v), L"Nonclient", NULL, FALSE); - check_node_provider_desc(V_BSTR(&v), L"Hwnd", NULL, FALSE); - VariantClear(&v); - } + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), hwnd); + check_node_provider_desc(V_BSTR(&v), L"Nonclient", NULL, FALSE); + check_node_provider_desc(V_BSTR(&v), L"Hwnd", NULL, FALSE); + VariantClear(&v); + }
- ok_method_sequence(node_from_hwnd7, "node_from_hwnd7"); - UiaNodeRelease(node2); + ok_method_sequence(node_from_hwnd7, "node_from_hwnd7"); + UiaNodeRelease(node2);
- if (Provider_child.ref != 1) - Sleep(50); - ok(Provider_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child.ref); - } + if (Provider_child.ref != 1) + Sleep(50); + ok(Provider_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child.ref);
ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); /* Win10v1809 can be slow to call Release on Provider. */ @@ -5452,68 +5445,65 @@ static DWORD WINAPI uia_node_from_handle_test_thread(LPVOID param) Provider_child.hwnd = NULL; Provider_child.frag_root = &Provider.IRawElementProviderFragmentRoot_iface; hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - todo_wine ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref);
+ hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); if (SUCCEEDED(hr)) { - hr = UiaHUiaNodeFromVariant(&v, &node2); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - if (SUCCEEDED(hr)) - { - check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); - check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child", TRUE); - VariantClear(&v); - } + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child", TRUE); + VariantClear(&v); + }
- hr = UiaGetPropertyValue(node2, UIA_ControlTypePropertyId, &v); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - ok(V_VT(&v) == VT_I4, "Unexpected VT %d\n", V_VT(&v)); - ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); - ok_method_sequence(node_from_hwnd6, "node_from_hwnd6"); + hr = UiaGetPropertyValue(node2, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(V_VT(&v) == VT_I4, "Unexpected VT %d\n", V_VT(&v)); + ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); + ok_method_sequence(node_from_hwnd6, "node_from_hwnd6");
- /* Get a new node for the same provider. */ - hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - ok(Provider_child.ref == 3, "Unexpected refcnt %ld\n", Provider_child.ref); + /* Get a new node for the same provider. */ + hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(Provider_child.ref == 3, "Unexpected refcnt %ld\n", Provider_child.ref);
- hr = UiaHUiaNodeFromVariant(&v, &node3); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - hr = UiaGetPropertyValue(node3, UIA_ProviderDescriptionPropertyId, &v); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - if (SUCCEEDED(hr)) - { - check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); - check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child", TRUE); - VariantClear(&v); - } + hr = UiaHUiaNodeFromVariant(&v, &node3); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node3, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child", TRUE); + VariantClear(&v); + }
- hr = UiaGetPropertyValue(node3, UIA_ControlTypePropertyId, &v); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - ok(V_VT(&v) == VT_I4, "Unexpected VT %d\n", V_VT(&v)); - ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); - ok_method_sequence(node_from_hwnd6, "node_from_hwnd6"); + hr = UiaGetPropertyValue(node3, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(V_VT(&v) == VT_I4, "Unexpected VT %d\n", V_VT(&v)); + ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); + ok_method_sequence(node_from_hwnd6, "node_from_hwnd6");
- /* - * Both node2 and node3 represent Provider_child, one call to - * UiaDisconnectProvider disconnects both. - */ - hr = pUiaDisconnectProvider(&Provider_child.IRawElementProviderSimple_iface); - ok(hr == S_OK, "Unexpected hr %#lx\n", hr); - ok(Provider_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child.ref); + /* + * Both node2 and node3 represent Provider_child, one call to + * UiaDisconnectProvider disconnects both. + */ + hr = pUiaDisconnectProvider(&Provider_child.IRawElementProviderSimple_iface); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(Provider_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child.ref);
- hr = UiaGetPropertyValue(node2, UIA_ControlTypePropertyId, &v); - ok(hr == UIA_E_ELEMENTNOTAVAILABLE, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node2, UIA_ControlTypePropertyId, &v); + ok(hr == UIA_E_ELEMENTNOTAVAILABLE, "Unexpected hr %#lx\n", hr);
- hr = UiaGetPropertyValue(node3, UIA_ControlTypePropertyId, &v); - ok(hr == UIA_E_ELEMENTNOTAVAILABLE, "Unexpected hr %#lx\n", hr); - ok_method_sequence(disconnect_prov1, "disconnect_prov1"); + hr = UiaGetPropertyValue(node3, UIA_ControlTypePropertyId, &v); + ok(hr == UIA_E_ELEMENTNOTAVAILABLE, "Unexpected hr %#lx\n", hr); + ok_method_sequence(disconnect_prov1, "disconnect_prov1");
- ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); - ok(UiaNodeRelease(node3), "UiaNodeRelease returned FALSE\n"); - } + ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); + ok(UiaNodeRelease(node3), "UiaNodeRelease returned FALSE\n");
/* * Returns same failure code as UiaGetRuntimeId when we fail to get a diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 90e13d56d3b..6a3e66c7e93 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -486,6 +486,8 @@ struct uia_provider {
IRawElementProviderSimple *elprov; IWineUiaNode *node; + + struct uia_node *container_node; };
static inline struct uia_provider *impl_from_IWineUiaProvider(IWineUiaProvider *iface) @@ -627,15 +629,26 @@ static HRESULT uia_provider_get_elem_prop_val(struct uia_provider *prov,
hr = IUnknown_QueryInterface(V_UNKNOWN(&v), &IID_IRawElementProviderSimple, (void **)&elprov); + VariantClear(&v); if (FAILED(hr)) goto exit;
hr = UiaNodeFromProvider(elprov, &node); + IRawElementProviderSimple_Release(elprov); if (SUCCEEDED(hr)) { - get_variant_for_node(node, ret_val); - VariantClear(&v); - IRawElementProviderSimple_Release(elprov); + if (prov->container_node->nested_node) + { + LRESULT lr = uia_lresult_from_node(node); + + if (!lr) + return E_FAIL; + + V_VT(ret_val) = VT_I4; + V_I4(ret_val) = lr; + } + else + get_variant_for_node(node, ret_val); } break; } @@ -734,6 +747,46 @@ static HRESULT uia_provider_get_special_prop_val(struct uia_provider *prov, return S_OK; }
+static HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode); +static HRESULT uia_provider_get_nested_node_prop_val(struct uia_provider *prov, + const struct uia_prop_info *prop_info, VARIANT *ret_val) +{ + HRESULT hr; + VARIANT v; + + if (prop_info->type == UIAutomationType_ElementArray) + { + FIXME("Element array property type currently unsupported for nested nodes.\n"); + return E_NOTIMPL; + } + + hr = IWineUiaNode_get_prop_val(prov->node, prop_info->guid, &v); + if (FAILED(hr)) + return hr; + + switch (prop_info->type) + { + case UIAutomationType_Element: + { + HUIANODE node; + + hr = uia_node_from_lresult((LRESULT)V_I4(&v), &node); + if (FAILED(hr)) + return hr; + + get_variant_for_node(node, ret_val); + VariantClear(&v); + break; + } + + default: + *ret_val = v; + break; + } + + return S_OK; +} + static HRESULT WINAPI uia_provider_get_prop_val(IWineUiaProvider *iface, const struct uia_prop_info *prop_info, VARIANT *ret_val) { @@ -743,15 +796,7 @@ static HRESULT WINAPI uia_provider_get_prop_val(IWineUiaProvider *iface,
VariantInit(ret_val); if (prov->node) - { - if (prop_info->type == UIAutomationType_Element || prop_info->type == UIAutomationType_ElementArray) - { - FIXME("Element property types currently unsupported for nested nodes.\n"); - return E_NOTIMPL; - } - - return IWineUiaNode_get_prop_val(prov->node, prop_info->guid, ret_val); - } + return uia_provider_get_nested_node_prop_val(prov, prop_info, ret_val);
switch (prop_info->prop_type) { @@ -796,6 +841,7 @@ static HRESULT create_wine_uia_provider(struct uia_node *node, IRawElementProvid prov->IWineUiaProvider_iface.lpVtbl = &uia_provider_vtbl; prov->elprov = elprov; prov->ref = 1; + prov->container_node = node; node->prov = &prov->IWineUiaProvider_iface; node->hwnd = get_hwnd_from_provider(elprov);
@@ -1042,10 +1088,12 @@ static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESU if (unwrap) { struct uia_node *node_data = unsafe_impl_from_IWineUiaNode(nested_node); + struct uia_provider *tmp_prov = impl_from_IWineUiaProvider(node_data->prov);
node->prov = node_data->prov; IWineUiaProvider_AddRef(node->prov); node->git_cookie = node_data->git_cookie; + tmp_prov->container_node = node;
node_data->git_cookie = 0; IWineUiaNode_Release(&node_data->IWineUiaNode_iface); @@ -1061,6 +1109,7 @@ static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESU prov->IWineUiaProvider_iface.lpVtbl = &uia_provider_vtbl; prov->node = nested_node; prov->ref = 1; + prov->container_node = node; node->prov = &prov->IWineUiaProvider_iface;
/* @@ -1111,6 +1160,32 @@ static HRESULT uia_get_provider_from_hwnd(struct uia_node *node) return SendMessageW(client_thread.hwnd, WM_UIA_CLIENT_GET_NODE_PROV, (WPARAM)&args, (LPARAM)node); }
+static HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode) +{ + struct uia_node *node; + HRESULT hr; + + *huianode = NULL; + node = heap_alloc_zero(sizeof(*node)); + if (!node) + return E_OUTOFMEMORY; + + node->IWineUiaNode_iface.lpVtbl = &uia_node_vtbl; + node->ref = 1; + + uia_start_client_thread(); + hr = create_wine_uia_nested_node_provider(node, lr, FALSE); + if (FAILED(hr)) + { + heap_free(node); + return hr; + } + + *huianode = (void *)&node->IWineUiaNode_iface; + + return hr; +} + /*********************************************************************** * UiaNodeFromHandle (uiautomationcore.@) */ diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 22f5efd3875..44ec03945fe 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -56,3 +56,4 @@ const struct uia_prop_info *uia_prop_info_from_id(PROPERTYID prop_id) DECLSPEC_H /* uia_provider.c */ void uia_stop_provider_thread(void) DECLSPEC_HIDDEN; void uia_provider_thread_remove_node(HUIANODE node) DECLSPEC_HIDDEN; +LRESULT uia_lresult_from_node(HUIANODE huianode) DECLSPEC_HIDDEN; diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c index 2e1eaa671e9..eb88b607f8b 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -1392,7 +1392,7 @@ void uia_stop_provider_thread(void) * Automation has to work regardless of whether or not COM is initialized on * the thread calling UiaReturnRawElementProvider. */ -static LRESULT uia_lresult_from_node(HUIANODE huianode) +LRESULT uia_lresult_from_node(HUIANODE huianode) { if (!uia_start_provider_thread()) {
V2: - Move provider thread nodes list into `struct uia_provider_thread` - Get rid of remove all nodes function on shutdown, replace it with a warning.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_client.c:
*/
- if (unwrap)
- {
struct uia_node *node_data = unsafe_impl_from_IWineUiaNode(nested_node);
node->prov = node_data->prov;
IWineUiaProvider_AddRef(node->prov);
node->git_cookie = node_data->git_cookie;
node_data->git_cookie = 0;
IWineUiaNode_Release(&node_data->IWineUiaNode_iface);
uia_stop_client_thread();
return S_OK;
- }
Is it safe to make the assumption that the IWineUiaNode object belongs to the current process in this case? I guess it'd be really weird to forward WM_GETOBJECT for UiaRootObjectId to some other process, but is there any reason you couldn't ?
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_provider.c:
- TRACE("Removing node %p\n", node);
- EnterCriticalSection(&provider_thread_cs);
- LIST_FOR_EACH_SAFE(cursor, cursor2, &provider_thread_nodes_list)
- {
prov = LIST_ENTRY(cursor, struct uia_provider_thread_list_entry, entry);
if (prov->node == node)
{
list_remove(cursor);
node_data = impl_from_IWineUiaNode((IWineUiaNode *)node);
node_data->in_provider_thread_list = FALSE;
found = TRUE;
heap_free(prov);
goto exit;
}
- }
Having to do a linear search seems not ideal. Could we put the list entry in the node itself instead?
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_provider.c:
{ PostMessageW(provider_thread.hwnd, WM_UIA_PROVIDER_THREAD_STOP, 0, 0); CloseHandle(provider_thread.hthread);
if (!list_empty(&provider_thread.nodes_list))
WARN("Provider thread shutdown with nodes still in the list\n");
This shouldn't be possible, should it? If not, it should be an ERR. If it is intended to be possible, the nodes here should probably be invalidated.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_provider.c:
- /* Provider thread hasn't been started, no nodes to disconnect. */
- if (!provider_thread.ref)
goto exit;
- LIST_FOR_EACH_SAFE(cursor, cursor2, &provider_thread.nodes_list)
- {
prov = LIST_ENTRY(cursor, struct uia_provider_thread_list_entry, entry);
if (uia_compare_runtime_ids(sa, prov->runtime_id))
{
list_remove(cursor);
node_data = impl_from_IWineUiaNode((IWineUiaNode *)prov->node);
node_data->in_provider_thread_list = FALSE;
IWineUiaNode_disconnect((IWineUiaNode *)prov->node);
SafeArrayDestroy(prov->runtime_id);
Can we avoid a linear search here? Perhaps with `wine/rbtree.h`?
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_client.c:
IRawElementProviderSimple *elprov; IWineUiaNode *node;
- struct uia_node *container_node;
Will this backlink be needed for anything except accessing the nested_node field? If not, it may be better to move or duplicate the field.
On Sat Sep 24 20:32:39 2022 +0000, Esme Povirk wrote:
Is it safe to make the assumption that the IWineUiaNode object belongs to the current process in this case? I guess it'd be really weird to forward WM_GETOBJECT for UiaRootObjectId to some other process, but is there any reason you couldn't ?
Just tried it, the scenario you're describing doesn't work on Windows. It just returns `E_FAIL`. I used `unsafe_impl_from_IWineUiaNode` here just as an extra precaution, I guess I could also add a NULL check if you think it's an important thing to check for.
Going to close this MR down since I can't cleanly push my changes to the existing branch, opening a new one.
This merge request was closed by Connor McAdams.