Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic implementation of UIA navigation functions #11412

Merged
merged 17 commits into from
Mar 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Add UIA Navigate to Fabric Providers",
"packageName": "react-native-windows",
"email": "adrum@microsoft.com",
"dependentChangeType": "patch"
}
2 changes: 1 addition & 1 deletion vnext/Microsoft.ReactNative/CompositionRootView.idl
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ namespace Microsoft.ReactNative

Int64 SendMessage(UInt32 Msg, UInt64 WParam, Int64 LParam);

Object GetUiaProvider(UInt64 hwnd);
Object GetUiaProvider();

//void OnPointerPressed(PointerPressedArgs args);
//void OnMouseUp(Windows.Foundation.Point point);
Expand Down
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include <functional/functor.h>
#include <inspectable.h>
#include <react/renderer/componentregistry/ComponentDescriptorProvider.h>
#include <react/renderer/components/view/TouchEventEmitter.h>
#include <react/renderer/components/view/ViewProps.h>
Expand Down Expand Up @@ -61,6 +62,7 @@ struct IComponentView {
virtual facebook::react::Tag tag() const noexcept = 0;
virtual facebook::react::Tag hitTest(facebook::react::Point pt, facebook::react::Point &localPt) const noexcept = 0;
virtual int64_t sendMessage(uint32_t msg, uint64_t wParam, int64_t lParam) noexcept = 0;
virtual winrt::IInspectable EnsureUiaProvider() noexcept = 0;
};

// Run fn on all nodes of the component view tree starting from this one until fn returns true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "AbiCompositionViewComponentView.h"

#include <Fabric/DWriteHelpers.h>
#include "CompositionDynamicAutomationProvider.h"
#include "Unicode.h"

namespace Microsoft::ReactNative {
Expand All @@ -23,6 +24,14 @@ AbiCompositionViewComponentView::AbiCompositionViewComponentView(
OuterVisual().InsertAt(m_visual, 0);
}

std::shared_ptr<AbiCompositionViewComponentView> AbiCompositionViewComponentView::Create(
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
facebook::react::Tag tag,
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder) noexcept {
return std::shared_ptr<AbiCompositionViewComponentView>(
new AbiCompositionViewComponentView(compContext, tag, builder));
}

winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder &
AbiCompositionViewComponentView::Builder() noexcept {
return *winrt::get_self<winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder>(m_builder);
Expand Down Expand Up @@ -124,4 +133,12 @@ facebook::react::Tag AbiCompositionViewComponentView::hitTest(
return -1;
}

winrt::IInspectable AbiCompositionViewComponentView::EnsureUiaProvider() noexcept {
if (m_uiaProvider == nullptr) {
m_uiaProvider = winrt::make<winrt::Microsoft::ReactNative::implementation::CompositionDynamicAutomationProvider>(
shared_from_this());
}
return m_uiaProvider;
}

} // namespace Microsoft::ReactNative
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ namespace Microsoft::ReactNative {

struct AbiCompositionViewComponentView : CompositionBaseComponentView {
using Super = CompositionBaseComponentView;
AbiCompositionViewComponentView(

[[nodiscard]] static std::shared_ptr<AbiCompositionViewComponentView> Create(
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
facebook::react::Tag tag,
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder);
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder) noexcept;

winrt::IInspectable EnsureUiaProvider() noexcept override;

void mountChildComponentView(IComponentView &childComponentView, uint32_t index) noexcept override;
void unmountChildComponentView(IComponentView &childComponentView, uint32_t index) noexcept override;
Expand All @@ -44,6 +47,11 @@ struct AbiCompositionViewComponentView : CompositionBaseComponentView {
winrt::Microsoft::ReactNative::Composition::IVisual Visual() const noexcept override;

private:
AbiCompositionViewComponentView(
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
facebook::react::Tag tag,
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder);

winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder &Builder() noexcept;

winrt::IInspectable m_handle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,30 +46,30 @@ ComponentViewDescriptor const &ComponentViewRegistry::dequeueComponentViewWithCo
std::shared_ptr<CompositionBaseComponentView> view;

if (componentHandle == facebook::react::ViewShadowNode::Handle()) {
view = std::make_shared<CompositionViewComponentView>(compContext, tag);
view = CompositionViewComponentView::Create(compContext, tag);
} else if (componentHandle == facebook::react::ParagraphShadowNode::Handle()) {
view = std::make_shared<ParagraphComponentView>(compContext, tag);
view = ParagraphComponentView::Create(compContext, tag);
} else if (componentHandle == facebook::react::ScrollViewShadowNode::Handle()) {
view = std::make_shared<ScrollViewComponentView>(compContext, tag);
view = ScrollViewComponentView::Create(compContext, tag);
} else if (componentHandle == facebook::react::ImageShadowNode::Handle()) {
view = std::make_shared<ImageComponentView>(compContext, tag, m_context);
view = ImageComponentView::Create(compContext, tag, m_context);
} else if (componentHandle == facebook::react::WindowsTextInputShadowNode::Handle()) {
view = std::make_shared<WindowsTextInputComponentView>(compContext, tag, m_context);
view = WindowsTextInputComponentView::Create(compContext, tag, m_context);
} else if (componentHandle == facebook::react::SwitchShadowNode::Handle()) {
view = std::make_shared<SwitchComponentView>(compContext, tag, m_context);
view = SwitchComponentView::Create(compContext, tag, m_context);
} else if (componentHandle == facebook::react::RootShadowNode::Handle()) {
view = std::make_shared<RootComponentView>(compContext, tag);
view = RootComponentView::Create(compContext, tag);
} else if (
componentHandle == facebook::react::RawTextShadowNode::Handle() ||
componentHandle == facebook::react::TextShadowNode::Handle()) {
// Review - Why do we get asked for ComponentViews for Text/RawText... do these get used?
view = std::make_shared<CompositionViewComponentView>(compContext, tag);
view = CompositionViewComponentView::Create(compContext, tag);
} else if (componentHandle == facebook::react::UnimplementedNativeViewShadowNode::Handle()) {
view = std::make_shared<UnimplementedNativeViewComponentView>(compContext, tag);
view = UnimplementedNativeViewComponentView::Create(compContext, tag);
} else {
auto descriptor =
WindowsComponentDescriptorRegistry::FromProperties(m_context.Properties())->GetDescriptor(componentHandle);
view = std::make_shared<AbiCompositionViewComponentView>(
view = AbiCompositionViewComponentView::Create(
compContext, tag, descriptor.as<winrt::Microsoft::ReactNative::IReactViewComponentBuilder>());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
#include "pch.h"
#include "CompositionDynamicAutomationProvider.h"
#include <Fabric/ComponentView.h>
#pragma warning(push)
#pragma warning(disable : 4229)
#define IN
#define OUT
#include <atlsafe.h>
#pragma warning(pop)
#include "RootComponentView.h"
#include "UiaHelpers.h"

namespace winrt::Microsoft::ReactNative::implementation {

CompositionDynamicAutomationProvider::CompositionDynamicAutomationProvider(
const std::shared_ptr<::Microsoft::ReactNative::CompositionBaseComponentView> &componentView) noexcept
: m_view{std::static_pointer_cast<::Microsoft::ReactNative::IComponentView>(componentView)} {}

HRESULT __stdcall CompositionDynamicAutomationProvider::Navigate(
NavigateDirection direction,
IRawElementProviderFragment **pRetVal) {
if (pRetVal == nullptr)
return E_POINTER;

return UiaNavigateHelper(m_view, direction, *pRetVal);
}

// Implementations should return NULL for a top-level element that is hosted in a window. Other elements should return
// an array that contains UiaAppendRuntimeId (defined in Uiautomationcoreapi.h), followed by a value that is unique
// within an instance of the fragment.
//
// We'll use the react tag as our identifier for those situations
HRESULT __stdcall CompositionDynamicAutomationProvider::GetRuntimeId(SAFEARRAY **pRetVal) {
if (pRetVal == nullptr)
return E_POINTER;

*pRetVal = nullptr;

auto strongView = m_view.view();

if (!strongView)
return UIA_E_ELEMENTNOTAVAILABLE;

CComSafeArray<int32_t> runtimeId;
auto hr = runtimeId.Create(2);

if (FAILED(hr))
return hr;

runtimeId[0] = UiaAppendRuntimeId;
runtimeId[1] = strongView->tag();

*pRetVal = runtimeId.Detach();

return S_OK;
}

HRESULT __stdcall CompositionDynamicAutomationProvider::get_BoundingRectangle(UiaRect *pRetVal) {
if (pRetVal == nullptr)
return E_POINTER;

return S_OK;
}

HRESULT __stdcall CompositionDynamicAutomationProvider::GetEmbeddedFragmentRoots(SAFEARRAY **pRetVal) {
if (pRetVal == nullptr)
return E_POINTER;

*pRetVal = nullptr;

return S_OK;
}

HRESULT __stdcall CompositionDynamicAutomationProvider::SetFocus(void) {
return S_OK;
}

HRESULT __stdcall CompositionDynamicAutomationProvider::get_FragmentRoot(IRawElementProviderFragmentRoot **pRetVal) {
if (pRetVal == nullptr)
return E_POINTER;

auto strongView = m_view.view();

if (!strongView)
return UIA_E_ELEMENTNOTAVAILABLE;

auto rootCV = strongView->rootComponentView();
if (rootCV == nullptr)
return UIA_E_ELEMENTNOTAVAILABLE;

auto uiaProvider = rootCV->EnsureUiaProvider();
if (uiaProvider != nullptr) {
winrt::com_ptr<IRawElementProviderFragmentRoot> spReps;
uiaProvider.as(spReps);
*pRetVal = spReps.detach();
}

return S_OK;
}

HRESULT __stdcall CompositionDynamicAutomationProvider::get_ProviderOptions(ProviderOptions *pRetVal) {
if (pRetVal == nullptr)
return E_POINTER;

*pRetVal = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading;
return S_OK;
}

HRESULT __stdcall CompositionDynamicAutomationProvider::GetPatternProvider(PATTERNID patternId, IUnknown **pRetVal) {
if (pRetVal == nullptr)
return E_POINTER;

*pRetVal = nullptr;

return S_OK;
}

long GetControlType(const std::string &role) noexcept {
if (role == "adjustable") {
return UIA_SliderControlTypeId;
} else if (role == "group" || role == "search" || role == "radiogroup" || role == "timer" || role.empty()) {
return UIA_GroupControlTypeId;
} else if (role == "button" || role == "imagebutton" || role == "switch" || role == "togglebutton") {
return UIA_ButtonControlTypeId;
} else if (role == "checkbox") {
return UIA_CheckBoxControlTypeId;
} else if (role == "combobox") {
return UIA_ComboBoxControlTypeId;
} else if (role == "alert" || role == "header" || role == "summary" || role == "text") {
return UIA_TextControlTypeId;
} else if (role == "image") {
return UIA_ImageControlTypeId;
} else if (role == "keyboardkey") {
return UIA_CustomControlTypeId;
} else if (role == "link") {
return UIA_HyperlinkControlTypeId;
}
// list and listitem were added by RNW to better support UIA Control Types
else if (role == "list") {
return UIA_ListControlTypeId;
} else if (role == "listitem") {
return UIA_ListItemControlTypeId;
} else if (role == "menu") {
return UIA_MenuControlTypeId;
} else if (role == "menubar") {
return UIA_MenuBarControlTypeId;
} else if (role == "menuitem") {
return UIA_MenuItemControlTypeId;
}
// If role is "none", remove the element from the control tree
// and expose it as a plain element would in the raw tree.
else if (role == "none") {
return UIA_GroupControlTypeId;
} else if (role == "progressbar") {
return UIA_ProgressBarControlTypeId;
} else if (role == "radio") {
return UIA_RadioButtonControlTypeId;
} else if (role == "scrollbar") {
return UIA_ScrollBarControlTypeId;
} else if (role == "spinbutton") {
return UIA_SpinnerControlTypeId;
} else if (role == "splitbutton") {
return UIA_SplitButtonControlTypeId;
} else if (role == "tab") {
return UIA_TabItemControlTypeId;
} else if (role == "tablist") {
return UIA_TabControlTypeId;
} else if (role == "toolbar") {
return UIA_ToolBarControlTypeId;
} else if (role == "tree") {
return UIA_TreeControlTypeId;
} else if (role == "treeitem") {
return UIA_TreeItemControlTypeId;
}
assert(false);
return UIA_GroupControlTypeId;
}

HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERTYID propertyId, VARIANT *pRetVal) {
if (pRetVal == nullptr)
return E_POINTER;

auto strongView = m_view.view();
if (strongView == nullptr)
return UIA_E_ELEMENTNOTAVAILABLE;

auto props = std::static_pointer_cast<const facebook::react::ViewProps>(strongView->props());
if (props == nullptr)
return UIA_E_ELEMENTNOTAVAILABLE;

switch (propertyId) {
case UIA_ControlTypePropertyId: {
pRetVal->vt = VT_I4;
auto role = props->accessibilityRole;
pRetVal->lVal = GetControlType(role);
break;
}
case UIA_AutomationIdPropertyId: {
pRetVal->vt = VT_BSTR;
auto testId = props->testId;
CComBSTR temp(testId.c_str());
pRetVal->bstrVal = temp.Detach();
break;
}
case UIA_NamePropertyId: {
pRetVal->vt = VT_BSTR;
auto name = props->accessibilityLabel;
CComBSTR temp(name.c_str());
pRetVal->bstrVal = temp.Detach();
break;
}
}

return S_OK;
}

HRESULT __stdcall CompositionDynamicAutomationProvider::get_HostRawElementProvider(
IRawElementProviderSimple **pRetVal) {
if (pRetVal == nullptr)
return E_POINTER;

*pRetVal = nullptr;

return S_OK;
}

} // namespace winrt::Microsoft::ReactNative::implementation
Loading