From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_client.c | 10 --- dlls/uiautomationcore/uia_event.c | 134 ++++++++++++++++++++++++++-- dlls/uiautomationcore/uia_private.h | 13 +-- 3 files changed, 127 insertions(+), 30 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index e5cdec27e0b..25e76db8128 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -567,16 +567,6 @@ static HRESULT WINAPI uia_node_attach_event(IWineUiaNode *iface, long proc_id, l return hr; }
- /* - * Attach this nested node to the serverside event to keep the provider - * thread alive. - */ - if (*ret_event) - { - IWineUiaNode_AddRef(iface); - event->u.serverside.node = iface; - } - /* * Pre-existing serverside event that has already had its initial * advise call and gotten event data - if we've got new advisers, we need diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index dd3578e7bd5..fbcb0f0b2cc 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -199,6 +199,119 @@ static void uia_event_map_entry_release(struct uia_event_map_entry *entry) } }
+/* + * UI Automation event thread. + */ +struct uia_event_thread +{ + HANDLE hthread; + HWND hwnd; + LONG ref; +}; + +#define WM_UIA_EVENT_THREAD_STOP (WM_USER + 1) +static struct uia_event_thread event_thread; +static CRITICAL_SECTION event_thread_cs; +static CRITICAL_SECTION_DEBUG event_thread_cs_debug = +{ + 0, 0, &event_thread_cs, + { &event_thread_cs_debug.ProcessLocksList, &event_thread_cs_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": event_thread_cs") } +}; +static CRITICAL_SECTION event_thread_cs = { &event_thread_cs_debug, -1, 0, 0, 0, 0 }; + +static DWORD WINAPI uia_event_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 (!hwnd) + { + WARN("CreateWindow failed: %ld\n", GetLastError()); + CoUninitialize(); + FreeLibraryAndExitThread(huia_module, 1); + } + + event_thread.hwnd = hwnd; + + /* Initialization complete, thread can now process window messages. */ + SetEvent(initialized_event); + TRACE("Event thread started.\n"); + while (GetMessageW(&msg, NULL, 0, 0)) + { + if (msg.message == WM_UIA_EVENT_THREAD_STOP) + break; + + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + TRACE("Shutting down UI Automation event thread.\n"); + + DestroyWindow(hwnd); + CoUninitialize(); + FreeLibraryAndExitThread(huia_module, 0); +} + +static BOOL uia_start_event_thread(void) +{ + BOOL started = TRUE; + + EnterCriticalSection(&event_thread_cs); + if (++event_thread.ref == 1) + { + HANDLE ready_event = NULL; + HANDLE events[2]; + HMODULE hmodule; + DWORD wait_obj; + + /* Increment DLL reference count. */ + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (const WCHAR *)uia_start_event_thread, &hmodule); + + events[0] = ready_event = CreateEventW(NULL, FALSE, FALSE, NULL); + if (!(event_thread.hthread = CreateThread(NULL, 0, uia_event_thread_proc, + ready_event, 0, NULL))) + { + FreeLibrary(hmodule); + started = FALSE; + goto exit; + } + + events[1] = event_thread.hthread; + wait_obj = WaitForMultipleObjects(2, events, FALSE, INFINITE); + if (wait_obj != WAIT_OBJECT_0) + { + CloseHandle(event_thread.hthread); + started = FALSE; + } + +exit: + if (ready_event) + CloseHandle(ready_event); + if (!started) + memset(&event_thread, 0, sizeof(event_thread)); + } + + LeaveCriticalSection(&event_thread_cs); + return started; +} + +static void uia_stop_event_thread(void) +{ + EnterCriticalSection(&event_thread_cs); + if (!--event_thread.ref) + { + PostMessageW(event_thread.hwnd, WM_UIA_EVENT_THREAD_STOP, 0, 0); + CloseHandle(event_thread.hthread); + memset(&event_thread, 0, sizeof(event_thread)); + } + LeaveCriticalSection(&event_thread_cs); +} + /* * IWineUiaEvent interface. */ @@ -248,8 +361,8 @@ static ULONG WINAPI uia_event_Release(IWineUiaEvent *iface) if (event->event_type == EVENT_TYPE_CLIENTSIDE) { uia_cache_request_destroy(&event->u.clientside.cache_req); - if (event->u.clientside.mta_cookie) - CoDecrementMTAUsage(event->u.clientside.mta_cookie); + if (event->u.clientside.git_cookie) + uia_stop_event_thread(); } else { @@ -259,8 +372,7 @@ static ULONG WINAPI uia_event_Release(IWineUiaEvent *iface) LeaveCriticalSection(&event_map_cs); if (event->u.serverside.event_iface) IWineUiaEvent_Release(event->u.serverside.event_iface); - if (event->u.serverside.node) - IWineUiaNode_Release(event->u.serverside.node); + uia_stop_event_thread(); }
for (i = 0; i < event->event_advisers_count; i++) @@ -414,6 +526,13 @@ HRESULT create_serverside_uia_event(struct uia_event **out_event, LONG process_i if (FAILED(hr)) goto exit;
+ if (!uia_start_event_thread()) + { + heap_free(event); + hr = E_FAIL; + goto exit; + } + event->u.serverside.proc_id = process_id; uia_event_map.serverside_event_count++; if (uia_event_map.serverside_event_count == 1) @@ -679,15 +798,14 @@ HRESULT uia_event_add_serverside_event_adviser(IWineUiaEvent *serverside_event, */ if (!event->u.clientside.git_cookie) { - hr = CoIncrementMTAUsage(&event->u.clientside.mta_cookie); - if (FAILED(hr)) - return hr; + if (!uia_start_event_thread()) + return E_FAIL;
hr = register_interface_in_git((IUnknown *)&event->IWineUiaEvent_iface, &IID_IWineUiaEvent, &event->u.clientside.git_cookie); if (FAILED(hr)) { - CoDecrementMTAUsage(event->u.clientside.mta_cookie); + uia_stop_event_thread(); return hr; } } diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 60830c7c8a5..5aeab22d3ee 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -125,22 +125,11 @@ struct uia_event struct UiaCacheRequest cache_req; UiaEventCallback *cback;
- /* - * This is temporarily used to keep the MTA alive prior to our - * introduction of a dedicated event thread. - */ - CO_MTA_USAGE_COOKIE mta_cookie; DWORD git_cookie; } clientside; struct { - /* - * Similar to the client MTA cookie, used to keep the provider - * thread alive as a temporary measure before introducing the - * event thread. - */ - IWineUiaNode *node; - IWineUiaEvent *event_iface; + struct rb_entry serverside_event_entry; LONG proc_id; } serverside;
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_event.c | 52 ++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-)
diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index fbcb0f0b2cc..242ee992c40 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -199,6 +199,38 @@ static void uia_event_map_entry_release(struct uia_event_map_entry *entry) } }
+/* + * Functions for struct uia_event_args, a reference counted structure + * used to store event arguments. This is necessary for serverside events + * as they're raised on a background thread after the event raising + * function has returned. + */ +struct uia_event_args +{ + struct UiaEventArgs simple_args; + LONG ref; +}; + +static struct uia_event_args *create_uia_event_args(const struct uia_event_info *event_info) +{ + struct uia_event_args *args = heap_alloc_zero(sizeof(*args)); + + if (!args) + return NULL; + + args->simple_args.Type = event_info->event_arg_type; + args->simple_args.EventId = event_info->event_id; + args->ref = 1; + + return args; +} + +static void uia_event_args_release(struct uia_event_args *args) +{ + if (!InterlockedDecrement(&args->ref)) + heap_free(args); +} + /* * UI Automation event thread. */ @@ -975,7 +1007,7 @@ HRESULT WINAPI UiaRemoveEvent(HUIAEVENT huiaevent) return S_OK; }
-static HRESULT uia_event_invoke(HUIANODE node, struct UiaEventArgs *args, struct uia_event *event) +static HRESULT uia_event_invoke(HUIANODE node, struct uia_event_args *args, struct uia_event *event) { SAFEARRAY *out_req; BSTR tree_struct; @@ -985,7 +1017,7 @@ static HRESULT uia_event_invoke(HUIANODE node, struct UiaEventArgs *args, struct &tree_struct); if (SUCCEEDED(hr)) { - event->u.clientside.cback(args, out_req, tree_struct); + event->u.clientside.cback(&args->simple_args, out_req, tree_struct); SafeArrayDestroy(out_req); SysFreeString(tree_struct); } @@ -996,7 +1028,8 @@ static HRESULT uia_event_invoke(HUIANODE node, struct UiaEventArgs *args, struct /* * Check if the provider that raised the event matches this particular event. */ -static HRESULT uia_event_check_match(HUIANODE node, SAFEARRAY *rt_id, struct UiaEventArgs *args, struct uia_event *event) +static HRESULT uia_event_check_match(HUIANODE node, SAFEARRAY *rt_id, struct uia_event_args *args, + struct uia_event *event) { struct UiaPropertyCondition prop_cond = { ConditionType_Property, UIA_RuntimeIdPropertyId }; struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); @@ -1055,7 +1088,7 @@ static HRESULT uia_event_check_match(HUIANODE node, SAFEARRAY *rt_id, struct Uia return hr; }
-static HRESULT uia_raise_event(IRawElementProviderSimple *elprov, struct UiaEventArgs *args) +static HRESULT uia_raise_event(IRawElementProviderSimple *elprov, struct uia_event_args *args) { struct uia_event_map_entry *event_entry; enum ProviderOptions prov_opts = 0; @@ -1069,7 +1102,7 @@ static HRESULT uia_raise_event(IRawElementProviderSimple *elprov, struct UiaEven return hr;
EnterCriticalSection(&event_map_cs); - if ((event_entry = uia_get_event_map_entry_for_event(args->EventId))) + if ((event_entry = uia_get_event_map_entry_for_event(args->simple_args.EventId))) InterlockedIncrement(&event_entry->refs); LeaveCriticalSection(&event_map_cs);
@@ -1120,7 +1153,7 @@ static HRESULT uia_raise_event(IRawElementProviderSimple *elprov, struct UiaEven HRESULT WINAPI UiaRaiseAutomationEvent(IRawElementProviderSimple *elprov, EVENTID id) { const struct uia_event_info *event_info = uia_event_info_from_id(id); - struct UiaEventArgs args = { EventArgsType_Simple, id }; + struct uia_event_args *args; HRESULT hr;
TRACE("(%p, %d)\n", elprov, id); @@ -1138,7 +1171,12 @@ HRESULT WINAPI UiaRaiseAutomationEvent(IRawElementProviderSimple *elprov, EVENTI return S_OK; }
- hr = uia_raise_event(elprov, &args); + args = create_uia_event_args(event_info); + if (!args) + return E_OUTOFMEMORY; + + hr = uia_raise_event(elprov, args); + uia_event_args_release(args); if (FAILED(hr)) return hr;
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 4 +- dlls/uiautomationcore/uia_classes.idl | 1 + dlls/uiautomationcore/uia_client.c | 14 +- dlls/uiautomationcore/uia_event.c | 206 +++++++++++++++++++-- dlls/uiautomationcore/uia_private.h | 12 ++ 5 files changed, 207 insertions(+), 30 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index f7f16927b38..788eb1a4eba 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -13976,8 +13976,8 @@ static void test_UiaAddEvent_client_proc(void) post_event_message(hwnd, WM_UIA_TEST_RAISE_EVENT, HandleToUlong(hwnd), PROVIDER_ID, ProviderOptions_ServerSideProvider); todo_wine ok(!WaitForSingleObject(EventData.event_handle, 2000), "Wait for event_handle failed.\n"); todo_wine CHECK_CALLED(uia_event_callback); - todo_wine CHECK_CALLED(prov_callback_base_hwnd); - todo_wine CHECK_CALLED(prov_callback_nonclient); + CHECK_CALLED(prov_callback_base_hwnd); + CHECK_CALLED(prov_callback_nonclient); todo_wine CHECK_CALLED(prov_callback_proxy);
/* diff --git a/dlls/uiautomationcore/uia_classes.idl b/dlls/uiautomationcore/uia_classes.idl index 25417d6c549..8eb9998f945 100644 --- a/dlls/uiautomationcore/uia_classes.idl +++ b/dlls/uiautomationcore/uia_classes.idl @@ -75,6 +75,7 @@ library UIA_wine_private HRESULT advise_events([in]BOOL advise_added, [in]long adviser_start_idx); HRESULT set_event_data([in]const GUID *event_guid, [in]long scope, [in]VARIANT runtime_id, [in]IWineUiaEvent *event_iface); + HRESULT raise_event([in]VARIANT in_node); }
[ diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 25e76db8128..492ca0e52de 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -1266,17 +1266,6 @@ static ULONG WINAPI uia_provider_Release(IWineUiaProvider *iface) return ref; }
-static void get_variant_for_node(HUIANODE node, VARIANT *v) -{ -#ifdef _WIN64 - V_VT(v) = VT_I8; - V_I8(v) = (UINT64)node; -#else - V_VT(v) = VT_I4; - V_I4(v) = (UINT32)node; -#endif -} - static HRESULT get_variant_for_elprov_node(IRawElementProviderSimple *elprov, BOOL out_nested, VARIANT *v) { @@ -2171,7 +2160,6 @@ static ULONG WINAPI uia_nested_node_provider_Release(IWineUiaProvider *iface) return ref; }
-static HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode); static HRESULT WINAPI uia_nested_node_provider_get_prop_val(IWineUiaProvider *iface, const struct uia_prop_info *prop_info, VARIANT *ret_val) { @@ -2430,7 +2418,7 @@ static HRESULT create_wine_uia_nested_node_provider(struct uia_node *node, LRESU return S_OK; }
-static HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode) +HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode) { struct uia_node *node; HRESULT hr; diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 242ee992c40..4f5483d4c74 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -79,6 +79,7 @@ struct uia_event_map_entry * ignored. */ struct list events_list; + struct list serverside_events_list; };
struct uia_event_identifier { @@ -146,6 +147,7 @@ static HRESULT uia_event_map_add_event(struct uia_event *event)
event_entry->event_id = event->event_id; list_init(&event_entry->events_list); + list_init(&event_entry->serverside_events_list);
if (!uia_event_map.event_count) rb_init(&uia_event_map.event_map, uia_event_map_id_compare); @@ -154,7 +156,10 @@ static HRESULT uia_event_map_add_event(struct uia_event *event) }
IWineUiaEvent_AddRef(&event->IWineUiaEvent_iface); - list_add_head(&event_entry->events_list, &event->event_list_entry); + if (event->event_type == EVENT_TYPE_SERVERSIDE) + list_add_head(&event_entry->serverside_events_list, &event->event_list_entry); + else + list_add_head(&event_entry->events_list, &event->event_list_entry); InterlockedIncrement(&event_entry->refs);
event->event_map_entry = event_entry; @@ -195,6 +200,13 @@ static void uia_event_map_entry_release(struct uia_event_map_entry *entry) IWineUiaEvent_Release(&event->IWineUiaEvent_iface); }
+ LIST_FOR_EACH_SAFE(cursor, cursor2, &entry->serverside_events_list) + { + struct uia_event *event = LIST_ENTRY(cursor, struct uia_event, event_list_entry); + + IWineUiaEvent_Release(&event->IWineUiaEvent_iface); + } + heap_free(entry); } } @@ -239,9 +251,12 @@ struct uia_event_thread HANDLE hthread; HWND hwnd; LONG ref; + + struct list *event_queue; };
#define WM_UIA_EVENT_THREAD_STOP (WM_USER + 1) +#define WM_UIA_EVENT_THREAD_PROCESS_QUEUE (WM_USER + 2) static struct uia_event_thread event_thread; static CRITICAL_SECTION event_thread_cs; static CRITICAL_SECTION_DEBUG event_thread_cs_debug = @@ -252,12 +267,108 @@ static CRITICAL_SECTION_DEBUG event_thread_cs_debug = }; static CRITICAL_SECTION event_thread_cs = { &event_thread_cs_debug, -1, 0, 0, 0, 0 };
+struct uia_queue_event +{ + struct list event_queue_entry; + + struct uia_event_args *args; + struct uia_event *event; + VARIANT node; +}; + +static HRESULT uia_event_queue_post_event(struct uia_event *event, VARIANT node, struct uia_event_args *args) +{ + struct uia_queue_event *queue_event = heap_alloc_zero(sizeof(*queue_event)); + + if (!queue_event) + return E_OUTOFMEMORY; + + IWineUiaEvent_AddRef(&event->IWineUiaEvent_iface); + InterlockedIncrement(&args->ref); + queue_event->args = args; + queue_event->event = event; + queue_event->node = node; + + EnterCriticalSection(&event_thread_cs); + + assert(event_thread.event_queue); + list_add_tail(event_thread.event_queue, &queue_event->event_queue_entry); + PostMessageW(event_thread.hwnd, WM_UIA_EVENT_THREAD_PROCESS_QUEUE, 0, 0); + + LeaveCriticalSection(&event_thread_cs); + + return S_OK; +} + +static struct uia_queue_event *uia_event_queue_pop(struct list *event_queue, BOOL *event_thread_shutdown) +{ + struct uia_queue_event *queue_event = NULL; + + if (!*event_thread_shutdown) + { + EnterCriticalSection(&event_thread_cs); + if (event_queue != event_thread.event_queue) + { + *event_thread_shutdown = TRUE; + LeaveCriticalSection(&event_thread_cs); + } + } + + if (!list_empty(event_queue)) + { + queue_event = LIST_ENTRY(list_head(event_queue), struct uia_queue_event, event_queue_entry); + list_remove(list_head(event_queue)); + } + + if (!*event_thread_shutdown) + LeaveCriticalSection(&event_thread_cs); + + return queue_event; +} + +static void uia_event_thread_process_queue(struct list *event_queue) +{ + BOOL event_thread_shutdown = FALSE; + + while (1) + { + struct uia_queue_event *event; + HUIANODE node; + HRESULT hr; + + if (!(event = uia_event_queue_pop(event_queue, &event_thread_shutdown))) + break; + + hr = UiaHUiaNodeFromVariant(&event->node, &node); + if (SUCCEEDED(hr)) + { + LRESULT lr; + VARIANT v; + + if ((lr = uia_lresult_from_node(node))) + { + V_VT(&v) = VT_I4; + V_I4(&v) = lr; + hr = IWineUiaEvent_raise_event(event->event->u.serverside.event_iface, v); + if (FAILED(hr)) + WARN("IWineUiaEvent_raise_event failed with hr %#lx\n", hr); + } + } + + uia_event_args_release(event->args); + IWineUiaEvent_Release(&event->event->IWineUiaEvent_iface); + heap_free(event); + } +} + static DWORD WINAPI uia_event_thread_proc(void *arg) { HANDLE initialized_event = arg; + struct list event_queue; HWND hwnd; MSG msg;
+ list_init(&event_queue); CoInitializeEx(NULL, COINIT_MULTITHREADED); hwnd = CreateWindowW(L"Message", NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); if (!hwnd) @@ -268,14 +379,19 @@ static DWORD WINAPI uia_event_thread_proc(void *arg) }
event_thread.hwnd = hwnd; + event_thread.event_queue = &event_queue;
/* Initialization complete, thread can now process window messages. */ SetEvent(initialized_event); TRACE("Event thread started.\n"); while (GetMessageW(&msg, NULL, 0, 0)) { - if (msg.message == WM_UIA_EVENT_THREAD_STOP) - break; + if (msg.message == WM_UIA_EVENT_THREAD_STOP || msg.message == WM_UIA_EVENT_THREAD_PROCESS_QUEUE) + { + uia_event_thread_process_queue(&event_queue); + if (msg.message == WM_UIA_EVENT_THREAD_STOP) + break; + }
TranslateMessage(&msg); DispatchMessageW(&msg); @@ -431,6 +547,17 @@ static HRESULT WINAPI uia_event_advise_events(IWineUiaEvent *iface, BOOL advise_ return hr; }
+ /* + * First call to advise events on a serverside provider, add it to the + * events list so it can be raised. + */ + if (!adviser_start_idx && advise_added && event->event_type == EVENT_TYPE_SERVERSIDE) + { + hr = uia_event_map_add_event(event); + if (FAILED(hr)) + WARN("Failed to add event to event map, hr %#lx\n", hr); + } + /* * Once we've advised of removal, no need to keep the advisers around. * We can also release our reference to the event map. @@ -438,9 +565,7 @@ static HRESULT WINAPI uia_event_advise_events(IWineUiaEvent *iface, BOOL advise_ if (!advise_added) { InterlockedIncrement(&event->event_defunct); - /* FIXME: Remove this check once we can raise serverside events. */ - if (event->event_type == EVENT_TYPE_CLIENTSIDE) - uia_event_map_entry_release(event->event_map_entry); + uia_event_map_entry_release(event->event_map_entry); event->event_map_entry = NULL;
for (i = 0; i < event->event_advisers_count; i++) @@ -480,12 +605,31 @@ static HRESULT WINAPI uia_event_set_event_data(IWineUiaEvent *iface, const GUID return S_OK; }
+static HRESULT WINAPI uia_event_raise_event(IWineUiaEvent *iface, VARIANT in_node) +{ + struct uia_event *event = impl_from_IWineUiaEvent(iface); + HUIANODE node; + HRESULT hr; + + FIXME("%p, %s: stub\n", iface, debugstr_variant(&in_node)); + + assert(event->event_type != EVENT_TYPE_SERVERSIDE); + + hr = uia_node_from_lresult((LRESULT)V_I4(&in_node), &node); + if (FAILED(hr)) + return hr; + + UiaNodeRelease(node); + return S_OK; +} + static const IWineUiaEventVtbl uia_event_vtbl = { uia_event_QueryInterface, uia_event_AddRef, uia_event_Release, uia_event_advise_events, uia_event_set_event_data, + uia_event_raise_event, };
static struct uia_event *unsafe_impl_from_IWineUiaEvent(IWineUiaEvent *iface) @@ -1009,17 +1153,31 @@ HRESULT WINAPI UiaRemoveEvent(HUIAEVENT huiaevent)
static HRESULT uia_event_invoke(HUIANODE node, struct uia_event_args *args, struct uia_event *event) { - SAFEARRAY *out_req; - BSTR tree_struct; - HRESULT hr; + HRESULT hr = S_OK;
- hr = UiaGetUpdatedCache(node, &event->u.clientside.cache_req, NormalizeState_View, NULL, &out_req, - &tree_struct); - if (SUCCEEDED(hr)) + if (event->event_type == EVENT_TYPE_CLIENTSIDE) { - event->u.clientside.cback(&args->simple_args, out_req, tree_struct); - SafeArrayDestroy(out_req); - SysFreeString(tree_struct); + SAFEARRAY *out_req; + BSTR tree_struct; + + hr = UiaGetUpdatedCache(node, &event->u.clientside.cache_req, NormalizeState_View, NULL, &out_req, + &tree_struct); + if (SUCCEEDED(hr)) + { + event->u.clientside.cback(&args->simple_args, out_req, tree_struct); + SafeArrayDestroy(out_req); + SysFreeString(tree_struct); + } + } + else + { + VARIANT v; + + get_variant_for_node(node, &v); + IWineUiaNode_AddRef((IWineUiaNode *)node); + hr = uia_event_queue_post_event(event, v, args); + if (FAILED(hr)) + UiaNodeRelease(node); }
return hr; @@ -1056,6 +1214,12 @@ static HRESULT uia_event_check_match(HUIANODE node, SAFEARRAY *rt_id, struct uia if (!(event->scope & (TreeScope_Descendants | TreeScope_Children))) return S_OK;
+ if (event->event_type == EVENT_TYPE_SERVERSIDE) + { + FIXME("Matching serverside events through navigation currently unimplemented\n"); + return S_OK; + } + V_VT(&prop_cond.Value) = VT_I4 | VT_ARRAY; V_ARRAY(&prop_cond.Value) = event->runtime_id;
@@ -1140,6 +1304,18 @@ static HRESULT uia_raise_event(IRawElementProviderSimple *elprov, struct uia_eve break; }
+ if (prov_opts & ProviderOptions_ServerSideProvider) + { + LIST_FOR_EACH_SAFE(cursor, cursor2, &event_entry->serverside_events_list) + { + struct uia_event *event = LIST_ENTRY(cursor, struct uia_event, event_list_entry); + + hr = uia_event_check_match(node, sa, args, event); + if (FAILED(hr)) + break; + } + } + uia_event_map_entry_release(event_entry); SafeArrayDestroy(sa); UiaNodeRelease(node); diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 5aeab22d3ee..325206c6d90 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -148,6 +148,17 @@ static inline void variant_init_i4(VARIANT *v, int val) V_I4(v) = val; }
+static inline void get_variant_for_node(HUIANODE node, VARIANT *v) +{ +#ifdef _WIN64 + V_VT(v) = VT_I8; + V_I8(v) = (UINT64)node; +#else + V_VT(v) = VT_I4; + V_I4(v) = (UINT32)node; +#endif +} + static inline BOOL uia_array_reserve(void **elements, SIZE_T *capacity, SIZE_T count, SIZE_T size) { SIZE_T max_capacity, new_capacity; @@ -184,6 +195,7 @@ HRESULT attach_event_to_uia_node(HUIANODE node, struct uia_event *event) DECLSPE HRESULT navigate_uia_node(struct uia_node *node, int nav_dir, HUIANODE *out_node) DECLSPEC_HIDDEN; HRESULT create_uia_node_from_elprov(IRawElementProviderSimple *elprov, HUIANODE *out_node, BOOL get_hwnd_providers) DECLSPEC_HIDDEN; +HRESULT uia_node_from_lresult(LRESULT lr, HUIANODE *huianode) DECLSPEC_HIDDEN; HRESULT uia_condition_check(HUIANODE node, struct UiaCondition *condition) DECLSPEC_HIDDEN; BOOL uia_condition_matched(HRESULT hr) DECLSPEC_HIDDEN;
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_client.c | 67 +++++++++++++++++++++++++++++ dlls/uiautomationcore/uia_event.c | 10 +++-- dlls/uiautomationcore/uia_private.h | 1 + 3 files changed, 75 insertions(+), 3 deletions(-)
diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 492ca0e52de..5fe3078bec1 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -673,6 +673,73 @@ static HRESULT prepare_uia_node(struct uia_node *node) return S_OK; }
+static HRESULT create_wine_uia_provider(struct uia_node *node, IRawElementProviderSimple *elprov, int prov_type); +HRESULT clone_uia_node(HUIANODE in_node, HUIANODE *out_node) +{ + struct uia_node *in_node_data = impl_from_IWineUiaNode((IWineUiaNode *)in_node); + struct uia_node *node; + HRESULT hr = S_OK; + int i; + + *out_node = NULL; + if (in_node_data->nested_node) + { + FIXME("Cloning of nested nodes currently unimplemented\n"); + return E_NOTIMPL; + } + + for (i = 0; i < PROV_TYPE_COUNT; i++) + { + if (in_node_data->prov[i] && is_nested_node_provider(in_node_data->prov[i])) + { + FIXME("Cloning of nested node providers currently unimplemented\n"); + return E_NOTIMPL; + } + } + + if (!(node = heap_alloc_zero(sizeof(*node)))) + return E_OUTOFMEMORY; + + node->IWineUiaNode_iface.lpVtbl = &uia_node_vtbl; + list_init(&node->prov_thread_list_entry); + list_init(&node->node_map_list_entry); + node->ref = 1; + node->hwnd = in_node_data->hwnd; + + for (i = 0; i < PROV_TYPE_COUNT; i++) + { + struct uia_provider *in_prov_data; + + if (!in_node_data->prov[i]) + continue; + + in_prov_data = impl_from_IWineUiaProvider(in_node_data->prov[i]); + hr = create_wine_uia_provider(node, in_prov_data->elprov, i); + if (FAILED(hr)) + goto exit; + + if (in_node_data->git_cookie[i]) + { + hr = register_interface_in_git((IUnknown *)node->prov[i], &IID_IWineUiaProvider, &node->git_cookie[i]); + if (FAILED(hr)) + goto exit; + } + } + + node->parent_link_idx = in_node_data->parent_link_idx; + node->creator_prov_idx = in_node_data->creator_prov_idx; + node->creator_prov_type = in_node_data->creator_prov_type; + + *out_node = (void *)&node->IWineUiaNode_iface; + TRACE("Created clone node %p from node %p\n", *out_node, in_node); + +exit: + if (FAILED(hr)) + IWineUiaNode_Release(&node->IWineUiaNode_iface); + + return hr; +} + static BOOL node_creator_is_parent_link(struct uia_node *node) { if (node->creator_prov_idx == node->parent_link_idx) diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 4f5483d4c74..44f184cda7d 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -1171,13 +1171,17 @@ static HRESULT uia_event_invoke(HUIANODE node, struct uia_event_args *args, stru } else { + HUIANODE node2; VARIANT v;
- get_variant_for_node(node, &v); - IWineUiaNode_AddRef((IWineUiaNode *)node); + hr = clone_uia_node(node, &node2); + if (FAILED(hr)) + return hr; + + get_variant_for_node(node2, &v); hr = uia_event_queue_post_event(event, v, args); if (FAILED(hr)) - UiaNodeRelease(node); + UiaNodeRelease(node2); }
return hr; diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 325206c6d90..727c0be6894 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -192,6 +192,7 @@ static inline BOOL uia_array_reserve(void **elements, SIZE_T *capacity, SIZE_T c /* uia_client.c */ int get_node_provider_type_at_idx(struct uia_node *node, int idx) DECLSPEC_HIDDEN; HRESULT attach_event_to_uia_node(HUIANODE node, struct uia_event *event) DECLSPEC_HIDDEN; +HRESULT clone_uia_node(HUIANODE in_node, HUIANODE *out_node) DECLSPEC_HIDDEN; HRESULT navigate_uia_node(struct uia_node *node, int nav_dir, HUIANODE *out_node) DECLSPEC_HIDDEN; HRESULT create_uia_node_from_elprov(IRawElementProviderSimple *elprov, HUIANODE *out_node, BOOL get_hwnd_providers) DECLSPEC_HIDDEN;
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 12 ++-- dlls/uiautomationcore/uia_event.c | 79 +++++++++++++++------- 2 files changed, 62 insertions(+), 29 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 788eb1a4eba..4845fe7ab33 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -13974,8 +13974,8 @@ static void test_UiaAddEvent_client_proc(void) SET_EXPECT(prov_callback_proxy); SET_EXPECT(uia_event_callback); post_event_message(hwnd, WM_UIA_TEST_RAISE_EVENT, HandleToUlong(hwnd), PROVIDER_ID, ProviderOptions_ServerSideProvider); - todo_wine ok(!WaitForSingleObject(EventData.event_handle, 2000), "Wait for event_handle failed.\n"); - todo_wine CHECK_CALLED(uia_event_callback); + ok(!WaitForSingleObject(EventData.event_handle, 2000), "Wait for event_handle failed.\n"); + CHECK_CALLED(uia_event_callback); CHECK_CALLED(prov_callback_base_hwnd); CHECK_CALLED(prov_callback_nonclient); todo_wine CHECK_CALLED(prov_callback_proxy); @@ -13995,8 +13995,8 @@ static void test_UiaAddEvent_client_proc(void)
SET_EXPECT(uia_event_callback); post_event_message(hwnd, WM_UIA_TEST_RAISE_EVENT, 0, PROVIDER2_ID, ProviderOptions_ServerSideProvider); - todo_wine ok(!WaitForSingleObject(EventData.event_handle, 2000), "Wait for event_handle failed.\n"); - todo_wine CHECK_CALLED(uia_event_callback); + ok(!WaitForSingleObject(EventData.event_handle, 2000), "Wait for event_handle failed.\n"); + CHECK_CALLED(uia_event_callback);
hr = UiaRemoveEvent(event); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); @@ -14109,8 +14109,8 @@ static void test_UiaAddEvent_client_proc(void) set_event_data(0, 0, 1, 1, &exp_node_desc, L"P)"); SET_EXPECT(uia_event_callback); post_event_message(hwnd, WM_UIA_TEST_RAISE_EVENT_RT_ID, 0xbeef, PROVIDER2_ID, 0x1337); - todo_wine ok(!WaitForSingleObject(EventData.event_handle, 2000), "Wait for event_handle failed.\n"); - todo_wine CHECK_CALLED(uia_event_callback); + ok(!WaitForSingleObject(EventData.event_handle, 2000), "Wait for event_handle failed.\n"); + CHECK_CALLED(uia_event_callback);
hr = UiaRemoveEvent(event); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 44f184cda7d..931b5106aed 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -326,6 +326,46 @@ static struct uia_queue_event *uia_event_queue_pop(struct list *event_queue, BOO return queue_event; }
+static HRESULT uia_event_invoke(HUIANODE node, struct uia_event_args *args, struct uia_event *event); +static HRESULT uia_raise_clientside_event(struct uia_queue_event *event) +{ + HUIANODE node; + HRESULT hr; + + hr = uia_node_from_lresult((LRESULT)V_I4(&event->node), &node); + if (SUCCEEDED(hr)) + { + hr = uia_event_invoke(node, event->args, event->event); + UiaNodeRelease(node); + } + + return hr; +} + +static HRESULT uia_raise_serverside_event(struct uia_queue_event *event) +{ + HUIANODE node; + HRESULT hr; + + hr = UiaHUiaNodeFromVariant(&event->node, &node); + if (SUCCEEDED(hr)) + { + LRESULT lr; + VARIANT v; + + if ((lr = uia_lresult_from_node(node))) + { + V_VT(&v) = VT_I4; + V_I4(&v) = lr; + hr = IWineUiaEvent_raise_event(event->event->u.serverside.event_iface, v); + if (FAILED(hr)) + WARN("IWineUiaEvent_raise_event failed with hr %#lx\n", hr); + } + } + + return hr; +} + static void uia_event_thread_process_queue(struct list *event_queue) { BOOL event_thread_shutdown = FALSE; @@ -333,27 +373,18 @@ static void uia_event_thread_process_queue(struct list *event_queue) while (1) { struct uia_queue_event *event; - HUIANODE node; HRESULT hr;
if (!(event = uia_event_queue_pop(event_queue, &event_thread_shutdown))) break;
- hr = UiaHUiaNodeFromVariant(&event->node, &node); - if (SUCCEEDED(hr)) - { - LRESULT lr; - VARIANT v; - - if ((lr = uia_lresult_from_node(node))) - { - V_VT(&v) = VT_I4; - V_I4(&v) = lr; - hr = IWineUiaEvent_raise_event(event->event->u.serverside.event_iface, v); - if (FAILED(hr)) - WARN("IWineUiaEvent_raise_event failed with hr %#lx\n", hr); - } - } + if (event->event->event_type == EVENT_TYPE_CLIENTSIDE) + hr = uia_raise_clientside_event(event); + else + hr = uia_raise_serverside_event(event); + + if (FAILED(hr)) + WARN("Event raising function failed with hr %#lx\n", hr);
uia_event_args_release(event->args); IWineUiaEvent_Release(&event->event->IWineUiaEvent_iface); @@ -608,19 +639,21 @@ static HRESULT WINAPI uia_event_set_event_data(IWineUiaEvent *iface, const GUID static HRESULT WINAPI uia_event_raise_event(IWineUiaEvent *iface, VARIANT in_node) { struct uia_event *event = impl_from_IWineUiaEvent(iface); - HUIANODE node; + struct uia_event_args *args; HRESULT hr;
- FIXME("%p, %s: stub\n", iface, debugstr_variant(&in_node)); + TRACE("%p, %s\n", iface, debugstr_variant(&in_node));
assert(event->event_type != EVENT_TYPE_SERVERSIDE);
- hr = uia_node_from_lresult((LRESULT)V_I4(&in_node), &node); - if (FAILED(hr)) - return hr; + args = create_uia_event_args(uia_event_info_from_id(event->event_id)); + if (!args) + return E_OUTOFMEMORY;
- UiaNodeRelease(node); - return S_OK; + hr = uia_event_queue_post_event(event, in_node, args); + uia_event_args_release(args); + + return hr; }
static const IWineUiaEventVtbl uia_event_vtbl = {
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_event.c:
- hwnd = CreateWindowW(L"Message", NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
- if (!hwnd)
- {
WARN("CreateWindow failed: %ld\n", GetLastError());
CoUninitialize();
FreeLibraryAndExitThread(huia_module, 1);
- }
- event_thread.hwnd = hwnd;
- /* Initialization complete, thread can now process window messages. */
- SetEvent(initialized_event);
- TRACE("Event thread started.\n");
- while (GetMessageW(&msg, NULL, 0, 0))
- {
if (msg.message == WM_UIA_EVENT_THREAD_STOP)
I think the hwnd should be checked here, or the message window should be given a WndProc that responds to this with PostQuitMessage.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_event.c:
EnterCriticalSection(&event_thread_cs);
if (event_queue != event_thread.event_queue)
{
*event_thread_shutdown = TRUE;
LeaveCriticalSection(&event_thread_cs);
}
- }
- if (!list_empty(event_queue))
- {
queue_event = LIST_ENTRY(list_head(event_queue), struct uia_queue_event, event_queue_entry);
list_remove(list_head(event_queue));
- }
- if (!*event_thread_shutdown)
LeaveCriticalSection(&event_thread_cs);
I don't think the very slight optimization here is worth the complexity of two different LeaveCriticalSection calls.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_event.c:
- struct uia_event *event;
- VARIANT node;
+};
+static HRESULT uia_event_queue_post_event(struct uia_event *event, VARIANT node, struct uia_event_args *args) +{
- struct uia_queue_event *queue_event = heap_alloc_zero(sizeof(*queue_event));
- if (!queue_event)
return E_OUTOFMEMORY;
- IWineUiaEvent_AddRef(&event->IWineUiaEvent_iface);
- InterlockedIncrement(&args->ref);
- queue_event->args = args;
- queue_event->event = event;
- queue_event->node = node;
How do you make sure the node will still be valid when this event is processed?
On Tue Jun 20 20:34:06 2023 +0000, Esme Povirk wrote:
How do you make sure the node will still be valid when this event is processed?
I see, the caller addrefs and the event dispatch process indirectly releases by calling uia_lresult_from_node. That's confusing.
I also don't understand why this is stored in a VARIANT.
On Tue Jun 20 20:56:57 2023 +0000, Esme Povirk wrote:
I see, the caller addrefs and the event dispatch process indirectly releases by calling uia_lresult_from_node. That's confusing. I also don't understand why this is stored in a VARIANT.
The idea of storing it in a VARIANT is that we can use one generic structure/variable for both clientside and serverside events - a serverside event posts a VARIANT containing an HUIANODE pointer, and a clientside event posts a VARIANT containing an lresult that can be converted into an HUIANODE.
I spent time thinking about this when I designed it, there's a possibility of a delay when converting the lresult into a node. This could cause events to be put into the queue out of sequence, e.g:
- Event 1 arrives, calls `uia_node_from_lresult()` - Event 2 arrives, calls `uia_node_from_lresult()` - `uia_node_from_lresult()` returns for event 2, it gets posted to the queue. - `uia_node_from_lresult()` returns for event 1, it gets posted to the queue.
So IMO, it's best to just post the lresult for the node to the queue quickly and let the event thread do the conversion.
We could also add different structures for each, or a union depending on the type of event in the queue.