From 4501e34233cb0b81f47083329f2600882accce19 Mon Sep 17 00:00:00 2001 From: Alicia Drummond Date: Wed, 24 May 2023 12:23:21 -0700 Subject: [PATCH 01/10] Add better focus management --- .../Composition/CompositionRootAutomationProvider.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp index 8ed4f784d5e..feb9eec42bb 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp @@ -32,7 +32,7 @@ HRESULT __stdcall CompositionRootAutomationProvider::GetEmbeddedFragmentRoots(SA } HRESULT __stdcall CompositionRootAutomationProvider::SetFocus(void) { - return S_OK; + return UiaSetFocusHelper(m_view); } HRESULT __stdcall CompositionRootAutomationProvider::GetPatternProvider(PATTERNID patternId, IUnknown **pRetVal) { @@ -122,10 +122,7 @@ HRESULT __stdcall CompositionRootAutomationProvider::ElementProviderFromPoint( return UIA_E_ELEMENTNOTAVAILABLE; } - auto spRootView = strongView->rootComponentView(); - if (spRootView == nullptr) { - return UIA_E_ELEMENTNOTAVAILABLE; - } + auto spRootView = std::static_pointer_cast<::Microsoft::ReactNative::RootComponentView>(strongView); if (m_hwnd == nullptr || !IsWindow(m_hwnd)) { // TODO: Add support for non-HWND based hosting From 71f585d11ef5958732a7ccd7e8ac6e5e9fed4421 Mon Sep 17 00:00:00 2001 From: Alicia Drummond Date: Wed, 24 May 2023 12:24:31 -0700 Subject: [PATCH 02/10] better focus management --- .../Playground-Composition.cpp | 2 +- .../CompositionDynamicAutomationProvider.cpp | 25 ++++++++++- .../CompositionViewComponentView.cpp | 27 ++++++++++++ .../CompositionViewComponentView.h | 3 ++ .../Fabric/Composition/RootComponentView.cpp | 15 ++++--- .../Fabric/Composition/RootComponentView.h | 1 + .../Composition/ScrollViewComponentView.cpp | 3 +- .../Fabric/Composition/UiaHelpers.cpp | 44 +++++++++++++++++++ .../Fabric/Composition/UiaHelpers.h | 7 +++ vnext/Shared/Shared.vcxitems.filters | 4 +- 10 files changed, 119 insertions(+), 12 deletions(-) diff --git a/packages/playground/windows/playground-composition/Playground-Composition.cpp b/packages/playground/windows/playground-composition/Playground-Composition.cpp index f1e98d7296c..10683ddad3f 100644 --- a/packages/playground/windows/playground-composition/Playground-Composition.cpp +++ b/packages/playground/windows/playground-composition/Playground-Composition.cpp @@ -486,7 +486,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) case WM_GETOBJECT: { if (lparam == UiaRootObjectId) { auto windowData = WindowData::GetFromWindow(hwnd); - if (!windowData->m_windowInited) + if (windowData == nullptr || !windowData->m_windowInited) break; auto hwndHost = windowData->m_CompositionHwndHost; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp index 8b873f050ef..6b1f646b0cd 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp @@ -96,7 +96,7 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetEmbeddedFragmentRoots } HRESULT __stdcall CompositionDynamicAutomationProvider::SetFocus(void) { - return S_OK; + return UiaSetFocusHelper(m_view); } HRESULT __stdcall CompositionDynamicAutomationProvider::get_FragmentRoot(IRawElementProviderFragmentRoot **pRetVal) { @@ -232,6 +232,29 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERT pRetVal->bstrVal = temp.Detach(); break; } + case UIA_IsKeyboardFocusablePropertyId: { + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = props->focusable ? VARIANT_TRUE : VARIANT_FALSE; + break; + } + case UIA_HasKeyboardFocusPropertyId: { + auto rootCV = strongView->rootComponentView(); + if (rootCV == nullptr) + return UIA_E_ELEMENTNOTAVAILABLE; + + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = rootCV->GetFocusedComponent() == strongView.get() ? VARIANT_TRUE : VARIANT_FALSE; + break; + } + case UIA_IsEnabledPropertyId: { + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = VARIANT_TRUE; + break; + } + //case UIA_IsControlElementPropertyId: { + // pRetVal->vt = VT_BOOL; + // pRetVal->boolVal = props->accessibilityRole != "none" ? VARIANT_TRUE : VARIANT_FALSE; + //} } return S_OK; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp index 6ad03c5fabf..3aa4d06f387 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp @@ -16,6 +16,7 @@ #include "CompositionHelpers.h" #include "RootComponentView.h" #include "d2d1helper.h" +#include "UiaHelpers.h" namespace Microsoft::ReactNative { @@ -78,6 +79,8 @@ bool CompositionBaseComponentView::runOnChildren(bool forward, Mso::FunctoronBlur(); showFocusVisual(false); + winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( + m_uiaProvider, UIA_HasKeyboardFocusPropertyId, true, false); } void CompositionBaseComponentView::onFocusGained() noexcept { @@ -85,6 +88,12 @@ void CompositionBaseComponentView::onFocusGained() noexcept { if (m_enableFocusVisual) { showFocusVisual(true); } + auto spProviderSimple = m_uiaProvider.try_as(); + if (UiaClientsAreListening() && spProviderSimple != nullptr) { + winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( + *spProviderSimple.get(), UIA_HasKeyboardFocusPropertyId, false, true); + UiaRaiseAutomationEvent(spProviderSimple.get(), UIA_AutomationFocusChangedEventId); + } } void CompositionBaseComponentView::updateEventEmitter( @@ -1030,6 +1039,23 @@ void CompositionBaseComponentView::updateBorderProps( } } +void CompositionBaseComponentView::updateAccessibilityProps( + const facebook::react::ViewProps &oldViewProps, + const facebook::react::ViewProps &newViewProps) noexcept { + + auto spProviderSimple = m_uiaProvider.try_as(); + + if (spProviderSimple == nullptr || !UiaClientsAreListening()) + return; + + //advise events + + auto hr = winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( + *spProviderSimple.get(), UIA_IsKeyboardFocusablePropertyId, oldViewProps.focusable, newViewProps.focusable); + + //check +} + void CompositionBaseComponentView::updateBorderLayoutMetrics( facebook::react::LayoutMetrics const &layoutMetrics, const facebook::react::ViewProps &viewProps) noexcept { @@ -1206,6 +1232,7 @@ void CompositionViewComponentView::updateProps( m_visual.Opacity(newViewProps.opacity); } + updateAccessibilityProps(oldViewProps, newViewProps); updateBorderProps(oldViewProps, newViewProps); // Shadow diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h index cb89637f25f..8c6c145bc16 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h @@ -50,6 +50,9 @@ struct CompositionBaseComponentView : public IComponentView, void updateBorderProps( const facebook::react::ViewProps &oldViewProps, const facebook::react::ViewProps &newViewProps) noexcept; + void updateAccessibilityProps( + const facebook::react::ViewProps &oldView, + const facebook::react::ViewProps &newViewProps) noexcept; void updateBorderLayoutMetrics( facebook::react::LayoutMetrics const &layoutMetrics, const facebook::react::ViewProps &viewProps) noexcept; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp index c0c6929e7c7..a44e8ce5d43 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp @@ -84,6 +84,14 @@ bool RootComponentView::NavigateFocus(const winrt::Microsoft::ReactNative::Focus return view != nullptr; } +bool RootComponentView::TrySetFocusedComponent(IComponentView &view) noexcept { + if (view.focusable()) { + view.rootComponentView()->SetFocusedComponent(&view); + return true; + } + return false; +} + bool RootComponentView::TryMoveFocus(bool next) noexcept { if (!m_focusedComponent) { return NavigateFocus(winrt::Microsoft::ReactNative::FocusNavigationRequest( @@ -94,11 +102,8 @@ bool RootComponentView::TryMoveFocus(bool next) noexcept { Mso::Functor fn = [currentlyFocused = m_focusedComponent](IComponentView &view) noexcept { if (&view == currentlyFocused) return false; - if (view.focusable()) { - view.rootComponentView()->SetFocusedComponent(&view); - return true; - } - return false; + + return view.rootComponentView()->TrySetFocusedComponent(view); }; return walkTree(*m_focusedComponent, next, fn); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h index 032907025eb..375a9bed869 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h @@ -25,6 +25,7 @@ struct RootComponentView : CompositionViewComponentView { ::Microsoft::ReactNative::IComponentView *GetFocusedComponent() noexcept; void SetFocusedComponent(::Microsoft::ReactNative::IComponentView *value) noexcept; + bool TrySetFocusedComponent(::Microsoft::ReactNative::IComponentView &view) noexcept; bool NavigateFocus(const winrt::Microsoft::ReactNative::FocusNavigationRequest &request) noexcept; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp index a073d204f37..d1644f2fe86 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp @@ -219,8 +219,7 @@ void ScrollViewComponentView::finalizeUpdates(RNComponentViewUpdateMask updateMa } void ScrollViewComponentView::prepareForRecycle() noexcept {} facebook::react::Props::Shared ScrollViewComponentView::props() noexcept { - assert(false); - return {}; + return m_props; } /* diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp index 2cc2f1d542b..5c6c0094422 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp @@ -2,6 +2,7 @@ #include "UiaHelpers.h" #include #include +#include "RootComponentView.h" namespace winrt::Microsoft::ReactNative::implementation { @@ -86,4 +87,47 @@ HRESULT UiaGetBoundingRectangleHelper(::Microsoft::ReactNative::ReactTaggedView return S_OK; } +HRESULT UiaSetFocusHelper(::Microsoft::ReactNative::ReactTaggedView& view) noexcept +{ + auto strongView = view.view(); + + if (!strongView) + return UIA_E_ELEMENTNOTAVAILABLE; + + auto rootCV = strongView->rootComponentView(); + if (rootCV == nullptr) + return UIA_E_ELEMENTNOTAVAILABLE; + + return rootCV->TrySetFocusedComponent(*strongView) ? S_OK : E_FAIL; +} + +HRESULT UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, bool oldValue, bool newValue) noexcept { + auto spProviderSimple = provider.try_as(); + + if (spProviderSimple == nullptr || !UiaClientsAreListening()) + return S_OK; + + return UpdateUiaProperty(*spProviderSimple.get(), propId, oldValue, newValue); +} + +HRESULT UpdateUiaProperty( + IRawElementProviderSimple &providerSimple, + PROPERTYID propId, + bool oldValue, + bool newValue) noexcept { + + if (oldValue == newValue) + return S_OK; + + VARIANT oldVariant; + oldVariant.vt = VT_BOOL; + oldVariant.boolVal = oldValue ? VARIANT_TRUE : VARIANT_FALSE; + + VARIANT newVariant; + newVariant.vt = VT_BOOL; + newVariant.boolVal = newValue ? VARIANT_TRUE : VARIANT_FALSE; + + return UiaRaiseAutomationPropertyChangedEvent(&providerSimple, propId, oldVariant, newVariant); +} + } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h index e296786c0f8..87d433c7405 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h @@ -15,4 +15,11 @@ UiaNavigateHelper( HRESULT UiaGetBoundingRectangleHelper(::Microsoft::ReactNative::ReactTaggedView &view, UiaRect &rect) noexcept; +HRESULT UiaSetFocusHelper(::Microsoft::ReactNative::ReactTaggedView &view) noexcept; + +HRESULT +UpdateUiaProperty(IRawElementProviderSimple &providerSimple, PROPERTYID prop, bool oldValue, bool newValue) noexcept; + +HRESULT UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, bool oldValue, bool newValue) noexcept; + } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index 566950e1030..f4c866d58f8 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -270,6 +270,7 @@ + @@ -680,9 +681,6 @@ Header Files\Fabric\Composition - - Header Files\Fabric\Composition - Header Files\Fabric\Composition\TextInput From 184aeb718bd182d3101b4738127af193cebcd328 Mon Sep 17 00:00:00 2001 From: Alicia Drummond Date: Tue, 30 May 2023 12:54:08 -0700 Subject: [PATCH 03/10] Add support for AdviseEventAdded Removed --- .../CompositionDynamicAutomationProvider.cpp | 9 +- .../CompositionRootAutomationProvider.cpp | 131 ++++++++++++++++++ .../CompositionRootAutomationProvider.h | 25 +++- .../CompositionViewComponentView.cpp | 34 ++--- .../Fabric/Composition/UiaHelpers.cpp | 51 ++++--- .../Fabric/Composition/UiaHelpers.h | 9 +- 6 files changed, 215 insertions(+), 44 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp index 6b1f646b0cd..4956a068284 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp @@ -211,6 +211,8 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERT if (props == nullptr) return UIA_E_ELEMENTNOTAVAILABLE; + auto hr = S_OK; + switch (propertyId) { case UIA_ControlTypePropertyId: { pRetVal->vt = VT_I4; @@ -247,17 +249,14 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERT break; } case UIA_IsEnabledPropertyId: { + // TODO: Implement accessibilityState pRetVal->vt = VT_BOOL; pRetVal->boolVal = VARIANT_TRUE; break; } - //case UIA_IsControlElementPropertyId: { - // pRetVal->vt = VT_BOOL; - // pRetVal->boolVal = props->accessibilityRole != "none" ? VARIANT_TRUE : VARIANT_FALSE; - //} } - return S_OK; + return hr; } HRESULT __stdcall CompositionDynamicAutomationProvider::get_HostRawElementProvider( diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp index feb9eec42bb..838a2635175 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp @@ -1,6 +1,13 @@ #include "pch.h" #include "CompositionRootAutomationProvider.h" #include "UiaHelpers.h" +#include +#pragma warning(push) +#pragma warning(disable : 4229) +#define IN +#define OUT +#include +#pragma warning(pop) namespace winrt::Microsoft::ReactNative::implementation { @@ -186,4 +193,128 @@ HRESULT __stdcall CompositionRootAutomationProvider::Navigate( return S_OK; } +class UIAPropertyArray { + CComSafeArray m_propArray{}; + public: + UIAPropertyArray(SAFEARRAY *psaProperties) noexcept { + VARTYPE vt; + if (psaProperties && SUCCEEDED(SafeArrayGetVartype(psaProperties, &vt)) && vt == m_propArray.GetType()) + { + m_propArray.Attach(psaProperties); + } + } + ~UIAPropertyArray() noexcept { + if (m_propArray.GetSafeArrayPtr() != nullptr) + m_propArray.Detach(); + } + bool IsValid() noexcept { + return m_propArray.GetSafeArrayPtr() != nullptr && m_propArray.GetDimensions() == 1 && m_propArray.GetCount() > 0; + } + CComSafeArray* operator->() noexcept + { + return &m_propArray; + } +}; + +void AdviseEventAddedImpl( + std::vector &advisedEvents, + EVENTID idEvent) noexcept { + auto it = std::find_if( + advisedEvents.begin(), + advisedEvents.end(), + [idEvent](const CompositionRootAutomationProvider::AdvisedEvent &ae) noexcept { + return ae.Event == idEvent; + }); + + if (it == advisedEvents.end()) { + advisedEvents.emplace_back(CompositionRootAutomationProvider::AdvisedEvent{idEvent, 1 /*Count*/}); + } else { + it->Count++; + } +} + +HRESULT CompositionRootAutomationProvider::AdvisePropertiesAdded(SAFEARRAY *psaProperties) noexcept { + UIAPropertyArray props(psaProperties); + + if (!props.IsValid()) { + return E_INVALIDARG; + } + + for (auto i = props->GetLowerBound(); i <= props->GetUpperBound(); i++) { + auto prop = props->GetAt(i); + AdviseEventAddedImpl(m_advisedProperties, prop); + } + return S_OK; +} + +HRESULT CompositionRootAutomationProvider::AdviseEventAdded(EVENTID idEvent, SAFEARRAY *psaProperties) { + if (idEvent == UIA_AutomationPropertyChangedEventId) { + return AdvisePropertiesAdded(psaProperties); + } + AdviseEventAddedImpl(m_advisedEvents, idEvent); + return S_OK; +} + +HRESULT AdviseEventRemovedImpl( + std::vector &advisedEvents, + EVENTID idEvent) noexcept { + auto it = std::find_if( + advisedEvents.begin(), + advisedEvents.end(), + [idEvent](const CompositionRootAutomationProvider::AdvisedEvent &ae) noexcept { + return ae.Event == idEvent; + }); + + if (it == advisedEvents.end()) { + assert(false); + return UIA_E_INVALIDOPERATION; + } else if (it->Count == 1) { + advisedEvents.erase(it); + } else { + it->Count--; + } + return S_OK; +} + +HRESULT CompositionRootAutomationProvider::AdvisePropertiesRemoved(SAFEARRAY *psaProperties) noexcept { + UIAPropertyArray props(psaProperties); + + if (!props.IsValid()) { + return E_INVALIDARG; + } + + auto returnHr = S_OK; + for (auto i = props->GetLowerBound(); i <= props->GetUpperBound(); i++) { + auto prop = props->GetAt(i); + auto hr = AdviseEventRemovedImpl(m_advisedProperties, prop); + if (FAILED(hr)) + { + returnHr = hr; + } + } + return returnHr; +} + + +HRESULT +CompositionRootAutomationProvider::AdviseEventRemoved(EVENTID idEvent, SAFEARRAY *psaProperties) { + if (idEvent == UIA_AutomationPropertyChangedEventId) { + return AdvisePropertiesRemoved(psaProperties); + } + + return AdviseEventRemovedImpl(m_advisedEvents, idEvent); +} + +bool CompositionRootAutomationProvider::WasEventAdvised(EVENTID event) noexcept { + return std::any_of(m_advisedEvents.begin(), m_advisedEvents.end(), [event](const AdvisedEvent &ae) { + return ae.Event == event && ae.Count > 0; + }); +} + +bool CompositionRootAutomationProvider::WasPropertyAdvised(PROPERTYID prop) noexcept { + return std::any_of(m_advisedProperties.begin(), m_advisedProperties.end(), [prop](const AdvisedEvent &ae) { + return ae.Property == prop && ae.Count > 0; + }); +} + } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.h b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.h index 6951f7be4b9..13119087610 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.h @@ -13,7 +13,8 @@ class CompositionRootAutomationProvider : public winrt::implements< IInspectable, IRawElementProviderFragmentRoot, IRawElementProviderFragment, - IRawElementProviderSimple> { + IRawElementProviderSimple, + IRawElementProviderAdviseEvents> { public: // inherited via IRawElementProviderFragmentRoot virtual HRESULT __stdcall ElementProviderFromPoint(double x, double y, IRawElementProviderFragment **pRetVal) @@ -34,11 +35,33 @@ class CompositionRootAutomationProvider : public winrt::implements< virtual HRESULT __stdcall GetPropertyValue(PROPERTYID propertyId, VARIANT *pRetVal) override; virtual HRESULT __stdcall get_HostRawElementProvider(IRawElementProviderSimple **pRetVal) override; + // IRawElementProviderAdviseEvents + virtual HRESULT __stdcall AdviseEventAdded(EVENTID idEvent, SAFEARRAY *psaProperties) override; + virtual HRESULT __stdcall AdviseEventRemoved(EVENTID idEvent, SAFEARRAY *psaProperties) + override; + CompositionRootAutomationProvider( const std::shared_ptr<::Microsoft::ReactNative::RootComponentView> &componentView) noexcept; + void SetHwnd(HWND hwnd) noexcept; + bool WasPropertyAdvised(PROPERTYID prop) noexcept; + bool WasEventAdvised(EVENTID event) noexcept; + + static_assert(std::is_same::value); + struct AdvisedEvent { + union { + EVENTID Event; + PROPERTYID Property; + }; + uint32_t Count; + }; private: + HRESULT AdvisePropertiesAdded(SAFEARRAY *psaProperties) noexcept; + HRESULT AdvisePropertiesRemoved(SAFEARRAY *psaProperties) noexcept; + + std::vector m_advisedEvents{}; + std::vector m_advisedProperties{}; ::Microsoft::ReactNative::ReactTaggedView m_view; HWND m_hwnd{nullptr}; }; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp index 3aa4d06f387..5875a5b415e 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp @@ -38,7 +38,6 @@ RootComponentView *CompositionBaseComponentView::rootComponentView() noexcept { if (m_parent) return m_parent->rootComponentView(); - assert(false); return nullptr; } @@ -79,8 +78,11 @@ bool CompositionBaseComponentView::runOnChildren(bool forward, Mso::FunctoronBlur(); showFocusVisual(false); - winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( - m_uiaProvider, UIA_HasKeyboardFocusPropertyId, true, false); + if (UiaClientsAreListening()) + { + winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( + EnsureUiaProvider(), UIA_HasKeyboardFocusPropertyId, true, false); + } } void CompositionBaseComponentView::onFocusGained() noexcept { @@ -88,11 +90,14 @@ void CompositionBaseComponentView::onFocusGained() noexcept { if (m_enableFocusVisual) { showFocusVisual(true); } - auto spProviderSimple = m_uiaProvider.try_as(); - if (UiaClientsAreListening() && spProviderSimple != nullptr) { - winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( - *spProviderSimple.get(), UIA_HasKeyboardFocusPropertyId, false, true); - UiaRaiseAutomationEvent(spProviderSimple.get(), UIA_AutomationFocusChangedEventId); + if (UiaClientsAreListening()) + { + auto spProviderSimple = EnsureUiaProvider().try_as(); + if (spProviderSimple != nullptr) { + winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( + m_uiaProvider, UIA_HasKeyboardFocusPropertyId, false, true); + UiaRaiseAutomationEvent(spProviderSimple.get(), UIA_AutomationFocusChangedEventId); + } } } @@ -1043,17 +1048,14 @@ void CompositionBaseComponentView::updateAccessibilityProps( const facebook::react::ViewProps &oldViewProps, const facebook::react::ViewProps &newViewProps) noexcept { - auto spProviderSimple = m_uiaProvider.try_as(); - - if (spProviderSimple == nullptr || !UiaClientsAreListening()) + if (!UiaClientsAreListening()) return; - //advise events - - auto hr = winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( - *spProviderSimple.get(), UIA_IsKeyboardFocusablePropertyId, oldViewProps.focusable, newViewProps.focusable); + winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( + EnsureUiaProvider(), UIA_IsKeyboardFocusablePropertyId, oldViewProps.focusable, newViewProps.focusable); - //check + winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( + EnsureUiaProvider(), UIA_NamePropertyId, oldViewProps.accessibilityLabel, newViewProps.accessibilityLabel); } void CompositionBaseComponentView::updateBorderLayoutMetrics( diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp index 5c6c0094422..cfaef02cb4c 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp @@ -1,8 +1,10 @@ #include "pch.h" #include "UiaHelpers.h" +#include #include #include #include "RootComponentView.h" +#include "CompositionRootAutomationProvider.h" namespace winrt::Microsoft::ReactNative::implementation { @@ -101,33 +103,44 @@ HRESULT UiaSetFocusHelper(::Microsoft::ReactNative::ReactTaggedView& view) noexc return rootCV->TrySetFocusedComponent(*strongView) ? S_OK : E_FAIL; } -HRESULT UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, bool oldValue, bool newValue) noexcept { - auto spProviderSimple = provider.try_as(); +bool WasUiaPropertyAdvised(winrt::com_ptr &providerSimple, PROPERTYID propId) noexcept { + auto spFragment = providerSimple.try_as(); + if (spFragment == nullptr) + return false; + + winrt::com_ptr spFragmentRoot; + spFragment->get_FragmentRoot(spFragmentRoot.put()); + if (spFragmentRoot == nullptr) + return false; - if (spProviderSimple == nullptr || !UiaClientsAreListening()) - return S_OK; + auto rootProvider = static_cast(spFragmentRoot.get()); - return UpdateUiaProperty(*spProviderSimple.get(), propId, oldValue, newValue); + return rootProvider->WasPropertyAdvised(propId); } -HRESULT UpdateUiaProperty( - IRawElementProviderSimple &providerSimple, - PROPERTYID propId, - bool oldValue, - bool newValue) noexcept { +void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, bool oldValue, bool newValue) noexcept { + auto spProviderSimple = provider.try_as(); - if (oldValue == newValue) - return S_OK; + if (spProviderSimple == nullptr || oldValue == newValue || !WasUiaPropertyAdvised(spProviderSimple, propId)) + return; - VARIANT oldVariant; - oldVariant.vt = VT_BOOL; - oldVariant.boolVal = oldValue ? VARIANT_TRUE : VARIANT_FALSE; + auto hr = UiaRaiseAutomationPropertyChangedEvent( + spProviderSimple.get(), propId, CComVariant(oldValue), CComVariant(newValue)); - VARIANT newVariant; - newVariant.vt = VT_BOOL; - newVariant.boolVal = newValue ? VARIANT_TRUE : VARIANT_FALSE; + assert(SUCCEEDED(hr)); +} - return UiaRaiseAutomationPropertyChangedEvent(&providerSimple, propId, oldVariant, newVariant); +void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, const std::string& oldValue, const std::string& newValue) noexcept { + auto spProviderSimple = provider.try_as(); + + if (spProviderSimple == nullptr || oldValue == newValue || !WasUiaPropertyAdvised(spProviderSimple, propId)) + return; + + auto hr = UiaRaiseAutomationPropertyChangedEvent( + spProviderSimple.get(), propId, CComVariant(oldValue.c_str()), CComVariant(newValue.c_str())); + + assert(SUCCEEDED(hr)); } + } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h index 87d433c7405..ba72f692c15 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h @@ -17,9 +17,12 @@ HRESULT UiaGetBoundingRectangleHelper(::Microsoft::ReactNative::ReactTaggedView HRESULT UiaSetFocusHelper(::Microsoft::ReactNative::ReactTaggedView &view) noexcept; -HRESULT -UpdateUiaProperty(IRawElementProviderSimple &providerSimple, PROPERTYID prop, bool oldValue, bool newValue) noexcept; +void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, bool oldValue, bool newValue) noexcept; -HRESULT UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, bool oldValue, bool newValue) noexcept; +void UpdateUiaProperty( + winrt::IInspectable provider, + PROPERTYID propId, + const std::string &oldValue, + const std::string &newValue) noexcept; } // namespace winrt::Microsoft::ReactNative::implementation From a393db9dbc85d4eeda7abb660d38788ade95efe2 Mon Sep 17 00:00:00 2001 From: Alicia Drummond Date: Tue, 30 May 2023 14:54:21 -0700 Subject: [PATCH 04/10] A little clean up --- .../CompositionDynamicAutomationProvider.cpp | 3 +-- .../Composition/CompositionViewComponentView.cpp | 12 ++++++++++-- .../Fabric/Composition/UiaHelpers.cpp | 8 ++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp index 4956a068284..e488c781a0f 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp @@ -249,9 +249,8 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERT break; } case UIA_IsEnabledPropertyId: { - // TODO: Implement accessibilityState pRetVal->vt = VT_BOOL; - pRetVal->boolVal = VARIANT_TRUE; + pRetVal->boolVal = !props->accessibilityState.disabled ? VARIANT_TRUE : VARIANT_FALSE; break; } } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp index 5875a5b415e..7aae0b3fdfe 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp @@ -1051,11 +1051,19 @@ void CompositionBaseComponentView::updateAccessibilityProps( if (!UiaClientsAreListening()) return; + auto provider = EnsureUiaProvider(); + + winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( + provider, UIA_IsKeyboardFocusablePropertyId, oldViewProps.focusable, newViewProps.focusable); + winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( - EnsureUiaProvider(), UIA_IsKeyboardFocusablePropertyId, oldViewProps.focusable, newViewProps.focusable); + provider, UIA_NamePropertyId, oldViewProps.accessibilityLabel, newViewProps.accessibilityLabel); winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( - EnsureUiaProvider(), UIA_NamePropertyId, oldViewProps.accessibilityLabel, newViewProps.accessibilityLabel); + provider, + UIA_IsEnabledPropertyId, + !oldViewProps.accessibilityState.disabled, + !newViewProps.accessibilityState.disabled); } void CompositionBaseComponentView::updateBorderLayoutMetrics( diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp index cfaef02cb4c..0db1d3afbbb 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp @@ -124,10 +124,8 @@ void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, bool old if (spProviderSimple == nullptr || oldValue == newValue || !WasUiaPropertyAdvised(spProviderSimple, propId)) return; - auto hr = UiaRaiseAutomationPropertyChangedEvent( + UiaRaiseAutomationPropertyChangedEvent( spProviderSimple.get(), propId, CComVariant(oldValue), CComVariant(newValue)); - - assert(SUCCEEDED(hr)); } void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, const std::string& oldValue, const std::string& newValue) noexcept { @@ -136,10 +134,8 @@ void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, const st if (spProviderSimple == nullptr || oldValue == newValue || !WasUiaPropertyAdvised(spProviderSimple, propId)) return; - auto hr = UiaRaiseAutomationPropertyChangedEvent( + UiaRaiseAutomationPropertyChangedEvent( spProviderSimple.get(), propId, CComVariant(oldValue.c_str()), CComVariant(newValue.c_str())); - - assert(SUCCEEDED(hr)); } From b5133e6cd7d5a5c3c26baf5f45d00654494b61f8 Mon Sep 17 00:00:00 2001 From: Alicia Drummond Date: Tue, 30 May 2023 15:26:27 -0700 Subject: [PATCH 05/10] Change files --- ...ative-windows-67407575-cd5b-4bf1-b812-6e0d80d943c3.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-67407575-cd5b-4bf1-b812-6e0d80d943c3.json diff --git a/change/react-native-windows-67407575-cd5b-4bf1-b812-6e0d80d943c3.json b/change/react-native-windows-67407575-cd5b-4bf1-b812-6e0d80d943c3.json new file mode 100644 index 00000000000..65cc7b635d8 --- /dev/null +++ b/change/react-native-windows-67407575-cd5b-4bf1-b812-6e0d80d943c3.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Add UIA focus management and AdvisedEvents for prop changes", + "packageName": "react-native-windows", + "email": "adrum@microsoft.com", + "dependentChangeType": "patch" +} From 8084d43654bf9ec48be7d8bdd4087d317b4a93d9 Mon Sep 17 00:00:00 2001 From: Alicia Drummond Date: Tue, 30 May 2023 15:26:59 -0700 Subject: [PATCH 06/10] yarn format yarn change --- .../CompositionRootAutomationProvider.cpp | 25 ++++++++----------- .../CompositionRootAutomationProvider.h | 10 ++++++-- .../CompositionViewComponentView.cpp | 9 +++---- .../Fabric/Composition/UiaHelpers.cpp | 17 +++++++------ 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp index 838a2635175..83263a516b0 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp @@ -1,7 +1,7 @@ #include "pch.h" #include "CompositionRootAutomationProvider.h" -#include "UiaHelpers.h" #include +#include "UiaHelpers.h" #pragma warning(push) #pragma warning(disable : 4229) #define IN @@ -193,13 +193,17 @@ HRESULT __stdcall CompositionRootAutomationProvider::Navigate( return S_OK; } +// The old C-style interface for SAFEARRAY is tedious to use. ATL provides CComSafeArray which allows us to interact +// with the data structure using much more modern methods. However, AdviseEventAdded/Removed don't expect us to +// deallocate their In param, so this is a simple RAII wrapper to Attach/Detach in the scope of those functions and +// perform a little validation that the incoming SAFEARRAY is well formed. class UIAPropertyArray { CComSafeArray m_propArray{}; + public: UIAPropertyArray(SAFEARRAY *psaProperties) noexcept { VARTYPE vt; - if (psaProperties && SUCCEEDED(SafeArrayGetVartype(psaProperties, &vt)) && vt == m_propArray.GetType()) - { + if (psaProperties && SUCCEEDED(SafeArrayGetVartype(psaProperties, &vt)) && vt == m_propArray.GetType()) { m_propArray.Attach(psaProperties); } } @@ -210,8 +214,7 @@ class UIAPropertyArray { bool IsValid() noexcept { return m_propArray.GetSafeArrayPtr() != nullptr && m_propArray.GetDimensions() == 1 && m_propArray.GetCount() > 0; } - CComSafeArray* operator->() noexcept - { + CComSafeArray *operator->() noexcept { return &m_propArray; } }; @@ -222,9 +225,7 @@ void AdviseEventAddedImpl( auto it = std::find_if( advisedEvents.begin(), advisedEvents.end(), - [idEvent](const CompositionRootAutomationProvider::AdvisedEvent &ae) noexcept { - return ae.Event == idEvent; - }); + [idEvent](const CompositionRootAutomationProvider::AdvisedEvent &ae) noexcept { return ae.Event == idEvent; }); if (it == advisedEvents.end()) { advisedEvents.emplace_back(CompositionRootAutomationProvider::AdvisedEvent{idEvent, 1 /*Count*/}); @@ -261,9 +262,7 @@ HRESULT AdviseEventRemovedImpl( auto it = std::find_if( advisedEvents.begin(), advisedEvents.end(), - [idEvent](const CompositionRootAutomationProvider::AdvisedEvent &ae) noexcept { - return ae.Event == idEvent; - }); + [idEvent](const CompositionRootAutomationProvider::AdvisedEvent &ae) noexcept { return ae.Event == idEvent; }); if (it == advisedEvents.end()) { assert(false); @@ -287,15 +286,13 @@ HRESULT CompositionRootAutomationProvider::AdvisePropertiesRemoved(SAFEARRAY *ps for (auto i = props->GetLowerBound(); i <= props->GetUpperBound(); i++) { auto prop = props->GetAt(i); auto hr = AdviseEventRemovedImpl(m_advisedProperties, prop); - if (FAILED(hr)) - { + if (FAILED(hr)) { returnHr = hr; } } return returnHr; } - HRESULT CompositionRootAutomationProvider::AdviseEventRemoved(EVENTID idEvent, SAFEARRAY *psaProperties) { if (idEvent == UIA_AutomationPropertyChangedEventId) { diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.h b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.h index 13119087610..8ba2f23a4e4 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.h @@ -37,8 +37,7 @@ class CompositionRootAutomationProvider : public winrt::implements< // IRawElementProviderAdviseEvents virtual HRESULT __stdcall AdviseEventAdded(EVENTID idEvent, SAFEARRAY *psaProperties) override; - virtual HRESULT __stdcall AdviseEventRemoved(EVENTID idEvent, SAFEARRAY *psaProperties) - override; + virtual HRESULT __stdcall AdviseEventRemoved(EVENTID idEvent, SAFEARRAY *psaProperties) override; CompositionRootAutomationProvider( const std::shared_ptr<::Microsoft::ReactNative::RootComponentView> &componentView) noexcept; @@ -47,7 +46,12 @@ class CompositionRootAutomationProvider : public winrt::implements< bool WasPropertyAdvised(PROPERTYID prop) noexcept; bool WasEventAdvised(EVENTID event) noexcept; + // It's unlikely for the underlying primitive types for EVENTID and PROPERTYID to ever change, but let's make sure static_assert(std::is_same::value); + // Helper class for AdviseEventAdded/Removed. I could've simply used a std::pair, but I find using structs with named + // members easier to read and more self-documenting than pair.first and pair.last. Since this is simply syntactic + // sugar, I'm leveraging the fact that both EVENTID and PROPERTYID are ints under the hood to share + // AdviseEventAddedImpl struct AdvisedEvent { union { EVENTID Event; @@ -60,6 +64,8 @@ class CompositionRootAutomationProvider : public winrt::implements< HRESULT AdvisePropertiesAdded(SAFEARRAY *psaProperties) noexcept; HRESULT AdvisePropertiesRemoved(SAFEARRAY *psaProperties) noexcept; + // Linear search on unsorted vectors is typically faster than more sophisticated data structures when N is small. In + // practice ATs tend to only listen to a dozen or so props and events, so std::vector is likely better than maps. std::vector m_advisedEvents{}; std::vector m_advisedProperties{}; ::Microsoft::ReactNative::ReactTaggedView m_view; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp index 7aae0b3fdfe..d5683d3dfee 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp @@ -15,8 +15,8 @@ #include "CompositionDynamicAutomationProvider.h" #include "CompositionHelpers.h" #include "RootComponentView.h" -#include "d2d1helper.h" #include "UiaHelpers.h" +#include "d2d1helper.h" namespace Microsoft::ReactNative { @@ -78,8 +78,7 @@ bool CompositionBaseComponentView::runOnChildren(bool forward, Mso::FunctoronBlur(); showFocusVisual(false); - if (UiaClientsAreListening()) - { + if (UiaClientsAreListening()) { winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( EnsureUiaProvider(), UIA_HasKeyboardFocusPropertyId, true, false); } @@ -90,8 +89,7 @@ void CompositionBaseComponentView::onFocusGained() noexcept { if (m_enableFocusVisual) { showFocusVisual(true); } - if (UiaClientsAreListening()) - { + if (UiaClientsAreListening()) { auto spProviderSimple = EnsureUiaProvider().try_as(); if (spProviderSimple != nullptr) { winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( @@ -1047,7 +1045,6 @@ void CompositionBaseComponentView::updateBorderProps( void CompositionBaseComponentView::updateAccessibilityProps( const facebook::react::ViewProps &oldViewProps, const facebook::react::ViewProps &newViewProps) noexcept { - if (!UiaClientsAreListening()) return; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp index 0db1d3afbbb..7fac3a34e51 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp @@ -1,10 +1,10 @@ #include "pch.h" #include "UiaHelpers.h" -#include #include +#include #include -#include "RootComponentView.h" #include "CompositionRootAutomationProvider.h" +#include "RootComponentView.h" namespace winrt::Microsoft::ReactNative::implementation { @@ -89,8 +89,7 @@ HRESULT UiaGetBoundingRectangleHelper(::Microsoft::ReactNative::ReactTaggedView return S_OK; } -HRESULT UiaSetFocusHelper(::Microsoft::ReactNative::ReactTaggedView& view) noexcept -{ +HRESULT UiaSetFocusHelper(::Microsoft::ReactNative::ReactTaggedView &view) noexcept { auto strongView = view.view(); if (!strongView) @@ -124,11 +123,14 @@ void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, bool old if (spProviderSimple == nullptr || oldValue == newValue || !WasUiaPropertyAdvised(spProviderSimple, propId)) return; - UiaRaiseAutomationPropertyChangedEvent( - spProviderSimple.get(), propId, CComVariant(oldValue), CComVariant(newValue)); + UiaRaiseAutomationPropertyChangedEvent(spProviderSimple.get(), propId, CComVariant(oldValue), CComVariant(newValue)); } -void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, const std::string& oldValue, const std::string& newValue) noexcept { +void UpdateUiaProperty( + winrt::IInspectable provider, + PROPERTYID propId, + const std::string &oldValue, + const std::string &newValue) noexcept { auto spProviderSimple = provider.try_as(); if (spProviderSimple == nullptr || oldValue == newValue || !WasUiaPropertyAdvised(spProviderSimple, propId)) @@ -138,5 +140,4 @@ void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, const st spProviderSimple.get(), propId, CComVariant(oldValue.c_str()), CComVariant(newValue.c_str())); } - } // namespace winrt::Microsoft::ReactNative::implementation From 1ba32fe9550cbb92d352fc6caa37ff1aab3dfef8 Mon Sep 17 00:00:00 2001 From: Alicia Drummond Date: Tue, 30 May 2023 15:31:30 -0700 Subject: [PATCH 07/10] more comments --- .../Fabric/Composition/CompositionRootAutomationProvider.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp index 83263a516b0..c71ce9499d8 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp @@ -241,6 +241,7 @@ HRESULT CompositionRootAutomationProvider::AdvisePropertiesAdded(SAFEARRAY *psaP return E_INVALIDARG; } + // Note SAFEARRAY's upperbound is inclusive for (auto i = props->GetLowerBound(); i <= props->GetUpperBound(); i++) { auto prop = props->GetAt(i); AdviseEventAddedImpl(m_advisedProperties, prop); From 78f070937f45c5f07d466991d4e87af851d8bef4 Mon Sep 17 00:00:00 2001 From: Alicia Drummond Date: Thu, 1 Jun 2023 14:26:51 -0700 Subject: [PATCH 08/10] Remove change from bad merge --- vnext/Shared/Shared.vcxitems.filters | 1 - 1 file changed, 1 deletion(-) diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index 2264ec1fb17..a920a0ab2ad 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -273,7 +273,6 @@ - From f477f291371e24bc1469f59a5b641098209f2124 Mon Sep 17 00:00:00 2001 From: Alicia Drummond Date: Tue, 6 Jun 2023 15:47:08 -0700 Subject: [PATCH 09/10] Remote atlsafe dependency --- .../CompositionDynamicAutomationProvider.cpp | 36 +++--- .../CompositionRootAutomationProvider.cpp | 104 +++++++++++------- 2 files changed, 79 insertions(+), 61 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp index e488c781a0f..fc4fa6650aa 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp @@ -1,14 +1,9 @@ #include "pch.h" #include "CompositionDynamicAutomationProvider.h" #include -#pragma warning(push) -#pragma warning(disable : 4229) -#define IN -#define OUT -#include -#pragma warning(pop) #include "RootComponentView.h" #include "UiaHelpers.h" +#include namespace winrt::Microsoft::ReactNative::implementation { @@ -41,16 +36,15 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetRuntimeId(SAFEARRAY * if (!strongView) return UIA_E_ELEMENTNOTAVAILABLE; - CComSafeArray runtimeId; - auto hr = runtimeId.Create(2); + *pRetVal = SafeArrayCreateVector(VT_I4, 0, 2); + + if (*pRetVal == nullptr) + return E_OUTOFMEMORY; - if (FAILED(hr)) - return hr; - - runtimeId[0] = UiaAppendRuntimeId; - runtimeId[1] = strongView->tag(); - - *pRetVal = runtimeId.Detach(); + int runtimeId[] = {UiaAppendRuntimeId, strongView->tag()}; + for (long i = 0; i < 2; i++) { + SafeArrayPutElement(*pRetVal, &i, static_cast(&runtimeId[i])); + } return S_OK; } @@ -222,16 +216,16 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERT } case UIA_AutomationIdPropertyId: { pRetVal->vt = VT_BSTR; - auto testId = props->testId; - CComBSTR temp(testId.c_str()); - pRetVal->bstrVal = temp.Detach(); + auto wideTestId = ::Microsoft::Common::Unicode::Utf8ToUtf16(props->testId); + pRetVal->bstrVal = SysAllocString(wideTestId.c_str()); + hr = pRetVal->bstrVal != nullptr ? S_OK : E_OUTOFMEMORY; break; } case UIA_NamePropertyId: { pRetVal->vt = VT_BSTR; - auto name = props->accessibilityLabel; - CComBSTR temp(name.c_str()); - pRetVal->bstrVal = temp.Detach(); + auto wideName = ::Microsoft::Common::Unicode::Utf8ToUtf16(props->accessibilityLabel); + pRetVal->bstrVal = SysAllocString(wideName.c_str()); + hr = pRetVal->bstrVal != nullptr ? S_OK : E_OUTOFMEMORY; break; } case UIA_IsKeyboardFocusablePropertyId: { diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp index c71ce9499d8..d7003e458ce 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp @@ -2,12 +2,6 @@ #include "CompositionRootAutomationProvider.h" #include #include "UiaHelpers.h" -#pragma warning(push) -#pragma warning(disable : 4229) -#define IN -#define OUT -#include -#pragma warning(pop) namespace winrt::Microsoft::ReactNative::implementation { @@ -193,29 +187,15 @@ HRESULT __stdcall CompositionRootAutomationProvider::Navigate( return S_OK; } -// The old C-style interface for SAFEARRAY is tedious to use. ATL provides CComSafeArray which allows us to interact -// with the data structure using much more modern methods. However, AdviseEventAdded/Removed don't expect us to -// deallocate their In param, so this is a simple RAII wrapper to Attach/Detach in the scope of those functions and -// perform a little validation that the incoming SAFEARRAY is well formed. -class UIAPropertyArray { - CComSafeArray m_propArray{}; +// RAII wrapper to unaccess SafeArray data so I can early return in the relevant functions +class SafeArrayAccessScope { + SAFEARRAY* m_pArray = nullptr; public: - UIAPropertyArray(SAFEARRAY *psaProperties) noexcept { - VARTYPE vt; - if (psaProperties && SUCCEEDED(SafeArrayGetVartype(psaProperties, &vt)) && vt == m_propArray.GetType()) { - m_propArray.Attach(psaProperties); - } - } - ~UIAPropertyArray() noexcept { - if (m_propArray.GetSafeArrayPtr() != nullptr) - m_propArray.Detach(); - } - bool IsValid() noexcept { - return m_propArray.GetSafeArrayPtr() != nullptr && m_propArray.GetDimensions() == 1 && m_propArray.GetCount() > 0; - } - CComSafeArray *operator->() noexcept { - return &m_propArray; + SafeArrayAccessScope(SAFEARRAY *psa) noexcept : m_pArray(psa) {} + ~SafeArrayAccessScope() noexcept { + if (m_pArray != nullptr) + SafeArrayUnaccessData(m_pArray); } }; @@ -235,16 +215,38 @@ void AdviseEventAddedImpl( } HRESULT CompositionRootAutomationProvider::AdvisePropertiesAdded(SAFEARRAY *psaProperties) noexcept { - UIAPropertyArray props(psaProperties); + if (psaProperties == nullptr) + return E_POINTER; + + long *pValues = nullptr; + auto hr = SafeArrayAccessData(psaProperties, reinterpret_cast(&pValues)); + if (FAILED(hr)) + return hr; - if (!props.IsValid()) { + SafeArrayAccessScope accessScope(psaProperties); + + if (SafeArrayGetDim(psaProperties) != 1) return E_INVALIDARG; - } - // Note SAFEARRAY's upperbound is inclusive - for (auto i = props->GetLowerBound(); i <= props->GetUpperBound(); i++) { - auto prop = props->GetAt(i); - AdviseEventAddedImpl(m_advisedProperties, prop); + VARTYPE vt; + hr = SafeArrayGetVartype(psaProperties, &vt); + if (FAILED(hr) || vt != VT_I4) + return E_INVALIDARG; + + long lower; + hr = SafeArrayGetLBound(psaProperties, 1, &lower); + if (FAILED(hr)) + return hr; + + long upper; + hr = SafeArrayGetUBound(psaProperties, 1, &upper); + if (FAILED(hr)) + return hr; + + long count = upper - lower + 1; + + for (int i = 0; i < count; i++) { + AdviseEventAddedImpl(m_advisedProperties, pValues[i]); } return S_OK; } @@ -277,16 +279,38 @@ HRESULT AdviseEventRemovedImpl( } HRESULT CompositionRootAutomationProvider::AdvisePropertiesRemoved(SAFEARRAY *psaProperties) noexcept { - UIAPropertyArray props(psaProperties); + if (psaProperties == nullptr) + return E_POINTER; + + long *pValues = nullptr; + auto hr = SafeArrayAccessData(psaProperties, reinterpret_cast(&pValues)); + if (FAILED(hr)) + return hr; + + SafeArrayAccessScope accessScope(psaProperties); - if (!props.IsValid()) { + if (SafeArrayGetDim(psaProperties) != 1) return E_INVALIDARG; - } + VARTYPE vt; + hr = SafeArrayGetVartype(psaProperties, &vt); + if (FAILED(hr) || vt != VT_I4) + return E_INVALIDARG; + + long lower; + hr = SafeArrayGetLBound(psaProperties, 1, &lower); + if (FAILED(hr)) + return hr; + + long upper; + hr = SafeArrayGetUBound(psaProperties, 1, &upper); + if (FAILED(hr)) + return hr; + + long count = upper - lower + 1; auto returnHr = S_OK; - for (auto i = props->GetLowerBound(); i <= props->GetUpperBound(); i++) { - auto prop = props->GetAt(i); - auto hr = AdviseEventRemovedImpl(m_advisedProperties, prop); + for (int i = 0; i < count; i++) { + auto hr = AdviseEventRemovedImpl(m_advisedProperties, pValues[i]); if (FAILED(hr)) { returnHr = hr; } From 1915b7bbafbde0a4a020f8f1bd93cb3fd1bdf2c9 Mon Sep 17 00:00:00 2001 From: Alicia Drummond Date: Tue, 6 Jun 2023 15:55:59 -0700 Subject: [PATCH 10/10] yarn format --- .../Composition/CompositionDynamicAutomationProvider.cpp | 4 ++-- .../Fabric/Composition/CompositionRootAutomationProvider.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp index fc4fa6650aa..fb9533bb034 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp @@ -1,9 +1,9 @@ #include "pch.h" #include "CompositionDynamicAutomationProvider.h" #include +#include #include "RootComponentView.h" #include "UiaHelpers.h" -#include namespace winrt::Microsoft::ReactNative::implementation { @@ -37,7 +37,7 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetRuntimeId(SAFEARRAY * return UIA_E_ELEMENTNOTAVAILABLE; *pRetVal = SafeArrayCreateVector(VT_I4, 0, 2); - + if (*pRetVal == nullptr) return E_OUTOFMEMORY; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp index d7003e458ce..f0dd6e35880 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp @@ -189,7 +189,7 @@ HRESULT __stdcall CompositionRootAutomationProvider::Navigate( // RAII wrapper to unaccess SafeArray data so I can early return in the relevant functions class SafeArrayAccessScope { - SAFEARRAY* m_pArray = nullptr; + SAFEARRAY *m_pArray = nullptr; public: SafeArrayAccessScope(SAFEARRAY *psa) noexcept : m_pArray(psa) {}