From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/uiautomationcore/uia_event.c | 96 +++++++++++++++++++++++++++++ dlls/uiautomationcore/uia_private.h | 3 + 2 files changed, 99 insertions(+)
diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index 7fd4eda8ead..fb3b40f3d1e 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -19,9 +19,86 @@ #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; + + 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; + + EnterCriticalSection(&event_map_cs); + + 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); + + LeaveCriticalSection(&event_map_cs); + return map_entry; +} + +static HRESULT uia_event_map_add_event(struct uia_event *event) +{ + struct uia_event_map_entry *event_entry = uia_get_event_map_entry_for_event(event->event_id); + + EnterCriticalSection(&event_map_cs); + if (!event_entry) + { + 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); + } + + list_add_head(&event_entry->events_list, &event->event_list_entry); + event->event_map_entry = event_entry; + LeaveCriticalSection(&event_map_cs); + + return S_OK; +} + /* * IWineUiaEvent interface. */ @@ -61,6 +138,21 @@ static ULONG WINAPI uia_event_Release(IWineUiaEvent *iface) { int i;
+ if (event->event_map_entry) + { + EnterCriticalSection(&event_map_cs); + list_remove(&event->event_list_entry); + + /* Map entry has no more events for this event ID - destroy it. */ + if (list_empty(&event->event_map_entry->events_list)) + { + rb_remove(&uia_event_map.event_map, &event->event_map_entry->entry); + heap_free(event->event_map_entry); + InterlockedDecrement(&uia_event_map.event_count); + } + LeaveCriticalSection(&event_map_cs); + } + SafeArrayDestroy(event->runtime_id); for (i = 0; i < event->event_advisers_count; i++) IWineUiaEventAdviser_Release(event->event_advisers[i]); @@ -326,6 +418,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..0d652dd7b13 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -106,6 +106,9 @@ 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; + 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 | 46 +++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+)
diff --git a/dlls/uiautomationcore/uia_event.c b/dlls/uiautomationcore/uia_event.c index fb3b40f3d1e..beefabc83f6 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -154,6 +154,7 @@ static ULONG WINAPI uia_event_Release(IWineUiaEvent *iface) }
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); @@ -410,6 +411,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 0d652dd7b13..aea9882b240 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -109,6 +109,7 @@ struct uia_event struct list event_list_entry; struct uia_event_map_entry *event_map_entry;
+ struct UiaCacheRequest cache_req; UiaEventCallback *cback; };
@@ -185,6 +186,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..97e903c2a45 100644 --- a/dlls/uiautomationcore/uia_utils.c +++ b/dlls/uiautomationcore/uia_utils.c @@ -98,6 +98,52 @@ 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) +{ + if (src->pViewCondition) + { + FIXME("Cache request condition cloning currently unimplemented\n"); + dst->pViewCondition = NULL; + } + + 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 | 147 +++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 5 deletions(-)
diff --git a/dlls/uiautomationcore/uia_utils.c b/dlls/uiautomationcore/uia_utils.c index 97e903c2a45..03fdef40e01 100644 --- a/dlls/uiautomationcore/uia_utils.c +++ b/dlls/uiautomationcore/uia_utils.c @@ -98,22 +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) { - if (src->pViewCondition) - { - FIXME("Cache request condition cloning currently unimplemented\n"); - dst->pViewCondition = NULL; - } + 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 | 157 +++++++++++++++++++++ dlls/uiautomationcore/uia_main.c | 9 -- dlls/uiautomationcore/uia_private.h | 3 + 5 files changed, 201 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 beefabc83f6..e1061003d56 100644 --- a/dlls/uiautomationcore/uia_event.c +++ b/dlls/uiautomationcore/uia_event.c @@ -456,3 +456,160 @@ 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; + + /* Can't match an event that doesn't have a runtime ID, early out. */ + if (!event->runtime_id) + return S_OK; + + IWineUiaEvent_AddRef(&event->IWineUiaEvent_iface); + 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); + goto exit; + } + + if (!(event->scope & (TreeScope_Descendants | TreeScope_Children))) + goto exit; + + 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) + goto exit; + + 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); + +exit: + IWineUiaEvent_Release(&event->IWineUiaEvent_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; + + event_entry = uia_get_event_map_entry_for_event(args->EventId); + 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)) + return hr; + + hr = UiaGetRuntimeId(node, &sa); + if (FAILED(hr)) + { + 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; + } + + 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 aea9882b240..dcdaaa69f1b 100644 --- a/dlls/uiautomationcore/uia_private.h +++ b/dlls/uiautomationcore/uia_private.h @@ -158,8 +158,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 | 37 +++++++++++++++++++ dlls/uiautomationcore/uia_private.h | 2 + dlls/uiautomationcore/uia_utils.c | 17 +++++++++ 5 files changed, 99 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 e1061003d56..7e757d9dee0 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. */ @@ -74,6 +100,11 @@ 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) { struct uia_event_map_entry *event_entry = uia_get_event_map_entry_for_event(event->event_id); + const int subtree_scope = TreeScope_Element | TreeScope_Descendants; + + 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) @@ -489,6 +520,12 @@ static HRESULT uia_event_check_match(HUIANODE node, SAFEARRAY *rt_id, struct Uia return S_OK;
IWineUiaEvent_AddRef(&event->IWineUiaEvent_iface); + if (event->desktop_subtree_event) + { + hr = uia_event_invoke(node, args, event); + goto exit; + } + 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 dcdaaa69f1b..9fd80dba7da 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; @@ -189,6 +190,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:
- 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;
- EnterCriticalSection(&event_map_cs);
- 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);
- LeaveCriticalSection(&event_map_cs);
I don't think this is safe. If you leave the critical section, the entry could be removed before you use it.
On Tue Jun 6 18:56:16 2023 +0000, Esme Povirk wrote:
I don't think this is safe. If you leave the critical section, the entry could be removed before you use it.
I would probably just make it a requirement that you hold the critical section when calling this function.
On Tue Jun 6 19:02:20 2023 +0000, Esme Povirk wrote:
I would probably just make it a requirement that you hold the critical section when calling this function.
Good catch. Yeah, that makes sense.
Esme Povirk (@madewokherd) commented about dlls/uiautomationcore/uia_utils.c:
heap_free(cache_req->pProperties); heap_free(cache_req->pPatterns);
}
HRESULT uia_cache_request_clone(struct UiaCacheRequest *dst, struct UiaCacheRequest *src) {
- if (src->pViewCondition)
- {
FIXME("Cache request condition cloning currently unimplemented\n");
dst->pViewCondition = NULL;
- }
- HRESULT hr;
- hr = uia_condition_clone(&dst->pViewCondition, src->pViewCondition);
- if (FAILED(hr))
return hr;
This removes a NULL check, is NULL pViewCondition expected or not?
On Tue Jun 6 19:54:59 2023 +0000, Esme Povirk wrote:
This removes a NULL check, is NULL pViewCondition expected or not?
It's not expected, it actually causes an access violation on Windows. This was mainly done so I could split the patches and have a FIXME to signify. I can just remove it completely if that's better.
On Tue Jun 6 19:59:44 2023 +0000, Connor McAdams wrote:
It's not expected, it actually causes an access violation on Windows. This was mainly done so I could split the patches and have a FIXME to signify. I can just remove it completely if that's better.
So I guess it should probably just spit out a FIXME unconditionally.
On Tue Jun 6 19:05:59 2023 +0000, Connor McAdams wrote:
Good catch. Yeah, that makes sense.
This could use some more work generally the more I think about it. In `uia_raise_event` I hold a reference to the `struct uia_event_map_entry`, which could be freed behind my back if someone called UiaRemoveEvent on the last event in the event list. Holding the critical section for the entirety of `uia_raise_event` sounded plausible, but that'd cause the event release to potentially hang if we hold the last event reference.
I might add something like a reference count to the event map entry, not sure.
On Tue Jun 6 20:04:05 2023 +0000, Connor McAdams wrote:
This could use some more work generally the more I think about it. In `uia_raise_event` I hold a reference to the `struct uia_event_map_entry`, which could be freed behind my back if someone called UiaRemoveEvent on the last event in the event list. Holding the critical section for the entirety of `uia_raise_event` sounded plausible, but that'd cause the event release to potentially hang if we hold the last event reference. I might add something like a reference count to the event map entry, not sure.
Having the list modified seems problematic as well. And you really don't want to call user code with a lock held if you can avoid it, but "user code" here may include getting the runtime id, navigating, and anything else you might need..
This is really tricky, and I have no good ideas at the moment.
On Tue Jun 6 20:45:19 2023 +0000, Esme Povirk wrote:
Having the list modified seems problematic as well. And you really don't want to call user code with a lock held if you can avoid it, but "user code" here may include getting the runtime id, navigating, and anything else you might need.. This is really tricky, and I have no good ideas at the moment.
I guess uia_event is already ref-counted and you could copy the list with the lock held, but that feels like overkill.