Changes since last MR: - Store list entry inside of `struct uia_node` instead of doing linear search for removal. - Replace `WARN` with `ERR` if provider thread shuts down with nodes still in the provider thread list. - Use `wine/rbtree.h` for looking up HUIANODEs by runtime ID. - Get rid of `struct uia_node *container_node` inside of `struct uia_provider`.
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 | 51 +++++++++++++++++++--- 2 files changed, 60 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..1d55ddd40ec 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,33 @@ 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); + + if (!node_data) + { + ERR("Failed to get uia_node structure from nested node\n"); + uia_stop_client_thread(); + return E_FAIL; + } + + 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 +1007,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 | 3 ++ dlls/uiautomationcore/uia_provider.c | 52 ++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 3 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 1d55ddd40ec..08d9b5eb5e1 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->prov_thread_list_entry) + 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..b72cb6c09c9 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -20,6 +20,7 @@
#include "uiautomation.h" #include "uia_classes.h" +#include "wine/list.h"
extern HMODULE huia_module DECLSPEC_HIDDEN;
@@ -38,6 +39,7 @@ struct uia_node {
HWND hwnd; BOOL nested_node; + struct list *prov_thread_list_entry; };
static inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) @@ -50,3 +52,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..3cb9ce10561 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -1130,6 +1130,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 +1146,45 @@ 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_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); + + TRACE("Removing node %p\n", node); + EnterCriticalSection(&provider_thread_cs); + list_remove(node_data->prov_thread_list_entry); + heap_free(node_data->prov_thread_list_entry); + node_data->prov_thread_list_entry = NULL; + 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); + + node_data->nested_node = TRUE; + + if (!prov) + return E_OUTOFMEMORY; + + TRACE("Adding node %p\n", node); + EnterCriticalSection(&provider_thread_cs); + prov->node = node; + list_add_tail(&provider_thread.nodes_list, &prov->entry); + node_data->prov_thread_list_entry = &prov->entry; + 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 +1195,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 +1282,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 +1320,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)) + ERR("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 | 99 +++++++- dlls/uiautomationcore/uia_main.c | 6 - dlls/uiautomationcore/uia_private.h | 5 + dlls/uiautomationcore/uia_provider.c | 171 ++++++++++++- include/uiautomationcoreapi.h | 1 + 7 files changed, 551 insertions(+), 9 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 08d9b5eb5e1..48f0ee0c388 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -49,6 +49,54 @@ static HRESULT get_safearray_bounds(SAFEARRAY *sa, LONG *lbound, LONG *elems) return S_OK; }
+int 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)) + { + ERR("Failed to get safearray bounds from sa1 with hr %#lx\n", hr); + return -1; + } + + hr = get_safearray_bounds(sa2, &lbound[1], &elems[1]); + if (FAILED(hr)) + { + ERR("Failed to get safearray bounds from sa2 with hr %#lx\n", hr); + return -1; + } + + if (elems[0] != elems[1]) + return elems[0] - elems[1]; + + for (i = 0; i < elems[0]; i++) + { + idx = lbound[0] + i; + hr = SafeArrayGetElement(sa1, &idx, &val[0]); + if (FAILED(hr)) + { + ERR("Failed to get element from sa1 with hr %#lx\n", hr); + return -1; + } + + idx = lbound[1] + i; + hr = SafeArrayGetElement(sa2, &idx, &val[1]); + if (FAILED(hr)) + { + ERR("Failed to get element from sa2 with hr %#lx\n", hr); + return -1; + } + + if (val[0] != val[1]) + return val[0] - val[1]; + } + + return 0; +} + static void clear_uia_node_ptr_safearray(SAFEARRAY *sa, LONG elems) { HUIANODE node; @@ -312,7 +360,8 @@ static ULONG WINAPI uia_node_Release(IWineUiaNode *iface) } }
- IWineUiaProvider_Release(node->prov); + if (node->prov) + IWineUiaProvider_Release(node->prov); if (node->prov_thread_list_entry) uia_provider_thread_remove_node((HUIANODE)iface); if (node->nested_node) @@ -328,6 +377,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 +415,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 +438,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 b72cb6c09c9..345290a838c 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -39,7 +39,9 @@ struct uia_node {
HWND hwnd; BOOL nested_node; + BOOL disconnected; struct list *prov_thread_list_entry; + struct list *node_map_list_entry; };
static inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) @@ -47,6 +49,9 @@ static inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) return CONTAINING_RECORD(iface, struct uia_node, IWineUiaNode_iface); }
+/* uia_client.c */ +int 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 3cb9ce10561..690dbb3724e 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/rbtree.h" #include "initguid.h" #include "wine/iaccessible2.h"
@@ -1131,6 +1132,7 @@ HRESULT WINAPI UiaProviderFromIAccessible(IAccessible *acc, long child_id, DWORD struct uia_provider_thread { struct list nodes_list; + struct rb_tree node_map; HANDLE hthread; HWND hwnd; LONG ref; @@ -1153,6 +1155,28 @@ struct uia_provider_thread_list_entry HUIANODE node; };
+struct uia_provider_thread_map_entry +{ + struct rb_entry entry; + + SAFEARRAY *runtime_id; + struct list nodes_list; +}; + +struct uia_node_map_list_entry +{ + struct list entry; + + HUIANODE node; + struct uia_provider_thread_map_entry *map; +}; + +static int uia_runtime_id_compare(const void *key, const struct rb_entry *entry) +{ + struct uia_provider_thread_map_entry *prov_entry = RB_ENTRY_VALUE(entry, struct uia_provider_thread_map_entry, entry); + return uia_compare_runtime_ids(prov_entry->runtime_id, (SAFEARRAY *)key); +} + void uia_provider_thread_remove_node(HUIANODE node) { struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); @@ -1162,6 +1186,63 @@ void uia_provider_thread_remove_node(HUIANODE node) list_remove(node_data->prov_thread_list_entry); heap_free(node_data->prov_thread_list_entry); node_data->prov_thread_list_entry = NULL; + + if (node_data->node_map_list_entry) + { + struct uia_provider_thread_map_entry *prov_map; + struct uia_node_map_list_entry *node_map_list; + + node_map_list = LIST_ENTRY(node_data->node_map_list_entry, struct uia_node_map_list_entry, entry); + prov_map = node_map_list->map; + + list_remove(node_data->node_map_list_entry); + if (list_empty(&prov_map->nodes_list)) + { + rb_remove(&provider_thread.node_map, &prov_map->entry); + SafeArrayDestroy(prov_map->runtime_id); + heap_free(prov_map); + } + + heap_free(node_data->node_map_list_entry); + node_data->node_map_list_entry = NULL; + } + + LeaveCriticalSection(&provider_thread_cs); +} + +void uia_provider_thread_disconnect_node(SAFEARRAY *sa) +{ + struct rb_entry *rb_entry; + + EnterCriticalSection(&provider_thread_cs); + rb_entry = rb_get(&provider_thread.node_map, sa); + if (rb_entry) + { + struct uia_provider_thread_map_entry *prov_map; + struct uia_node_map_list_entry *node; + struct list *cursor, *cursor2; + struct uia_node *node_data; + + prov_map = RB_ENTRY_VALUE(rb_entry, struct uia_provider_thread_map_entry, entry); + LIST_FOR_EACH_SAFE(cursor, cursor2, &prov_map->nodes_list) + { + node = LIST_ENTRY(cursor, struct uia_node_map_list_entry, entry); + + list_remove(cursor); + node_data = impl_from_IWineUiaNode((IWineUiaNode *)node->node); + list_remove(node_data->prov_thread_list_entry); + heap_free(node_data->prov_thread_list_entry); + node_data->prov_thread_list_entry = NULL; + node_data->node_map_list_entry = NULL; + + IWineUiaNode_disconnect((IWineUiaNode *)node->node); + heap_free(node); + } + + rb_remove(&provider_thread.node_map, &prov_map->entry); + SafeArrayDestroy(prov_map->runtime_id); + heap_free(prov_map); + } LeaveCriticalSection(&provider_thread_cs); }
@@ -1169,20 +1250,77 @@ 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); + SAFEARRAY *sa; + HRESULT hr;
node_data->nested_node = TRUE;
if (!prov) return E_OUTOFMEMORY;
+ hr = UiaGetRuntimeId(node, &sa); + if (FAILED(hr)) + { + heap_free(prov); + return hr; + } + TRACE("Adding node %p\n", node); + EnterCriticalSection(&provider_thread_cs); + + /* If we have a runtime ID, create an entry in the rb tree. */ + if (sa) + { + struct uia_node_map_list_entry *node_map_list = heap_alloc_zero(sizeof(*node_map_list)); + struct uia_provider_thread_map_entry *prov_map; + struct rb_entry *rb_entry; + + if (!node_map_list) + { + hr = E_OUTOFMEMORY; + goto exit; + } + + if ((rb_entry = rb_get(&provider_thread.node_map, sa))) + { + 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) + { + heap_free(node_map_list); + hr = E_OUTOFMEMORY; + goto exit; + } + prov_map->runtime_id = sa; + list_init(&prov_map->nodes_list); + rb_put(&provider_thread.node_map, sa, &prov_map->entry); + } + + node_map_list->node = node; + node_map_list->map = prov_map; + list_add_tail(&prov_map->nodes_list, &node_map_list->entry); + node_data->node_map_list_entry = &node_map_list->entry; + } + prov->node = node; list_add_tail(&provider_thread.nodes_list, &prov->entry); node_data->prov_thread_list_entry = &prov->entry; + +exit: + if (FAILED(hr)) + { + heap_free(prov); + SafeArrayDestroy(sa); + } + LeaveCriticalSection(&provider_thread_cs);
- return S_OK; + return hr; }
#define WM_GET_OBJECT_UIA_NODE (WM_USER + 1) @@ -1283,6 +1421,7 @@ static BOOL uia_start_provider_thread(void) (const WCHAR *)uia_start_provider_thread, &hmodule);
list_init(&provider_thread.nodes_list); + rb_init(&provider_thread.node_map, uia_runtime_id_compare); events[0] = ready_event = CreateEventW(NULL, FALSE, FALSE, NULL); if (!(provider_thread.hthread = CreateThread(NULL, 0, uia_provider_thread_proc, ready_event, 0, NULL))) @@ -1375,3 +1514,33 @@ 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 | 108 ++++++++--- dlls/uiautomationcore/uia_private.h | 16 ++ dlls/uiautomationcore/uia_provider.c | 5 +- 4 files changed, 204 insertions(+), 139 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 48f0ee0c388..e1de8337009 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -492,19 +492,6 @@ static struct uia_node *unsafe_impl_from_IWineUiaNode(IWineUiaNode *iface) /* * IWineUiaProvider interface. */ -struct uia_provider { - IWineUiaProvider IWineUiaProvider_iface; - LONG ref; - - IRawElementProviderSimple *elprov; - IWineUiaNode *node; -}; - -static inline struct uia_provider *impl_from_IWineUiaProvider(IWineUiaProvider *iface) -{ - return CONTAINING_RECORD(iface, struct uia_provider, IWineUiaProvider_iface); -} - static HRESULT WINAPI uia_provider_QueryInterface(IWineUiaProvider *iface, REFIID riid, void **ppv) { *ppv = NULL; @@ -639,15 +626,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->nested_node_prov) + { + 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; } @@ -746,6 +744,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) { @@ -755,15 +793,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) { @@ -1054,6 +1084,7 @@ 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);
if (!node_data) { @@ -1065,6 +1096,7 @@ static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESU node->prov = node_data->prov; IWineUiaProvider_AddRef(node->prov); node->git_cookie = node_data->git_cookie; + tmp_prov->nested_node_prov = FALSE;
node_data->git_cookie = 0; IWineUiaNode_Release(&node_data->IWineUiaNode_iface); @@ -1130,6 +1162,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 345290a838c..b0d96fcf513 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -49,6 +49,21 @@ static inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) return CONTAINING_RECORD(iface, struct uia_node, IWineUiaNode_iface); }
+struct uia_provider { + IWineUiaProvider IWineUiaProvider_iface; + LONG ref; + + IRawElementProviderSimple *elprov; + IWineUiaNode *node; + + BOOL nested_node_prov; +}; + +static inline struct uia_provider *impl_from_IWineUiaProvider(IWineUiaProvider *iface) +{ + return CONTAINING_RECORD(iface, struct uia_provider, IWineUiaProvider_iface); +} + /* uia_client.c */ int uia_compare_runtime_ids(SAFEARRAY *sa1, SAFEARRAY *sa2) DECLSPEC_HIDDEN;
@@ -58,3 +73,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 690dbb3724e..c2346de6ba9 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -1250,10 +1250,11 @@ 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); + struct uia_provider *prov_data = impl_from_IWineUiaProvider(node_data->prov); SAFEARRAY *sa; HRESULT hr;
- node_data->nested_node = TRUE; + node_data->nested_node = prov_data->nested_node_prov = TRUE;
if (!prov) return E_OUTOFMEMORY; @@ -1471,7 +1472,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()) {
Updated old MR, thought I wouldn't be able to but I was wrong. Closing this one. :)
This merge request was closed by Connor McAdams.