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.
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 | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+)
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..6d1a641dafc 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)
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_client.c | 107 +++++++++++++++++++- dlls/uiautomationcore/uiautomationcore.spec | 2 +- include/uiautomationcoreapi.h | 1 + 3 files changed, 108 insertions(+), 2 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 6d1a641dafc..d3a09c90267 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -398,6 +398,7 @@ struct uia_provider { LONG ref;
IRawElementProviderSimple *elprov; + IWineUiaNode *node; };
static inline struct uia_provider *impl_from_IWineUiaProvider(IWineUiaProvider *iface) @@ -434,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); }
@@ -646,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: @@ -756,6 +772,95 @@ 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) + { + 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 | 185 +++++++++++++++++++++++++++-- 1 file changed, 176 insertions(+), 9 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index d3a09c90267..a0d7e06feec 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,167 @@ 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; +}; + +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_GET_UIA_PROV_FROM_HWND (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_GET_UIA_PROV_FROM_HWND: + { + struct uia_node *node = (struct uia_node *)lparam; + LRESULT lr; + + lr = SendMessageW(node->hwnd, WM_GETOBJECT, 0, UiaRootObjectId); + if (!lr) + { + 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); + } + + 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 +946,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,18 +977,17 @@ 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; - - lr = SendMessageW(node->hwnd, WM_GETOBJECT, 0, UiaRootObjectId); - if (!lr) - { - FIXME("No native UIA provider for hwnd %p, MSAA proxy currently unimplemented.\n", node->hwnd); - return E_NOTIMPL; - } + if (!uia_start_client_thread()) + return E_FAIL;
- return create_wine_uia_nested_node_provider(node, lr); + return SendMessageW(client_thread.hwnd, WM_GET_UIA_PROV_FROM_HWND, 0, (LPARAM)node); }
/***********************************************************************
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_classes.idl:
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);
This seems to be unused in the patch that introduces it?
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_client.c:
+/***********************************************************************
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))
Seems like a potential time-of-check to time-of-use issue.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_client.c:
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);
Is it OK to do this while client_thread may still be processing messages?
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_client.c:
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.
Wouldn't this cause hangs in a single remote application to block requests from all threads? On Windows, I had to have Xalia use multiple request threads to account for slow applications, and I don't think that will work with this implementation.
On Tue Sep 13 22:11:36 2022 +0000, Esme Povirk wrote:
This seems to be unused in the patch that introduces it?
Correct. I was trying to split things up to make it easier for review. I can put them back together if it isn't a big deal.
On Tue Sep 13 22:11:37 2022 +0000, Esme Povirk wrote:
Seems like a potential time-of-check to time-of-use issue.
Hm... do you mean it should also be checked further down the line? I feel like this check makes sense here, given you shouldn't try to get an HUIANODE for an HWND that doesn't exist. It seems to match Windows behavior as well.
On Tue Sep 13 22:11:38 2022 +0000, Esme Povirk wrote:
Is it OK to do this while client_thread may still be processing messages?
Do you mean exiting the MTA? As far as I understand, there shouldn't be any issues with it. MTAs don't operate with Window messages.
On Tue Sep 13 22:11:38 2022 +0000, Esme Povirk wrote:
Wouldn't this cause hangs in a single remote application to block requests from all threads? On Windows, I had to have Xalia use multiple request threads to account for slow applications, and I don't think that will work with this implementation.
Hm... I'm not sure I understand what you're asking. We're passing the node to an MTA to be marshaled, which for remote applications means that all calls to the COM interface are done on threadpool threads. There shouldn't be any blocking in this case.
On Wed Sep 14 12:49:45 2022 +0000, Connor McAdams wrote:
Hm... do you mean it should also be checked further down the line? I feel like this check makes sense here, given you shouldn't try to get an HUIANODE for an HWND that doesn't exist. It seems to match Windows behavior as well.
If possible, it should be checked by using it. In this case, I think SendMessage would fail with an error indicating an invalid handle.
On Wed Sep 14 12:50:56 2022 +0000, Connor McAdams wrote:
Do you mean exiting the MTA? As far as I understand, there shouldn't be any issues with it. MTAs don't operate with Window messages.
Yes but if it still has WM_GET_UIA_PROV_FROM_HWND messages in the queue, or is processing one, the thread may do COM things.
On Wed Sep 14 12:52:51 2022 +0000, Connor McAdams wrote:
Hm... I'm not sure I understand what you're asking. We're passing the node to an MTA to be marshaled, which for remote applications means that all calls to the COM interface are done on threadpool threads. There shouldn't be any blocking in this case.
SendMessage blocks.
On Wed Sep 14 14:30:53 2022 +0000, Esme Povirk wrote:
Yes but if it still has WM_GET_UIA_PROV_FROM_HWND messages in the queue, or is processing one, the thread may do COM things.
Any `WM_GET_UIA_PROV_FROM_HWND` messages are accompanied by the thread's refcnt being incremented, and it will only end up decremented once a node retrieved from the thread is released (which means we don't need to do anymore COM things), or it fails to get a node. So we shouldn't worry about that. We shouldn't ever be in a situation where we're still processing a `WM_GET_UIA_PROV_FROM_HWND` and the thread refcnt is 0.
On Wed Sep 14 14:28:07 2022 +0000, Esme Povirk wrote:
If possible, it should be checked by using it. In this case, I think SendMessage would fail with an error indicating an invalid handle.
My thinking here was to try to avoid doing any work around allocating/starting the client thread if we know that the HWND passed in is invalid early on. But if that's an unnecessary optimization, I can change it to a check of SendMessage.
On Wed Sep 14 14:43:26 2022 +0000, Connor McAdams wrote:
My thinking here was to try to avoid doing any work around allocating/starting the client thread if we know that the HWND passed in is invalid early on. But if that's an unnecessary optimization, I can change it to a check of SendMessage.
I think that's OK but the result of SendMessage still needs to be checked in case the window is destroyed after the initial check.
On Wed Sep 14 14:31:22 2022 +0000, Esme Povirk wrote:
SendMessage blocks.
Hm... I think I can see a scenario where this is an issue. Maybe we should send WM_GETOBJECT from the thread calling UiaNodeFromHandle, and then pass the LRESULT if it's valid to the client thread to be unmarshaled. That'd also make the check for the HWND being valid with SendMessage less resource intensive
On Wed Sep 14 14:46:56 2022 +0000, Connor McAdams wrote:
Hm... I think I can see a scenario where this is an issue. Maybe we should send WM_GETOBJECT from the thread calling UiaNodeFromHandle, and then pass the LRESULT if it's valid to the client thread to be unmarshaled. That'd also make the check for the HWND being valid with SendMessage less resource intensive
You could also use something like SendMessageCallback, although maybe that approach would end up unnecessarily complicated.