From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 126 +++++++++++++++++++++ dlls/uiautomationcore/uia_classes.idl | 39 ++++--- dlls/uiautomationcore/uia_client.c | 83 +++++++++++++- dlls/uiautomationcore/uia_main.c | 4 +- 4 files changed, 233 insertions(+), 19 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index ed3dfbb543c..7dd598822a4 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -1101,6 +1101,7 @@ static struct Provider enum ProviderOptions prov_opts; HWND hwnd; BOOL ret_invalid_prop_type; + DWORD expected_tid; } Provider, Provider2, Provider_child, Provider_child2;
static const WCHAR *uia_bstr_prop_str = L"uia-string"; @@ -1443,6 +1444,8 @@ HRESULT WINAPI ProviderSimple_get_ProviderOptions(IRawElementProviderSimple *ifa struct Provider *This = impl_from_ProviderSimple(iface);
add_method_call(This, PROV_GET_PROVIDER_OPTIONS); + if (This->expected_tid) + ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId());
*ret_val = 0; if (This->prov_opts) @@ -1467,6 +1470,8 @@ HRESULT WINAPI ProviderSimple_GetPropertyValue(IRawElementProviderSimple *iface, struct Provider *This = impl_from_ProviderSimple(iface);
add_method_call(This, PROV_GET_PROPERTY_VALUE); + if (This->expected_tid) + ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId());
VariantInit(ret_val); switch (prop_id) @@ -1654,6 +1659,8 @@ HRESULT WINAPI ProviderSimple_get_HostRawElementProvider(IRawElementProviderSimp struct Provider *This = impl_from_ProviderSimple(iface);
add_method_call(This, PROV_GET_HOST_RAW_ELEMENT_PROVIDER); + if (This->expected_tid) + ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId());
*ret_val = NULL; if (This->hwnd) @@ -1702,6 +1709,8 @@ static HRESULT WINAPI ProviderFragment_Navigate(IRawElementProviderFragment *ifa struct Provider *This = impl_from_ProviderFragment(iface);
add_method_call(This, FRAG_NAVIGATE); + if (This->expected_tid) + ok(This->expected_tid == GetCurrentThreadId(), "Unexpected tid %ld\n", GetCurrentThreadId());
*ret_val = NULL; if ((direction == NavigateDirection_Parent) && This->parent) @@ -3759,6 +3768,108 @@ static const struct prov_method_sequence node_from_prov8[] = { { 0 } };
+static void check_uia_prop_val(PROPERTYID prop_id, enum UIAutomationType type, VARIANT *v); +static DWORD WINAPI uia_node_from_provider_test_com_thread(LPVOID param) +{ + HUIANODE node = param; + HRESULT hr; + VARIANT v; + + /* + * Since this is a node representing an IRawElementProviderSimple with + * ProviderOptions_UseComThreading set, it is only usable in threads that + * have initialized COM. + */ + hr = UiaGetPropertyValue(node, UIA_ProcessIdPropertyId, &v); + ok(hr == CO_E_NOTINITIALIZED, "Unexpected hr %#lx\n", hr); + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + + hr = UiaGetPropertyValue(node, UIA_ProcessIdPropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + check_uia_prop_val(UIA_ProcessIdPropertyId, UIAutomationType_Int, &v); + + /* + * When retrieving a UIAutomationType_Element property, if UseComThreading + * is set, we'll get an HUIANODE that will make calls inside of the + * apartment of the node it is retrieved from. I.e, if we received a node + * with UseComThreading set from another node with UseComThreading set + * inside of an STA, the returned node will have all of its methods called + * from the STA thread. + */ + Provider_child.prov_opts = ProviderOptions_UseComThreading | ProviderOptions_ServerSideProvider; + Provider_child.expected_tid = Provider.expected_tid; + hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + check_uia_prop_val(UIA_LabeledByPropertyId, UIAutomationType_Element, &v); + + /* Unset ProviderOptions_UseComThreading. */ + Provider_child.prov_opts = ProviderOptions_ServerSideProvider; + hr = UiaGetPropertyValue(node, UIA_LabeledByPropertyId, &v); + ok(hr == S_OK, "Unexpected hr %#lx\n", hr); + + /* + * ProviderOptions_UseComThreading not set, GetPropertyValue will be + * called on the current thread. + */ + Provider_child.expected_tid = GetCurrentThreadId(); + check_uia_prop_val(UIA_LabeledByPropertyId, UIAutomationType_Element, &v); + + CoUninitialize(); + + return 0; +} + +static void test_uia_node_from_prov_com_threading(void) +{ + HANDLE thread; + HUIANODE node; + HRESULT hr; + + /* Test ProviderOptions_UseComThreading. */ + Provider.hwnd = NULL; + prov_root = NULL; + Provider.prov_opts = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading; + hr = UiaNodeFromProvider(&Provider.IRawElementProviderSimple_iface, &node); + ok_method_sequence(node_from_prov8, "node_from_prov8"); + + /* + * On Windows versions prior to Windows 10, UiaNodeFromProvider ignores the + * ProviderOptions_UseComThreading flag. + */ + if (hr == S_OK) + { + win_skip("Skipping ProviderOptions_UseComThreading tests for UiaNodeFromProvider.\n"); + UiaNodeRelease(node); + return; + } + ok(hr == CO_E_NOTINITIALIZED, "Unexpected hr %#lx.\n", hr); + + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + hr = UiaNodeFromProvider(&Provider.IRawElementProviderSimple_iface, &node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(Provider.ref == 2, "Unexpected refcnt %ld\n", Provider.ref); + ok_method_sequence(node_from_prov8, "node_from_prov8"); + + Provider.expected_tid = GetCurrentThreadId(); + thread = CreateThread(NULL, 0, uia_node_from_provider_test_com_thread, (void *)node, 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); + + ok(UiaNodeRelease(node), "UiaNodeRelease returned FALSE\n"); + ok(Provider.ref == 1, "Unexpected refcnt %ld\n", Provider.ref); + Provider_child.expected_tid = Provider.expected_tid = 0; + + CoUninitialize(); +} static void test_UiaNodeFromProvider(void) { WNDCLASSA cls; @@ -3784,6 +3895,9 @@ static void test_UiaNodeFromProvider(void) hwnd = CreateWindowA("UiaNodeFromProvider class", "Test window", WS_OVERLAPPEDWINDOW, 0, 0, 100, 100, NULL, NULL, NULL, NULL);
+ /* Run these tests early, we end up in an implicit MTA later. */ + test_uia_node_from_prov_com_threading(); + CoInitializeEx(NULL, COINIT_MULTITHREADED);
hr = UiaNodeFromProvider(NULL, &node); @@ -4352,6 +4466,18 @@ static void test_UiaGetPropertyValue(void) START_TEST(uiautomation) { HMODULE uia_dll = LoadLibraryA("uiautomationcore.dll"); + BOOL (WINAPI *pImmDisableIME)(DWORD); + HMODULE hModuleImm32; + + /* Make sure COM isn't initialized by imm32. */ + hModuleImm32 = LoadLibraryA("imm32.dll"); + if (hModuleImm32) { + pImmDisableIME = (void *)GetProcAddress(hModuleImm32, "ImmDisableIME"); + if (pImmDisableIME) + pImmDisableIME(0); + } + pImmDisableIME = NULL; + FreeLibrary(hModuleImm32);
test_UiaHostProviderFromHwnd(); test_uia_reserved_value_ifaces(); diff --git a/dlls/uiautomationcore/uia_classes.idl b/dlls/uiautomationcore/uia_classes.idl index 1ab09ebb9d0..8781c451f99 100644 --- a/dlls/uiautomationcore/uia_classes.idl +++ b/dlls/uiautomationcore/uia_classes.idl @@ -17,6 +17,7 @@ */
#pragma makedep header +#pragma makedep regtypelib
import "oaidl.idl";
@@ -27,21 +28,31 @@ struct uia_prop_info { };
[ - object, - uuid(57865755-6c05-4522-98df-4ca658b768ef), - pointer_default(unique), + version(1.0), + uuid(8a9ca8eb-856b-43d9-abd7-4a590054064f), ] -interface IWineUiaProvider : IUnknown +library UIA_wine_private { - HRESULT get_prop_val([in]const struct uia_prop_info *prop_info, [out, retval]VARIANT *ret_val); -} + importlib("stdole2.tlb");
-[ - object, - uuid(bccb6799-d831-4057-bd50-6425823ff1a3), - pointer_default(unique), -] -interface IWineUiaNode : IUnknown -{ - HRESULT get_provider([out, retval]IWineUiaProvider **out_prov); + [ + object, + uuid(57865755-6c05-4522-98df-4ca658b768ef), + pointer_default(unique), + oleautomation, + ] + interface IWineUiaProvider : IUnknown + { + HRESULT get_prop_val([in]const struct uia_prop_info *prop_info, [out, retval]VARIANT *ret_val); + } + + [ + object, + uuid(bccb6799-d831-4057-bd50-6425823ff1a3), + pointer_default(unique), + ] + interface IWineUiaNode : IUnknown + { + HRESULT get_provider([out, retval]IWineUiaProvider **out_prov); + } } diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 1ce540a885a..ba15ed0c1f1 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -148,6 +148,18 @@ exit: } }
+static HRESULT get_global_interface_table(IGlobalInterfaceTable **git) +{ + HRESULT hr; + + hr = CoCreateInstance(&CLSID_StdGlobalInterfaceTable, NULL, + CLSCTX_INPROC_SERVER, &IID_IGlobalInterfaceTable, (void **)git); + if (FAILED(hr)) + WARN("Failed to get GlobalInterfaceTable, hr %#lx\n", hr); + + return hr; +} + /* * IWineUiaNode interface. */ @@ -156,6 +168,7 @@ struct uia_node { LONG ref;
IWineUiaProvider *prov; + DWORD git_cookie; };
static inline struct uia_node *impl_from_IWineUiaNode(IWineUiaNode *iface) @@ -192,6 +205,20 @@ static ULONG WINAPI uia_node_Release(IWineUiaNode *iface) TRACE("%p, refcount %ld\n", node, ref); if (!ref) { + 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); + } + } + IWineUiaProvider_Release(node->prov); heap_free(node); } @@ -203,8 +230,30 @@ static HRESULT WINAPI uia_node_get_provider(IWineUiaNode *iface, IWineUiaProvide { struct uia_node *node = impl_from_IWineUiaNode(iface);
- *out_prov = node->prov; - IWineUiaProvider_AddRef(node->prov); + if (node->git_cookie) + { + IGlobalInterfaceTable *git; + IWineUiaProvider *prov; + HRESULT hr; + + hr = get_global_interface_table(&git); + if (FAILED(hr)) + return hr; + + hr = IGlobalInterfaceTable_GetInterfaceFromGlobal(git, node->git_cookie, + &IID_IWineUiaProvider, (void **)&prov); + if (FAILED(hr)) + { + ERR("Failed to get provider interface from GlobalInterfaceTable, hr %#lx\n", hr); + return hr; + } + *out_prov = prov; + } + else + { + *out_prov = node->prov; + IWineUiaProvider_AddRef(node->prov); + }
return S_OK; } @@ -377,7 +426,7 @@ static const IWineUiaProviderVtbl uia_provider_vtbl = {
static HRESULT create_wine_uia_provider(struct uia_node *node, IRawElementProviderSimple *elprov) { - static const int supported_prov_opts = ProviderOptions_ServerSideProvider; + static const int supported_prov_opts = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading; enum ProviderOptions prov_opts; struct uia_provider *prov; HRESULT hr; @@ -395,10 +444,36 @@ static HRESULT create_wine_uia_provider(struct uia_node *node, IRawElementProvid
prov->IWineUiaProvider_iface.lpVtbl = &uia_provider_vtbl; prov->elprov = elprov; - IRawElementProviderSimple_AddRef(elprov); prov->ref = 1; node->prov = &prov->IWineUiaProvider_iface;
+ /* + * If the UseComThreading ProviderOption is specified, all calls to the + * provided IRawElementProviderSimple need to respect the apartment type + * of the thread that creates the HUIANODE. i.e, if it's created in an + * STA, and the HUIANODE is used in an MTA, we need to provide a proxy. + */ + if (prov_opts & ProviderOptions_UseComThreading) + { + IGlobalInterfaceTable *git; + + hr = get_global_interface_table(&git); + if (FAILED(hr)) + { + heap_free(prov); + return hr; + } + + hr = IGlobalInterfaceTable_RegisterInterfaceInGlobal(git, (IUnknown *)&prov->IWineUiaProvider_iface, + &IID_IWineUiaProvider, &node->git_cookie); + if (FAILED(hr)) + { + heap_free(prov); + return hr; + } + } + + IRawElementProviderSimple_AddRef(elprov); return S_OK; }
diff --git a/dlls/uiautomationcore/uia_main.c b/dlls/uiautomationcore/uia_main.c index b48fb262cd0..3a6f10c1647 100644 --- a/dlls/uiautomationcore/uia_main.c +++ b/dlls/uiautomationcore/uia_main.c @@ -16,9 +16,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#define COBJMACROS + +#include "combaseapi.h" #include "initguid.h" #include "uia_private.h" -#include "ocidl.h"
#include "wine/debug.h" #include "wine/heap.h"