-- v3: uiautomationcore: Implement UiaDisconnectProvider. uiautomationcore: Implement UiaNodeFromHandle. uiautomationcore: Implement UiaReturnRawElementProvider.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_classes.idl | 1 + dlls/uiautomationcore/uia_client.c | 93 ++++++++--- dlls/uiautomationcore/uia_main.c | 33 ++-- dlls/uiautomationcore/uia_private.h | 23 +++ dlls/uiautomationcore/uia_provider.c | 220 ++++++++++++++++++++++++++ 5 files changed, 336 insertions(+), 34 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_client.c b/dlls/uiautomationcore/uia_client.c index 0ff01eba5af..deb8dd0d7af 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -64,8 +64,32 @@ static void clear_uia_node_ptr_safearray(SAFEARRAY *sa, LONG elems) } }
-static void create_uia_node_safearray(VARIANT *in, VARIANT *out) +static void clear_uia_node_lresult_safearray(SAFEARRAY *sa, LONG elems) { + IWineUiaNode *node; + LRESULT lr; + HRESULT hr; + LONG i; + + for (i = 0; i < elems; i++) + { + hr = SafeArrayGetElement(sa, &i, &lr); + if (FAILED(hr)) + break; + if (!lr) + continue; + + hr = ObjectFromLresult(lr, &IID_IWineUiaNode, 0, (void **)&node); + if (FAILED(hr)) + break; + + IWineUiaNode_Release(node); + } +} + +static void create_uia_node_safearray(VARIANT *in, VARIANT *out, BOOL out_nested) +{ + const VARTYPE out_vt = out_nested ? VT_I4 : VT_UINT_PTR; LONG i, idx, lbound, elems; HUIANODE node; SAFEARRAY *sa; @@ -74,7 +98,7 @@ static void create_uia_node_safearray(VARIANT *in, VARIANT *out) if (FAILED(get_safearray_bounds(V_ARRAY(in), &lbound, &elems))) return;
- if (!(sa = SafeArrayCreateVector(VT_UINT_PTR, 0, elems))) + if (!(sa = SafeArrayCreateVector(out_vt, 0, elems))) return;
for (i = 0; i < elems; i++) @@ -97,19 +121,36 @@ static void create_uia_node_safearray(VARIANT *in, VARIANT *out) break;
IRawElementProviderSimple_Release(elprov); - hr = SafeArrayPutElement(sa, &i, &node); + if (out_nested) + { + LRESULT lr; + + lr = uia_lresult_from_node(node); + if (!lr) + { + hr = E_FAIL; + break; + } + + hr = SafeArrayPutElement(sa, &i, &lr); + } + else + hr = SafeArrayPutElement(sa, &i, &node); if (FAILED(hr)) break; }
if (FAILED(hr)) { - clear_uia_node_ptr_safearray(sa, elems); + if (out_nested) + clear_uia_node_lresult_safearray(sa, elems); + else + clear_uia_node_ptr_safearray(sa, elems); SafeArrayDestroy(sa); return; }
- V_VT(out) = VT_UINT_PTR | VT_ARRAY; + V_VT(out) = out_vt | VT_ARRAY; V_ARRAY(out) = sa; }
@@ -269,21 +310,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 +354,8 @@ static ULONG WINAPI uia_node_Release(IWineUiaNode *iface) }
IWineUiaProvider_Release(node->prov); + if (node->nested_node) + uia_stop_provider_thread(); heap_free(node); }
@@ -389,6 +417,7 @@ struct uia_provider { LONG ref;
IRawElementProviderSimple *elprov; + struct uia_node *node; };
static inline struct uia_provider *impl_from_IWineUiaProvider(IWineUiaProvider *iface) @@ -529,9 +558,23 @@ static HRESULT uia_provider_get_elem_prop_val(struct uia_provider *prov, hr = UiaNodeFromProvider(elprov, &node); if (SUCCEEDED(hr)) { - get_variant_for_node(node, ret_val); + if (prov->node->nested_node) + { + LRESULT lr = uia_lresult_from_node(node); + + if (lr) + { + V_VT(ret_val) = VT_I4; + V_I4(ret_val) = lr; + } + } + else + get_variant_for_node(node, ret_val); + VariantClear(&v); IRawElementProviderSimple_Release(elprov); + if (prov->node->nested_node && V_VT(ret_val) != VT_I4) + return E_FAIL; } break; } @@ -542,9 +585,10 @@ static HRESULT uia_provider_get_elem_prop_val(struct uia_provider *prov, WARN("Invalid vt %d for UIAutomationType_ElementArray\n", V_VT(&v)); goto exit; } - create_uia_node_safearray(&v, ret_val); - if (V_VT(ret_val) == (VT_UINT_PTR | VT_ARRAY)) - VariantClear(&v); + create_uia_node_safearray(&v, ret_val, prov->node->nested_node); + VariantClear(&v); + if (prov->node->nested_node && V_VT(ret_val) == VT_EMPTY) + return E_FAIL; break;
default: @@ -680,6 +724,7 @@ static HRESULT create_wine_uia_provider(struct uia_node *node, IRawElementProvid prov->IWineUiaProvider_iface.lpVtbl = &uia_provider_vtbl; prov->elprov = elprov; prov->ref = 1; + prov->node = node; node->prov = &prov->IWineUiaProvider_iface; node->hwnd = get_hwnd_from_provider(elprov);
diff --git a/dlls/uiautomationcore/uia_main.c b/dlls/uiautomationcore/uia_main.c index 3a6f10c1647..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; @@ -297,16 +299,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.@) */ @@ -359,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..5dc254ec195 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -21,10 +21,33 @@ #include "uiautomation.h" #include "uia_classes.h"
+extern HMODULE huia_module DECLSPEC_HIDDEN; + enum uia_prop_type { PROP_TYPE_UNKNOWN, PROP_TYPE_ELEM_PROP, PROP_TYPE_SPECIAL, };
+struct uia_node { + IWineUiaNode IWineUiaNode_iface; + LONG ref; + + IWineUiaProvider *prov; + DWORD git_cookie; + + HWND hwnd; + BOOL nested_node; +}; + +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 */ +LRESULT uia_lresult_from_node(HUIANODE huianode) DECLSPEC_HIDDEN; +void uia_stop_provider_thread(void) DECLSPEC_HIDDEN; diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c index 9c2dfd65989..0f46a7a4429 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -1124,3 +1124,223 @@ 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; + LONG ref; +}; + +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) +#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) +{ + switch (msg) + { + case WM_GET_OBJECT_UIA_NODE: + { + HUIANODE node = (HUIANODE)lparam; + struct uia_node *node_data; + SAFEARRAY *sa; + LRESULT lr; + HRESULT hr; + + hr = UiaGetRuntimeId(node, &sa); + if (FAILED(hr)) + { + WARN("Failed to get runtime ID for node %p\n", node); + UiaNodeRelease(node); + uia_stop_provider_thread(); + return 0; + } + + SafeArrayDestroy(sa); + lr = LresultFromObject(&IID_IWineUiaNode, 0, (IUnknown *)node); + + /* + * LresultFromObject returns an index into the global atom string table, + * which has a valid range of 0xc000-0xffff. + */ + if ((lr > 0xffff) || (lr < 0xc000)) + { + WARN("Got invalid lresult %Ix\n", lr); + uia_stop_provider_thread(); + lr = 0; + } + else + { + node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); + node_data->nested_node = TRUE; + } + + /* + * LresultFromObject increases refcnt by 1. If LresultFromObject + * failed, this is expected to release the node. + */ + UiaNodeRelease(node); + return lr; + } + + case WM_UIA_PROVIDER_THREAD_STOP: + DestroyWindow(hwnd); + break; + + 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(); + FreeLibraryAndExitThread(huia_module, 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)) + { + if (msg.message == WM_UIA_PROVIDER_THREAD_STOP) + break; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + TRACE("Shutting down UI Automation provider thread.\n"); + + DestroyWindow(hwnd); + CoUninitialize(); + FreeLibraryAndExitThread(huia_module, 0); +} + +static BOOL uia_start_provider_thread(void) +{ + BOOL started = TRUE; + + EnterCriticalSection(&provider_thread_cs); + if (++provider_thread.ref == 1) + { + HANDLE ready_event; + HANDLE events[2]; + HMODULE hmodule; + DWORD wait_obj; + + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (const WCHAR *)uia_start_provider_thread, &hmodule); + + ready_event = CreateEventW(NULL, FALSE, FALSE, NULL); + if (!(provider_thread.hthread = CreateThread(NULL, 0, uia_provider_thread_proc, + ready_event, 0, NULL))) + { + WARN("Failed to create provider thread.\n"); + CloseHandle(ready_event); + FreeLibrary(huia_module); + memset(&provider_thread, 0, sizeof(provider_thread)); + LeaveCriticalSection(&provider_thread_cs); + return FALSE; + } + + events[0] = ready_event; + events[1] = provider_thread.hthread; + wait_obj = WaitForMultipleObjects(2, events, FALSE, INFINITE); + if (wait_obj != WAIT_OBJECT_0) + { + WARN("Failed to start provider thread\n"); + CloseHandle(provider_thread.hthread); + memset(&provider_thread, 0, sizeof(provider_thread)); + started = FALSE; + } + + CloseHandle(ready_event); + } + + LeaveCriticalSection(&provider_thread_cs); + 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); +} + +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); + UiaNodeRelease(node); + return 0; + } + + return uia_lresult_from_node(node); +}
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 745 ++++++++++++++++++++ dlls/uiautomationcore/uia_classes.idl | 1 + dlls/uiautomationcore/uia_client.c | 480 ++++++++++++- dlls/uiautomationcore/uiautomationcore.spec | 2 +- include/uiautomationcoreapi.h | 1 + 5 files changed, 1210 insertions(+), 19 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 9af859f1a7a..4431455eb8a 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,707 @@ 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, PROV_GET_PROPERTY_VALUE }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + /* Only done in Windows 8+. */ + { &Provider, FRAG_GET_RUNTIME_ID, METHOD_OPTIONAL }, + { &Provider, FRAG_GET_FRAGMENT_ROOT, METHOD_OPTIONAL }, + /* These three are only done on Windows 7. */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_OPTIONAL }, + { &Provider, FRAG_NAVIGATE, METHOD_OPTIONAL }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_ProviderDescriptionPropertyId */ + { 0 } +}; + +static const struct prov_method_sequence 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, 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_hwnd4[] = { + { &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_hwnd5[] = { + { &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_hwnd6[] = { + { &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_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, 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_hwnd8[] = { + { &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_hwnd9[] = { + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + /* Win10v1507 and below call this. */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider, 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_hwnd4, "node_from_hwnd4"); + + 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); + 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); + } + ok_method_sequence(node_from_hwnd6, "node_from_hwnd6"); + + /* RuntimeId check succeeds, we'll get a nested node. */ + Provider_child.frag_root = &Provider.IRawElementProviderFragmentRoot_iface; + hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + + hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + /* + * 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_hwnd7, "node_from_hwnd7"); + + 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); + 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; + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + + hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + 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_hwnd8, "node_from_hwnd8"); + 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"); + + /* + * COM initialized, but provider passed into UiaReturnRawElementProvider + * fails to get a valid RuntimeId due to a lack of a fragment root. Win8+ + * will fall back to MSAA proxy. + */ + Provider.prov_opts = ProviderOptions_ServerSideProvider; + Provider.hwnd = NULL; + prov_root = &Provider.IRawElementProviderSimple_iface; + node = (void *)0xdeadbeef; + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + SET_EXPECT_MULTI(winproc_GETOBJECT_CLIENT, 2); + Provider.frag_root = NULL; + Provider.runtime_id[0] = UiaAppendRuntimeId; + Provider.runtime_id[1] = 1; + hr = UiaNodeFromHandle(hwnd, &node); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + CHECK_CALLED(winproc_GETOBJECT_UiaRoot); + todo_wine CHECK_CALLED(winproc_GETOBJECT_CLIENT); + + hr = UiaGetPropertyValue(node, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), hwnd); + + if (get_provider_desc(V_BSTR(&v), L"Annotation:", NULL)) + { + check_node_provider_desc(V_BSTR(&v), L"Annotation", NULL, FALSE); + check_node_provider_desc(V_BSTR(&v), L"Main", NULL, FALSE); + } + else + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider", FALSE); + + check_node_provider_desc(V_BSTR(&v), L"Nonclient", NULL, FALSE); + check_node_provider_desc(V_BSTR(&v), L"Hwnd", NULL, TRUE); + VariantClear(&v); + } + ok_method_sequence(node_from_hwnd2, "node_from_hwnd2"); + todo_wine ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + /* + * Bug on Windows 8 through Win10v1709 - if we have a RuntimeId failure, + * refcount doesn't get decremented. + */ + ok(Provider.ref == 1 || broken(Provider.ref == 2), "Unexpected refcnt %ld\n", Provider.ref); + if (Provider.ref == 2) + IRawElementProviderSimple_Release(&Provider.IRawElementProviderSimple_iface); + + CoUninitialize(); + + /* + * 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_hwnd3, "node_from_hwnd3"); + + /* + * 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); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + + hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + 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_hwnd5, "node_from_hwnd5"); + + Provider.expected_tid = Provider_child.expected_tid = 0; + ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); + 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_hwnd9, "node_from_hwnd9"); + + 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 +5672,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 +5691,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"); 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 deb8dd0d7af..e1ae08288f0 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -23,6 +23,171 @@
WINE_DEFAULT_DEBUG_CHANNEL(uiautomation);
+/* + * 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 void uia_stop_client_thread(void); +static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESULT lr, BOOL unwrap_local); +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", hwnd); + uia_stop_client_thread(); + return E_NOTIMPL; + } + + return create_wine_uia_nested_node_provider(node, lr, wparam); + } + + 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; + HANDLE events[2]; + HMODULE hmodule; + DWORD wait_obj; + HRESULT hr; + + hr = CoIncrementMTAUsage(&client_thread.mta_cookie); + if (FAILED(hr)) + { + WARN("CoIncrementMTAUsage failed with hr %#lx\n", hr); + memset(&client_thread, 0, sizeof(client_thread)); + LeaveCriticalSection(&client_thread_cs); + return FALSE; + } + + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (const WCHAR *)uia_start_client_thread, &hmodule); + + ready_event = CreateEventW(NULL, FALSE, FALSE, NULL); + if (!(client_thread.hthread = CreateThread(NULL, 0, uia_client_thread_proc, + ready_event, 0, NULL))) + { + WARN("Failed to create client thread.\n"); + CloseHandle(ready_event); + FreeLibrary(huia_module); + CoDecrementMTAUsage(client_thread.mta_cookie); + LeaveCriticalSection(&client_thread_cs); + return FALSE; + } + + events[0] = ready_event; + events[1] = client_thread.hthread; + wait_obj = WaitForMultipleObjects(2, events, FALSE, INFINITE); + if (wait_obj != WAIT_OBJECT_0) + { + WARN("Failed to start client thread\n"); + CloseHandle(client_thread.hthread); + CoDecrementMTAUsage(client_thread.mta_cookie); + memset(&client_thread, 0, sizeof(client_thread)); + started = FALSE; + } + + CloseHandle(ready_event); + } + + 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 uia_get_prov_from_hwnd(struct uia_node *node) +{ + BOOL same_thread = GetCurrentThreadId() == GetWindowThreadProcessId(node->hwnd, NULL); + + if (!uia_start_client_thread()) + return E_FAIL; + + return SendMessageW(client_thread.hwnd, WM_GET_UIA_PROV_FROM_HWND, same_thread, (LPARAM)node); +} + static HRESULT get_safearray_bounds(SAFEARRAY *sa, LONG *lbound, LONG *elems) { LONG ubound; @@ -87,7 +252,8 @@ static void clear_uia_node_lresult_safearray(SAFEARRAY *sa, LONG elems) } }
-static void create_uia_node_safearray(VARIANT *in, VARIANT *out, BOOL out_nested) +static HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode); +static void create_uia_node_safearray(VARIANT *in, BOOL in_nested, VARIANT *out, BOOL out_nested) { const VARTYPE out_vt = out_nested ? VT_I4 : VT_UINT_PTR; LONG i, idx, lbound, elems; @@ -103,24 +269,40 @@ static void create_uia_node_safearray(VARIANT *in, VARIANT *out, BOOL out_nested
for (i = 0; i < elems; i++) { - IRawElementProviderSimple *elprov; - IUnknown *unk; - idx = lbound + i; - hr = SafeArrayGetElement(V_ARRAY(in), &idx, &unk); - if (FAILED(hr)) - break; + if (in_nested) + { + LRESULT lr;
- hr = IUnknown_QueryInterface(unk, &IID_IRawElementProviderSimple, (void **)&elprov); - IUnknown_Release(unk); - if (FAILED(hr)) - break; + hr = SafeArrayGetElement(V_ARRAY(in), &idx, &lr); + if (FAILED(hr)) + break;
- hr = UiaNodeFromProvider(elprov, &node); - if (FAILED(hr)) - break; + hr = uia_node_from_lresult(lr, &node); + if (FAILED(hr)) + break; + } + else + { + IRawElementProviderSimple *elprov; + IUnknown *unk; + + hr = SafeArrayGetElement(V_ARRAY(in), &idx, &unk); + if (FAILED(hr)) + break; + + hr = IUnknown_QueryInterface(unk, &IID_IRawElementProviderSimple, (void **)&elprov); + if (FAILED(hr)) + break; + + IUnknown_Release(unk); + hr = UiaNodeFromProvider(elprov, &node); + if (FAILED(hr)) + break; + + IRawElementProviderSimple_Release(elprov); + }
- IRawElementProviderSimple_Release(elprov); if (out_nested) { LRESULT lr; @@ -146,6 +328,10 @@ static void create_uia_node_safearray(VARIANT *in, VARIANT *out, BOOL out_nested clear_uia_node_lresult_safearray(sa, elems); else clear_uia_node_ptr_safearray(sa, elems); + + if (in_nested) + clear_uia_node_lresult_safearray(V_ARRAY(in), elems); + SafeArrayDestroy(sa); return; } @@ -394,11 +580,30 @@ 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); + 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) @@ -585,7 +790,7 @@ static HRESULT uia_provider_get_elem_prop_val(struct uia_provider *prov, WARN("Invalid vt %d for UIAutomationType_ElementArray\n", V_VT(&v)); goto exit; } - create_uia_node_safearray(&v, ret_val, prov->node->nested_node); + create_uia_node_safearray(&v, FALSE, ret_val, prov->node->nested_node); VariantClear(&v); if (prov->node->nested_node && V_VT(ret_val) == VT_EMPTY) return E_FAIL; @@ -792,6 +997,244 @@ HRESULT WINAPI UiaNodeFromProvider(IRawElementProviderSimple *elprov, HUIANODE * return hr; }
+/* + * IWineUiaProvider interface for nested node providers. + */ +struct uia_nested_node_provider { + IWineUiaProvider IWineUiaProvider_iface; + LONG ref; + + IWineUiaNode *node; +}; + +static inline struct uia_nested_node_provider *impl_from_nested_node_IWineUiaProvider(IWineUiaProvider *iface) +{ + return CONTAINING_RECORD(iface, struct uia_nested_node_provider, IWineUiaProvider_iface); +} + +static HRESULT WINAPI uia_nested_node_provider_QueryInterface(IWineUiaProvider *iface, REFIID riid, void **ppv) +{ + *ppv = NULL; + if (IsEqualIID(riid, &IID_IWineUiaProvider) || IsEqualIID(riid, &IID_IUnknown)) + *ppv = iface; + else + return E_NOINTERFACE; + + IWineUiaProvider_AddRef(iface); + return S_OK; +} + +static ULONG WINAPI uia_nested_node_provider_AddRef(IWineUiaProvider *iface) +{ + struct uia_nested_node_provider *prov = impl_from_nested_node_IWineUiaProvider(iface); + ULONG ref = InterlockedIncrement(&prov->ref); + + TRACE("%p, refcount %ld\n", prov, ref); + return ref; +} + +static ULONG WINAPI uia_nested_node_provider_Release(IWineUiaProvider *iface) +{ + struct uia_nested_node_provider *prov = impl_from_nested_node_IWineUiaProvider(iface); + ULONG ref = InterlockedDecrement(&prov->ref); + + TRACE("%p, refcount %ld\n", prov, ref); + if (!ref) + { + IWineUiaNode_Release(prov->node); + uia_stop_client_thread(); + heap_free(prov); + } + + return ref; +} + +static HRESULT WINAPI uia_nested_node_provider_get_prop_val(IWineUiaProvider *iface, + const struct uia_prop_info *prop_info, VARIANT *ret_val) +{ + struct uia_nested_node_provider *prov = impl_from_nested_node_IWineUiaProvider(iface); + HRESULT hr; + VARIANT v; + + TRACE("%p, %p, %p\n", iface, prop_info, ret_val); + + VariantInit(ret_val); + hr = IWineUiaNode_get_prop_val(prov->node, prop_info->guid, &v); + if (FAILED(hr)) + return hr; + + if (V_VT(&v) == VT_EMPTY) + goto exit; + + /* + * When we get elements from a nested node, we need to convert them + * to nested node provider HUIANODEs. + */ + switch (prop_info->type) + { + case UIAutomationType_Element: + { + HUIANODE node; + + hr = uia_node_from_lresult((LRESULT)V_I4(&v), &node); + if (FAILED(hr)) + return hr; + + get_variant_for_node(node, ret_val); + VariantClear(&v); + break; + } + + case UIAutomationType_ElementArray: + create_uia_node_safearray(&v, TRUE, ret_val, FALSE); + break; + + default: + *ret_val = v; + break; + } + +exit: + if (V_VT(ret_val) == VT_EMPTY) + VariantClear(&v); + + return S_OK; +} + +static const IWineUiaProviderVtbl uia_nested_node_provider_vtbl = { + uia_nested_node_provider_QueryInterface, + uia_nested_node_provider_AddRef, + uia_nested_node_provider_Release, + uia_nested_node_provider_get_prop_val, +}; + +static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESULT lr, BOOL unwrap_local) +{ + struct uia_nested_node_provider *prov; + IGlobalInterfaceTable *git; + IWineUiaNode *nested_node; + HRESULT hr; + + hr = ObjectFromLresult(lr, &IID_IWineUiaNode, 0, (void **)&nested_node); + if (FAILED(hr)) + { + uia_stop_client_thread(); + return hr; + } + + /* + * If we're retrieving a node from an HWND that belongs to the same thread + * as the client making the request, we don't create a nested node, we + * just pass through the retrieved HUIANODE's IWineUiaProvider. + */ + if (unwrap_local) + { + struct uia_node *node_data = unsafe_impl_from_IWineUiaNode(nested_node); + struct uia_provider *tmp_prov = impl_from_IWineUiaProvider(node_data->prov); + + node->prov = node_data->prov; + node->git_cookie = node_data->git_cookie; + + IWineUiaProvider_AddRef(node->prov); + node_data->git_cookie = 0; + IWineUiaNode_Release(&node_data->IWineUiaNode_iface); + + tmp_prov->node = node; + uia_stop_client_thread(); + return S_OK; + } + + prov = heap_alloc_zero(sizeof(*prov)); + if (!prov) + return E_OUTOFMEMORY; + + prov->IWineUiaProvider_iface.lpVtbl = &uia_nested_node_provider_vtbl; + prov->node = nested_node; + prov->ref = 1; + node->prov = &prov->IWineUiaProvider_iface; + + /* + * Need to use GIT so we use this IWineUiaNode proxy from the right thread. + */ + hr = get_global_interface_table(&git); + if (FAILED(hr)) + goto exit; + + hr = IGlobalInterfaceTable_RegisterInterfaceInGlobal(git, (IUnknown *)&prov->IWineUiaProvider_iface, + &IID_IWineUiaProvider, &node->git_cookie); + if (FAILED(hr)) + goto exit; + +exit: + if (FAILED(hr)) + IWineUiaProvider_Release(&prov->IWineUiaProvider_iface); + + return hr; +} + +static HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode) +{ + struct uia_node *node; + HRESULT hr; + + *huianode = NULL; + node = heap_alloc_zero(sizeof(*node)); + if (!node) + return E_OUTOFMEMORY; + + uia_start_client_thread(); + hr = create_wine_uia_nested_node_provider(node, lr, FALSE); + if (FAILED(hr)) + { + heap_free(node); + return hr; + } + + node->IWineUiaNode_iface.lpVtbl = &uia_node_vtbl; + node->ref = 1; + *huianode = (void *)&node->IWineUiaNode_iface; + + return hr; +} + +/*********************************************************************** + * 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; + + if (!IsWindow(hwnd)) + return UIA_E_ELEMENTNOTAVAILABLE; + + *huianode = NULL; + + 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_prov_from_hwnd(node); + if (FAILED(hr) || !node->prov) + { + heap_free(node); + return hr; + } + + *huianode = (void *)&node->IWineUiaNode_iface; + + return S_OK; +} + /*********************************************************************** * UiaNodeRelease (uiautomationcore.@) */ @@ -878,9 +1321,10 @@ HRESULT WINAPI UiaGetPropertyValue(HUIANODE huianode, PROPERTYID prop_id, VARIAN /* * ElementArray types come back as an array of pointers to prevent the * HUIANODEs from getting marshaled. We need to convert them to - * VT_UNKNOWN here. + * VT_UNKNOWN here. For nested nodes, just pass through the array of + * LRESULT values. */ - if (prop_info->type == UIAutomationType_ElementArray) + if (prop_info->type == UIAutomationType_ElementArray && !node->nested_node) { uia_node_ptr_to_unk_safearray(&v); if (V_VT(&v) != VT_EMPTY) 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/tests/uiautomation.c | 208 ++++++++++++++++++++- dlls/uiautomationcore/uia_classes.idl | 1 + dlls/uiautomationcore/uia_client.c | 88 ++++++++- dlls/uiautomationcore/uia_main.c | 6 - dlls/uiautomationcore/uia_private.h | 5 + dlls/uiautomationcore/uia_provider.c | 156 ++++++++++++++-- include/uiautomationcoreapi.h | 1 + 7 files changed, 446 insertions(+), 19 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 4431455eb8a..a4e86f25a55 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -29,6 +29,7 @@ #include "wine/test.h"
static HRESULT (WINAPI *pUiaProviderFromIAccessible)(IAccessible *, long, DWORD, IRawElementProviderSimple **); +static HRESULT (WINAPI *pUiaDisconnectProvider)(IRawElementProviderSimple *);
#define DEFINE_EXPECT(func) \ static int expect_ ## func = 0, called_ ## func = 0 @@ -5101,6 +5102,56 @@ static const struct prov_method_sequence node_from_hwnd9[] = { { 0 } };
+static const struct prov_method_sequence disconnect_prov1[] = { + { &Provider_child, PROV_GET_PROVIDER_OPTIONS }, + /* Win10v1507 and below call this. */ + { &Provider_child, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider_child, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider_child, PROV_GET_PROPERTY_VALUE }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider_child, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider_child, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider_child, FRAG_GET_RUNTIME_ID }, + { &Provider_child, FRAG_GET_FRAGMENT_ROOT }, + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + { 0 } +}; + +static const struct prov_method_sequence disconnect_prov2[] = { + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + /* Win10v1507 and below call this. */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider, FRAG_GET_RUNTIME_ID }, + { &Provider, FRAG_GET_FRAGMENT_ROOT }, + { 0 } +}; + +static const struct prov_method_sequence disconnect_prov3[] = { + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + /* Win10v1507 and below call this. */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider, PROV_GET_PROPERTY_VALUE }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { &Provider, FRAG_GET_RUNTIME_ID }, + { 0 } +}; + +static const struct prov_method_sequence disconnect_prov4[] = { + { &Provider, PROV_GET_PROVIDER_OPTIONS }, + /* Win10v1507 and below call this. */ + { &Provider, PROV_GET_PROPERTY_VALUE, METHOD_OPTIONAL }, /* UIA_NativeWindowHandlePropertyId */ + { &Provider, PROV_GET_HOST_RAW_ELEMENT_PROVIDER }, + { &Provider, FRAG_NAVIGATE, METHOD_TODO }, /* NavigateDirection_Parent */ + { &Provider, PROV_GET_PROVIDER_OPTIONS, METHOD_TODO }, + { 0 } +}; + static void test_UiaNodeFromHandle_client_proc(void) { APTTYPEQUALIFIER apt_qualifier; @@ -5175,8 +5226,8 @@ static void test_UiaNodeFromHandle_client_proc(void)
static DWORD WINAPI uia_node_from_handle_test_thread(LPVOID param) { + HUIANODE node, node2, node3; HWND hwnd = (HWND)param; - HUIANODE node, node2; WCHAR buf[2048]; HRESULT hr; VARIANT v; @@ -5337,6 +5388,158 @@ static DWORD WINAPI uia_node_from_handle_test_thread(LPVOID param) Sleep(50); ok(Provider.ref == 1, "Unexpected refcnt %ld\n", Provider.ref);
+ if (!pUiaDisconnectProvider) + { + win_skip("UiaDisconnectProvider not exported by uiautomationcore.dll\n"); + goto exit; + } + + /* + * UiaDisconnectProvider tests. + */ + SET_EXPECT(winproc_GETOBJECT_UiaRoot); + prov_root = &Provider.IRawElementProviderSimple_iface; + Provider.prov_opts = ProviderOptions_ServerSideProvider; + Provider.hwnd = hwnd; + Provider.runtime_id[0] = Provider.runtime_id[1] = 0; + Provider.frag_root = NULL; + Provider.prov_opts = ProviderOptions_ServerSideProvider; + hr = UiaNodeFromHandle(hwnd, &node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider.ref == 2, "Unexpected refcnt %ld\n", Provider.ref); + CHECK_CALLED(winproc_GETOBJECT_UiaRoot); + + hr = UiaGetPropertyValue(node, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + memset(buf, 0, sizeof(buf)); + + ok(get_nested_provider_desc(V_BSTR(&v), L"Main", FALSE, buf), "Failed to get nested provider description\n"); + check_node_provider_desc_prefix(buf, GetCurrentProcessId(), hwnd); + /* Win10v1507 and below have the nested provider as 'Hwnd'. */ + if (get_provider_desc(buf, L"Hwnd(parent link):", NULL)) + check_node_provider_desc(buf, L"Hwnd", L"Provider", TRUE); + else + check_node_provider_desc(buf, L"Main", L"Provider", TRUE); + + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), hwnd); + check_node_provider_desc(V_BSTR(&v), L"Nonclient", NULL, FALSE); + check_node_provider_desc(V_BSTR(&v), L"Hwnd", NULL, TRUE); + VariantClear(&v); + } + + ok_method_sequence(node_from_hwnd4, "node_from_hwnd4"); + + hr = UiaGetPropertyValue(node, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + check_uia_prop_val(UIA_ControlTypePropertyId, UIAutomationType_Int, &v); + + /* Nodes returned from a nested node will be tracked and disconnectable. */ + Provider_child.prov_opts = ProviderOptions_ServerSideProvider; + Provider_child.runtime_id[0] = UiaAppendRuntimeId; + Provider_child.runtime_id[1] = 2; + Provider_child.hwnd = NULL; + Provider_child.frag_root = &Provider.IRawElementProviderFragmentRoot_iface; + hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(Provider_child.ref == 2, "Unexpected refcnt %ld\n", Provider_child.ref); + + hr = UiaHUiaNodeFromVariant(&v, &node2); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node2, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + 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_hwnd7, "node_from_hwnd7"); + + /* Get a new node for the same provider. */ + hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(Provider_child.ref == 3, "Unexpected refcnt %ld\n", Provider_child.ref); + + hr = UiaHUiaNodeFromVariant(&v, &node3); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + hr = UiaGetPropertyValue(node3, UIA_ProviderDescriptionPropertyId, &v); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + if (SUCCEEDED(hr)) + { + check_node_provider_desc_prefix(V_BSTR(&v), GetCurrentProcessId(), NULL); + check_node_provider_desc(V_BSTR(&v), L"Main", L"Provider_child", TRUE); + VariantClear(&v); + } + + hr = UiaGetPropertyValue(node3, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(V_VT(&v) == VT_I4, "Unexpected VT %d\n", V_VT(&v)); + ok(V_I4(&v) == uia_i4_prop_val, "Unexpected I4 %#lx\n", V_I4(&v)); + ok_method_sequence(node_from_hwnd7, "node_from_hwnd7"); + + /* + * Both node2 and node3 represent Provider_child, one call to + * UiaDisconnectProvider disconnects both. + */ + hr = pUiaDisconnectProvider(&Provider_child.IRawElementProviderSimple_iface); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + ok(Provider_child.ref == 1, "Unexpected refcnt %ld\n", Provider_child.ref); + + hr = UiaGetPropertyValue(node2, UIA_ControlTypePropertyId, &v); + ok(hr == UIA_E_ELEMENTNOTAVAILABLE, "Unexpected hr %#lx\n", hr); + + hr = UiaGetPropertyValue(node3, UIA_ControlTypePropertyId, &v); + ok(hr == UIA_E_ELEMENTNOTAVAILABLE, "Unexpected hr %#lx\n", hr); + ok_method_sequence(disconnect_prov1, "disconnect_prov1"); + + ok(UiaNodeRelease(node2), "UiaNodeRelease returned FALSE\n"); + ok(UiaNodeRelease(node3), "UiaNodeRelease returned FALSE\n"); + + /* + * Returns same failure code as UiaGetRuntimeId when we fail to get a + * fragment root for AppendRuntimeId. + */ + Provider.hwnd = NULL; + Provider.runtime_id[0] = UiaAppendRuntimeId; + Provider.runtime_id[1] = 2; + Provider.frag_root = NULL; + hr = pUiaDisconnectProvider(&Provider.IRawElementProviderSimple_iface); + ok(hr == E_FAIL, "Unexpected hr %#lx\n", hr); + ok_method_sequence(disconnect_prov2, "disconnect_prov2"); + + /* + * Comparisons for disconnection are only based on RuntimeId comparisons, + * not interface pointer values. If an interface returns a NULL RuntimeId, + * no disconnection will occur. + */ + Provider.runtime_id[0] = Provider.runtime_id[1] = 0; + hr = pUiaDisconnectProvider(&Provider.IRawElementProviderSimple_iface); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx\n", hr); + ok_method_sequence(disconnect_prov3, "disconnect_prov3"); + + hr = UiaGetPropertyValue(node, UIA_ControlTypePropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + check_uia_prop_val(UIA_ControlTypePropertyId, UIAutomationType_Int, &v); + + /* Finally, disconnect node. */ + Provider.hwnd = hwnd; + hr = pUiaDisconnectProvider(&Provider.IRawElementProviderSimple_iface); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + + hr = UiaGetPropertyValue(node, UIA_ControlTypePropertyId, &v); + ok(hr == UIA_E_ELEMENTNOTAVAILABLE, "Unexpected hr %#lx\n", hr); + ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + ok_method_sequence(disconnect_prov4, "disconnect_prov4"); + +exit: + CoUninitialize();
return 0; @@ -5672,6 +5875,9 @@ START_TEST(uiautomation) pImmDisableIME = NULL; FreeLibrary(hModuleImm32);
+ if (uia_dll) + pUiaDisconnectProvider = (void *)GetProcAddress(uia_dll, "UiaDisconnectProvider"); + argc = winetest_get_mainargs(&argv); if (argc == 3) { diff --git a/dlls/uiautomationcore/uia_classes.idl b/dlls/uiautomationcore/uia_classes.idl index e2889901fbb..2fb0e88086b 100644 --- a/dlls/uiautomationcore/uia_classes.idl +++ b/dlls/uiautomationcore/uia_classes.idl @@ -56,5 +56,6 @@ library UIA_wine_private { HRESULT get_provider([out, retval]IWineUiaProvider **out_prov); HRESULT get_prop_val([in]const GUID *prop_guid, [out, retval]VARIANT *ret_val); + HRESULT disconnect(); } } diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index e1ae08288f0..6342c2bd410 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -214,6 +214,42 @@ static HRESULT get_safearray_bounds(SAFEARRAY *sa, LONG *lbound, LONG *elems) return S_OK; }
+BOOL uia_compare_runtime_ids(SAFEARRAY *sa1, SAFEARRAY *sa2) +{ + LONG i, idx, lbound[2], elems[2]; + int val[2]; + HRESULT hr; + + hr = get_safearray_bounds(sa1, &lbound[0], &elems[0]); + if (FAILED(hr)) + return FALSE; + + hr = get_safearray_bounds(sa2, &lbound[1], &elems[1]); + if (FAILED(hr)) + return FALSE; + + if (elems[0] != elems[1]) + return FALSE; + + for (i = 0; i < elems[0]; i++) + { + idx = lbound[0] + i; + hr = SafeArrayGetElement(sa1, &idx, &val[0]); + if (FAILED(hr)) + return FALSE; + + idx = lbound[1] + i; + hr = SafeArrayGetElement(sa2, &idx, &val[1]); + if (FAILED(hr)) + return FALSE; + + if (val[0] != val[1]) + return FALSE; + } + + return TRUE; +} + static void clear_uia_node_ptr_safearray(SAFEARRAY *sa, LONG elems) { HUIANODE node; @@ -539,9 +575,16 @@ static ULONG WINAPI uia_node_Release(IWineUiaNode *iface) } }
- IWineUiaProvider_Release(node->prov); + if (node->prov) + IWineUiaProvider_Release(node->prov); + if (node->nested_node) + { + if (!node->disconnected) + uia_provider_thread_remove_node((HUIANODE)iface); + uia_stop_provider_thread(); + } heap_free(node); }
@@ -552,6 +595,12 @@ static HRESULT WINAPI uia_node_get_provider(IWineUiaNode *iface, IWineUiaProvide { struct uia_node *node = impl_from_IWineUiaNode(iface);
+ if (node->disconnected) + { + *out_prov = NULL; + return UIA_E_ELEMENTNOTAVAILABLE; + } + if (node->git_cookie) { IGlobalInterfaceTable *git; @@ -584,11 +633,18 @@ static HRESULT WINAPI uia_node_get_prop_val(IWineUiaNode *iface, const GUID *pro VARIANT *ret_val) { int prop_id = UiaLookupId(AutomationIdentifierType_Property, prop_guid); + struct uia_node *node = impl_from_IWineUiaNode(iface); HRESULT hr; VARIANT v;
TRACE("%p, %s, %p\n", iface, debugstr_guid(prop_guid), ret_val);
+ if (node->disconnected) + { + VariantInit(ret_val); + return UIA_E_ELEMENTNOTAVAILABLE; + } + hr = UiaGetPropertyValue((HUIANODE)iface, prop_id, &v); if (V_VT(&v) == VT_UNKNOWN) V_VT(ret_val) = VT_EMPTY; @@ -598,12 +654,42 @@ static HRESULT WINAPI uia_node_get_prop_val(IWineUiaNode *iface, const GUID *pro return hr; }
+static HRESULT WINAPI uia_node_disconnect(IWineUiaNode *iface) +{ + struct uia_node *node = impl_from_IWineUiaNode(iface); + + TRACE("%p\n", iface); + + if (node->git_cookie) + { + IGlobalInterfaceTable *git; + HRESULT hr; + + hr = get_global_interface_table(&git); + if (SUCCEEDED(hr)) + { + hr = IGlobalInterfaceTable_RevokeInterfaceFromGlobal(git, node->git_cookie); + if (FAILED(hr)) + WARN("Failed to get revoke provider interface from Global Interface Table, hr %#lx\n", hr); + } + + node->git_cookie = 0; + } + + IWineUiaProvider_Release(node->prov); + node->prov = NULL; + node->disconnected = TRUE; + + return S_OK; +} + static const IWineUiaNodeVtbl uia_node_vtbl = { uia_node_QueryInterface, uia_node_AddRef, uia_node_Release, uia_node_get_provider, uia_node_get_prop_val, + uia_node_disconnect, };
static struct uia_node *unsafe_impl_from_IWineUiaNode(IWineUiaNode *iface) diff --git a/dlls/uiautomationcore/uia_main.c b/dlls/uiautomationcore/uia_main.c index f141b4ca716..c888b51a019 100644 --- a/dlls/uiautomationcore/uia_main.c +++ b/dlls/uiautomationcore/uia_main.c @@ -346,12 +346,6 @@ HRESULT WINAPI UiaHostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **pr return S_OK; }
-HRESULT WINAPI UiaDisconnectProvider(IRawElementProviderSimple *provider) -{ - FIXME("(%p): stub\n", provider); - return E_NOTIMPL; -} - /*********************************************************************** * DllMain (uiautomationcore.@) */ diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 5dc254ec195..f3aea481a1e 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -38,6 +38,7 @@ struct uia_node {
HWND hwnd; BOOL nested_node; + BOOL disconnected; };
inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) @@ -49,5 +50,9 @@ inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) const struct uia_prop_info *uia_prop_info_from_id(PROPERTYID prop_id) DECLSPEC_HIDDEN;
/* uia_provider.c */ +void uia_provider_thread_remove_node(HUIANODE node) DECLSPEC_HIDDEN; LRESULT uia_lresult_from_node(HUIANODE huianode) DECLSPEC_HIDDEN; void uia_stop_provider_thread(void) DECLSPEC_HIDDEN; + +/* uia_client.c */ +BOOL uia_compare_runtime_ids(SAFEARRAY *sa1, SAFEARRAY *sa2) DECLSPEC_HIDDEN; diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c index 0f46a7a4429..67164608001 100644 --- a/dlls/uiautomationcore/uia_provider.c +++ b/dlls/uiautomationcore/uia_provider.c @@ -21,6 +21,7 @@
#include "wine/debug.h" #include "wine/heap.h" +#include "wine/list.h" #include "initguid.h" #include "wine/iaccessible2.h"
@@ -1145,6 +1146,118 @@ static CRITICAL_SECTION_DEBUG provider_thread_cs_debug = }; static CRITICAL_SECTION provider_thread_cs = { &provider_thread_cs_debug, -1, 0, 0, 0, 0 };
+struct uia_provider_thread_list_entry +{ + struct list entry; + + HUIANODE node; + SAFEARRAY *runtime_id; +}; +static struct list provider_thread_nodes_list = LIST_INIT(provider_thread_nodes_list); + +void uia_provider_thread_remove_node(HUIANODE node) +{ + struct uia_provider_thread_list_entry *prov; + struct list *cursor, *cursor2; + BOOL found = FALSE; + + TRACE("Removing node %p\n", node); + EnterCriticalSection(&provider_thread_cs); + LIST_FOR_EACH_SAFE(cursor, cursor2, &provider_thread_nodes_list) + { + prov = LIST_ENTRY(cursor, struct uia_provider_thread_list_entry, entry); + if (prov->node == node) + { + list_remove(cursor); + SafeArrayDestroy(prov->runtime_id); + heap_free(prov); + found = TRUE; + goto exit; + } + } + +exit: + LeaveCriticalSection(&provider_thread_cs); + if (!found) + ERR("Failed to find node %p in provider list\n", node); +} + +static void uia_provider_thread_disconnect_node(SAFEARRAY *sa) +{ + struct uia_provider_thread_list_entry *prov; + struct list *cursor, *cursor2; + + EnterCriticalSection(&provider_thread_cs); + + /* Provider thread hasn't been started, no nodes to disconnect. */ + if (!provider_thread.ref) + goto exit; + + LIST_FOR_EACH_SAFE(cursor, cursor2, &provider_thread_nodes_list) + { + prov = LIST_ENTRY(cursor, struct uia_provider_thread_list_entry, entry); + + if (uia_compare_runtime_ids(sa, prov->runtime_id)) + { + list_remove(cursor); + IWineUiaNode_disconnect((IWineUiaNode *)prov->node); + SafeArrayDestroy(prov->runtime_id); + heap_free(prov); + } + } + +exit: + LeaveCriticalSection(&provider_thread_cs); +} + +static void uia_provider_thread_remove_all_nodes(void) +{ + struct uia_provider_thread_list_entry *prov; + struct list *cursor, *cursor2; + struct uia_node *node; + + TRACE("Removing all nodes.\n"); + EnterCriticalSection(&provider_thread_cs); + LIST_FOR_EACH_SAFE(cursor, cursor2, &provider_thread_nodes_list) + { + prov = LIST_ENTRY(cursor, struct uia_provider_thread_list_entry, entry); + list_remove(cursor); + node = impl_from_IWineUiaNode((IWineUiaNode *)prov->node); + node->disconnected = TRUE; + SafeArrayDestroy(prov->runtime_id); + UiaNodeRelease(prov->node); + heap_free(prov); + } + + LeaveCriticalSection(&provider_thread_cs); +} + +static HRESULT uia_provider_thread_add_node(HUIANODE node) +{ + struct uia_provider_thread_list_entry *prov = heap_alloc_zero(sizeof(*prov)); + struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); + HRESULT hr; + + if (!prov) + return E_OUTOFMEMORY; + + TRACE("Adding node %p\n", node); + prov->node = node; + hr = UiaGetRuntimeId(node, &prov->runtime_id); + if (FAILED(hr)) + { + heap_free(prov); + return hr; + } + + EnterCriticalSection(&provider_thread_cs); + list_add_tail(&provider_thread_nodes_list, &prov->entry); + node_data->nested_node = TRUE; + LeaveCriticalSection(&provider_thread_cs); + + return S_OK; +} + #define WM_GET_OBJECT_UIA_NODE (WM_USER + 1) #define WM_UIA_PROVIDER_THREAD_STOP (WM_USER + 2) static LRESULT CALLBACK uia_provider_thread_msg_proc(HWND hwnd, UINT msg, WPARAM wparam, @@ -1155,21 +1268,18 @@ 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; - SAFEARRAY *sa; LRESULT lr; HRESULT hr;
- hr = UiaGetRuntimeId(node, &sa); + hr = uia_provider_thread_add_node(node); if (FAILED(hr)) { - WARN("Failed to get runtime ID for node %p\n", node); + WARN("Failed to add node %p to provider list\n", node); UiaNodeRelease(node); uia_stop_provider_thread(); return 0; }
- SafeArrayDestroy(sa); lr = LresultFromObject(&IID_IWineUiaNode, 0, (IUnknown *)node);
/* @@ -1179,14 +1289,8 @@ static LRESULT CALLBACK uia_provider_thread_msg_proc(HWND hwnd, UINT msg, WPARAM if ((lr > 0xffff) || (lr < 0xc000)) { WARN("Got invalid lresult %Ix\n", lr); - uia_stop_provider_thread(); lr = 0; } - else - { - node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); - node_data->nested_node = TRUE; - }
/* * LresultFromObject increases refcnt by 1. If LresultFromObject @@ -1239,6 +1343,7 @@ static DWORD WINAPI uia_provider_thread_proc(void *arg) TRACE("Shutting down UI Automation provider thread.\n");
DestroyWindow(hwnd); + uia_provider_thread_remove_all_nodes(); CoUninitialize(); FreeLibraryAndExitThread(huia_module, 0); } @@ -1344,3 +1449,32 @@ LRESULT WINAPI UiaReturnRawElementProvider(HWND hwnd, WPARAM wParam,
return uia_lresult_from_node(node); } + +/*********************************************************************** + * UiaDisconnectProvider (uiautomationcore.@) + */ +HRESULT WINAPI UiaDisconnectProvider(IRawElementProviderSimple *elprov) +{ + SAFEARRAY *sa; + HUIANODE node; + HRESULT hr; + + TRACE("(%p)\n", elprov); + + hr = UiaNodeFromProvider(elprov, &node); + if (FAILED(hr)) + return hr; + + hr = UiaGetRuntimeId(node, &sa); + UiaNodeRelease(node); + if (FAILED(hr)) + return hr; + + if (!sa) + return E_INVALIDARG; + + uia_provider_thread_disconnect_node(sa); + SafeArrayDestroy(sa); + + return S_OK; +} diff --git a/include/uiautomationcoreapi.h b/include/uiautomationcoreapi.h index b4814b9e857..025b811ba5e 100644 --- a/include/uiautomationcoreapi.h +++ b/include/uiautomationcoreapi.h @@ -398,6 +398,7 @@ BOOL WINAPI UiaNodeRelease(HUIANODE huianode); HRESULT WINAPI UiaGetRuntimeId(HUIANODE huianode, SAFEARRAY **runtime_id); HRESULT WINAPI UiaHUiaNodeFromVariant(VARIANT *in_val, HUIANODE *huianode); HRESULT WINAPI UiaNodeFromHandle(HWND hwnd, HUIANODE *huianode); +HRESULT WINAPI UiaDisconnectProvider(IRawElementProviderSimple *elprov);
#ifdef __cplusplus }
V3: Change a few traces to fix Gitlab CI build failure.
Huw Davies (@huw) commented about dlls/uiautomationcore/uia_provider.c:
+}
+static BOOL uia_start_provider_thread(void) +{
- BOOL started = TRUE;
- EnterCriticalSection(&provider_thread_cs);
- if (++provider_thread.ref == 1)
- {
HANDLE ready_event;
HANDLE events[2];
HMODULE hmodule;
DWORD wait_obj;
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(const WCHAR *)uia_start_provider_thread, &hmodule);
Isn't this a complicated way of retrieving the current module? In any case, `hmodule` isn't used after this.
In general, there's any awful lot of code in each commit of this MR, which makes it very hard to review. Please try to split things up.
On Wed Sep 7 09:02:26 2022 +0000, Huw Davies wrote:
Isn't this a complicated way of retrieving the current module? In any case, `hmodule` isn't used after this. In general, there's any awful lot of code in each commit of this MR, which makes it very hard to review. Please try to split things up.
The idea here was to get hmodule to increment the module reference count, and then we release the global variable `HMODULE huia_module` to decrement it.
I guess I could just create separate global variables in both `uia_provider.c` and `uia_client.c`, but it's theoretically possible another thread could start while a prior one is still shutting down, and I didn't want them to fight over variable assignment. Having a single global module handle, then incrementing it with `GetModuleHandleExW` made sense to me. This is similar to what is done [here.](https://gitlab.winehq.org/wine/wine/-/blob/master/dlls/vcomp/main.c#L1673)
I'll try to split things up a bit more, sorry about that.
This merge request was closed by Connor McAdams.