Changes since prior MR: - Check if HWND is valid after sending `WM_GETOBJECT` and getting a 0 return value. - Block on thread calling `UiaNodeFromHandle()` rather than the marshaling thread. - Squash `IWineUiaNode::get_prop_val` commit into commit where it is actually used.
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");
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_provider.c:
- 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))
I don't think there's a need to check IsWindow here? If CreateWindow fails, it should return NULL.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_provider.c:
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))
Same thing here, I don't see the need to call IsWindow.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_client.c:
&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)
Unsure what the best way to handle this is. AFAICT SendMessage will set the last error to ERROR_INVALID_WINDOW_HANDLE if the window doesn't exist, but it may not set the last error if it succeeds and returns 0. Thus you would either have to call IsWindow or set last error beforehand.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_client.c:
- *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)
When can node->prov be NULL while hr is successful? It doesn't seem right to give the caller a NULL pointer with a successful hr.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_client.c:
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 {
You do realize messages have 2 pointer-sized arguments you can use, right?
On Wed Sep 14 21:56:30 2022 +0000, Esme Povirk wrote:
I don't think there's a need to check IsWindow here? If CreateWindow fails, it should return NULL.
Hm fair, I guess I've gotten into the habit of using `IsWindow()` instead of null checks universally, but a null check here is probably good enough.
On Wed Sep 14 21:56:31 2022 +0000, Esme Povirk wrote:
Unsure what the best way to handle this is. AFAICT SendMessage will set the last error to ERROR_INVALID_WINDOW_HANDLE if the window doesn't exist, but it may not set the last error if it succeeds and returns 0. Thus you would either have to call IsWindow or set last error beforehand.
Ah, didn't know about doing `SetLastError`. I'll probably do that then, and then check if it's `ERROR_INVALID_WINDOW_HANDLE` after the call.
On Wed Sep 14 21:56:32 2022 +0000, Esme Povirk wrote:
When can node->prov be NULL while hr is successful? It doesn't seem right to give the caller a NULL pointer with a successful hr.
This seems to be something that got leftover when splitting patches from later ones, I'll remove it until it's necessary.
On Wed Sep 14 21:56:32 2022 +0000, Esme Povirk wrote:
You do realize messages have 2 pointer-sized arguments you can use, right?
Yeah, I use wparam in a later patch that isn't part of this MR, but I guess I should probably not use a structure until I introduce that argument.