From dac9f65182dd7f760c5ea9e6965d86db0779c59a Mon Sep 17 00:00:00 2001 From: Sharath Manchala <10109130+sharath2727@users.noreply.github.com> Date: Tue, 2 Sep 2025 20:47:18 +0530 Subject: [PATCH 1/2] Inital validation --- packages/sample-app-fabric/App.tsx | 5 +- .../SampleAppFabric/SampleAppFabric.cpp | 4 + .../SampleAppFabric/SampleAppFabric.vcxproj | 5 +- vnext/Microsoft.ReactNative/App.xaml | 6 + .../ContentIslandComponentView.cpp | 4 +- .../Microsoft.ReactNative.vcxproj | 2 +- vnext/Microsoft.ReactNative/Pch/pch.h | 17 ++ .../Microsoft.ReactNative/XamlApplication.cpp | 79 +++++++ vnext/Microsoft.ReactNative/XamlApplication.h | 47 ++++ .../Microsoft.ReactNative/XamlApplication.idl | 17 ++ vnext/Microsoft.ReactNative/XamlHost.cpp | 93 ++++++++ vnext/Microsoft.ReactNative/XamlHost.h | 10 + .../microsoft.reactnative.def | 1 + vnext/Shared/Shared.vcxitems | 24 +++ vnext/Shared/Shared.vcxitems.filters | 3 + .../components/rnwcore/ComponentDescriptors.h | 1 + .../components/rnwcore/EventEmitters.cpp | 1 + .../react/components/rnwcore/EventEmitters.h | 7 + .../react/components/rnwcore/Props.cpp | 7 + .../codegen/react/components/rnwcore/Props.h | 10 + .../react/components/rnwcore/ShadowNodes.cpp | 1 + .../react/components/rnwcore/ShadowNodes.h | 11 + .../codegen/react/components/rnwcore/States.h | 12 ++ .../react/components/rnwcore/XamlHost.g.h | 200 ++++++++++++++++++ vnext/fmt/packages.lock.json | 13 ++ .../Libraries/Components/Xaml/XamlHost.d.ts | 13 ++ .../Components/Xaml/XamlHost.windows.js | 7 + vnext/src-win/index.windows.js | 4 + .../Xaml/XamlHostNativeComponent.js | 19 ++ 29 files changed, 617 insertions(+), 6 deletions(-) create mode 100644 vnext/Microsoft.ReactNative/App.xaml create mode 100644 vnext/Microsoft.ReactNative/XamlApplication.cpp create mode 100644 vnext/Microsoft.ReactNative/XamlApplication.h create mode 100644 vnext/Microsoft.ReactNative/XamlApplication.idl create mode 100644 vnext/Microsoft.ReactNative/XamlHost.cpp create mode 100644 vnext/Microsoft.ReactNative/XamlHost.h create mode 100644 vnext/codegen/react/components/rnwcore/XamlHost.g.h create mode 100644 vnext/fmt/packages.lock.json create mode 100644 vnext/src-win/Libraries/Components/Xaml/XamlHost.d.ts create mode 100644 vnext/src-win/Libraries/Components/Xaml/XamlHost.windows.js create mode 100644 vnext/src-win/src/private/specs_DEPRECATED/components/Xaml/XamlHostNativeComponent.js diff --git a/packages/sample-app-fabric/App.tsx b/packages/sample-app-fabric/App.tsx index 3ac27a50420..53748ea279e 100644 --- a/packages/sample-app-fabric/App.tsx +++ b/packages/sample-app-fabric/App.tsx @@ -8,6 +8,7 @@ import React from 'react'; import {SafeAreaView, StatusBar, useColorScheme} from 'react-native'; import {NewAppScreen} from '@react-native/new-app-screen'; +import XamlHost from 'react-native-windows/Libraries/Components/Xaml/XamlHost'; function App(): React.JSX.Element { const isDarkMode = useColorScheme() === 'dark'; @@ -18,7 +19,9 @@ function App(): React.JSX.Element { barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor="transparent" translucent - /> + /> + + ); diff --git a/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.cpp b/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.cpp index 2aeaae1397e..548eada80f2 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.cpp +++ b/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.cpp @@ -8,12 +8,16 @@ #include "NativeModules.h" +#include "../../../../vnext/Microsoft.ReactNative/XamlHost.h" + + // A PackageProvider containing any turbo modules you define within this app project struct CompReactPackageProvider : winrt::implements { public: // IReactPackageProvider void CreatePackage(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept { AddAttributedModules(packageBuilder, true); + RegisterXamlHostComponentView(packageBuilder); } }; diff --git a/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.vcxproj b/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.vcxproj index 415ba11b336..3d40a5d0b84 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.vcxproj +++ b/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.vcxproj @@ -77,8 +77,9 @@ 4453;28204 - shell32.lib;user32.lib;windowsapp.lib;%(AdditionalDependenices) - Windows + shell32.lib;user32.lib;windowsapp.lib;Microsoft.ReactNative.lib;%(AdditionalDependencies) + ..\..\..\..\vnext\target\x64\Debug\Microsoft.ReactNative\ + Windows true diff --git a/vnext/Microsoft.ReactNative/App.xaml b/vnext/Microsoft.ReactNative/App.xaml new file mode 100644 index 00000000000..3354935ec8d --- /dev/null +++ b/vnext/Microsoft.ReactNative/App.xaml @@ -0,0 +1,6 @@ + + diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp index 57aa7d14656..37736a34f40 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp @@ -178,14 +178,14 @@ ContentIslandComponentView::~ContentIslandComponentView() noexcept { void ContentIslandComponentView::MountChildComponentView( const winrt::Microsoft::ReactNative::ComponentView &childComponentView, uint32_t index) noexcept { - assert(false); + //assert(false); base_type::MountChildComponentView(childComponentView, index); } void ContentIslandComponentView::UnmountChildComponentView( const winrt::Microsoft::ReactNative::ComponentView &childComponentView, uint32_t index) noexcept { - assert(false); + //assert(false); base_type::UnmountChildComponentView(childComponentView, index); } diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index b50fab3cadb..92f3f109ea4 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -68,7 +68,7 @@ - + diff --git a/vnext/Microsoft.ReactNative/Pch/pch.h b/vnext/Microsoft.ReactNative/Pch/pch.h index 34b0b02b9be..9447187a9c1 100644 --- a/vnext/Microsoft.ReactNative/Pch/pch.h +++ b/vnext/Microsoft.ReactNative/Pch/pch.h @@ -77,3 +77,20 @@ #include #include #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/vnext/Microsoft.ReactNative/XamlApplication.cpp b/vnext/Microsoft.ReactNative/XamlApplication.cpp new file mode 100644 index 00000000000..4bc71b863ed --- /dev/null +++ b/vnext/Microsoft.ReactNative/XamlApplication.cpp @@ -0,0 +1,79 @@ +#include "pch.h" +#include "XamlApplication.h" +#include "XamlApplication.g.cpp" + +#include "winrt/Microsoft.UI.Xaml.XamlTypeInfo.h" + +namespace winrt::Microsoft::ReactNative::implementation +{ +using namespace ::winrt::Microsoft::UI::Xaml; +using namespace ::winrt::Microsoft::UI::Xaml::Markup; +using namespace ::winrt::Windows::UI::Xaml::Interop; + XamlApplication::XamlApplication() + { + //m_providers.push_back(winrt::make_self().as()); + s_current = *this; + + // TODO: It's probably not a good idea to only load the controls pri file, there are other ones too. + auto resourceManager = + winrt::Microsoft::Windows::ApplicationModel::Resources::ResourceManager(L"Microsoft.UI.Xaml.Controls.pri"); + + this->ResourceManagerRequested([resourceManager](auto &&, ResourceManagerRequestedEventArgs args) { + args.CustomResourceManager(resourceManager); + }); + winrt::Microsoft::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread(); + + m_providers.push_back( + winrt::make_self() + .as()); // Default generated provider + m_providers.push_back(winrt::Microsoft::UI::Xaml::XamlTypeInfo::XamlControlsXamlMetaDataProvider()); + + auto winUIResources = winrt::Microsoft::UI::Xaml::Controls::XamlControlsResources(); + Resources().MergedDictionaries().Append(winUIResources); + } + + XamlApplication::~XamlApplication() + { + s_current = nullptr; + } + + void XamlApplication::AddMetadataProvider(winrt::Microsoft::UI::Xaml::Markup::IXamlMetadataProvider const& otherProvider) + { + m_providers.push_back(otherProvider); + } + + winrt::Microsoft::UI::Xaml::Markup::IXamlType XamlApplication::GetXamlType(winrt::Windows::UI::Xaml::Interop::TypeName const& type) + { + for (auto &&provider : m_providers) { + if (auto result = provider.GetXamlType(type)) { + return result; + } + } + return nullptr; + } + + winrt::Microsoft::UI::Xaml::Markup::IXamlType XamlApplication::GetXamlType(hstring const& fullName) + { + for (auto &&provider : m_providers) { + if (auto result = provider.GetXamlType(fullName)) { + return result; + } + } + + return nullptr; + } + com_array XamlApplication::GetXmlnsDefinitions() + { + std::vector<::winrt::Microsoft::UI::Xaml::Markup::XmlnsDefinition> allDefinitions; + for (const auto &provider : m_providers) { + auto definitionsCurrentProvider = provider.GetXmlnsDefinitions(); + for (const auto &definition : definitionsCurrentProvider) { + allDefinitions.insert(allDefinitions.begin(), definition); + } + } + return winrt::com_array<::winrt::Microsoft::UI::Xaml::Markup::XmlnsDefinition>( + allDefinitions.begin(), allDefinitions.end()); + } + + winrt::Microsoft::ReactNative::XamlApplication XamlApplication::s_current{nullptr}; +} diff --git a/vnext/Microsoft.ReactNative/XamlApplication.h b/vnext/Microsoft.ReactNative/XamlApplication.h new file mode 100644 index 00000000000..6a9e6d3b7a6 --- /dev/null +++ b/vnext/Microsoft.ReactNative/XamlApplication.h @@ -0,0 +1,47 @@ +#pragma once +#include "XamlApplication.g.h" + +#include "winrt/Microsoft.UI.Xaml.Hosting.h" +#include "winrt/Microsoft.UI.Xaml.Interop.h" +#include "winrt/Microsoft.UI.Xaml.Markup.h" +#include "winrt/Microsoft.UI.Xaml.h" + +#include "winrt/Windows.UI.Xaml.Interop.h" + +#include "XamlMetaDataProvider.h" + +namespace winrt::Microsoft::ReactNative::implementation +{ + struct XamlApplication : XamlApplicationT + { + XamlApplication(); + ~XamlApplication(); + + static void EnsureCreated() + { + if (Current() == nullptr) { + s_current = winrt::make(); + } + } + + static winrt::Microsoft::ReactNative::XamlApplication Current() + { + return s_current; + } + + void AddMetadataProvider(winrt::Microsoft::UI::Xaml::Markup::IXamlMetadataProvider const& otherProvider); + winrt::Microsoft::UI::Xaml::Markup::IXamlType GetXamlType(winrt::Windows::UI::Xaml::Interop::TypeName const& type); + winrt::Microsoft::UI::Xaml::Markup::IXamlType GetXamlType(hstring const& fullName); + com_array GetXmlnsDefinitions(); + + private: + static winrt::Microsoft::ReactNative::XamlApplication s_current; + std::vector m_providers; + }; +} +namespace winrt::Microsoft::ReactNative::factory_implementation +{ + struct XamlApplication : XamlApplicationT + { + }; +} diff --git a/vnext/Microsoft.ReactNative/XamlApplication.idl b/vnext/Microsoft.ReactNative/XamlApplication.idl new file mode 100644 index 00000000000..928ac01617f --- /dev/null +++ b/vnext/Microsoft.ReactNative/XamlApplication.idl @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.ReactNative { +[webhosthidden][default_interface] runtimeclass XamlApplication : Microsoft.UI.Xaml.Application, + Microsoft.UI.Xaml.Markup.IXamlMetadataProvider { + XamlApplication(); + + static void EnsureCreated(); + + static XamlApplication Current { + get; + }; + + void AddMetadataProvider(Microsoft.UI.Xaml.Markup.IXamlMetadataProvider otherProvider); +} +} // namespace Microsoft.ReactNative. Xaml diff --git a/vnext/Microsoft.ReactNative/XamlHost.cpp b/vnext/Microsoft.ReactNative/XamlHost.cpp new file mode 100644 index 00000000000..779dc79688e --- /dev/null +++ b/vnext/Microsoft.ReactNative/XamlHost.cpp @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" + +#include "XamlApplication.h" +#include "XamlHost.h" + +#if defined(RNW_NEW_ARCH) + +#include "..\codegen\react\components\rnwcore\XamlHost.g.h" + +namespace winrt::Microsoft::ReactNative { + +struct XamlHostComponentView : public winrt::implements, + ::Microsoft::ReactNativeSpecs::BaseXamlHost { + void InitializeContentIsland( + const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView &islandView) noexcept { + winrt::Microsoft::ReactNative::implementation::XamlApplication::EnsureCreated(); + + m_xamlIsland = winrt::Microsoft::UI::Xaml::XamlIsland{}; + + /* auto grid = winrt::Microsoft::UI::Xaml::Controls::Grid{}; + auto button = winrt::Microsoft::UI::Xaml::Controls::Button{}; + button.Content(winrt::box_value(L"(This is placeholder content for the XamlHost.)")); + grid.Children().Append(button); + m_xamlIsland.Content(grid); + m_xamlIsland.Content(m_XamlHost);*/ + // Create a Button control + + winrt::Microsoft::UI::Xaml::Controls::TextBlock textBlock; + textBlock.Text(L"Hello from XAML!"); + textBlock.HorizontalAlignment(winrt::Microsoft::UI::Xaml::HorizontalAlignment::Center); + textBlock.VerticalAlignment(winrt::Microsoft::UI::Xaml::VerticalAlignment::Center); + + m_xamlIsland.Content(textBlock); + + islandView.Connect(m_xamlIsland.ContentIsland()); + } + + void MountChildComponentView( + const winrt::Microsoft::ReactNative::ComponentView & /*view*/, + const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &args) noexcept override { + // Add the xaml child to the m_xamlIsland here. + /* + auto childXamlControl = args.Child().UserData().as(); + if (childXamlControl) { + auto xamlElement = childXamlControl.GetXamlElement(); + m_xamlIsland.Content(xamlElement); + } + */ + + // Create a Button control + winrt::Microsoft::UI::Xaml::Controls::Button button; + button.Content(winrt::box_value(L"Click Me")); + button.HorizontalAlignment(winrt::Microsoft::UI::Xaml::HorizontalAlignment::Center); + button.VerticalAlignment(winrt::Microsoft::UI::Xaml::VerticalAlignment::Center); + + // Attach Click event handler + button.Click([](winrt::IInspectable const &, winrt::Microsoft::UI::Xaml::RoutedEventArgs const &) { + OutputDebugString(L"Welcome to XAML world\n"); + }); + + m_xamlIsland.Content(button); + + } + + void UnmountChildComponentView( + const winrt::Microsoft::ReactNative::ComponentView & /*view*/, + const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &) noexcept override { + m_xamlIsland.Content(nullptr); + } + + private: + winrt::Microsoft::UI::Xaml::XamlIsland m_xamlIsland{nullptr}; +}; + +} // namespace winrt::Microsoft::ReactNative + +void RegisterXamlHostComponentView(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) { + ::Microsoft::ReactNativeSpecs::RegisterXamlHostNativeComponent( + packageBuilder, + [](const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder &builder) { + builder.SetContentIslandComponentViewInitializer( + [](const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView &islandView) noexcept { + OutputDebugString(L"RegisterXamlHostComponentView called\n"); + auto userData = winrt::make_self(); + userData->InitializeContentIsland(islandView); + islandView.UserData(*userData); + }); + }); +} + +#endif // defined(RNW_NEW_ARCH) && defined(USE_EXPERIMENTAL_WINUI3) diff --git a/vnext/Microsoft.ReactNative/XamlHost.h b/vnext/Microsoft.ReactNative/XamlHost.h new file mode 100644 index 00000000000..d64bb111f95 --- /dev/null +++ b/vnext/Microsoft.ReactNative/XamlHost.h @@ -0,0 +1,10 @@ +#pragma once + +#if defined(RNW_NEW_ARCH) + +__declspec(dllexport) void RegisterXamlHostComponentView( + winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder); + +//void RegisterXamlControl(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder); + +#endif // defined(RNW_NEW_ARCH) && defined(USE_EXPERIMENTAL_WINUI3) diff --git a/vnext/Microsoft.ReactNative/microsoft.reactnative.def b/vnext/Microsoft.ReactNative/microsoft.reactnative.def index d04a02ea81e..faff126f6c4 100644 --- a/vnext/Microsoft.ReactNative/microsoft.reactnative.def +++ b/vnext/Microsoft.ReactNative/microsoft.reactnative.def @@ -1,3 +1,4 @@ EXPORTS DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE +RegisterXamlHostComponentView diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index fbfc3740fb4..be03cff75f7 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -241,6 +241,16 @@ true $(ReactNativeWindowsDir)Microsoft.ReactNative\ReactNativeAppBuilder.idl Code + + + true + $(ReactNativeWindowsDir)Microsoft.ReactNative\XamlApplication.idl + Code + + + true + $(ReactNativeWindowsDir)Microsoft.ReactNative\XamlApplication.idl + Code @@ -353,6 +363,16 @@ true $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\ReactNativeAppBuilder.idl Code + + + true + $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\XamlApplication.idl + Code + + + true + $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\XamlApplication.idl + Code $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IJSValueReader.idl @@ -717,6 +737,7 @@ + @@ -732,4 +753,7 @@ NotUsing + + + diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index 8a8fb167d88..a9fb6dc1699 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -858,4 +858,7 @@ + + + \ No newline at end of file diff --git a/vnext/codegen/react/components/rnwcore/ComponentDescriptors.h b/vnext/codegen/react/components/rnwcore/ComponentDescriptors.h index 45b7b5ea904..f3d7509b0e9 100644 --- a/vnext/codegen/react/components/rnwcore/ComponentDescriptors.h +++ b/vnext/codegen/react/components/rnwcore/ComponentDescriptors.h @@ -23,6 +23,7 @@ using DebuggingOverlayComponentDescriptor = ConcreteComponentDescriptor; using SwitchComponentDescriptor = ConcreteComponentDescriptor; using UnimplementedNativeViewComponentDescriptor = ConcreteComponentDescriptor; +using XamlHostComponentDescriptor = ConcreteComponentDescriptor; void rnwcore_registerComponentDescriptorsFromCodegen( std::shared_ptr registry); diff --git a/vnext/codegen/react/components/rnwcore/EventEmitters.cpp b/vnext/codegen/react/components/rnwcore/EventEmitters.cpp index b606e786965..4c80fea3870 100644 --- a/vnext/codegen/react/components/rnwcore/EventEmitters.cpp +++ b/vnext/codegen/react/components/rnwcore/EventEmitters.cpp @@ -129,4 +129,5 @@ payload.setProperty(runtime, "target", event.target); } + } // namespace facebook::react diff --git a/vnext/codegen/react/components/rnwcore/EventEmitters.h b/vnext/codegen/react/components/rnwcore/EventEmitters.h index 0c3bce4a218..ccac8315736 100644 --- a/vnext/codegen/react/components/rnwcore/EventEmitters.h +++ b/vnext/codegen/react/components/rnwcore/EventEmitters.h @@ -165,5 +165,12 @@ class UnimplementedNativeViewEventEmitter : public ViewEventEmitter { +}; +class XamlHostEventEmitter : public ViewEventEmitter { + public: + using ViewEventEmitter::ViewEventEmitter; + + + }; } // namespace facebook::react diff --git a/vnext/codegen/react/components/rnwcore/Props.cpp b/vnext/codegen/react/components/rnwcore/Props.cpp index 3d33bfb0980..9e1ca41bd42 100644 --- a/vnext/codegen/react/components/rnwcore/Props.cpp +++ b/vnext/codegen/react/components/rnwcore/Props.cpp @@ -153,5 +153,12 @@ UnimplementedNativeViewProps::UnimplementedNativeViewProps( name(convertRawProp(context, rawProps, "name", sourceProps.name, {""})) {} +XamlHostProps::XamlHostProps( + const PropsParserContext &context, + const XamlHostProps &sourceProps, + const RawProps &rawProps): ViewProps(context, sourceProps, rawProps), + + label(convertRawProp(context, rawProps, "label", sourceProps.label, {})) + {} } // namespace facebook::react diff --git a/vnext/codegen/react/components/rnwcore/Props.h b/vnext/codegen/react/components/rnwcore/Props.h index 397bc1706cb..5805acbd7d5 100644 --- a/vnext/codegen/react/components/rnwcore/Props.h +++ b/vnext/codegen/react/components/rnwcore/Props.h @@ -395,4 +395,14 @@ class UnimplementedNativeViewProps final : public ViewProps { std::string name{""}; }; +class XamlHostProps final : public ViewProps { + public: + XamlHostProps() = default; + XamlHostProps(const PropsParserContext& context, const XamlHostProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + std::string label{}; +}; + } // namespace facebook::react diff --git a/vnext/codegen/react/components/rnwcore/ShadowNodes.cpp b/vnext/codegen/react/components/rnwcore/ShadowNodes.cpp index dccefb1d478..627e9a2d3fe 100644 --- a/vnext/codegen/react/components/rnwcore/ShadowNodes.cpp +++ b/vnext/codegen/react/components/rnwcore/ShadowNodes.cpp @@ -19,5 +19,6 @@ extern const char DebuggingOverlayComponentName[] = "DebuggingOverlay"; extern const char PullToRefreshViewComponentName[] = "PullToRefreshView"; extern const char SwitchComponentName[] = "Switch"; extern const char UnimplementedNativeViewComponentName[] = "UnimplementedNativeView"; +extern const char XamlHostComponentName[] = "XamlHost"; } // namespace facebook::react diff --git a/vnext/codegen/react/components/rnwcore/ShadowNodes.h b/vnext/codegen/react/components/rnwcore/ShadowNodes.h index 2c7c94720cf..9b45530d5ad 100644 --- a/vnext/codegen/react/components/rnwcore/ShadowNodes.h +++ b/vnext/codegen/react/components/rnwcore/ShadowNodes.h @@ -95,4 +95,15 @@ using UnimplementedNativeViewShadowNode = ConcreteViewShadowNode< UnimplementedNativeViewEventEmitter, UnimplementedNativeViewState>; +JSI_EXPORT extern const char XamlHostComponentName[]; + +/* + * `ShadowNode` for component. + */ +using XamlHostShadowNode = ConcreteViewShadowNode< + XamlHostComponentName, + XamlHostProps, + XamlHostEventEmitter, + XamlHostState>; + } // namespace facebook::react diff --git a/vnext/codegen/react/components/rnwcore/States.h b/vnext/codegen/react/components/rnwcore/States.h index df8b9e54057..efc143b39cc 100644 --- a/vnext/codegen/react/components/rnwcore/States.h +++ b/vnext/codegen/react/components/rnwcore/States.h @@ -98,4 +98,16 @@ class UnimplementedNativeViewState { #endif }; +class XamlHostState { +public: + XamlHostState() = default; + +#ifdef ANDROID + XamlHostState(XamlHostState const &previousState, folly::dynamic data){}; + folly::dynamic getDynamic() const { + return {}; + }; +#endif +}; + } // namespace facebook::react \ No newline at end of file diff --git a/vnext/codegen/react/components/rnwcore/XamlHost.g.h b/vnext/codegen/react/components/rnwcore/XamlHost.g.h new file mode 100644 index 00000000000..26bd059c3c3 --- /dev/null +++ b/vnext/codegen/react/components/rnwcore/XamlHost.g.h @@ -0,0 +1,200 @@ + +/* + * This file is auto-generated from XamlHostNativeComponent spec file in flow / TypeScript. + */ +// clang-format off +#pragma once + +#include + +#ifdef RNW_NEW_ARCH +#include + +#include +#include +#endif // #ifdef RNW_NEW_ARCH + +#ifdef RNW_NEW_ARCH + +namespace Microsoft::ReactNativeSpecs { + +REACT_STRUCT(XamlHostProps) +struct XamlHostProps : winrt::implements { + XamlHostProps(winrt::Microsoft::ReactNative::ViewProps props, const winrt::Microsoft::ReactNative::IComponentProps& cloneFrom) + : ViewProps(props) + { + if (cloneFrom) { + auto cloneFromProps = cloneFrom.as(); + label = cloneFromProps->label; + } + } + + void SetProp(uint32_t hash, winrt::hstring propName, winrt::Microsoft::ReactNative::IJSValueReader value) noexcept { + winrt::Microsoft::ReactNative::ReadProp(hash, propName, value, *this); + } + + REACT_FIELD(label) + std::optional label; + + const winrt::Microsoft::ReactNative::ViewProps ViewProps; +}; + +struct XamlHostEventEmitter { + XamlHostEventEmitter(const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter) + : m_eventEmitter(eventEmitter) {} + + private: + winrt::Microsoft::ReactNative::EventEmitter m_eventEmitter{nullptr}; +}; + +template +struct BaseXamlHost { + + virtual void UpdateProps( + const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::com_ptr &newProps, + const winrt::com_ptr &/*oldProps*/) noexcept { + m_props = newProps; + } + + // UpdateLayoutMetrics will only be called if this method is overridden + virtual void UpdateLayoutMetrics( + const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::Microsoft::ReactNative::LayoutMetrics &/*newLayoutMetrics*/, + const winrt::Microsoft::ReactNative::LayoutMetrics &/*oldLayoutMetrics*/) noexcept { + } + + // UpdateState will only be called if this method is overridden + virtual void UpdateState( + const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::Microsoft::ReactNative::IComponentState &/*newState*/) noexcept { + } + + virtual void UpdateEventEmitter(const std::shared_ptr &eventEmitter) noexcept { + m_eventEmitter = eventEmitter; + } + + // MountChildComponentView will only be called if this method is overridden + virtual void MountChildComponentView(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &/*args*/) noexcept { + } + + // UnmountChildComponentView will only be called if this method is overridden + virtual void UnmountChildComponentView(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &/*args*/) noexcept { + } + + // Initialize will only be called if this method is overridden + virtual void Initialize(const winrt::Microsoft::ReactNative::ComponentView &/*view*/) noexcept { + } + + // CreateVisual will only be called if this method is overridden + virtual winrt::Microsoft::UI::Composition::Visual CreateVisual(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { + return view.as().Compositor().CreateSpriteVisual(); + } + + // FinalizeUpdate will only be called if this method is overridden + virtual void FinalizeUpdate(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + winrt::Microsoft::ReactNative::ComponentViewUpdateMask /*mask*/) noexcept { + } + + + + const std::shared_ptr& EventEmitter() const { return m_eventEmitter; } + const winrt::com_ptr& Props() const { return m_props; } + +private: + winrt::com_ptr m_props; + std::shared_ptr m_eventEmitter; +}; + +template +void RegisterXamlHostNativeComponent( + winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder, + std::function builderCallback) noexcept { + packageBuilder.as().AddViewComponent( + L"XamlHost", [builderCallback](winrt::Microsoft::ReactNative::IReactViewComponentBuilder const &builder) noexcept { + auto compBuilder = builder.as(); + + builder.SetCreateProps([](winrt::Microsoft::ReactNative::ViewProps props, + const winrt::Microsoft::ReactNative::IComponentProps& cloneFrom) noexcept { + return winrt::make(props, cloneFrom); + }); + + builder.SetUpdatePropsHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::IComponentProps &newProps, + const winrt::Microsoft::ReactNative::IComponentProps &oldProps) noexcept { + auto userData = view.UserData().as(); + userData->UpdateProps(view, newProps ? newProps.as() : nullptr, oldProps ? oldProps.as() : nullptr); + }); + + compBuilder.SetUpdateLayoutMetricsHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::LayoutMetrics &newLayoutMetrics, + const winrt::Microsoft::ReactNative::LayoutMetrics &oldLayoutMetrics) noexcept { + auto userData = view.UserData().as(); + userData->UpdateLayoutMetrics(view, newLayoutMetrics, oldLayoutMetrics); + }); + + builder.SetUpdateEventEmitterHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter) noexcept { + auto userData = view.UserData().as(); + userData->UpdateEventEmitter(std::make_shared(eventEmitter)); + }); + + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::FinalizeUpdate != &BaseXamlHost::FinalizeUpdate) { + builder.SetFinalizeUpdateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + winrt::Microsoft::ReactNative::ComponentViewUpdateMask mask) noexcept { + auto userData = view.UserData().as(); + userData->FinalizeUpdate(view, mask); + }); + } + + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::UpdateState != &BaseXamlHost::UpdateState) { + builder.SetUpdateStateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::IComponentState &newState) noexcept { + auto userData = view.UserData().as(); + userData->UpdateState(view, newState); + }); + } + + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::MountChildComponentView != &BaseXamlHost::MountChildComponentView) { + builder.SetMountChildComponentViewHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &args) noexcept { + auto userData = view.UserData().as(); + return userData->MountChildComponentView(view, args); + }); + } + + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::UnmountChildComponentView != &BaseXamlHost::UnmountChildComponentView) { + builder.SetUnmountChildComponentViewHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &args) noexcept { + auto userData = view.UserData().as(); + return userData->UnmountChildComponentView(view, args); + }); + } + + compBuilder.SetViewComponentViewInitializer([](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { + auto userData = winrt::make_self(); + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::Initialize != &BaseXamlHost::Initialize) { + userData->Initialize(view); + } + view.UserData(*userData); + }); + + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::CreateVisual != &BaseXamlHost::CreateVisual) { + compBuilder.SetCreateVisualHandler([](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { + auto userData = view.UserData().as(); + return userData->CreateVisual(view); + }); + } + + // Allow app to further customize the builder + if (builderCallback) { + builderCallback(compBuilder); + } + }); +} + +} // namespace Microsoft::ReactNativeSpecs + +#endif // #ifdef RNW_NEW_ARCH diff --git a/vnext/fmt/packages.lock.json b/vnext/fmt/packages.lock.json new file mode 100644 index 00000000000..a31237b580e --- /dev/null +++ b/vnext/fmt/packages.lock.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "dependencies": { + "native,Version=v0.0": {}, + "native,Version=v0.0/win10-arm": {}, + "native,Version=v0.0/win10-arm-aot": {}, + "native,Version=v0.0/win10-arm64-aot": {}, + "native,Version=v0.0/win10-x64": {}, + "native,Version=v0.0/win10-x64-aot": {}, + "native,Version=v0.0/win10-x86": {}, + "native,Version=v0.0/win10-x86-aot": {} + } +} \ No newline at end of file diff --git a/vnext/src-win/Libraries/Components/Xaml/XamlHost.d.ts b/vnext/src-win/Libraries/Components/Xaml/XamlHost.d.ts new file mode 100644 index 00000000000..42f34d63e58 --- /dev/null +++ b/vnext/src-win/Libraries/Components/Xaml/XamlHost.d.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + * @flow + */ +/// +import type {ViewProps} from 'react-native'; +export interface XamlHostProps extends ViewProps { + label: string; +} +declare const _default: import('react-native/Libraries/Utilities/codegenNativeComponent').NativeComponentType; +export default _default; \ No newline at end of file diff --git a/vnext/src-win/Libraries/Components/Xaml/XamlHost.windows.js b/vnext/src-win/Libraries/Components/Xaml/XamlHost.windows.js new file mode 100644 index 00000000000..c481f62c8fb --- /dev/null +++ b/vnext/src-win/Libraries/Components/Xaml/XamlHost.windows.js @@ -0,0 +1,7 @@ +/** + * @format + * @flow + */ + +import XamlHost from '../../../src/private/specs_DEPRECATED/components/Xaml/XamlHostNativeComponent'; +export default XamlHost; \ No newline at end of file diff --git a/vnext/src-win/index.windows.js b/vnext/src-win/index.windows.js index c81a0b7b60a..d3b161b193a 100644 --- a/vnext/src-win/index.windows.js +++ b/vnext/src-win/index.windows.js @@ -46,6 +46,7 @@ import typeof SafeAreaView from './Libraries/Components/SafeAreaView/SafeAreaVie import typeof ScrollView from './Libraries/Components/ScrollView/ScrollView'; import typeof StatusBar from './Libraries/Components/StatusBar/StatusBar'; import typeof Switch from './Libraries/Components/Switch/Switch'; +import typeof XamlHost from './Libraries/Components/Xaml/XamlHost'; import typeof InputAccessoryView from './Libraries/Components/TextInput/InputAccessoryView'; import TextInput from './Libraries/Components/TextInput/TextInput'; import typeof ToastAndroid from './Libraries/Components/ToastAndroid/ToastAndroid'; @@ -183,6 +184,9 @@ module.exports = { get Switch(): Switch { return require('./Libraries/Components/Switch/Switch').default; }, + get XamlHost(): XamlHost { + return require('./Libraries/Components/Xaml/XamlHost').default; + }, get Text(): Text { return require('./Libraries/Text/Text').default; }, diff --git a/vnext/src-win/src/private/specs_DEPRECATED/components/Xaml/XamlHostNativeComponent.js b/vnext/src-win/src/private/specs_DEPRECATED/components/Xaml/XamlHostNativeComponent.js new file mode 100644 index 00000000000..4cf39730380 --- /dev/null +++ b/vnext/src-win/src/private/specs_DEPRECATED/components/Xaml/XamlHostNativeComponent.js @@ -0,0 +1,19 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + * @format + * @flow + */ + +'use strict'; + +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; + +type XamlHostProps = $ReadOnly<{| + ...ViewProps, + + // Props + label?: string, +|}>; + +export default codegenNativeComponent('XamlHost'); From 1136065fff7da8e99c34ac126f54e81ae452ea6a Mon Sep 17 00:00:00 2001 From: Sharath Manchala <10109130+sharath2727@users.noreply.github.com> Date: Tue, 16 Sep 2025 15:59:45 +0530 Subject: [PATCH 2/2] Accessibility validation --- packages/sample-app-fabric/App.tsx | 7 ++- .../CompositionDynamicAutomationProvider.cpp | 27 ++++++++++ .../CompositionDynamicAutomationProvider.h | 8 +++ .../CompositionRootAutomationProvider.cpp | 3 +- .../CompositionViewComponentView.cpp | 4 +- .../ContentIslandComponentView.cpp | 53 ++++++++++++++++++- .../Composition/ContentIslandComponentView.h | 4 ++ .../Fabric/Composition/RootComponentView.cpp | 36 ++++++++++++- .../Fabric/Composition/RootComponentView.h | 2 +- vnext/Microsoft.ReactNative/XamlHost.cpp | 40 ++++++++++++-- 10 files changed, 170 insertions(+), 14 deletions(-) diff --git a/packages/sample-app-fabric/App.tsx b/packages/sample-app-fabric/App.tsx index 53748ea279e..d0d90d03cde 100644 --- a/packages/sample-app-fabric/App.tsx +++ b/packages/sample-app-fabric/App.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import {SafeAreaView, StatusBar, useColorScheme} from 'react-native'; +import {View, SafeAreaView, StatusBar, useColorScheme, ScrollView} from 'react-native'; import {NewAppScreen} from '@react-native/new-app-screen'; import XamlHost from 'react-native-windows/Libraries/Components/Xaml/XamlHost'; @@ -20,9 +20,8 @@ function App(): React.JSX.Element { backgroundColor="transparent" translucent /> - - - + + ); } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp index d67f6d9ca29..449c608964b 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp @@ -68,11 +68,19 @@ CompositionDynamicAutomationProvider::CompositionDynamicAutomationProvider( HRESULT __stdcall CompositionDynamicAutomationProvider::Navigate( NavigateDirection direction, IRawElementProviderFragment **pRetVal) { + + OutputDebugString(L"[UIA] Navigate returning provider instance: "); + OutputDebugString(std::to_wstring(reinterpret_cast(this)).c_str()); + OutputDebugString(L"\n"); + if (pRetVal == nullptr) return E_POINTER; if (m_childSiteLink) { if (direction == NavigateDirection_FirstChild || direction == NavigateDirection_LastChild) { + OutputDebugString(L"[UIA] Navigate returning provider instance - 2: "); + OutputDebugString(std::to_wstring(reinterpret_cast(this)).c_str()); + OutputDebugString(L"\n"); auto fragment = m_childSiteLink.AutomationProvider().try_as(); *pRetVal = fragment.detach(); return S_OK; @@ -115,6 +123,18 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::get_BoundingRectangle(Ui if (pRetVal == nullptr) return E_POINTER; + OutputDebugString(L"[UIA] get_BoundingRectangle called: "); + + if (m_childSiteLink == nullptr) { + OutputDebugString(L"[UIA] m_childSiteLink is nullptr — using fallback RNW bounds\n"); + OutputDebugString(std::to_wstring(reinterpret_cast(this)).c_str()); + OutputDebugString(L"\n"); + } else { + OutputDebugString(L"[UIA] m_childSiteLink is valid — expected to use XamlIsland bounds\n"); + OutputDebugString(std::to_wstring(reinterpret_cast(this)).c_str()); + OutputDebugString(L"\n"); + } + auto hr = UiaGetBoundingRectangleHelper(m_view, *pRetVal); if (FAILED(hr)) return hr; @@ -161,6 +181,13 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::SetFocus(void) { return UiaSetFocusHelper(m_view); } +winrt::IUnknown CompositionDynamicAutomationProvider::TryGetChildSiteLinkAutomationProvider() { + if (m_childSiteLink) { + return m_childSiteLink.AutomationProvider().as(); + } + return nullptr; +} + HRESULT __stdcall CompositionDynamicAutomationProvider::get_FragmentRoot(IRawElementProviderFragmentRoot **pRetVal) { if (pRetVal == nullptr) return E_POINTER; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.h b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.h index 7009a6e21b8..5049a52c4b3 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.h @@ -98,6 +98,14 @@ class CompositionDynamicAutomationProvider : public winrt::implements< void AddToSelectionItems(winrt::com_ptr &item); void RemoveFromSelectionItems(winrt::com_ptr &item); + void SetChildSiteLink(winrt::Microsoft::UI::Content::ChildSiteLink childSiteLink) { + m_childSiteLink = childSiteLink; + } + + // If this object is for a ChildSiteLink, returns the ChildSiteLink's automation provider. + // This will be a provider object from the hosted framework (for example, WinUI). + winrt::IUnknown TryGetChildSiteLinkAutomationProvider(); + private: ::Microsoft::ReactNative::ReactTaggedView m_view; winrt::com_ptr m_textProvider; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp index 6ead642c857..e254eb4c367 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp @@ -219,7 +219,8 @@ HRESULT __stdcall CompositionRootAutomationProvider::ElementProviderFromPoint( auto local = rootView->ConvertScreenToLocal({static_cast(x), static_cast(y)}); auto provider = rootView->UiaProviderFromPoint( {static_cast(local.X * rootView->LayoutMetrics().PointScaleFactor), - static_cast(local.Y * rootView->LayoutMetrics().PointScaleFactor)}); + static_cast(local.Y * rootView->LayoutMetrics().PointScaleFactor)}, + {static_cast(x), static_cast(y)}); auto spFragment = provider.try_as(); if (spFragment) { *pRetVal = spFragment.detach(); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp index 681a93c976c..bb7619ebca2 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp @@ -1003,11 +1003,11 @@ std::string ComponentView::DefaultControlType() const noexcept { } std::string ComponentView::DefaultAccessibleName() const noexcept { - return ""; + return "DefaultAccessString"; } std::string ComponentView::DefaultHelpText() const noexcept { - return ""; + return "DefaultHelpString"; } facebook::react::SharedViewProps ViewComponentView::defaultProps() noexcept { diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp index 37736a34f40..719c735a280 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp @@ -64,9 +64,11 @@ void ContentIslandComponentView::OnMounted() noexcept { // We configure automation even if there's no UIA client at this point, because it's possible the first UIA // request we'll get will be for a child of this island calling upward in the UIA tree. ConfigureChildSiteLinkAutomation(); + OutputDebugString(L"Configure childsite Connected"); if (m_islandToConnect) { m_childSiteLink.Connect(m_islandToConnect); + OutputDebugString(L"ChildSiteLinK Connected"); m_islandToConnect = nullptr; } @@ -85,6 +87,25 @@ void ContentIslandComponentView::OnMounted() noexcept { } } +facebook::react::Tag ContentIslandComponentView::hitTest( + facebook::react::Point pt, + facebook::react::Point &localPt, + bool ignorePointerEvents) const noexcept { + facebook::react::Point ptLocal{pt.x - m_layoutMetrics.frame.origin.x, pt.y - m_layoutMetrics.frame.origin.y}; + + // This is similar to ViewComponentView::hitTest, but we don't want to hit test the children of this node, + // because the child ComponentView is kind of a dummy representation of the XamlElement and doesn't do anything. + // So, we just hit test the ContentIsland itself to make the UIA ElementProviderFromPoint call work. + // TODO: Will this cause a problem -- does this function need to do something different for non-UIA scenarios? + if (ptLocal.x >= 0 && ptLocal.x <= m_layoutMetrics.frame.size.width && ptLocal.y >= 0 && + ptLocal.y <= m_layoutMetrics.frame.size.height) { + localPt = ptLocal; + return Tag(); + } + + return -1; +} + void ContentIslandComponentView::OnUnmounted() noexcept { m_layoutMetricChangedRevokers.clear(); if (m_navigationHostDepartFocusRequestedToken && m_navigationHost) { @@ -115,6 +136,7 @@ winrt::IInspectable ContentIslandComponentView::EnsureUiaProvider() noexcept { if (m_uiaProvider == nullptr) { m_uiaProvider = winrt::make( *get_strong(), m_childSiteLink); + OutputDebugString(L"[EnsureUiaProvider] Created CompositionDynamicAutomationProvider\n"); } return m_uiaProvider; } @@ -196,6 +218,7 @@ void ContentIslandComponentView::updateLayoutMetrics( m_childSiteLink.ActualSize({layoutMetrics.frame.size.width, layoutMetrics.frame.size.height}); ParentLayoutChanged(); } + base_type::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics); } @@ -203,8 +226,10 @@ void ContentIslandComponentView::Connect(const winrt::Microsoft::UI::Content::Co if (m_childSiteLink) { m_islandToConnect = nullptr; m_childSiteLink.Connect(contentIsland); + OutputDebugString(L"1-ChildSiteLink connected to XamlIsland\n"); } else { m_islandToConnect = contentIsland; + OutputDebugString(L"2-ChildSiteLink connected to XamlIsland\n"); } } @@ -217,7 +242,7 @@ void ContentIslandComponentView::ConfigureChildSiteLinkAutomation() noexcept { // It puts the child content into a mode where it won't own its own framework root. Instead, the child island's // automation peers will use the same framework root as the automation peer of this ContentIslandComponentView. m_childSiteLink.AutomationOption(winrt::Microsoft::UI::Content::ContentAutomationOptions::FragmentBased); - + //OutputDebugString(L"[ContentIsland] AutomationOption set to FragmentBased\n"); // These events are raised in response to the child ContentIsland asking for providers. // For example, the ContentIsland.FragmentRootAutomationProvider property will return // the provider we provide here in FragmentRootAutomationProviderRequested. @@ -231,10 +256,20 @@ void ContentIslandComponentView::ConfigureChildSiteLinkAutomation() noexcept { const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { // The child island's fragment tree doesn't have its own fragment root. // Here's how we can provide the correct fragment root to the child's UIA logic. + // OutputDebugString(L"[ContentIsland] FragmentRootAutomationProviderRequested entered\n"); winrt::com_ptr fragmentRoot{nullptr}; auto uiaProvider = this->EnsureUiaProvider(); + // OutputDebugString(L"[UIA] FragmentRootAutomationProviderRequested uiaprovider: "); + + OutputDebugString(std::to_wstring(reinterpret_cast(winrt::get_abi(uiaProvider))).c_str()); + OutputDebugString(L"\n"); + uiaProvider.as()->get_FragmentRoot(fragmentRoot.put()); + OutputDebugString(L"[UIA] FragmentRoot pointer: "); + OutputDebugString(std::to_wstring(reinterpret_cast(fragmentRoot.get())).c_str()); + OutputDebugString(L"\n"); args.AutomationProvider(fragmentRoot.as()); + OutputDebugString(L"[ContentIsland] FragmentRootAutomationProviderRequested handled\n"); args.Handled(true); }); @@ -242,8 +277,14 @@ void ContentIslandComponentView::ConfigureChildSiteLinkAutomation() noexcept { [this]( const winrt::Microsoft::UI::Content::IContentSiteAutomation &, const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { + OutputDebugString(L"[ContentIsland] ParentAutomationProviderRequested entered\n"); auto uiaProvider = this->EnsureUiaProvider(); + OutputDebugString(L"[UIA] ParentAutomationProviderRequested uiaprovider: "); + + OutputDebugString(std::to_wstring(reinterpret_cast(winrt::get_abi(uiaProvider))).c_str()); + OutputDebugString(L"\n"); args.AutomationProvider(uiaProvider); + OutputDebugString(L"[ContentIsland] ParentAutomationProviderRequested handled\n"); args.Handled(true); }); @@ -251,7 +292,9 @@ void ContentIslandComponentView::ConfigureChildSiteLinkAutomation() noexcept { [](const winrt::Microsoft::UI::Content::IContentSiteAutomation &, const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { // The ContentIsland will always be the one and only child of this node, so it won't have siblings. + OutputDebugString(L"[ContentIsland] NextSiblingAutomationProviderRequested entered\n"); args.AutomationProvider(nullptr); + OutputDebugString(L"[ContentIsland] m_nextSiblingAutomationProviderRequestedToken handled\n"); args.Handled(true); }); @@ -259,9 +302,17 @@ void ContentIslandComponentView::ConfigureChildSiteLinkAutomation() noexcept { [](const winrt::Microsoft::UI::Content::IContentSiteAutomation &, const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { // The ContentIsland will always be the one and only child of this node, so it won't have siblings. + OutputDebugString(L"[ContentIsland] PreviousSiblingAutomationProviderRequested entered\n"); args.AutomationProvider(nullptr); + OutputDebugString(L"[ContentIsland] m_previousSiblingAutomationProviderRequestedToken handled\n"); args.Handled(true); }); + + if (m_uiaProvider) { + auto providerImpl = + m_uiaProvider.as(); + providerImpl->SetChildSiteLink(m_childSiteLink); + } } } // namespace winrt::Microsoft::ReactNative::Composition::implementation diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h index f530baa3400..8f51b76b231 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h @@ -51,6 +51,10 @@ struct ContentIslandComponentView : ContentIslandComponentViewT #include #include "CompositionRootAutomationProvider.h" +#include "CompositionDynamicAutomationProvider.h" #include "ReactNativeIsland.h" #include "Theme.h" @@ -275,7 +276,7 @@ facebook::react::Point RootComponentView::getClientOffset() const noexcept { return {}; } -winrt::IInspectable RootComponentView::UiaProviderFromPoint(const POINT &ptPixels) noexcept { +winrt::IUnknown RootComponentView::UiaProviderFromPoint(const POINT &ptPixels, const POINT &ptScreen) noexcept { facebook::react::Point ptDips{ static_cast(ptPixels.x) / m_layoutMetrics.pointScaleFactor, static_cast(ptPixels.y) / m_layoutMetrics.pointScaleFactor}; @@ -295,7 +296,38 @@ winrt::IInspectable RootComponentView::UiaProviderFromPoint(const POINT &ptPixel if (view == nullptr) return nullptr; - return winrt::get_self(view)->EnsureUiaProvider(); + // return winrt::get_self(view)->EnsureUiaProvider(); + auto uiaProvider = + winrt::get_self(view)->EnsureUiaProvider(); + + // HACKHACK: It's ugly to have the RootComponentView know about the CompositionDynamicAutomationProvider type. + // HACKHACK: Can we clean this up? + auto dynamicProvider = + uiaProvider.try_as(); + if (dynamicProvider) { + if (auto childProvider = dynamicProvider->TryGetChildSiteLinkAutomationProvider()) { + // childProvider is the the automation provider from the ChildSiteLink. In the case of WinUI, this + // is a pointer to WinUI's internal CUIAHostWindow object. + // It seems odd, but even though this node doesn't behave as a fragment root in our case (the real fragment root + // is the RootComponentView's UIA provider), we still use its IRawElementProviderFragmentRoot -- just so + // we can do the ElementProviderFromPoint call. (this was recommended by the team who did the initial + // architecture work). + if (auto fragmentRoot = childProvider.try_as()) { + com_ptr frag; + fragmentRoot->ElementProviderFromPoint( + ptScreen.x, // Note since we're going through IRawElementProviderFragment the coordinates are in screen space. + ptScreen.y, + frag.put()); + // In the case of WinUI, frag is now the UIA provider for the specific WinUI element that was hit. + // (A Microsoft_UI_Xaml!CUIAWrapper object) + if (frag) { + return frag.as(); + } + } + } + } + + return uiaProvider; } float RootComponentView::FontSizeMultiplier() const noexcept { diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h index 3a037fc6d61..83f633d212e 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h @@ -64,7 +64,7 @@ struct RootComponentView : RootComponentViewT +#include #if defined(RNW_NEW_ARCH) @@ -27,12 +29,45 @@ struct XamlHostComponentView : public winrt::implements(); + + auto string = buttonPeer.GetHelpText(); + + button.IsTabStop(true); + button.TabIndex(0); + + m_xamlIsland.Content(button); + OutputDebugString(L"[XamlHost] Setting Content\n"); islandView.Connect(m_xamlIsland.ContentIsland()); } @@ -82,7 +117,6 @@ void RegisterXamlHostComponentView(winrt::Microsoft::ReactNative::IReactPackageB [](const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder &builder) { builder.SetContentIslandComponentViewInitializer( [](const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView &islandView) noexcept { - OutputDebugString(L"RegisterXamlHostComponentView called\n"); auto userData = winrt::make_self(); userData->InitializeContentIsland(islandView); islandView.UserData(*userData);