-- v2: uiautomationcore: Unconditionally match all events registered on the desktop node with a scope of subtree. uiautomationcore: Implement UiaRaiseAutomationEvent. uiautomationcore: Add support for cloning UiaCondition structures. uiautomationcore: Clone UiaCacheRequest structure passed to UiaAddEvent. uiautomationcore: Store all events in an event list.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_event.c | 131 +++++++++++++++++++++++++++- dlls/uiautomationcore/uia_private.h | 4 + 2 files changed, 134 insertions(+), 1 deletion(-)
diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 7fd4eda8ead..417532fa7a6 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -19,9 +19,124 @@ #include "uia_private.h"
#include "wine/debug.h" +#include "wine/rbtree.h"
WINE_DEFAULT_DEBUG_CHANNEL(uiautomation);
+/* + * UI Automation event map. + */ +static struct uia_event_map +{ + struct rb_tree event_map; + LONG event_count; +} uia_event_map; + +struct uia_event_map_entry +{ + struct rb_entry entry; + LONG refs; + + int event_id; + struct list events_list; +}; + +static CRITICAL_SECTION event_map_cs; +static CRITICAL_SECTION_DEBUG event_map_cs_debug = +{ + 0, 0, &event_map_cs, + { &event_map_cs_debug.ProcessLocksList, &event_map_cs_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": event_map_cs") } +}; +static CRITICAL_SECTION event_map_cs = { &event_map_cs_debug, -1, 0, 0, 0, 0 }; + +static int uia_event_map_id_compare(const void *key, const struct rb_entry *entry) +{ + struct uia_event_map_entry *event_entry = RB_ENTRY_VALUE(entry, struct uia_event_map_entry, entry); + int event_id = *((int *)key); + + return (event_entry->event_id > event_id) - (event_entry->event_id < event_id); +} + +static struct uia_event_map_entry *uia_get_event_map_entry_for_event(int event_id) +{ + struct uia_event_map_entry *map_entry = NULL; + struct rb_entry *rb_entry; + + if (uia_event_map.event_count && (rb_entry = rb_get(&uia_event_map.event_map, &event_id))) + map_entry = RB_ENTRY_VALUE(rb_entry, struct uia_event_map_entry, entry); + + return map_entry; +} + +static HRESULT uia_event_map_add_event(struct uia_event *event) +{ + struct uia_event_map_entry *event_entry; + + EnterCriticalSection(&event_map_cs); + + if (!(event_entry = uia_get_event_map_entry_for_event(event->event_id))) + { + if (!(event_entry = heap_alloc_zero(sizeof(*event_entry)))) + { + LeaveCriticalSection(&event_map_cs); + return E_OUTOFMEMORY; + } + + event_entry->event_id = event->event_id; + list_init(&event_entry->events_list); + + if (InterlockedIncrement(&uia_event_map.event_count) == 1) + rb_init(&uia_event_map.event_map, uia_event_map_id_compare); + rb_put(&uia_event_map.event_map, &event->event_id, &event_entry->entry); + } + + IWineUiaEvent_AddRef(&event->IWineUiaEvent_iface); + list_add_head(&event_entry->events_list, &event->event_list_entry); + InterlockedIncrement(&event_entry->refs); + + event->event_map_entry = event_entry; + LeaveCriticalSection(&event_map_cs); + + return S_OK; +} + +static void uia_event_map_entry_release(struct uia_event_map_entry *entry) +{ + ULONG ref = InterlockedDecrement(&entry->refs); + + if (!ref) + { + struct list *cursor, *cursor2; + + EnterCriticalSection(&event_map_cs); + + /* + * Someone grabbed this while we were waiting to enter the CS, abort + * destruction. + */ + if (InterlockedCompareExchange(&entry->refs, 0, 0) != 0) + { + LeaveCriticalSection(&event_map_cs); + return; + } + + rb_remove(&uia_event_map.event_map, &entry->entry); + InterlockedDecrement(&uia_event_map.event_count); + LeaveCriticalSection(&event_map_cs); + + /* Release all events in the list. */ + LIST_FOR_EACH_SAFE(cursor, cursor2, &entry->events_list) + { + struct uia_event *event = LIST_ENTRY(cursor, struct uia_event, event_list_entry); + + IWineUiaEvent_Release(&event->IWineUiaEvent_iface); + } + + heap_free(entry); + } +} + /* * IWineUiaEvent interface. */ @@ -61,6 +176,9 @@ static ULONG WINAPI uia_event_Release(IWineUiaEvent *iface) { int i;
+ if (event->event_map_entry) + uia_event_map_entry_release(event->event_map_entry); + SafeArrayDestroy(event->runtime_id); for (i = 0; i < event->event_advisers_count; i++) IWineUiaEventAdviser_Release(event->event_advisers[i]); @@ -86,9 +204,16 @@ static HRESULT WINAPI uia_event_advise_events(IWineUiaEvent *iface, BOOL advise_ return hr; }
- /* Once we've advised of removal, no need to keep the advisers around. */ + /* + * Once we've advised of removal, no need to keep the advisers around. + * We can also release our reference to the event map. + */ if (!advise_added) { + InterlockedIncrement(&event->event_defunct); + uia_event_map_entry_release(event->event_map_entry); + event->event_map_entry = NULL; + for (i = 0; i < event->event_advisers_count; i++) IWineUiaEventAdviser_Release(event->event_advisers[i]); heap_free(event->event_advisers); @@ -326,6 +451,10 @@ HRESULT WINAPI UiaAddEvent(HUIANODE huianode, EVENTID event_id, UiaEventCallback if (FAILED(hr)) goto exit;
+ hr = uia_event_map_add_event(event); + if (FAILED(hr)) + goto exit; + *huiaevent = (HUIAEVENT)event;
exit: diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index bd5ca714b94..67fa9febba7 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -106,6 +106,10 @@ struct uia_event int event_advisers_count; SIZE_T event_advisers_arr_size;
+ struct list event_list_entry; + struct uia_event_map_entry *event_map_entry; + LONG event_defunct; + UiaEventCallback *cback; };
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_event.c | 5 ++++ dlls/uiautomationcore/uia_private.h | 3 +++ dlls/uiautomationcore/uia_utils.c | 42 +++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+)
diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 417532fa7a6..4b55534c18a 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -180,6 +180,7 @@ static ULONG WINAPI uia_event_Release(IWineUiaEvent *iface) uia_event_map_entry_release(event->event_map_entry);
SafeArrayDestroy(event->runtime_id); + uia_cache_request_destroy(&event->cache_req); for (i = 0; i < event->event_advisers_count; i++) IWineUiaEventAdviser_Release(event->event_advisers[i]); heap_free(event->event_advisers); @@ -443,6 +444,10 @@ HRESULT WINAPI UiaAddEvent(HUIANODE huianode, EVENTID event_id, UiaEventCallback return hr; }
+ hr = uia_cache_request_clone(&event->cache_req, cache_req); + if (FAILED(hr)) + goto exit; + hr = attach_event_to_uia_node(huianode, event); if (FAILED(hr)) goto exit; diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 67fa9febba7..26e70948c07 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -110,6 +110,7 @@ struct uia_event struct uia_event_map_entry *event_map_entry; LONG event_defunct;
+ struct UiaCacheRequest cache_req; UiaEventCallback *cback; };
@@ -186,6 +187,8 @@ HRESULT create_msaa_provider(IAccessible *acc, long child_id, HWND hwnd, BOOL kn HRESULT register_interface_in_git(IUnknown *iface, REFIID riid, DWORD *ret_cookie) DECLSPEC_HIDDEN; HRESULT unregister_interface_in_git(DWORD git_cookie) DECLSPEC_HIDDEN; HRESULT get_interface_in_git(REFIID riid, DWORD git_cookie, IUnknown **ret_iface) DECLSPEC_HIDDEN; +void uia_cache_request_destroy(struct UiaCacheRequest *cache_req) DECLSPEC_HIDDEN; +HRESULT uia_cache_request_clone(struct UiaCacheRequest *dst, struct UiaCacheRequest *src) DECLSPEC_HIDDEN; HRESULT get_safearray_dim_bounds(SAFEARRAY *sa, UINT dim, LONG *lbound, LONG *elems) DECLSPEC_HIDDEN; HRESULT get_safearray_bounds(SAFEARRAY *sa, LONG *lbound, LONG *elems) DECLSPEC_HIDDEN; int uia_compare_safearrays(SAFEARRAY *sa1, SAFEARRAY *sa2, int prop_type) DECLSPEC_HIDDEN; diff --git a/dlls/uiautomationcore/uia_utils.c b/dlls/uiautomationcore/uia_utils.c index ef7cf5ff9d2..03416418cce 100644 --- a/dlls/uiautomationcore/uia_utils.c +++ b/dlls/uiautomationcore/uia_utils.c @@ -98,6 +98,48 @@ HRESULT get_interface_in_git(REFIID riid, DWORD git_cookie, IUnknown **ret_iface return S_OK; }
+/* + * UiaCacheRequest cloning functions. + */ +void uia_cache_request_destroy(struct UiaCacheRequest *cache_req) +{ + heap_free(cache_req->pProperties); + heap_free(cache_req->pPatterns); +} + +HRESULT uia_cache_request_clone(struct UiaCacheRequest *dst, struct UiaCacheRequest *src) +{ + FIXME("Cache request condition cloning currently unimplemented\n"); + + dst->Scope = src->Scope; + dst->automationElementMode = src->automationElementMode; + if (src->cProperties) + { + if (!(dst->pProperties = heap_alloc_zero(sizeof(*dst->pProperties) * src->cProperties))) + { + uia_cache_request_destroy(dst); + return E_OUTOFMEMORY; + } + + dst->cProperties = src->cProperties; + memcpy(dst->pProperties, src->pProperties, sizeof(*dst->pProperties) * dst->cProperties); + } + + if (src->cPatterns) + { + if (!(dst->pPatterns = heap_alloc_zero(sizeof(*dst->pPatterns) * src->cPatterns))) + { + uia_cache_request_destroy(dst); + return E_OUTOFMEMORY; + } + + dst->cPatterns = src->cPatterns; + memcpy(dst->pPatterns, src->pPatterns, sizeof(*dst->pPatterns) * dst->cPatterns); + } + + return S_OK; +} + HRESULT get_safearray_dim_bounds(SAFEARRAY *sa, UINT dim, LONG *lbound, LONG *elems) { LONG ubound;
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_utils.c | 143 +++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 1 deletion(-)
diff --git a/dlls/uiautomationcore/uia_utils.c b/dlls/uiautomationcore/uia_utils.c index 03416418cce..03fdef40e01 100644 --- a/dlls/uiautomationcore/uia_utils.c +++ b/dlls/uiautomationcore/uia_utils.c @@ -98,18 +98,159 @@ HRESULT get_interface_in_git(REFIID riid, DWORD git_cookie, IUnknown **ret_iface return S_OK; }
+/* + * UiaCondition cloning functions. + */ +static void uia_condition_destroy(struct UiaCondition *cond) +{ + if (!cond) + return; + + switch (cond->ConditionType) + { + case ConditionType_Property: + { + struct UiaPropertyCondition *prop_cond = (struct UiaPropertyCondition *)cond; + + VariantClear(&prop_cond->Value); + break; + } + + case ConditionType_Not: + { + struct UiaNotCondition *not_cond = (struct UiaNotCondition *)cond; + + uia_condition_destroy(not_cond->pConditions); + break; + } + + case ConditionType_And: + case ConditionType_Or: + { + struct UiaAndOrCondition *and_or_cond = (struct UiaAndOrCondition *)cond; + int i; + + for (i = 0; i < and_or_cond->cConditions; i++) + uia_condition_destroy(and_or_cond->ppConditions[i]); + heap_free(and_or_cond->ppConditions); + break; + } + + default: + break; + } + + heap_free(cond); +} + +static HRESULT uia_condition_clone(struct UiaCondition **dst, struct UiaCondition *src) +{ + HRESULT hr = S_OK; + + *dst = NULL; + switch (src->ConditionType) + { + case ConditionType_True: + case ConditionType_False: + if (!(*dst = heap_alloc_zero(sizeof(*dst)))) + return E_OUTOFMEMORY; + + (*dst)->ConditionType = src->ConditionType; + break; + + case ConditionType_Property: + { + struct UiaPropertyCondition *prop_cond = heap_alloc_zero(sizeof(*prop_cond)); + struct UiaPropertyCondition *src_cond = (struct UiaPropertyCondition *)src; + + if (!prop_cond) + return E_OUTOFMEMORY; + + *dst = (struct UiaCondition *)prop_cond; + prop_cond->ConditionType = ConditionType_Property; + prop_cond->PropertyId = src_cond->PropertyId; + prop_cond->Flags = src_cond->Flags; + VariantInit(&prop_cond->Value); + hr = VariantCopy(&prop_cond->Value, &src_cond->Value); + break; + } + + case ConditionType_Not: + { + struct UiaNotCondition *not_cond = heap_alloc_zero(sizeof(*not_cond)); + struct UiaNotCondition *src_cond = (struct UiaNotCondition *)src; + + if (!not_cond) + return E_OUTOFMEMORY; + + *dst = (struct UiaCondition *)not_cond; + not_cond->ConditionType = ConditionType_Not; + hr = uia_condition_clone(¬_cond->pConditions, src_cond->pConditions); + break; + } + + case ConditionType_And: + case ConditionType_Or: + { + struct UiaAndOrCondition *and_or_cond = heap_alloc_zero(sizeof(*and_or_cond)); + struct UiaAndOrCondition *src_cond = (struct UiaAndOrCondition *)src; + int i; + + if (!and_or_cond) + return E_OUTOFMEMORY; + + *dst = (struct UiaCondition *)and_or_cond; + and_or_cond->ConditionType = src_cond->ConditionType; + and_or_cond->ppConditions = heap_alloc_zero(sizeof(*and_or_cond->ppConditions) * src_cond->cConditions); + if (!and_or_cond->ppConditions) + { + hr = E_OUTOFMEMORY; + goto exit; + } + + and_or_cond->cConditions = src_cond->cConditions; + for (i = 0; i < src_cond->cConditions; i++) + { + hr = uia_condition_clone(&and_or_cond->ppConditions[i], src_cond->ppConditions[i]); + if (FAILED(hr)) + goto exit; + } + + break; + } + + default: + WARN("Tried to clone condition with invalid type %d\n", src->ConditionType); + return E_INVALIDARG; + } + +exit: + if (FAILED(hr)) + { + uia_condition_destroy(*dst); + *dst = NULL; + } + + return hr; +} + /* * UiaCacheRequest cloning functions. */ void uia_cache_request_destroy(struct UiaCacheRequest *cache_req) { + uia_condition_destroy(cache_req->pViewCondition); heap_free(cache_req->pProperties); heap_free(cache_req->pPatterns); }
HRESULT uia_cache_request_clone(struct UiaCacheRequest *dst, struct UiaCacheRequest *src) { - FIXME("Cache request condition cloning currently unimplemented\n"); + HRESULT hr; + + hr = uia_condition_clone(&dst->pViewCondition, src->pViewCondition); + if (FAILED(hr)) + return hr;
dst->Scope = src->Scope; dst->automationElementMode = src->automationElementMode;
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 51 +++++-- dlls/uiautomationcore/uia_client.c | 8 +- dlls/uiautomationcore/uia_event.c | 167 +++++++++++++++++++++ dlls/uiautomationcore/uia_main.c | 9 -- dlls/uiautomationcore/uia_private.h | 3 + 5 files changed, 211 insertions(+), 27 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 838ff3fbe11..0ad9a883c1c 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -13869,6 +13869,32 @@ static void test_UiaRemoveEvent_args(HUIANODE node) ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); }
+static void test_UiaRaiseAutomationEvent_args(void) +{ + HRESULT hr; + + hr = UiaRaiseAutomationEvent(NULL, UIA_AutomationFocusChangedEventId); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + /* Returns failure code from get_ProviderOptions. */ + initialize_provider(&Provider2, 0, NULL, TRUE); + hr = UiaRaiseAutomationEvent(&Provider2.IRawElementProviderSimple_iface, UIA_AutomationFocusChangedEventId); + /* Windows 7 returns S_OK. */ + ok(hr == E_NOTIMPL || broken(hr == S_OK), "Unexpected hr %#lx.\n", hr); + + /* Invalid event ID - doesn't return failure code. */ + initialize_provider(&Provider2, ProviderOptions_ServerSideProvider, NULL, TRUE); + hr = UiaRaiseAutomationEvent(&Provider2.IRawElementProviderSimple_iface, 1); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* + * UIA_StructureChangedEventId should use UiaRaiseStructureChangedEvent. + * No failure code is returned, however. + */ + hr = UiaRaiseAutomationEvent(&Provider2.IRawElementProviderSimple_iface, UIA_StructureChangedEventId); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +} + static void test_UiaAddEvent(void) { IRawElementProviderFragmentRoot *embedded_roots[2] = { &Provider_child.IRawElementProviderFragmentRoot_iface, @@ -13908,6 +13934,7 @@ static void test_UiaAddEvent(void) /* Test valid function input arguments. */ test_UiaAddEvent_args(node); test_UiaRemoveEvent_args(node); + test_UiaRaiseAutomationEvent_args();
/* * Raise event without any registered event handlers. @@ -13915,8 +13942,7 @@ static void test_UiaAddEvent(void) method_sequences_enabled = TRUE; hr = UiaRaiseAutomationEvent(&Provider.IRawElementProviderSimple_iface, UIA_AutomationFocusChangedEventId); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - if (SUCCEEDED(hr) && sequence_cnt) - ok_method_sequence(event_seq1, "event_seq1"); + ok_method_sequence(event_seq1, "event_seq1");
/* * Register an event on a node without an HWND/RuntimeId. The event will @@ -13941,8 +13967,7 @@ static void test_UiaAddEvent(void) */ hr = UiaRaiseAutomationEvent(&Provider.IRawElementProviderSimple_iface, UIA_AutomationFocusChangedEventId); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - if (SUCCEEDED(hr) && sequence_cnt) - ok_method_sequence(event_seq3, "event_seq3"); + ok_method_sequence(event_seq3, "event_seq3");
hr = UiaRemoveEvent(event); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); @@ -13976,7 +14001,7 @@ static void test_UiaAddEvent(void) SET_EXPECT(uia_event_callback); hr = UiaRaiseAutomationEvent(&Provider.IRawElementProviderSimple_iface, UIA_AutomationFocusChangedEventId); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine CHECK_CALLED(uia_event_callback); + CHECK_CALLED(uia_event_callback);
/* * Runtime ID matches so event callback is invoked, but nothing matches @@ -13991,7 +14016,7 @@ static void test_UiaAddEvent(void) SET_EXPECT(uia_event_callback); hr = UiaRaiseAutomationEvent(&Provider.IRawElementProviderSimple_iface, UIA_AutomationFocusChangedEventId); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine CHECK_CALLED(uia_event_callback); + CHECK_CALLED(uia_event_callback); set_provider_prop_override(&Provider, NULL, 0);
method_sequences_enabled = TRUE; @@ -14027,7 +14052,7 @@ static void test_UiaAddEvent(void) SET_EXPECT(uia_event_callback); hr = UiaRaiseAutomationEvent(&Provider_child.IRawElementProviderSimple_iface, UIA_AutomationFocusChangedEventId); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine CHECK_CALLED(uia_event_callback); + CHECK_CALLED(uia_event_callback);
/* Provider_child doesn't match the view condition, but Provider does. */ variant_init_bool(&v, FALSE); @@ -14040,7 +14065,7 @@ static void test_UiaAddEvent(void) SET_EXPECT(uia_event_callback); hr = UiaRaiseAutomationEvent(&Provider_child.IRawElementProviderSimple_iface, UIA_AutomationFocusChangedEventId); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine CHECK_CALLED(uia_event_callback); + CHECK_CALLED(uia_event_callback); set_provider_prop_override(&Provider_child, NULL, 0);
hr = UiaRemoveEvent(event); @@ -14061,7 +14086,7 @@ static void test_UiaAddEvent(void) SET_EXPECT(uia_event_callback); hr = UiaRaiseAutomationEvent(&Provider_child_child.IRawElementProviderSimple_iface, UIA_AutomationFocusChangedEventId); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine CHECK_CALLED(uia_event_callback); + CHECK_CALLED(uia_event_callback);
hr = UiaRemoveEvent(event); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); @@ -14206,11 +14231,11 @@ static void test_UiaAddEvent(void) Provider_hwnd2.prov_opts = ProviderOptions_ClientSideProvider; hr = UiaRaiseAutomationEvent(&Provider_hwnd2.IRawElementProviderSimple_iface, UIA_AutomationFocusChangedEventId); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine CHECK_CALLED(winproc_GETOBJECT_UiaRoot); - todo_wine CHECK_CALLED(prov_callback_base_hwnd); - todo_wine CHECK_CALLED(prov_callback_nonclient); + CHECK_CALLED(winproc_GETOBJECT_UiaRoot); + CHECK_CALLED(prov_callback_base_hwnd); + CHECK_CALLED(prov_callback_nonclient); todo_wine CHECK_CALLED_MULTI(prov_callback_proxy, 2); - todo_wine CHECK_CALLED(uia_event_callback); + CHECK_CALLED(uia_event_callback);
hr = UiaRemoveEvent(event); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index 9c5595eaf4b..a94a6b8db81 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -747,7 +747,7 @@ static HRESULT get_child_for_node(struct uia_node *node, int start_prov_idx, int return S_OK; }
-static HRESULT navigate_uia_node(struct uia_node *node, int nav_dir, HUIANODE *out_node) +HRESULT navigate_uia_node(struct uia_node *node, int nav_dir, HUIANODE *out_node) { HRESULT hr; VARIANT v; @@ -835,8 +835,6 @@ static HRESULT navigate_uia_node(struct uia_node *node, int nav_dir, HUIANODE *o return S_OK; }
-static HRESULT uia_condition_check(HUIANODE node, struct UiaCondition *condition); -static BOOL uia_condition_matched(HRESULT hr); static HRESULT conditional_navigate_uia_node(struct uia_node *node, int nav_dir, struct UiaCondition *cond, HUIANODE *out_node) { @@ -3024,7 +3022,7 @@ void WINAPI UiaRegisterProviderCallback(UiaProviderCallback *callback) uia_provider_callback = default_uia_provider_callback; }
-static BOOL uia_condition_matched(HRESULT hr) +BOOL uia_condition_matched(HRESULT hr) { if (hr == S_FALSE) return FALSE; @@ -3085,7 +3083,7 @@ static HRESULT uia_property_condition_check(HUIANODE node, struct UiaPropertyCon return hr; }
-static HRESULT uia_condition_check(HUIANODE node, struct UiaCondition *condition) +HRESULT uia_condition_check(HUIANODE node, struct UiaCondition *condition) { HRESULT hr;
diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 4b55534c18a..3b24084d383 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -489,3 +489,170 @@ HRESULT WINAPI UiaRemoveEvent(HUIAEVENT huiaevent)
return S_OK; } + +static HRESULT uia_event_invoke(HUIANODE node, struct UiaEventArgs *args, struct uia_event *event) +{ + SAFEARRAY *out_req; + BSTR tree_struct; + HRESULT hr; + + hr = UiaGetUpdatedCache(node, &event->cache_req, NormalizeState_View, NULL, &out_req, + &tree_struct); + if (SUCCEEDED(hr)) + { + event->cback(args, out_req, tree_struct); + SafeArrayDestroy(out_req); + SysFreeString(tree_struct); + } + + return hr; +} + +/* + * 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) +{ + struct UiaPropertyCondition prop_cond = { ConditionType_Property, UIA_RuntimeIdPropertyId }; + struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node); + HRESULT hr = S_OK; + + /* Event is no longer valid. */ + if (InterlockedCompareExchange(&event->event_defunct, 0, 0) != 0) + return S_OK; + + /* Can't match an event that doesn't have a runtime ID, early out. */ + if (!event->runtime_id) + return S_OK; + + if (rt_id && !uia_compare_safearrays(rt_id, event->runtime_id, UIAutomationType_IntArray)) + { + if (event->scope & TreeScope_Element) + hr = uia_event_invoke(node, args, event); + return hr; + } + + if (!(event->scope & (TreeScope_Descendants | TreeScope_Children))) + return S_OK; + + V_VT(&prop_cond.Value) = VT_I4 | VT_ARRAY; + V_ARRAY(&prop_cond.Value) = event->runtime_id; + + IWineUiaNode_AddRef(&node_data->IWineUiaNode_iface); + while (1) + { + HUIANODE node2 = NULL; + + hr = navigate_uia_node(node_data, NavigateDirection_Parent, &node2); + IWineUiaNode_Release(&node_data->IWineUiaNode_iface); + if (FAILED(hr) || !node2) + return hr; + + node_data = impl_from_IWineUiaNode((IWineUiaNode *)node2); + hr = uia_condition_check(node2, (struct UiaCondition *)&prop_cond); + if (FAILED(hr)) + break; + + if (uia_condition_matched(hr)) + { + hr = uia_event_invoke(node, args, event); + break; + } + + if (!(event->scope & TreeScope_Descendants)) + break; + } + IWineUiaNode_Release(&node_data->IWineUiaNode_iface); + + return hr; +} + +static HRESULT uia_raise_event(IRawElementProviderSimple *elprov, struct UiaEventArgs *args) +{ + struct uia_event_map_entry *event_entry; + enum ProviderOptions prov_opts = 0; + struct list *cursor, *cursor2; + HUIANODE node; + SAFEARRAY *sa; + HRESULT hr; + + hr = IRawElementProviderSimple_get_ProviderOptions(elprov, &prov_opts); + if (FAILED(hr)) + return hr; + + EnterCriticalSection(&event_map_cs); + if ((event_entry = uia_get_event_map_entry_for_event(args->EventId))) + InterlockedIncrement(&event_entry->refs); + LeaveCriticalSection(&event_map_cs); + + if (!event_entry) + return S_OK; + + /* + * For events raised on server-side providers, we don't want to add any + * clientside HWND providers. + */ + if (prov_opts & ProviderOptions_ServerSideProvider) + hr = create_uia_node_from_elprov(elprov, &node, FALSE); + else + hr = create_uia_node_from_elprov(elprov, &node, TRUE); + if (FAILED(hr)) + { + uia_event_map_entry_release(event_entry); + return hr; + } + + hr = UiaGetRuntimeId(node, &sa); + if (FAILED(hr)) + { + uia_event_map_entry_release(event_entry); + UiaNodeRelease(node); + return hr; + } + + LIST_FOR_EACH_SAFE(cursor, cursor2, &event_entry->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); + + return hr; +} + +/*********************************************************************** + * UiaRaiseAutomationEvent (uiautomationcore.@) + */ +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 }; + HRESULT hr; + + TRACE("(%p, %d)\n", elprov, id); + + if (!elprov) + return E_INVALIDARG; + + if (!event_info || event_info->event_arg_type != EventArgsType_Simple) + { + if (!event_info) + FIXME("No event info structure for event id %d\n", id); + else + WARN("Wrong event raising function for event args type %d\n", event_info->event_arg_type); + + return S_OK; + } + + hr = uia_raise_event(elprov, &args); + if (FAILED(hr)) + return hr; + + return S_OK; +} diff --git a/dlls/uiautomationcore/uia_main.c b/dlls/uiautomationcore/uia_main.c index d0ab909f102..0ae6b744591 100644 --- a/dlls/uiautomationcore/uia_main.c +++ b/dlls/uiautomationcore/uia_main.c @@ -298,15 +298,6 @@ HRESULT WINAPI UiaGetReservedNotSupportedValue(IUnknown **value) return S_OK; }
-/*********************************************************************** - * UiaRaiseAutomationEvent (uiautomationcore.@) - */ -HRESULT WINAPI UiaRaiseAutomationEvent(IRawElementProviderSimple *provider, EVENTID id) -{ - FIXME("(%p, %d): stub\n", provider, id); - return S_OK; -} - /*********************************************************************** * UiaRaiseAutomationPropertyChangedEvent (uiautomationcore.@) */ diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 26e70948c07..4fa9043d7df 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -159,8 +159,11 @@ 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 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_condition_check(HUIANODE node, struct UiaCondition *condition) DECLSPEC_HIDDEN; +BOOL uia_condition_matched(HRESULT hr) DECLSPEC_HIDDEN;
/* uia_com_client.c */ HRESULT create_uia_iface(IUnknown **iface, BOOL is_cui8) DECLSPEC_HIDDEN;
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/tests/uiautomation.c | 43 ++++++++++++++++++++++ dlls/uiautomationcore/uia_client.c | 18 --------- dlls/uiautomationcore/uia_event.c | 34 +++++++++++++++++ dlls/uiautomationcore/uia_private.h | 2 + dlls/uiautomationcore/uia_utils.c | 17 +++++++++ 5 files changed, 96 insertions(+), 18 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c index 0ad9a883c1c..efcba1c2b5f 100644 --- a/dlls/uiautomationcore/tests/uiautomation.c +++ b/dlls/uiautomationcore/tests/uiautomation.c @@ -14237,6 +14237,49 @@ static void test_UiaAddEvent(void) todo_wine CHECK_CALLED_MULTI(prov_callback_proxy, 2); CHECK_CALLED(uia_event_callback);
+ hr = UiaRemoveEvent(event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* + * Register an event on the desktop HWND with a scope of TreeScope_Element + * and TreeScope_Descendants. This is a special case where all providers + * will match, regardless of whether or not they can navigate to the + * desktop node. + */ + set_cache_request(&cache_req, (struct UiaCondition *)&UiaTrueCondition, TreeScope_Element, NULL, 0, NULL, 0, + AutomationElementMode_Full); + hr = UiaAddEvent(node, UIA_AutomationFocusChangedEventId, uia_event_callback, TreeScope_Element | TreeScope_Descendants, NULL, 0, + &cache_req, &event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!event, "event == NULL\n"); + + /* + * Raise an event on Provider2 - completely disconnected from all other + * providers, will still trigger the event callback. + */ + initialize_provider(&Provider2, ProviderOptions_ServerSideProvider, NULL, TRUE); + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), NULL); + add_provider_desc(&exp_node_desc, L"Main", L"Provider2", TRUE); + set_event_data(0, 0, 1, 1, &exp_node_desc, L"P)"); + SET_EXPECT(uia_event_callback); + hr = UiaRaiseAutomationEvent(&Provider2.IRawElementProviderSimple_iface, UIA_AutomationFocusChangedEventId); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + CHECK_CALLED(uia_event_callback); + + /* + * No clientside providers to match us to the desktop node through + * navigation, but event will still be triggered. + */ + init_node_provider_desc(&exp_node_desc, GetCurrentProcessId(), hwnd); + add_provider_desc(&exp_node_desc, L"Main", L"Provider_hwnd2", TRUE); + set_event_data(0, 0, 1, 1, &exp_node_desc, L"P)"); + SET_EXPECT(uia_event_callback); + Provider_hwnd2.prov_opts = ProviderOptions_ServerSideProvider; + Provider_hwnd2.ignore_hwnd_prop = TRUE; + hr = UiaRaiseAutomationEvent(&Provider_hwnd2.IRawElementProviderSimple_iface, UIA_AutomationFocusChangedEventId); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + CHECK_CALLED(uia_event_callback); + hr = UiaRemoveEvent(event); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); UiaNodeRelease(node); diff --git a/dlls/uiautomationcore/uia_client.c b/dlls/uiautomationcore/uia_client.c index a94a6b8db81..e1dd7d0bd23 100644 --- a/dlls/uiautomationcore/uia_client.c +++ b/dlls/uiautomationcore/uia_client.c @@ -2692,30 +2692,12 @@ HRESULT WINAPI UiaGetPropertyValue(HUIANODE huianode, PROPERTYID prop_id, VARIAN return hr; }
-#define UIA_RUNTIME_ID_PREFIX 42 - enum fragment_root_prov_type_ids { FRAGMENT_ROOT_NONCLIENT_TYPE_ID = 0x03, FRAGMENT_ROOT_MAIN_TYPE_ID = 0x04, FRAGMENT_ROOT_OVERRIDE_TYPE_ID = 0x05, };
-static HRESULT write_runtime_id_base(SAFEARRAY *sa, HWND hwnd) -{ - const int rt_id[2] = { UIA_RUNTIME_ID_PREFIX, HandleToUlong(hwnd) }; - HRESULT hr; - LONG idx; - - for (idx = 0; idx < ARRAY_SIZE(rt_id); idx++) - { - hr = SafeArrayPutElement(sa, &idx, (void *)&rt_id[idx]); - if (FAILED(hr)) - return hr; - } - - return S_OK; -} - static SAFEARRAY *append_uia_runtime_id(SAFEARRAY *sa, HWND hwnd, enum ProviderOptions root_opts) { LONG i, idx, lbound, elems; diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 3b24084d383..025c87dbbad 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -23,6 +23,32 @@
WINE_DEFAULT_DEBUG_CHANNEL(uiautomation);
+static SAFEARRAY *uia_desktop_node_rt_id; +static BOOL WINAPI uia_init_desktop_rt_id(INIT_ONCE *once, void *param, void **ctx) +{ + SAFEARRAY *sa; + + if ((sa = SafeArrayCreateVector(VT_I4, 0, 2))) + { + if (SUCCEEDED(write_runtime_id_base(sa, GetDesktopWindow()))) + uia_desktop_node_rt_id = sa; + else + SafeArrayDestroy(sa); + } + + return !!uia_desktop_node_rt_id; +} + +static SAFEARRAY *uia_get_desktop_rt_id(void) +{ + static INIT_ONCE once = INIT_ONCE_STATIC_INIT; + + if (!uia_desktop_node_rt_id) + InitOnceExecuteOnce(&once, uia_init_desktop_rt_id, NULL, NULL); + + return uia_desktop_node_rt_id; +} + /* * UI Automation event map. */ @@ -71,8 +97,13 @@ static struct uia_event_map_entry *uia_get_event_map_entry_for_event(int event_i
static HRESULT uia_event_map_add_event(struct uia_event *event) { + const int subtree_scope = TreeScope_Element | TreeScope_Descendants; struct uia_event_map_entry *event_entry;
+ if (((event->scope & subtree_scope) == subtree_scope) && event->runtime_id && + !uia_compare_safearrays(uia_get_desktop_rt_id(), event->runtime_id, UIAutomationType_IntArray)) + event->desktop_subtree_event = TRUE; + EnterCriticalSection(&event_map_cs);
if (!(event_entry = uia_get_event_map_entry_for_event(event->event_id))) @@ -525,6 +556,9 @@ static HRESULT uia_event_check_match(HUIANODE node, SAFEARRAY *rt_id, struct Uia if (!event->runtime_id) return S_OK;
+ if (event->desktop_subtree_event) + return uia_event_invoke(node, args, event); + if (rt_id && !uia_compare_safearrays(rt_id, event->runtime_id, UIAutomationType_IntArray)) { if (event->scope & TreeScope_Element) diff --git a/dlls/uiautomationcore/uia_private.h b/dlls/uiautomationcore/uia_private.h index 4fa9043d7df..18b97b74e65 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -98,6 +98,7 @@ struct uia_event IWineUiaEvent IWineUiaEvent_iface; LONG ref;
+ BOOL desktop_subtree_event; SAFEARRAY *runtime_id; int event_id; int scope; @@ -190,6 +191,7 @@ HRESULT create_msaa_provider(IAccessible *acc, long child_id, HWND hwnd, BOOL kn HRESULT register_interface_in_git(IUnknown *iface, REFIID riid, DWORD *ret_cookie) DECLSPEC_HIDDEN; HRESULT unregister_interface_in_git(DWORD git_cookie) DECLSPEC_HIDDEN; HRESULT get_interface_in_git(REFIID riid, DWORD git_cookie, IUnknown **ret_iface) DECLSPEC_HIDDEN; +HRESULT write_runtime_id_base(SAFEARRAY *sa, HWND hwnd) DECLSPEC_HIDDEN; void uia_cache_request_destroy(struct UiaCacheRequest *cache_req) DECLSPEC_HIDDEN; HRESULT uia_cache_request_clone(struct UiaCacheRequest *dst, struct UiaCacheRequest *src) DECLSPEC_HIDDEN; HRESULT get_safearray_dim_bounds(SAFEARRAY *sa, UINT dim, LONG *lbound, LONG *elems) DECLSPEC_HIDDEN; diff --git a/dlls/uiautomationcore/uia_utils.c b/dlls/uiautomationcore/uia_utils.c index 03fdef40e01..8e034916397 100644 --- a/dlls/uiautomationcore/uia_utils.c +++ b/dlls/uiautomationcore/uia_utils.c @@ -98,6 +98,23 @@ HRESULT get_interface_in_git(REFIID riid, DWORD git_cookie, IUnknown **ret_iface return S_OK; }
+#define UIA_RUNTIME_ID_PREFIX 42 +HRESULT write_runtime_id_base(SAFEARRAY *sa, HWND hwnd) +{ + const int rt_id[2] = { UIA_RUNTIME_ID_PREFIX, HandleToUlong(hwnd) }; + HRESULT hr; + LONG idx; + + for (idx = 0; idx < ARRAY_SIZE(rt_id); idx++) + { + hr = SafeArrayPutElement(sa, &idx, (void *)&rt_id[idx]); + if (FAILED(hr)) + return hr; + } + + return S_OK; +} + /* * UiaCondition cloning functions. */
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_event.c:
- struct uia_event_map_entry *event_entry;
- EnterCriticalSection(&event_map_cs);
- if (!(event_entry = uia_get_event_map_entry_for_event(event->event_id)))
- {
if (!(event_entry = heap_alloc_zero(sizeof(*event_entry))))
{
LeaveCriticalSection(&event_map_cs);
return E_OUTOFMEMORY;
}
event_entry->event_id = event->event_id;
list_init(&event_entry->events_list);
if (InterlockedIncrement(&uia_event_map.event_count) == 1)
Accesses to this field are gated by `event_map_cs` so I don't think the Interlocked calls are necessary.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_event.c:
{ int i;
if (event->event_map_entry)
uia_event_map_entry_release(event->event_map_entry);
Can this happen? I think the event_map_entry would always hold a reference to the event.
I don't see anywhere that events can be removed from the list (unless all events in the list are defunct). There should probably be some documentation of this if it's intentional.
On Sat Jun 10 20:53:47 2023 +0000, Esme Povirk wrote:
Can this happen? I think the event_map_entry would always hold a reference to the event.
Technically no, this _shouldn't_ happen. At one point I accidentally tested using an HUIAEVENT after it had already had `UiaRemoveEvent` called on it, and not checking this was causing it to segfault. Since that's technically undefined behavior, I guess having this check around is more confusing than helpful. I'll remove it then.
On Sat Jun 10 20:59:04 2023 +0000, Esme Povirk wrote:
I don't see anywhere that events can be removed from the list (unless all events in the list are defunct). There should probably be some documentation of this if it's intentional.
Yes, it is intentional. It's easier to avoid dealing with a removal in the middle of the list by just leaving events around marked as defunct instead. I can add a comment somewhere to clarify this.
On Mon Jun 12 12:16:50 2023 +0000, Connor McAdams wrote:
Technically no, this _shouldn't_ happen. At one point I accidentally tested using an HUIAEVENT after it had already had `UiaRemoveEvent` called on it, and not checking this was causing it to segfault. Since that's technically undefined behavior, I guess having this check around is more confusing than helpful. I'll remove it then.
Oh, wait, I thought this was in `advise_events`. In the `Release` method, this is more of a potential cleanup behavior. This should be released and set to NULL in `advise_events` when `advise_events` is called for removal. But if that doesn't occur for some reason and the entry is still around, we want to release it.