Add the ability to disconnect nested node providers, and return element properties for nested nodes.
-- v7: uiautomationcore: Add UIAutomationType_Element property support for nested node providers. uiautomationcore: Store provider thread nodes that have a runtime ID in an rbtree. uiautomationcore: Implement UiaDisconnectProvider. uiautomationcore: Track all HUIANODEs returned from the provider thread. uiautomationcore: Don't use nested node providers for same-thread HWNDs. uiautomationcore: Use a separate vtbl for nested node IWineUiaProviders.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_client.c | 106 +++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 22 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 1d6360682c2..f8317dbb090 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -398,7 +398,6 @@ struct uia_provider { LONG ref;
IRawElementProviderSimple *elprov; - IWineUiaNode *node; };
static inline struct uia_provider *impl_from_IWineUiaProvider(IWineUiaProvider *iface) @@ -436,13 +435,7 @@ static ULONG WINAPI uia_provider_Release(IWineUiaProvider *iface) TRACE("%p, refcount %ld\n", prov, ref); if (!ref) { - if (prov->node) - { - IWineUiaNode_Release(prov->node); - uia_stop_client_thread(); - } - else - IRawElementProviderSimple_Release(prov->elprov); + IRawElementProviderSimple_Release(prov->elprov); heap_free(prov); }
@@ -655,17 +648,6 @@ static HRESULT WINAPI uia_provider_get_prop_val(IWineUiaProvider *iface, TRACE("%p, %p, %p\n", iface, prop_info, ret_val);
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); - } - switch (prop_info->prop_type) { case PROP_TYPE_ELEM_PROP: @@ -924,10 +906,90 @@ static void uia_stop_client_thread(void) LeaveCriticalSection(&client_thread_cs); }
+/* + * IWineUiaProvider interface for nested node providers. + * + * Nested node providers represent an HUIANODE that resides in another + * thread or process. We retrieve values from this HUIANODE through the + * IWineUiaNode interface. + */ +struct uia_nested_node_provider { + IWineUiaProvider IWineUiaProvider_iface; + LONG ref; + + IWineUiaNode *nested_node; +}; + +static inline struct uia_nested_node_provider *impl_from_nested_node_IWineUiaProvider(IWineUiaProvider *iface) +{ + return CONTAINING_RECORD(iface, struct uia_nested_node_provider, IWineUiaProvider_iface); +} + +static HRESULT WINAPI uia_nested_node_provider_QueryInterface(IWineUiaProvider *iface, REFIID riid, void **ppv) +{ + *ppv = NULL; + if (IsEqualIID(riid, &IID_IWineUiaProvider) || IsEqualIID(riid, &IID_IUnknown)) + *ppv = iface; + else + return E_NOINTERFACE; + + IWineUiaProvider_AddRef(iface); + return S_OK; +} + +static ULONG WINAPI uia_nested_node_provider_AddRef(IWineUiaProvider *iface) +{ + struct uia_nested_node_provider *prov = impl_from_nested_node_IWineUiaProvider(iface); + ULONG ref = InterlockedIncrement(&prov->ref); + + TRACE("%p, refcount %ld\n", prov, ref); + return ref; +} + +static ULONG WINAPI uia_nested_node_provider_Release(IWineUiaProvider *iface) +{ + struct uia_nested_node_provider *prov = impl_from_nested_node_IWineUiaProvider(iface); + ULONG ref = InterlockedDecrement(&prov->ref); + + TRACE("%p, refcount %ld\n", prov, ref); + if (!ref) + { + IWineUiaNode_Release(prov->nested_node); + uia_stop_client_thread(); + heap_free(prov); + } + + return ref; +} + +static HRESULT WINAPI uia_nested_node_provider_get_prop_val(IWineUiaProvider *iface, + const struct uia_prop_info *prop_info, VARIANT *ret_val) +{ + struct uia_nested_node_provider *prov = impl_from_nested_node_IWineUiaProvider(iface); + + TRACE("%p, %p, %p\n", iface, prop_info, ret_val); + + VariantInit(ret_val); + 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->nested_node, prop_info->guid, ret_val); +} + +static const IWineUiaProviderVtbl uia_nested_node_provider_vtbl = { + uia_nested_node_provider_QueryInterface, + uia_nested_node_provider_AddRef, + uia_nested_node_provider_Release, + uia_nested_node_provider_get_prop_val, +}; + static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESULT lr) { + struct uia_nested_node_provider *prov; IGlobalInterfaceTable *git; - struct uia_provider *prov; IWineUiaNode *nested_node; HRESULT hr;
@@ -942,8 +1004,8 @@ static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESU if (!prov) return E_OUTOFMEMORY;
- prov->IWineUiaProvider_iface.lpVtbl = &uia_provider_vtbl; - prov->node = nested_node; + prov->IWineUiaProvider_iface.lpVtbl = &uia_nested_node_provider_vtbl; + prov->nested_node = nested_node; prov->ref = 1; node->prov = &prov->IWineUiaProvider_iface;
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 | 101 +++++++++++++++------ 2 files changed, 90 insertions(+), 46 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 f8317dbb090..08fe3b863ae 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -769,6 +769,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 = @@ -781,14 +786,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; @@ -986,11 +994,14 @@ static const IWineUiaProviderVtbl uia_nested_node_provider_vtbl = { uia_nested_node_provider_get_prop_val, };
-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) { + IWineUiaProvider *provider_iface = NULL; struct uia_nested_node_provider *prov; IGlobalInterfaceTable *git; IWineUiaNode *nested_node; + DWORD git_cookie; HRESULT hr;
hr = ObjectFromLresult(lr, &IID_IWineUiaNode, 0, (void **)&nested_node); @@ -1000,30 +1011,65 @@ static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESU return hr; }
- prov = heap_alloc_zero(sizeof(*prov)); - if (!prov) - return E_OUTOFMEMORY; - - prov->IWineUiaProvider_iface.lpVtbl = &uia_nested_node_provider_vtbl; - prov->nested_node = nested_node; - prov->ref = 1; - node->prov = &prov->IWineUiaProvider_iface; - /* - * We need to use the GIT on all nested node providers so that our - * IWineUiaNode proxy is used in the correct apartment. + * 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. */ - hr = get_global_interface_table(&git); - if (FAILED(hr)) - goto exit; + if (unwrap) + { + struct uia_node *node_data = unsafe_impl_from_IWineUiaNode(nested_node);
- hr = IGlobalInterfaceTable_RegisterInterfaceInGlobal(git, (IUnknown *)&prov->IWineUiaProvider_iface, - &IID_IWineUiaProvider, &node->git_cookie); -exit: - if (FAILED(hr)) - IWineUiaProvider_Release(&prov->IWineUiaProvider_iface); + if (!node_data) + { + ERR("Failed to get uia_node structure from nested node\n"); + uia_stop_client_thread(); + return E_FAIL; + }
- return hr; + IWineUiaProvider_AddRef(node_data->prov); + provider_iface = node_data->prov; + git_cookie = node_data->git_cookie; + + node_data->git_cookie = 0; + IWineUiaNode_Release(&node_data->IWineUiaNode_iface); + uia_stop_client_thread(); + } + else + { + prov = heap_alloc_zero(sizeof(*prov)); + if (!prov) + return E_OUTOFMEMORY; + + prov->IWineUiaProvider_iface.lpVtbl = &uia_nested_node_provider_vtbl; + prov->nested_node = nested_node; + prov->ref = 1; + provider_iface = &prov->IWineUiaProvider_iface; + + /* + * We need to use the GIT on all nested node providers so that our + * IWineUiaNode proxy is used in the correct apartment. + */ + hr = get_global_interface_table(&git); + if (FAILED(hr)) + { + IWineUiaProvider_Release(&prov->IWineUiaProvider_iface); + return hr; + } + + hr = IGlobalInterfaceTable_RegisterInterfaceInGlobal(git, (IUnknown *)&prov->IWineUiaProvider_iface, + &IID_IWineUiaProvider, &git_cookie); + if (FAILED(hr)) + { + IWineUiaProvider_Release(&prov->IWineUiaProvider_iface); + return hr; + } + } + + node->prov = provider_iface; + node->git_cookie = git_cookie; + + return S_OK; }
/* @@ -1033,27 +1079,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 | 4 +++ dlls/uiautomationcore/uia_private.h | 3 +++ dlls/uiautomationcore/uia_provider.c | 40 +++++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 3 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 08fe3b863ae..a648d99f103 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 (!list_empty(&node->prov_thread_list_entry)) + uia_provider_thread_remove_node((HUIANODE)iface); if (node->nested_node) uia_stop_provider_thread();
@@ -751,6 +753,7 @@ HRESULT WINAPI UiaNodeFromProvider(IRawElementProviderSimple *elprov, HUIANODE * }
node->IWineUiaNode_iface.lpVtbl = &uia_node_vtbl; + list_init(&node->prov_thread_list_entry); node->ref = 1;
*huianode = (void *)&node->IWineUiaNode_iface; @@ -1127,6 +1130,7 @@ HRESULT WINAPI UiaNodeFromHandle(HWND hwnd, HUIANODE *huianode)
node->hwnd = hwnd; node->IWineUiaNode_iface.lpVtbl = &uia_node_vtbl; + list_init(&node->prov_thread_list_entry); node->ref = 1;
hr = uia_get_provider_from_hwnd(node); diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 1efc8fbd1e4..da7f54c8ce3 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..fe7dff9112e 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,33 @@ static CRITICAL_SECTION_DEBUG provider_thread_cs_debug = }; static CRITICAL_SECTION provider_thread_cs = { &provider_thread_cs_debug, -1, 0, 0, 0, 0 };
+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); + list_init(&node_data->prov_thread_list_entry); + LeaveCriticalSection(&provider_thread_cs); +} + +static HRESULT uia_provider_thread_add_node(HUIANODE node) +{ + struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); + + TRACE("Adding node %p\n", node); + + node_data->nested_node = TRUE; + + EnterCriticalSection(&provider_thread_cs); + list_add_tail(&provider_thread.nodes_list, &node_data->prov_thread_list_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 +1183,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 +1270,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 +1308,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 | 288 ++++++++++++++++++++- dlls/uiautomationcore/uia_classes.idl | 1 + dlls/uiautomationcore/uia_client.c | 99 ++++++- dlls/uiautomationcore/uia_main.c | 6 - dlls/uiautomationcore/uia_private.h | 6 + dlls/uiautomationcore/uia_provider.c | 74 +++++- include/uiautomationcoreapi.h | 1 + 7 files changed, 465 insertions(+), 10 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index aa5a9afab7f..d3bd49cc8c1 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,57 @@ 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 + * on Win8+ will have its RuntimeId queried for UiaDisconnectProvider. + * It will fail due to the lack of a valid fragment root, and we'll fall + * back to an MSAA proxy. On Win7, RuntimeId isn't queried because the + * disconnection functions weren't implemented yet. + */ + 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();
/* @@ -5471,6 +5745,15 @@ static void test_UiaNodeFromHandle(const char *name)
ok_method_sequence(node_from_hwnd2, "node_from_hwnd2");
+ /* For same-thread HWND nodes, no disconnection will occur. */ + if (pUiaDisconnectProvider) + { + hr = pUiaDisconnectProvider(&Provider.IRawElementProviderSimple_iface); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + + ok_method_sequence(disconnect_prov4, "disconnect_prov4"); + } + /* * This is relevant too: Since we don't get a 'nested' node, all calls * will occur on the current thread. @@ -5617,6 +5900,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 a648d99f103..2d97ff13159 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]) - (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]) - (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 (!list_empty(&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 da7f54c8ce3..c4512ecd046 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -39,6 +39,9 @@ struct uia_node {
HWND hwnd; BOOL nested_node; + BOOL disconnected; + /* This RuntimeId is used as a comparison for UiaDisconnectProvider(). */ + SAFEARRAY *runtime_id; struct list prov_thread_list_entry; };
@@ -47,6 +50,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 fe7dff9112e..76eea75f855 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -1155,18 +1155,58 @@ void uia_provider_thread_remove_node(HUIANODE node) EnterCriticalSection(&provider_thread_cs); list_remove(&node_data->prov_thread_list_entry); list_init(&node_data->prov_thread_list_entry); + SafeArrayDestroy(node_data->runtime_id); + node_data->runtime_id = NULL; + LeaveCriticalSection(&provider_thread_cs); +} + +static void uia_provider_thread_disconnect_node(SAFEARRAY *sa) +{ + 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) + { + node_data = LIST_ENTRY(cursor, struct uia_node, prov_thread_list_entry); + + if (node_data->runtime_id) + { + if (!uia_compare_runtime_ids(sa, node_data->runtime_id)) + { + list_remove(cursor); + list_init(&node_data->prov_thread_list_entry); + IWineUiaNode_disconnect(&node_data->IWineUiaNode_iface); + SafeArrayDestroy(node_data->runtime_id); + node_data->runtime_id = NULL; + } + } + } + +exit: LeaveCriticalSection(&provider_thread_cs); }
static HRESULT uia_provider_thread_add_node(HUIANODE node) { struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); - - TRACE("Adding node %p\n", node); + SAFEARRAY *sa; + HRESULT hr;
node_data->nested_node = TRUE; + hr = UiaGetRuntimeId(node, &sa); + if (FAILED(hr)) + return hr; + + TRACE("Adding node %p\n", node);
EnterCriticalSection(&provider_thread_cs); + node_data->runtime_id = sa; list_add_tail(&provider_thread.nodes_list, &node_data->prov_thread_list_entry); LeaveCriticalSection(&provider_thread_cs);
@@ -1363,3 +1403,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/uia_client.c | 2 + dlls/uiautomationcore/uia_private.h | 4 +- dlls/uiautomationcore/uia_provider.c | 99 +++++++++++++++++++++++----- 3 files changed, 87 insertions(+), 18 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 2d97ff13159..24de251eb8a 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -851,6 +851,7 @@ HRESULT WINAPI UiaNodeFromProvider(IRawElementProviderSimple *elprov, HUIANODE *
node->IWineUiaNode_iface.lpVtbl = &uia_node_vtbl; list_init(&node->prov_thread_list_entry); + list_init(&node->node_map_list_entry); node->ref = 1;
*huianode = (void *)&node->IWineUiaNode_iface; @@ -1228,6 +1229,7 @@ HRESULT WINAPI UiaNodeFromHandle(HWND hwnd, HUIANODE *huianode) node->hwnd = hwnd; node->IWineUiaNode_iface.lpVtbl = &uia_node_vtbl; list_init(&node->prov_thread_list_entry); + list_init(&node->node_map_list_entry); node->ref = 1;
hr = uia_get_provider_from_hwnd(node); diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index c4512ecd046..c94f2b6c1eb 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -40,9 +40,9 @@ struct uia_node { HWND hwnd; BOOL nested_node; BOOL disconnected; - /* This RuntimeId is used as a comparison for UiaDisconnectProvider(). */ - SAFEARRAY *runtime_id; struct list prov_thread_list_entry; + struct list node_map_list_entry; + struct uia_provider_thread_map_entry *map; };
static inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c index 76eea75f855..17adf622ffe 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"
@@ -1130,6 +1131,7 @@ HRESULT WINAPI UiaProviderFromIAccessible(IAccessible *acc, long child_id, DWORD */ struct uia_provider_thread { + struct rb_tree node_map; struct list nodes_list; HANDLE hthread; HWND hwnd; @@ -1146,6 +1148,20 @@ 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_map_entry +{ + struct rb_entry entry; + + SAFEARRAY *runtime_id; + struct list nodes_list; +}; + +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); @@ -1153,17 +1169,28 @@ void uia_provider_thread_remove_node(HUIANODE node) TRACE("Removing node %p\n", node);
EnterCriticalSection(&provider_thread_cs); + list_remove(&node_data->prov_thread_list_entry); list_init(&node_data->prov_thread_list_entry); - SafeArrayDestroy(node_data->runtime_id); - node_data->runtime_id = NULL; + if (!list_empty(&node_data->node_map_list_entry)) + { + list_remove(&node_data->node_map_list_entry); + list_init(&node_data->node_map_list_entry); + if (list_empty(&node_data->map->nodes_list)) + { + rb_remove(&provider_thread.node_map, &node_data->map->entry); + SafeArrayDestroy(node_data->map->runtime_id); + heap_free(node_data->map); + } + node_data->map = NULL; + } + LeaveCriticalSection(&provider_thread_cs); }
static void uia_provider_thread_disconnect_node(SAFEARRAY *sa) { - struct list *cursor, *cursor2; - struct uia_node *node_data; + struct rb_entry *rb_entry;
EnterCriticalSection(&provider_thread_cs);
@@ -1171,21 +1198,30 @@ static void uia_provider_thread_disconnect_node(SAFEARRAY *sa) if (!provider_thread.ref) goto exit;
- LIST_FOR_EACH_SAFE(cursor, cursor2, &provider_thread.nodes_list) + rb_entry = rb_get(&provider_thread.node_map, sa); + if (rb_entry) { - node_data = LIST_ENTRY(cursor, struct uia_node, prov_thread_list_entry); + struct uia_provider_thread_map_entry *prov_map; + struct list *cursor, *cursor2; + struct uia_node *node_data;
- if (node_data->runtime_id) + prov_map = RB_ENTRY_VALUE(rb_entry, struct uia_provider_thread_map_entry, entry); + LIST_FOR_EACH_SAFE(cursor, cursor2, &prov_map->nodes_list) { - if (!uia_compare_runtime_ids(sa, node_data->runtime_id)) - { - list_remove(cursor); - list_init(&node_data->prov_thread_list_entry); - IWineUiaNode_disconnect(&node_data->IWineUiaNode_iface); - SafeArrayDestroy(node_data->runtime_id); - node_data->runtime_id = NULL; - } + node_data = LIST_ENTRY(cursor, struct uia_node, node_map_list_entry); + + list_remove(cursor); + list_remove(&node_data->prov_thread_list_entry); + list_init(&node_data->prov_thread_list_entry); + list_init(&node_data->node_map_list_entry); + node_data->map = NULL; + + IWineUiaNode_disconnect(&node_data->IWineUiaNode_iface); } + + rb_remove(&provider_thread.node_map, &prov_map->entry); + SafeArrayDestroy(prov_map->runtime_id); + heap_free(prov_map); }
exit: @@ -1206,8 +1242,38 @@ static HRESULT uia_provider_thread_add_node(HUIANODE node) TRACE("Adding node %p\n", node);
EnterCriticalSection(&provider_thread_cs); - node_data->runtime_id = sa; list_add_tail(&provider_thread.nodes_list, &node_data->prov_thread_list_entry); + + /* If we have a runtime ID, create an entry in the rb tree. */ + if (sa) + { + struct uia_provider_thread_map_entry *prov_map; + struct rb_entry *rb_entry; + + 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) + { + SafeArrayDestroy(sa); + LeaveCriticalSection(&provider_thread_cs); + return E_OUTOFMEMORY; + } + + prov_map->runtime_id = sa; + list_init(&prov_map->nodes_list); + rb_put(&provider_thread.node_map, sa, &prov_map->entry); + } + + list_add_tail(&prov_map->nodes_list, &node_data->node_map_list_entry); + node_data->map = prov_map; + } + LeaveCriticalSection(&provider_thread_cs);
return S_OK; @@ -1311,6 +1377,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)))
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 | 93 +++++++-- dlls/uiautomationcore/uia_private.h | 14 ++ dlls/uiautomationcore/uia_provider.c | 5 +- 4 files changed, 194 insertions(+), 132 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index d3bd49cc8c1..85270ca7f27 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 24de251eb8a..b8c94b831e6 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -492,18 +492,6 @@ static struct uia_node *unsafe_impl_from_IWineUiaNode(IWineUiaNode *iface) /* * IWineUiaProvider interface. */ -struct uia_provider { - IWineUiaProvider IWineUiaProvider_iface; - LONG ref; - - IRawElementProviderSimple *elprov; -}; - -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; @@ -632,15 +620,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->return_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; } @@ -1071,21 +1070,48 @@ static ULONG WINAPI uia_nested_node_provider_Release(IWineUiaProvider *iface) return ref; }
+static HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode); static HRESULT WINAPI uia_nested_node_provider_get_prop_val(IWineUiaProvider *iface, const struct uia_prop_info *prop_info, VARIANT *ret_val) { struct uia_nested_node_provider *prov = impl_from_nested_node_IWineUiaProvider(iface); + HRESULT hr; + VARIANT v;
TRACE("%p, %p, %p\n", iface, prop_info, ret_val);
VariantInit(ret_val); - if (prop_info->type == UIAutomationType_Element || prop_info->type == UIAutomationType_ElementArray) + if (prop_info->type == UIAutomationType_ElementArray) { - FIXME("Element property types currently unsupported for nested nodes.\n"); + FIXME("Element array property types currently unsupported for nested nodes.\n"); return E_NOTIMPL; }
- return IWineUiaNode_get_prop_val(prov->nested_node, prop_info->guid, ret_val); + hr = IWineUiaNode_get_prop_val(prov->nested_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 const IWineUiaProviderVtbl uia_nested_node_provider_vtbl = { @@ -1120,6 +1146,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 *prov_data;
if (!node_data) { @@ -1131,6 +1158,8 @@ static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESU IWineUiaProvider_AddRef(node_data->prov); provider_iface = node_data->prov; git_cookie = node_data->git_cookie; + prov_data = impl_from_IWineUiaProvider(node_data->prov); + prov_data->return_nested_node = FALSE;
node_data->git_cookie = 0; IWineUiaNode_Release(&node_data->IWineUiaNode_iface); @@ -1173,6 +1202,34 @@ static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESU return S_OK; }
+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; + list_init(&node->prov_thread_list_entry); + list_init(&node->node_map_list_entry); + 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 is expected to work even if the calling thread hasn't * initialized COM. We marshal our node on a separate thread that initializes diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index c94f2b6c1eb..40589d538be 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -50,6 +50,19 @@ 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; + BOOL return_nested_node; +}; + +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;
@@ -59,3 +72,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 17adf622ffe..44850a55b05 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -1231,10 +1231,11 @@ exit: static HRESULT uia_provider_thread_add_node(HUIANODE node) { 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->return_nested_node = TRUE; hr = UiaGetRuntimeId(node, &sa); if (FAILED(hr)) return hr; @@ -1427,7 +1428,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()) {
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=124871
Your paranoid android.
=== debian11 (build log) ===
Use of uninitialized value $Flaky in addition (+) at /home/testbot/lib/WineTestBot/LogUtils.pm line 720, <$LogFile> line 24863. Use of uninitialized value $Flaky in addition (+) at /home/testbot/lib/WineTestBot/LogUtils.pm line 720, <$LogFile> line 24863. Use of uninitialized value $Flaky in addition (+) at /home/testbot/lib/WineTestBot/LogUtils.pm line 720, <$LogFile> line 24863.
v7: - Fix runtime ID comparison function. - Use a separate vtbl for nested node IWineUiaProvider's. - Split rbtree implementation into a separate patch from `UiaDisconnectProvider()` implementation.
On Mon Oct 10 21:43:13 2022 +0000, Connor McAdams wrote:
changed this line in [version 7 of the diff](/wine/wine/-/merge_requests/908/diffs?diff_id=13294&start_sha=fb568fd6ed434987454d4e3d343a19270eb39688#58d90e31c9d28aee1bd4c53093a1f039ee491f9d_637_631)
I decided on a separate vtbl, for me it makes it easier to read in traces. But if you think that's overkill, I can just change the variable name instead.
On Mon Oct 10 21:45:58 2022 +0000, Connor McAdams wrote:
I decided on a separate vtbl, for me it makes it easier to read in traces. But if you think that's overkill, I can just change the variable name instead.
I think it's much clearer now.
On Tue Oct 11 05:58:02 2022 +0000, **** wrote:
Marvin replied on the mailing list:
Hi, It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated. The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details: The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=124871 Your paranoid android. === debian11 (build log) === Use of uninitialized value $Flaky in addition (+) at /home/testbot/lib/WineTestBot/LogUtils.pm line 720, <$LogFile> line 24863. Use of uninitialized value $Flaky in addition (+) at /home/testbot/lib/WineTestBot/LogUtils.pm line 720, <$LogFile> line 24863. Use of uninitialized value $Flaky in addition (+) at /home/testbot/lib/WineTestBot/LogUtils.pm line 720, <$LogFile> line 24863.
This is happening to all MRs for some reason, shouldn't be an issue here.
This merge request was approved by Esme Povirk.