This series has been split up more than the prior two, keeping the non-tests diffs below 200. Hopefully it's easier to review now. If not, please let me know and I can _try_ to split it further, but I'm not sure it can get much more split up than this.
-- v2: uiautomationcore: Add tests for UiaNodeFromHandle. uiautomationcore: Create UI Automation client thread. uiautomationcore: Implement UiaNodeFromHandle. uiautomationcore: Shutdown provider thread when all returned nodes are released. uiautomationcore: Increment module reference count when starting provider thread. uiautomationcore: Implement UiaReturnRawElementProvider.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_classes.idl | 1 + dlls/uiautomationcore/uia_main.c | 10 -- dlls/uiautomationcore/uia_provider.c | 179 ++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 10 deletions(-)
diff --git a/dlls/uiautomationcore/uia_classes.idl b/dlls/uiautomationcore/uia_classes.idl index f7d3bb90c73..030606cec93 100644 --- a/dlls/uiautomationcore/uia_classes.idl +++ b/dlls/uiautomationcore/uia_classes.idl @@ -50,6 +50,7 @@ library UIA_wine_private object, uuid(bccb6799-d831-4057-bd50-6425823ff1a3), pointer_default(unique), + oleautomation, ] interface IWineUiaNode : IUnknown { diff --git a/dlls/uiautomationcore/uia_main.c b/dlls/uiautomationcore/uia_main.c index 3a6f10c1647..37657032b53 100644 --- a/dlls/uiautomationcore/uia_main.c +++ b/dlls/uiautomationcore/uia_main.c @@ -297,16 +297,6 @@ HRESULT WINAPI UiaGetReservedNotSupportedValue(IUnknown **value) return S_OK; }
-/*********************************************************************** - * UiaReturnRawElementProvider (uiautomationcore.@) - */ -LRESULT WINAPI UiaReturnRawElementProvider(HWND hwnd, WPARAM wParam, - LPARAM lParam, IRawElementProviderSimple *elprov) -{ - FIXME("(%p, %Ix, %Ix, %p) stub!\n", hwnd, wParam, lParam, elprov); - return 0; -} - /*********************************************************************** * UiaRaiseAutomationEvent (uiautomationcore.@) */ diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c index 9c2dfd65989..9ba74a4271f 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -1124,3 +1124,182 @@ HRESULT WINAPI UiaProviderFromIAccessible(IAccessible *acc, long child_id, DWORD
return S_OK; } + +/* + * UI Automation provider thread functions. + */ +struct uia_provider_thread +{ + HANDLE hthread; + HWND hwnd; +}; + +static struct uia_provider_thread provider_thread; +static CRITICAL_SECTION provider_thread_cs; +static CRITICAL_SECTION_DEBUG provider_thread_cs_debug = +{ + 0, 0, &provider_thread_cs, + { &provider_thread_cs_debug.ProcessLocksList, &provider_thread_cs_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": provider_thread_cs") } +}; +static CRITICAL_SECTION provider_thread_cs = { &provider_thread_cs_debug, -1, 0, 0, 0, 0 }; + +#define WM_GET_OBJECT_UIA_NODE (WM_USER + 1) +static LRESULT CALLBACK uia_provider_thread_msg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) + { + case WM_GET_OBJECT_UIA_NODE: + { + HUIANODE node = (HUIANODE)lparam; + LRESULT lr; + + /* + * LresultFromObject returns an index into the global atom string table, + * which has a valid range of 0xc000-0xffff. + */ + lr = LresultFromObject(&IID_IWineUiaNode, 0, (IUnknown *)node); + if ((lr > 0xffff) || (lr < 0xc000)) + { + WARN("Got invalid lresult %Ix\n", lr); + lr = 0; + } + + /* + * LresultFromObject increases refcnt by 1. If LresultFromObject + * failed, this is expected to release the node. + */ + UiaNodeRelease(node); + return lr; + } + + default: + break; + } + + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +static DWORD WINAPI uia_provider_thread_proc(void *arg) +{ + HANDLE initialized_event = arg; + HWND hwnd; + MSG msg; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + hwnd = CreateWindowW(L"Message", NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); + if (!IsWindow(hwnd)) + { + WARN("CreateWindow failed: %ld\n", GetLastError()); + CoUninitialize(); + ExitThread(1); + } + + SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)uia_provider_thread_msg_proc); + provider_thread.hwnd = hwnd; + + /* Initialization complete, thread can now process window messages. */ + SetEvent(initialized_event); + TRACE("Provider thread started.\n"); + while (GetMessageW(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + TRACE("Shutting down UI Automation provider thread.\n"); + + DestroyWindow(hwnd); + CoUninitialize(); + ExitThread(0); +} + +static BOOL uia_start_provider_thread(void) +{ + BOOL started = TRUE; + + EnterCriticalSection(&provider_thread_cs); + if (!IsWindow(provider_thread.hwnd)) + { + HANDLE ready_event; + HANDLE events[2]; + DWORD wait_obj; + + events[0] = ready_event = CreateEventW(NULL, FALSE, FALSE, NULL); + if (!(provider_thread.hthread = CreateThread(NULL, 0, uia_provider_thread_proc, + ready_event, 0, NULL))) + { + started = FALSE; + goto exit; + } + + events[1] = provider_thread.hthread; + wait_obj = WaitForMultipleObjects(2, events, FALSE, INFINITE); + if (wait_obj != WAIT_OBJECT_0) + { + CloseHandle(provider_thread.hthread); + started = FALSE; + } + +exit: + CloseHandle(ready_event); + if (!started) + { + WARN("Failed to start provider thread\n"); + memset(&provider_thread, 0, sizeof(provider_thread)); + } + } + + LeaveCriticalSection(&provider_thread_cs); + return started; +} + +/* + * Pass our IWineUiaNode interface to the provider thread for marshaling. UI + * 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) +{ + if (!uia_start_provider_thread()) + { + UiaNodeRelease(huianode); + return 0; + } + + return SendMessageW(provider_thread.hwnd, WM_GET_OBJECT_UIA_NODE, 0, (LPARAM)huianode); +} + +/*********************************************************************** + * UiaReturnRawElementProvider (uiautomationcore.@) + */ +LRESULT WINAPI UiaReturnRawElementProvider(HWND hwnd, WPARAM wparam, + LPARAM lparam, IRawElementProviderSimple *elprov) +{ + HUIANODE node; + HRESULT hr; + + TRACE("(%p, %Ix, %#Ix, %p)\n", hwnd, wparam, lparam, elprov); + + if (!wparam && !lparam && !elprov) + { + FIXME("UIA-to-MSAA bridge not implemented, no provider map to free.\n"); + return 0; + } + + if (lparam != UiaRootObjectId) + { + FIXME("Unsupported object id %Id, ignoring.\n", lparam); + return 0; + } + + hr = UiaNodeFromProvider(elprov, &node); + if (FAILED(hr)) + { + WARN("Failed to create HUIANODE with hr %#lx\n", hr); + return 0; + } + + return uia_lresult_from_node(node); +}
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_main.c | 23 +++++++++++++++++++++++ dlls/uiautomationcore/uia_private.h | 2 ++ dlls/uiautomationcore/uia_provider.c | 10 ++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/dlls/uiautomationcore/uia_main.c b/dlls/uiautomationcore/uia_main.c index 37657032b53..f141b4ca716 100644 --- a/dlls/uiautomationcore/uia_main.c +++ b/dlls/uiautomationcore/uia_main.c @@ -27,6 +27,8 @@
WINE_DEFAULT_DEBUG_CHANNEL(uiautomation);
+HMODULE huia_module; + struct uia_object_wrapper { IUnknown IUnknown_iface; @@ -349,3 +351,24 @@ HRESULT WINAPI UiaDisconnectProvider(IRawElementProviderSimple *provider) FIXME("(%p): stub\n", provider); return E_NOTIMPL; } + +/*********************************************************************** + * DllMain (uiautomationcore.@) + */ +BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, void *reserved) +{ + TRACE("(%p, %ld, %p)\n", hinst, reason, reserved); + + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hinst); + huia_module = hinst; + break; + + default: + break; + } + + return TRUE; +} diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index ed53da58471..f92d4bd3721 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -21,6 +21,8 @@ #include "uiautomation.h" #include "uia_classes.h"
+extern HMODULE huia_module DECLSPEC_HIDDEN; + enum uia_prop_type { PROP_TYPE_UNKNOWN, PROP_TYPE_ELEM_PROP, diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c index 9ba74a4271f..f05a3e1ff8a 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -1193,7 +1193,7 @@ static DWORD WINAPI uia_provider_thread_proc(void *arg) { WARN("CreateWindow failed: %ld\n", GetLastError()); CoUninitialize(); - ExitThread(1); + FreeLibraryAndExitThread(huia_module, 1); }
SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)uia_provider_thread_msg_proc); @@ -1212,7 +1212,7 @@ static DWORD WINAPI uia_provider_thread_proc(void *arg)
DestroyWindow(hwnd); CoUninitialize(); - ExitThread(0); + FreeLibraryAndExitThread(huia_module, 0); }
static BOOL uia_start_provider_thread(void) @@ -1224,12 +1224,18 @@ static BOOL uia_start_provider_thread(void) { HANDLE ready_event; HANDLE events[2]; + HMODULE hmodule; DWORD wait_obj;
+ /* Increment DLL reference count. */ + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (const WCHAR *)uia_start_provider_thread, &hmodule); + events[0] = ready_event = CreateEventW(NULL, FALSE, FALSE, NULL); if (!(provider_thread.hthread = CreateThread(NULL, 0, uia_provider_thread_proc, ready_event, 0, NULL))) { + FreeLibrary(hmodule); started = FALSE; goto exit; }
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_client.c | 18 +++--------------- dlls/uiautomationcore/uia_private.h | 20 ++++++++++++++++++++ dlls/uiautomationcore/uia_provider.c | 22 +++++++++++++++++++++- 3 files changed, 44 insertions(+), 16 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 0ff01eba5af..c1fb880c9f1 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -269,21 +269,6 @@ static IRawElementProviderSimple *get_provider_hwnd_fragment_root(IRawElementPro /* * IWineUiaNode interface. */ -struct uia_node { - IWineUiaNode IWineUiaNode_iface; - LONG ref; - - IWineUiaProvider *prov; - DWORD git_cookie; - - HWND hwnd; -}; - -static inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) -{ - return CONTAINING_RECORD(iface, struct uia_node, IWineUiaNode_iface); -} - static HRESULT WINAPI uia_node_QueryInterface(IWineUiaNode *iface, REFIID riid, void **ppv) { *ppv = NULL; @@ -328,6 +313,9 @@ static ULONG WINAPI uia_node_Release(IWineUiaNode *iface) }
IWineUiaProvider_Release(node->prov); + if (node->nested_node) + uia_stop_provider_thread(); + heap_free(node); }
diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index f92d4bd3721..1efc8fbd1e4 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -29,4 +29,24 @@ enum uia_prop_type { PROP_TYPE_SPECIAL, };
+struct uia_node { + IWineUiaNode IWineUiaNode_iface; + LONG ref; + + IWineUiaProvider *prov; + DWORD git_cookie; + + HWND hwnd; + BOOL nested_node; +}; + +static inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) +{ + return CONTAINING_RECORD(iface, struct uia_node, IWineUiaNode_iface); +} + +/* uia_ids.c */ const struct uia_prop_info *uia_prop_info_from_id(PROPERTYID prop_id) DECLSPEC_HIDDEN; + +/* uia_provider.c */ +void uia_stop_provider_thread(void) DECLSPEC_HIDDEN; diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c index f05a3e1ff8a..94aa1948871 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -1132,6 +1132,7 @@ struct uia_provider_thread { HANDLE hthread; HWND hwnd; + LONG ref; };
static struct uia_provider_thread provider_thread; @@ -1145,6 +1146,7 @@ static CRITICAL_SECTION_DEBUG provider_thread_cs_debug = static CRITICAL_SECTION provider_thread_cs = { &provider_thread_cs_debug, -1, 0, 0, 0, 0 };
#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, LPARAM lparam) { @@ -1153,8 +1155,12 @@ 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; + /* * LresultFromObject returns an index into the global atom string table, * which has a valid range of 0xc000-0xffff. @@ -1204,6 +1210,8 @@ static DWORD WINAPI uia_provider_thread_proc(void *arg) TRACE("Provider thread started.\n"); while (GetMessageW(&msg, NULL, 0, 0)) { + if (msg.message == WM_UIA_PROVIDER_THREAD_STOP) + break; TranslateMessage(&msg); DispatchMessageW(&msg); } @@ -1220,7 +1228,7 @@ static BOOL uia_start_provider_thread(void) BOOL started = TRUE;
EnterCriticalSection(&provider_thread_cs); - if (!IsWindow(provider_thread.hwnd)) + if (++provider_thread.ref == 1) { HANDLE ready_event; HANDLE events[2]; @@ -1261,6 +1269,18 @@ exit: return started; }
+void uia_stop_provider_thread(void) +{ + EnterCriticalSection(&provider_thread_cs); + if (!--provider_thread.ref) + { + PostMessageW(provider_thread.hwnd, WM_UIA_PROVIDER_THREAD_STOP, 0, 0); + CloseHandle(provider_thread.hthread); + memset(&provider_thread, 0, sizeof(provider_thread)); + } + LeaveCriticalSection(&provider_thread_cs); +} + /* * Pass our IWineUiaNode interface to the provider thread for marshaling. UI * Automation has to work regardless of whether or not COM is initialized on
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_classes.idl | 1 + dlls/uiautomationcore/uia_client.c | 130 +++++++++++++++++++- dlls/uiautomationcore/uiautomationcore.spec | 2 +- include/uiautomationcoreapi.h | 1 + 4 files changed, 132 insertions(+), 2 deletions(-)
diff --git a/dlls/uiautomationcore/uia_classes.idl b/dlls/uiautomationcore/uia_classes.idl index 030606cec93..e2889901fbb 100644 --- a/dlls/uiautomationcore/uia_classes.idl +++ b/dlls/uiautomationcore/uia_classes.idl @@ -55,5 +55,6 @@ library UIA_wine_private interface IWineUiaNode : IUnknown { HRESULT get_provider([out, retval]IWineUiaProvider **out_prov); + HRESULT get_prop_val([in]const GUID *prop_guid, [out, retval]VARIANT *ret_val); } } diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index c1fb880c9f1..193bad60230 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -354,11 +354,32 @@ static HRESULT WINAPI uia_node_get_provider(IWineUiaNode *iface, IWineUiaProvide return S_OK; }
+static HRESULT WINAPI uia_node_get_prop_val(IWineUiaNode *iface, const GUID *prop_guid, + VARIANT *ret_val) +{ + int prop_id = UiaLookupId(AutomationIdentifierType_Property, prop_guid); + HRESULT hr; + VARIANT v; + + TRACE("%p, %s, %p\n", iface, debugstr_guid(prop_guid), ret_val); + + hr = UiaGetPropertyValue((HUIANODE)iface, prop_id, &v); + + /* VT_UNKNOWN is UiaGetReservedNotSupported value, no need to marshal it. */ + if (V_VT(&v) == VT_UNKNOWN) + V_VT(ret_val) = VT_EMPTY; + else + *ret_val = v; + + return hr; +} + static const IWineUiaNodeVtbl uia_node_vtbl = { uia_node_QueryInterface, uia_node_AddRef, uia_node_Release, uia_node_get_provider, + uia_node_get_prop_val, };
static struct uia_node *unsafe_impl_from_IWineUiaNode(IWineUiaNode *iface) @@ -377,6 +398,7 @@ struct uia_provider { LONG ref;
IRawElementProviderSimple *elprov; + IWineUiaNode *node; };
static inline struct uia_provider *impl_from_IWineUiaProvider(IWineUiaProvider *iface) @@ -413,7 +435,10 @@ static ULONG WINAPI uia_provider_Release(IWineUiaProvider *iface) TRACE("%p, refcount %ld\n", prov, ref); if (!ref) { - IRawElementProviderSimple_Release(prov->elprov); + if (prov->node) + IWineUiaNode_Release(prov->node); + else + IRawElementProviderSimple_Release(prov->elprov); heap_free(prov); }
@@ -625,6 +650,18 @@ 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: @@ -735,6 +772,97 @@ HRESULT WINAPI UiaNodeFromProvider(IRawElementProviderSimple *elprov, HUIANODE * return hr; }
+static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESULT lr) +{ + IGlobalInterfaceTable *git; + struct uia_provider *prov; + IWineUiaNode *nested_node; + HRESULT hr; + + hr = ObjectFromLresult(lr, &IID_IWineUiaNode, 0, (void **)&nested_node); + if (FAILED(hr)) + return hr; + + prov = heap_alloc_zero(sizeof(*prov)); + if (!prov) + return E_OUTOFMEMORY; + + prov->IWineUiaProvider_iface.lpVtbl = &uia_provider_vtbl; + prov->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. + */ + hr = get_global_interface_table(&git); + if (FAILED(hr)) + goto exit; + + hr = IGlobalInterfaceTable_RegisterInterfaceInGlobal(git, (IUnknown *)&prov->IWineUiaProvider_iface, + &IID_IWineUiaProvider, &node->git_cookie); +exit: + if (FAILED(hr)) + IWineUiaProvider_Release(&prov->IWineUiaProvider_iface); + + return hr; +} + +static HRESULT uia_get_provider_from_hwnd(struct uia_node *node) +{ + LRESULT lr; + + lr = SendMessageW(node->hwnd, WM_GETOBJECT, 0, UiaRootObjectId); + if (!lr) + { + if (!IsWindow(node->hwnd) && GetLastError() == ERROR_INVALID_WINDOW_HANDLE) + return UIA_E_ELEMENTNOTAVAILABLE; + FIXME("No native UIA provider for hwnd %p, MSAA proxy currently unimplemented.\n", node->hwnd); + return E_NOTIMPL; + } + + return create_wine_uia_nested_node_provider(node, lr); +} + +/*********************************************************************** + * UiaNodeFromHandle (uiautomationcore.@) + */ +HRESULT WINAPI UiaNodeFromHandle(HWND hwnd, HUIANODE *huianode) +{ + struct uia_node *node; + HRESULT hr; + + TRACE("(%p, %p)\n", hwnd, huianode); + + if (!huianode) + return E_INVALIDARG; + + *huianode = NULL; + + if (!IsWindow(hwnd)) + return UIA_E_ELEMENTNOTAVAILABLE; + + node = heap_alloc_zero(sizeof(*node)); + if (!node) + return E_OUTOFMEMORY; + + node->hwnd = hwnd; + node->IWineUiaNode_iface.lpVtbl = &uia_node_vtbl; + node->ref = 1; + + hr = uia_get_provider_from_hwnd(node); + if (FAILED(hr) || !node->prov) + { + heap_free(node); + return hr; + } + + *huianode = (void *)&node->IWineUiaNode_iface; + + return S_OK; +} + /*********************************************************************** * UiaNodeRelease (uiautomationcore.@) */ diff --git a/dlls/uiautomationcore/uiautomationcore.spec b/dlls/uiautomationcore/uiautomationcore.spec index f4e745a644a..03a747219ed 100644 --- a/dlls/uiautomationcore/uiautomationcore.spec +++ b/dlls/uiautomationcore/uiautomationcore.spec @@ -77,7 +77,7 @@ @ stdcall UiaLookupId(long ptr) @ stub UiaNavigate @ stub UiaNodeFromFocus -@ stub UiaNodeFromHandle +@ stdcall UiaNodeFromHandle(long ptr) @ stub UiaNodeFromPoint @ stdcall UiaNodeFromProvider(ptr ptr) @ stdcall UiaNodeRelease(ptr) diff --git a/include/uiautomationcoreapi.h b/include/uiautomationcoreapi.h index 946c8b0fadc..b4814b9e857 100644 --- a/include/uiautomationcoreapi.h +++ b/include/uiautomationcoreapi.h @@ -397,6 +397,7 @@ HRESULT WINAPI UiaNodeFromProvider(IRawElementProviderSimple *elprov, HUIANODE * 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);
#ifdef __cplusplus }
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_client.c | 182 ++++++++++++++++++++++++++++- 1 file changed, 178 insertions(+), 4 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 193bad60230..3de18174b39 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -427,6 +427,7 @@ static ULONG WINAPI uia_provider_AddRef(IWineUiaProvider *iface) return ref; }
+static void uia_stop_client_thread(void); static ULONG WINAPI uia_provider_Release(IWineUiaProvider *iface) { struct uia_provider *prov = impl_from_IWineUiaProvider(iface); @@ -436,7 +437,10 @@ static ULONG WINAPI uia_provider_Release(IWineUiaProvider *iface) if (!ref) { if (prov->node) + { IWineUiaNode_Release(prov->node); + uia_stop_client_thread(); + } else IRawElementProviderSimple_Release(prov->elprov); heap_free(prov); @@ -772,6 +776,163 @@ HRESULT WINAPI UiaNodeFromProvider(IRawElementProviderSimple *elprov, HUIANODE * return hr; }
+/* + * UI Automation client thread functions. + */ +struct uia_client_thread +{ + CO_MTA_USAGE_COOKIE mta_cookie; + HANDLE hthread; + HWND hwnd; + LONG ref; +}; + +struct uia_get_node_prov_args { + struct uia_node *node; + LRESULT lr; +}; + +static struct uia_client_thread client_thread; +static CRITICAL_SECTION client_thread_cs; +static CRITICAL_SECTION_DEBUG client_thread_cs_debug = +{ + 0, 0, &client_thread_cs, + { &client_thread_cs_debug.ProcessLocksList, &client_thread_cs_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": client_thread_cs") } +}; +static CRITICAL_SECTION client_thread_cs = { &client_thread_cs_debug, -1, 0, 0, 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 LRESULT CALLBACK uia_client_thread_msg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) + { + case WM_UIA_CLIENT_GET_NODE_PROV: + { + struct uia_get_node_prov_args *args = (struct uia_get_node_prov_args *)lparam; + + return create_wine_uia_nested_node_provider(args->node, args->lr); + } + + default: + break; + } + + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +static DWORD WINAPI uia_client_thread_proc(void *arg) +{ + HANDLE initialized_event = arg; + HWND hwnd; + MSG msg; + + hwnd = CreateWindowW(L"Message", NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); + if (!IsWindow(hwnd)) + { + WARN("CreateWindow failed: %ld\n", GetLastError()); + FreeLibraryAndExitThread(huia_module, 1); + } + + SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)uia_client_thread_msg_proc); + client_thread.hwnd = hwnd; + + /* Initialization complete, thread can now process window messages. */ + SetEvent(initialized_event); + TRACE("Client thread started.\n"); + while (GetMessageW(&msg, NULL, 0, 0)) + { + if (msg.message == WM_UIA_CLIENT_THREAD_STOP) + break; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + TRACE("Shutting down UI Automation client thread.\n"); + + DestroyWindow(hwnd); + FreeLibraryAndExitThread(huia_module, 0); +} + +static BOOL uia_start_client_thread(void) +{ + BOOL started = TRUE; + + EnterCriticalSection(&client_thread_cs); + if (++client_thread.ref == 1) + { + HANDLE ready_event = NULL; + HANDLE events[2]; + HMODULE hmodule; + DWORD wait_obj; + HRESULT hr; + + /* + * We use CoIncrementMTAUsage here instead of CoInitialize because it + * allows us to exit the implicit MTA immediately when the thread + * reference count hits 0, rather than waiting for the thread to + * shutdown and call CoUninitialize like the provider thread. + */ + hr = CoIncrementMTAUsage(&client_thread.mta_cookie); + if (FAILED(hr)) + { + started = FALSE; + goto exit; + } + + /* Increment DLL reference count. */ + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (const WCHAR *)uia_start_client_thread, &hmodule); + + events[0] = ready_event = CreateEventW(NULL, FALSE, FALSE, NULL); + if (!(client_thread.hthread = CreateThread(NULL, 0, uia_client_thread_proc, + ready_event, 0, NULL))) + { + FreeLibrary(hmodule); + started = FALSE; + goto exit; + } + + events[1] = client_thread.hthread; + wait_obj = WaitForMultipleObjects(2, events, FALSE, INFINITE); + if (wait_obj != WAIT_OBJECT_0) + { + CloseHandle(client_thread.hthread); + started = FALSE; + } + +exit: + if (ready_event) + CloseHandle(ready_event); + if (!started) + { + WARN("Failed to start client thread\n"); + if (client_thread.mta_cookie) + CoDecrementMTAUsage(client_thread.mta_cookie); + memset(&client_thread, 0, sizeof(client_thread)); + } + } + + LeaveCriticalSection(&client_thread_cs); + return started; +} + +static void uia_stop_client_thread(void) +{ + EnterCriticalSection(&client_thread_cs); + if (!--client_thread.ref) + { + PostMessageW(client_thread.hwnd, WM_UIA_CLIENT_THREAD_STOP, 0, 0); + CoDecrementMTAUsage(client_thread.mta_cookie); + CloseHandle(client_thread.hthread); + memset(&client_thread, 0, sizeof(client_thread)); + } + LeaveCriticalSection(&client_thread_cs); +} + static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESULT lr) { IGlobalInterfaceTable *git; @@ -781,7 +942,10 @@ static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESU
hr = ObjectFromLresult(lr, &IID_IWineUiaNode, 0, (void **)&nested_node); if (FAILED(hr)) + { + uia_stop_client_thread(); return hr; + }
prov = heap_alloc_zero(sizeof(*prov)); if (!prov) @@ -809,20 +973,30 @@ exit: 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 + * COM for this reason. + */ 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;
- lr = SendMessageW(node->hwnd, WM_GETOBJECT, 0, UiaRootObjectId); - if (!lr) + args.node = node; + args.lr = SendMessageW(node->hwnd, WM_GETOBJECT, 0, UiaRootObjectId); + if (!args.lr) { if (!IsWindow(node->hwnd) && GetLastError() == ERROR_INVALID_WINDOW_HANDLE) return UIA_E_ELEMENTNOTAVAILABLE; FIXME("No native UIA provider for hwnd %p, MSAA proxy currently unimplemented.\n", node->hwnd); + uia_stop_client_thread(); return E_NOTIMPL; }
- return create_wine_uia_nested_node_provider(node, lr); + return SendMessageW(client_thread.hwnd, WM_UIA_CLIENT_GET_NODE_PROV, 0, (LPARAM)&args); }
/***********************************************************************
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 693 +++++++++++++++++++++ 1 file changed, 693 insertions(+)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 9af859f1a7a..ff711ccb325 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -1106,6 +1106,7 @@ static struct Provider BOOL ret_invalid_prop_type; DWORD expected_tid; int runtime_id[2]; + DWORD last_call_tid; } Provider, Provider2, Provider_child, Provider_child2;
static const WCHAR *uia_bstr_prop_str = L"uia-string"; @@ -1413,6 +1414,35 @@ static void check_node_provider_desc_prefix_(BSTR prov_desc, DWORD pid, HWND pro ok_(file, line)(prov_hwnd == prov_id, "Unexpected hwnd %p\n", prov_hwnd); }
+/* + * For node providers that come from an HWND belonging to another process + * or another thread, the provider is considered 'nested', a node in a node. + */ +static BOOL get_nested_provider_desc(BSTR prov_desc, const WCHAR *prov_type, BOOL parent_link, WCHAR *out_desc) +{ + const WCHAR *str, *str2; + WCHAR buf[1024]; + + if (!parent_link) + wsprintfW(buf, L"%s:Nested ", prov_type); + else + wsprintfW(buf, L"%s(parent link):Nested ", prov_type); + str = wcsstr(prov_desc, buf); + /* Check with and without parent-link. */ + if (!str) + return FALSE; + + if (!out_desc) + return TRUE; + + str += wcslen(buf); + str2 = wcschr(str, L']'); + /* We want to include the ']' character, so + 2. */ + lstrcpynW(out_desc, str, ((str2 - str) + 2)); + + return TRUE; +} + #define check_runtime_id( exp_runtime_id, exp_size, runtime_id ) \ check_runtime_id_( (exp_runtime_id), (exp_size), (runtime_id), __FILE__, __LINE__) static void check_runtime_id_(int *exp_runtime_id, int exp_size, SAFEARRAY *runtime_id, const char *file, int line) @@ -1486,6 +1516,7 @@ HRESULT WINAPI ProviderSimple_get_ProviderOptions(IRawElementProviderSimple *ifa add_method_call(This, PROV_GET_PROVIDER_OPTIONS); if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); + This->last_call_tid = GetCurrentThreadId();
*ret_val = 0; if (This->prov_opts) @@ -1512,6 +1543,7 @@ HRESULT WINAPI ProviderSimple_GetPropertyValue(IRawElementProviderSimple *iface, add_method_call(This, PROV_GET_PROPERTY_VALUE); if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); + This->last_call_tid = GetCurrentThreadId();
VariantInit(ret_val); switch (prop_id) @@ -1701,6 +1733,7 @@ HRESULT WINAPI ProviderSimple_get_HostRawElementProvider(IRawElementProviderSimp add_method_call(This, PROV_GET_HOST_RAW_ELEMENT_PROVIDER); if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); + This->last_call_tid = GetCurrentThreadId();
*ret_val = NULL; if (This->hwnd) @@ -1751,6 +1784,7 @@ static HRESULT WINAPI ProviderFragment_Navigate(IRawElementProviderFragment *ifa add_method_call(This, FRAG_NAVIGATE); if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); + This->last_call_tid = GetCurrentThreadId();
*ret_val = NULL; if ((direction == NavigateDirection_Parent) && This->parent) @@ -1769,6 +1803,7 @@ static HRESULT WINAPI ProviderFragment_GetRuntimeId(IRawElementProviderFragment add_method_call(This, FRAG_GET_RUNTIME_ID); if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); + This->last_call_tid = GetCurrentThreadId();
*ret_val = NULL; if (This->runtime_id[0] || This->runtime_id[1]) @@ -1817,6 +1852,7 @@ static HRESULT WINAPI ProviderFragment_get_FragmentRoot(IRawElementProviderFragm add_method_call(This, FRAG_GET_FRAGMENT_ROOT); if (This->expected_tid) ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId()); + This->last_call_tid = GetCurrentThreadId();
*ret_val = NULL; if (This->frag_root) @@ -4924,11 +4960,655 @@ static void test_UiaHUiaNodeFromVariant(void) #endif }
+static const struct prov_method_sequence node_from_hwnd1[] = { + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + { 0 } +}; + +static const struct prov_method_sequence node_from_hwnd2[] = { + { &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 }, + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + /* Windows 10+ calls this. */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_OPTIONAL }, + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 } +}; + +static const struct prov_method_sequence node_from_hwnd3[] = { + { &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 }, + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 } +}; + +static const struct prov_method_sequence node_from_hwnd4[] = { + { &Provider, PROV_GET_PROPERTY_VALUE }, + { &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 */ + /* Only called on Windows versions past Win10v1507. */ + { &Provider_child, PROV_GET_PROVIDER_OPTIONS, METHOD_OPTIONAL }, + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ControlTypePropertyId */ + { 0 } +}; + +static const struct prov_method_sequence node_from_hwnd5[] = { + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_LabeledByPropertyId */ + { &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 }, + /* Only done in Windows 8+. */ + { &Provider_child, FRAG_GET_RUNTIME_ID, METHOD_OPTIONAL }, + { &Provider_child, FRAG_GET_FRAGMENT_ROOT, METHOD_OPTIONAL }, + /* These two are only done on Windows 7. */ + { &Provider_child, PROV_GET_PROVIDER_OPTIONS, METHOD_OPTIONAL }, + { &Provider_child, FRAG_NAVIGATE, METHOD_OPTIONAL }, /* NavigateDirection_Parent */ + { 0 } +}; + +static const struct prov_method_sequence node_from_hwnd6[] = { + { &Provider, PROV_GET_PROPERTY_VALUE }, + { &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 }, + /* Next 4 are only done in Windows 8+. */ + { &Provider_child, FRAG_GET_RUNTIME_ID, METHOD_OPTIONAL }, + { &Provider_child, FRAG_GET_FRAGMENT_ROOT, METHOD_OPTIONAL }, + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER, METHOD_OPTIONAL }, + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_OPTIONAL }, + { &Provider_child, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + /* Next two are only done on Win10v1809+. */ + { &Provider_child, FRAG_GET_FRAGMENT_ROOT, METHOD_OPTIONAL }, + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER, METHOD_OPTIONAL }, + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_ProviderDescriptionPropertyId */ + /* Next two are only done on Win10v1809+. */ + { &Provider_child, FRAG_GET_FRAGMENT_ROOT, METHOD_OPTIONAL }, + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER, METHOD_OPTIONAL }, + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ControlTypePropertyId */ + { 0 } +}; + +static const struct prov_method_sequence node_from_hwnd7[] = { + { &Provider, PROV_GET_PROPERTY_VALUE }, + { &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, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider_child, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider_child, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER, METHOD_TODO }, + { &Provider, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ProviderDescriptionPropertyId */ + { 0 } +}; + +static const struct prov_method_sequence node_from_hwnd8[] = { + { &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 }, + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_ProviderDescriptionPropertyId */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_TODO }, /* UIA_ControlTypePropertyId */ + { 0 } +}; + +static void test_UiaNodeFromHandle_client_proc(void) +{ + APTTYPEQUALIFIER apt_qualifier; + APTTYPE apt_type; + WCHAR buf[2048]; + HUIANODE node; + HRESULT hr; + HWND hwnd; + VARIANT v; + + hwnd = FindWindowA("UiaNodeFromHandle class", "Test window"); + + hr = CoGetApartmentType(&apt_type, &apt_qualifier); + ok(hr == CO_E_NOTINITIALIZED, "Unexpected hr %#lx\n", hr); + + hr = UiaNodeFromHandle(hwnd, &node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = UiaGetPropertyValue(node, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + DWORD pid; + + memset(buf, 0, sizeof(buf)); + GetWindowThreadProcessId(hwnd, &pid); + + 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, pid, 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); + } + + hr = UiaGetPropertyValue(node, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(V_VT(&v) == VT_I4, "V_VT(&v) = %d\n", V_VT(&v)); + ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); + + /* + * On Windows 8 and above, if a node contains a nested provider, the + * process will be in an implicit MTA until the node is released. + */ + hr = CoGetApartmentType(&apt_type, &apt_qualifier); + ok(hr == S_OK || broken(hr == CO_E_NOTINITIALIZED), "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + ok(apt_type == APTTYPE_MTA, "Unexpected apt_type %#x\n", apt_type); + ok(apt_qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA, "Unexpected apt_qualifier %#x\n", apt_qualifier); + } + + UiaNodeRelease(node); + + /* Node released, we're out of the implicit MTA. */ + hr = CoGetApartmentType(&apt_type, &apt_qualifier); + + /* Windows 10v1709-1809 are stuck in an implicit MTA. */ + ok(hr == CO_E_NOTINITIALIZED || broken(hr == S_OK), "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + ok(apt_type == APTTYPE_MTA, "Unexpected apt_type %#x\n", apt_type); + ok(apt_qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA, "Unexpected apt_qualifier %#x\n", apt_qualifier); + } +} + +static DWORD WINAPI uia_node_from_handle_test_thread(LPVOID param) +{ + HWND hwnd = (HWND)param; + HUIANODE node, node2; + WCHAR buf[2048]; + HRESULT hr; + VARIANT v; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + /* Only sent on Win7. */ + SET_EXPECT(winproc_GETOBJECT_CLIENT); + 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); + called_winproc_GETOBJECT_CLIENT = expect_winproc_GETOBJECT_CLIENT = 0; + + 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); + + /* + * On Windows, nested providers are always called on a separate thread if + * UseComThreading isn't set. Since we're doing COM marshaling, if we're + * currently in an MTA, we just call the nested provider from the current + * thread. + */ + todo_wine ok(Provider.last_call_tid != GetCurrentThreadId() && + Provider.last_call_tid != GetWindowThreadProcessId(hwnd, NULL), "Expected method call on separate thread\n"); + + /* + * Elements returned from nested providers have to be able to get a + * RuntimeId, or else we'll get E_FAIL on Win8+. + */ + Provider_child.prov_opts = ProviderOptions_ServerSideProvider; + Provider_child.expected_tid = Provider.expected_tid; + Provider_child.runtime_id[0] = UiaAppendRuntimeId; + 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); + 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"); + + /* 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); + + 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. + */ + 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 + * added to them. + */ + Provider_child.hwnd = hwnd; + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + /* 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); + 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); + + 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)); + + 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); + } + + 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); + } + + ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + /* Win10v1809 can be slow to call Release on Provider. */ + if (Provider.ref != 1) + Sleep(50); + ok(Provider.ref == 1, "Unexpected refcnt %ld\n", Provider.ref); + + CoUninitialize(); + + return 0; +} + +static void test_UiaNodeFromHandle(const char *name) +{ + APTTYPEQUALIFIER apt_qualifier; + PROCESS_INFORMATION proc; + char cmdline[MAX_PATH]; + STARTUPINFOA startup; + HUIANODE node, node2; + APTTYPE apt_type; + DWORD exit_code; + WNDCLASSA cls; + HANDLE thread; + HRESULT hr; + HWND hwnd; + VARIANT v; + + cls.style = 0; + cls.lpfnWndProc = test_wnd_proc; + cls.cbClsExtra = 0; + cls.cbWndExtra = 0; + cls.hInstance = GetModuleHandleA(NULL); + cls.hIcon = 0; + cls.hCursor = NULL; + cls.hbrBackground = NULL; + cls.lpszMenuName = NULL; + cls.lpszClassName = "UiaNodeFromHandle class"; + RegisterClassA(&cls); + + hwnd = CreateWindowA("UiaNodeFromHandle class", "Test window", WS_OVERLAPPEDWINDOW, + 0, 0, 100, 100, NULL, NULL, NULL, NULL); + + hr = UiaNodeFromHandle(hwnd, NULL); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + hr = UiaNodeFromHandle(NULL, &node); + ok(hr == UIA_E_ELEMENTNOTAVAILABLE, "Unexpected hr %#lx.\n", hr); + + /* COM uninitialized, no provider returned by UiaReturnRawElementProvider. */ + prov_root = NULL; + node = (void *)0xdeadbeef; + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + /* Only sent twice on Win7. */ + SET_EXPECT_MULTI(winproc_GETOBJECT_CLIENT, 2); + hr = UiaNodeFromHandle(hwnd, &node); + todo_wine ok(hr == E_FAIL, "Unexpected hr %#lx.\n", hr); + CHECK_CALLED(winproc_GETOBJECT_UiaRoot); + todo_wine CHECK_CALLED(winproc_GETOBJECT_CLIENT); + + /* + * COM initialized, no provider returned by UiaReturnRawElementProvider. + * In this case, we get a default MSAA proxy. + */ + CoInitializeEx(NULL, COINIT_MULTITHREADED); + prov_root = NULL; + node = (void *)0xdeadbeef; + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + SET_EXPECT_MULTI(winproc_GETOBJECT_CLIENT, 2); + hr = UiaNodeFromHandle(hwnd, &node); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + 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); + check_node_provider_desc(V_BSTR(&v), L"Annotation", NULL, FALSE); + check_node_provider_desc(V_BSTR(&v), L"Main", NULL, 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); + } + + todo_wine ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + + /* + * COM initialized, but provider passed into UiaReturnRawElementProvider + * returns a failure code on get_ProviderOptions. Same behavior as before. + */ + Provider.prov_opts = 0; + Provider.hwnd = hwnd; + prov_root = &Provider.IRawElementProviderSimple_iface; + node = (void *)0xdeadbeef; + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + SET_EXPECT_MULTI(winproc_GETOBJECT_CLIENT, 2); + hr = UiaNodeFromHandle(hwnd, &node); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + 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); + check_node_provider_desc(V_BSTR(&v), L"Annotation", NULL, FALSE); + check_node_provider_desc(V_BSTR(&v), L"Main", NULL, 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_hwnd1, "node_from_hwnd1"); + + todo_wine ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + CoUninitialize(); + + /* + * COM uninitialized, return a Provider from UiaReturnRawElementProvider + * with ProviderOptions_ServerSideProvider. + */ + Provider.prov_opts = ProviderOptions_ServerSideProvider; + Provider.hwnd = hwnd; + prov_root = &Provider.IRawElementProviderSimple_iface; + node = (void *)0xdeadbeef; + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + /* Only sent on Win7. */ + SET_EXPECT(winproc_GETOBJECT_CLIENT); + ok(Provider.ref == 1, "Unexpected refcnt %ld\n", Provider.ref); + 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); + called_winproc_GETOBJECT_CLIENT = expect_winproc_GETOBJECT_CLIENT = 0; + + 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); + 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_hwnd2, "node_from_hwnd2"); + + /* + * This is relevant too: Since we don't get a 'nested' node, all calls + * will occur on the current thread. + */ + Provider.expected_tid = GetCurrentThreadId(); + hr = UiaGetPropertyValue(node, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + check_uia_prop_val(UIA_ControlTypePropertyId, UIAutomationType_Int, &v); + + /* UIAutomationType_Element properties will return a normal node. */ + 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); + + 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); + } + + 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"); + } + + Provider.expected_tid = Provider_child.expected_tid = 0; + ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + + /* + * On Windows 8 and above, after the first successful call to + * UiaReturnRawElementProvider the process ends up in an implicit MTA + * until the process exits. + */ + hr = CoGetApartmentType(&apt_type, &apt_qualifier); + /* Wine's provider thread doesn't always terminate immediately. */ + if (hr == S_OK && !strcmp(winetest_platform, "wine")) + { + Sleep(10); + hr = CoGetApartmentType(&apt_type, &apt_qualifier); + } + todo_wine ok(hr == S_OK || broken(hr == CO_E_NOTINITIALIZED), "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + ok(apt_type == APTTYPE_MTA, "Unexpected apt_type %#x\n", apt_type); + ok(apt_qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA, "Unexpected apt_qualifier %#x\n", apt_qualifier); + } + + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + thread = CreateThread(NULL, 0, uia_node_from_handle_test_thread, (void *)hwnd, 0, NULL); + while (MsgWaitForMultipleObjects(1, &thread, FALSE, INFINITE, QS_ALLINPUT) != WAIT_OBJECT_0) + { + MSG msg; + while(PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + CloseHandle(thread); + + /* Test behavior from separate process. */ + Provider.prov_opts = ProviderOptions_ServerSideProvider; + Provider.hwnd = hwnd; + prov_root = &Provider.IRawElementProviderSimple_iface; + sprintf(cmdline, ""%s" uiautomation UiaNodeFromHandle_client_proc", name); + memset(&startup, 0, sizeof(startup)); + startup.cb = sizeof(startup); + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + /* Only sent on Win7. */ + SET_EXPECT(winproc_GETOBJECT_CLIENT); + CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &proc); + while (MsgWaitForMultipleObjects(1, &proc.hProcess, FALSE, INFINITE, QS_ALLINPUT) != WAIT_OBJECT_0) + { + MSG msg; + while(PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + + GetExitCodeProcess(proc.hProcess, &exit_code); + if (exit_code > 255) + ok(0, "unhandled exception %08x in child process %04x\n", (UINT)exit_code, (UINT)GetProcessId(proc.hProcess)); + else if (exit_code) + ok(0, "%u failures in child process\n", (UINT)exit_code); + + CloseHandle(proc.hProcess); + CHECK_CALLED(winproc_GETOBJECT_UiaRoot); + called_winproc_GETOBJECT_CLIENT = expect_winproc_GETOBJECT_CLIENT = 0; + ok_method_sequence(node_from_hwnd8, "node_from_hwnd8"); + + CoUninitialize(); + + DestroyWindow(hwnd); + UnregisterClassA("UiaNodeFromHandle class", NULL); + prov_root = NULL; +} + +/* + * Once a process returns a UI Automation provider with + * UiaReturnRawElementProvider it ends up in an implicit MTA until exit. This + * messes with tests around COM initialization, so we run these tests in + * separate processes. + */ +static void launch_test_process(const char *name, const char *test_name) +{ + PROCESS_INFORMATION proc; + STARTUPINFOA startup; + char cmdline[MAX_PATH]; + + sprintf(cmdline, ""%s" uiautomation %s", name, test_name); + memset(&startup, 0, sizeof(startup)); + startup.cb = sizeof(startup); + CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &proc); + wait_child_process(proc.hProcess); +} + START_TEST(uiautomation) { HMODULE uia_dll = LoadLibraryA("uiautomationcore.dll"); BOOL (WINAPI *pImmDisableIME)(DWORD); HMODULE hModuleImm32; + char **argv; + int argc;
/* Make sure COM isn't initialized by imm32. */ hModuleImm32 = LoadLibraryA("imm32.dll"); @@ -4940,6 +5620,18 @@ START_TEST(uiautomation) pImmDisableIME = NULL; FreeLibrary(hModuleImm32);
+ argc = winetest_get_mainargs(&argv); + if (argc == 3) + { + if (!strcmp(argv[2], "UiaNodeFromHandle")) + test_UiaNodeFromHandle(argv[0]); + else if (!strcmp(argv[2], "UiaNodeFromHandle_client_proc")) + test_UiaNodeFromHandle_client_proc(); + + FreeLibrary(uia_dll); + return; + } + test_UiaHostProviderFromHwnd(); test_uia_reserved_value_ifaces(); test_UiaLookupId(); @@ -4947,6 +5639,7 @@ START_TEST(uiautomation) test_UiaGetPropertyValue(); test_UiaGetRuntimeId(); test_UiaHUiaNodeFromVariant(); + launch_test_process(argv[0], "UiaNodeFromHandle"); if (uia_dll) { pUiaProviderFromIAccessible = (void *)GetProcAddress(uia_dll, "UiaProviderFromIAccessible");
On Wed Sep 14 14:50:22 2022 +0000, Esme Povirk wrote:
You could also use something like SendMessageCallback, although maybe that approach would end up unnecessarily complicated.
I've changed things now to only block on the thread calling `UiaNodeFromHandle`, so the scenario you're describing shouldn't be an issue now.
argh, somehow squashing one of the commits broke the MR, it says I've added more commits than I actually have. I think I'll just close this MR and open a new one.
This merge request was closed by Connor McAdams.