diff --git a/change/react-native-windows-2020-05-19-13-43-33-MS_Notifications.json b/change/react-native-windows-2020-05-19-13-43-33-MS_Notifications.json new file mode 100644 index 00000000000..c54854a0b5f --- /dev/null +++ b/change/react-native-windows-2020-05-19-13-43-33-MS_Notifications.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "ReactNotificationService to allow communications between native modules", + "packageName": "react-native-windows", + "email": "vmorozov@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-05-19T20:43:32.803Z" +} diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCPP/SampleModuleCPP.h b/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCPP/SampleModuleCPP.h index 161adc8f9c8..fd6a18f6d83 100644 --- a/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCPP/SampleModuleCPP.h +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCPP/SampleModuleCPP.h @@ -39,8 +39,18 @@ struct SampleModuleCppImpl { DEBUG_OUTPUT("C++ Properties.Prop1:", *reactContext.Properties().Get(myProp1)); DEBUG_OUTPUT("C++ Properties.Prop2:", winrt::to_string(*reactContext.Properties().Get(myProp2))); + const ReactNotificationId cppTimerNotification{L"SampleModuleCppImpl", L"TimerNotification"}; + const ReactNotificationId csTimerNotification{L"SampleModuleCS", L"TimerNotification"}; + + reactContext.Notifications().Subscribe(csTimerNotification, [ + ](winrt::Windows::Foundation::IInspectable const &, ReactNotificationArgs const &args) noexcept { + DEBUG_OUTPUT("C++ module, C# timer:", *args.Data()); + }); + m_timer = winrt::Windows::System::Threading::ThreadPoolTimer::CreatePeriodicTimer( - [this](const winrt::Windows::System::Threading::ThreadPoolTimer) noexcept { + [ this, cppTimerNotification, notifications = reactContext.Notifications() ]( + const winrt::Windows::System::Threading::ThreadPoolTimer) noexcept { + notifications.SendNotification(cppTimerNotification, m_timerCount); TimedEvent(++m_timerCount); if (m_timer && m_timerCount == 5) { m_timer.Cancel(); diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCS/SampleModuleCS.cs b/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCS/SampleModuleCS.cs index 732219ba1e4..577e037a9ba 100644 --- a/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCS/SampleModuleCS.cs +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCS/SampleModuleCS.cs @@ -29,8 +29,15 @@ public void Initialize(ReactContext reactContext) Debug.WriteLine($"C# Properties.Prop1: {reactContext.Handle.Properties.Get(ReactPropertyBagHelper.GetName(null, "Prop1"))}"); Debug.WriteLine($"C# Properties.Prop2: {reactContext.Handle.Properties.Get(ReactPropertyBagHelper.GetName(null, "Prop2"))}"); + var cppTimerNotification = ReactPropertyBagHelper.GetName(ReactPropertyBagHelper.GetNamespace("SampleModuleCppImpl"), "TimerNotification"); + var csTimerNotification = ReactPropertyBagHelper.GetName(ReactPropertyBagHelper.GetNamespace("SampleModuleCS"), "TimerNotification"); + + reactContext.Handle.Notifications.Subscribe(cppTimerNotification, null, + (object sender, IReactNotificationArgs args) => Debug.WriteLine($"C# module, C++ timer:: {args.Data}")); + _timer = ThreadPoolTimer.CreatePeriodicTimer(new TimerElapsedHandler((timer) => { + reactContext.Handle.Notifications.SendNotification(csTimerNotification, null, _timerCount); TimedEvent?.Invoke(++_timerCount); if (_timerCount == 5) { diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj b/vnext/Desktop/React.Windows.Desktop.vcxproj index 44954fad2d8..bf2397c594d 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj @@ -122,6 +122,8 @@ + + @@ -147,6 +149,7 @@ ..\Microsoft.ReactNative\ReactNativeHost.idl $(IntDir)\ABI\ + diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters index 6a4be8c5886..1aa9154c621 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters @@ -30,49 +30,55 @@ - + ABI - + ABI - + ABI - + ABI - + + ABI + + + ABI + + ABI ABI - + ABI - + ABI - + ABI - + ABI - + ABI - + ABI - + ABI - + ABI - + ABI @@ -116,9 +122,6 @@ Generated Files - - Source Files - Source Files\Modules @@ -129,6 +132,9 @@ Source Files\Modules + + Source Files + diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj b/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj index 2a6954d74c6..8ec2437a7cd 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj @@ -141,12 +141,14 @@ + + + + - - - + diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp index 4fec5d137c1..c7ce0a1651e 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp @@ -12,6 +12,10 @@ struct ReactContextStub : implements { VerifyElseCrashSz(false, "Not implemented"); } + IReactNotificationService Notifications() noexcept { + VerifyElseCrashSz(false, "Not implemented"); + } + void DispatchEvent( xaml::FrameworkElement const & /*view*/, hstring const & /*eventName*/, diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h index 8fc22495bba..e49548661b7 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h @@ -115,6 +115,10 @@ struct ReactContextMock : implements { VerifyElseCrashSz(false, "Not implemented"); } + IReactNotificationService Notifications() noexcept { + VerifyElseCrashSz(false, "Not implemented"); + } + void DispatchEvent( xaml::FrameworkElement const & /*view*/, hstring const & /*eventName*/, diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/pch.h b/vnext/Microsoft.ReactNative.Cxx.UnitTests/pch.h index 06661b920e8..a6f6676eca7 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/pch.h +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/pch.h @@ -10,9 +10,9 @@ #undef GetCurrentTime +#include #include #include -#include "winrt/Microsoft.ReactNative.h" #include "gtest/gtest.h" #include "motifCpp/gTestAdapter.h" diff --git a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems index d4533ce3d6c..b31da20fa40 100644 --- a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems +++ b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems @@ -19,6 +19,7 @@ + @@ -28,7 +29,9 @@ + + diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactContext.h b/vnext/Microsoft.ReactNative.Cxx/ReactContext.h index b80b03e8ad0..433abe83e2b 100644 --- a/vnext/Microsoft.ReactNative.Cxx/ReactContext.h +++ b/vnext/Microsoft.ReactNative.Cxx/ReactContext.h @@ -8,6 +8,7 @@ #include #include #include "JSValueWriter.h" +#include "ReactNotificationService.h" #include "ReactPropertyBag.h" namespace winrt::Microsoft::ReactNative { @@ -25,13 +26,17 @@ struct ReactContext { } explicit operator bool() const noexcept { - return static_cast(m_handle); + return m_handle ? true : false; } ReactPropertyBag Properties() const noexcept { return ReactPropertyBag{m_handle.Properties()}; } + ReactNotificationService Notifications() const noexcept { + return ReactNotificationService{m_handle.Notifications()}; + } + // Call methodName JS function of module with moduleName. // args are either function arguments or a single lambda with 'IJSValueWriter const&' argument. template diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactDispatcher.h b/vnext/Microsoft.ReactNative.Cxx/ReactDispatcher.h new file mode 100644 index 00000000000..a28e4b09468 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactDispatcher.h @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#ifndef MICROSOFT_REACTNATIVE_REACTDISPATCHER +#define MICROSOFT_REACTNATIVE_REACTDISPATCHER + +#include +#include "ReactHandleHelper.h" + +namespace winrt::Microsoft::ReactNative { + +// Represents a dispatcher queue to invoke work item asynchronously. +// It wraps up the IReactDispatcher and adds convenience methods for +// working with C++ types. +struct ReactDispatcher { + ReactDispatcher(std::nullptr_t = nullptr) noexcept {} + + explicit ReactDispatcher(IReactDispatcher const &handle) noexcept : m_handle{handle} {} + + IReactDispatcher const &Handle() const noexcept { + return m_handle; + } + + explicit operator bool() const noexcept { + return m_handle ? true : false; + } + + void Post(ReactDispatcherCallback const &callback) const noexcept { + if (m_handle) { + m_handle.Post(callback); + } + } + + bool HasThreadAccess() const noexcept { + return m_handle ? m_handle.HasThreadAccess() : false; + } + + static ReactDispatcher CreateSerialDispatcher() noexcept { + return ReactDispatcher{ReactDispatcherHelper::CreateSerialDispatcher()}; + } + + private: + IReactDispatcher m_handle; +}; + +} // namespace winrt::Microsoft::ReactNative + +#endif // MICROSOFT_REACTNATIVE_REACTDISPATCHER diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactHandleHelper.h b/vnext/Microsoft.ReactNative.Cxx/ReactHandleHelper.h new file mode 100644 index 00000000000..5b6209d4017 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactHandleHelper.h @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#ifndef MICROSOFT_REACTNATIVE_REACTHANDLEHELPER +#define MICROSOFT_REACTNATIVE_REACTHANDLEHELPER + +// +// Helper methods for types that have Handle() method that return +// an IInspectable-inherited value. +// + +#include +#include + +namespace winrt::Microsoft::ReactNative { + +namespace Internal { +template +auto TestHandle(int) -> decltype(std::declval().Handle()); +template +auto TestHandle(int *) -> void; +} // namespace Internal + +template +inline constexpr bool HasHandleV = + std::is_base_of_v(0))>>; + +// True if two types with Handle() have the same handle. +template , int> = 0> +inline bool operator==(T const &left, T const &right) noexcept { + return left.Handle() == right.Handle(); +} + +// True if two types with Handle() have different handles. +template , int> = 0> +inline bool operator!=(T const &left, T const &right) noexcept { + return !(left.Handle() == right.Handle()); +} + +// True if handle of left is null. +template , int> = 0> +inline bool operator==(T const &left, std::nullptr_t) noexcept { + return !static_cast(left.Handle()); +} + +// True if handle of left is not null. +template , int> = 0> +inline bool operator!=(T const &left, std::nullptr_t) noexcept { + return static_cast(left.Handle()); +} + +// True if handle of right is null. +template , int> = 0> +inline bool operator==(std::nullptr_t, T const &right) noexcept { + return !static_cast(right.Handle()); +} + +// True if handle of left is not null. +template , int> = 0> +inline bool operator!=(std::nullptr_t, T const &right) noexcept { + return static_cast(right.Handle()); +} + +} // namespace winrt::Microsoft::ReactNative + +#endif // MICROSOFT_REACTNATIVE_REACTHANDLEHELPER diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactNotificationService.h b/vnext/Microsoft.ReactNative.Cxx/ReactNotificationService.h new file mode 100644 index 00000000000..6fa83baa660 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactNotificationService.h @@ -0,0 +1,301 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#ifndef MICROSOFT_REACTNATIVE_REACTNOTIFICATIONSERVICE +#define MICROSOFT_REACTNATIVE_REACTNOTIFICATIONSERVICE + +#include "ReactDispatcher.h" +#include "ReactPropertyBag.h" + +namespace winrt::Microsoft::ReactNative { + +// Encapsulates the IReactPropertyName and the notification data type +template +struct ReactNotificationId : ReactPropertyName { + using NotificationDataType = T; + using ReactPropertyName::ReactPropertyName; + + ReactPropertyName const &NotificationName() const noexcept { + return *this; + } +}; + +struct ReactNotificationSubscription { + ReactNotificationSubscription(std::nullptr_t = nullptr) noexcept {} + + explicit ReactNotificationSubscription(IReactNotificationSubscription const &handle) noexcept : m_handle{handle} {} + + IReactNotificationSubscription const &Handle() const noexcept { + return m_handle; + } + + explicit operator bool() const noexcept { + return m_handle ? true : false; + } + + ReactDispatcher Dispatcher() const noexcept { + return ReactDispatcher{m_handle ? m_handle.Dispatcher() : nullptr}; + } + + // Name of the notification. + ReactPropertyName NotificationName() const noexcept { + return ReactPropertyName{m_handle ? m_handle.NotificationName() : nullptr}; + }; + + // True if the subscription is still active. + // This property is checked before notification handler is invoked. + bool IsSubscribed() const noexcept { + return m_handle ? m_handle.IsSubscribed() : false; + }; + + // Remove the subscription. + // Because of the multi-threaded nature of the notifications, the handler can be still called + // after the Unsubscribe method called if the IsSubscribed property is already checked. + // Consider calling the Unsubscribe method and the handler in the same IReactDispatcher + // to ensure that no handler is invoked after the Unsubscribe method call. + void Unsubscribe() const noexcept { + if (m_handle) { + m_handle.Unsubscribe(); + } + } + + private: + IReactNotificationSubscription m_handle; +}; + +struct ReactNotificationSubscriptionRevoker : ReactNotificationSubscription { + ReactNotificationSubscriptionRevoker(std::nullptr_t = nullptr) noexcept : ReactNotificationSubscription{nullptr} {} + + explicit ReactNotificationSubscriptionRevoker(IReactNotificationSubscription const &handle) noexcept + : ReactNotificationSubscription{handle} {} + + ReactNotificationSubscriptionRevoker(ReactNotificationSubscriptionRevoker const &) = delete; + ReactNotificationSubscriptionRevoker(ReactNotificationSubscriptionRevoker &&) = default; + ReactNotificationSubscriptionRevoker &operator=(ReactNotificationSubscriptionRevoker const &) = delete; + + ReactNotificationSubscriptionRevoker &operator=(ReactNotificationSubscriptionRevoker &&other) noexcept { + if (this != &other) { + Unsubscribe(); + ReactNotificationSubscription::operator=(std::move(other)); + } + + return *this; + } + + ~ReactNotificationSubscriptionRevoker() noexcept { + Unsubscribe(); + } +}; + +struct ReactNotificationArgsBase { + IReactNotificationArgs const &Handle() const noexcept { + return m_handle; + } + + explicit operator bool() const noexcept { + return m_handle ? true : false; + } + + ReactNotificationSubscription Subscription() const noexcept { + return ReactNotificationSubscription{m_handle ? m_handle.Subscription() : nullptr}; + } + + protected: + ReactNotificationArgsBase() = default; + + explicit ReactNotificationArgsBase(IReactNotificationArgs const &handle) noexcept : m_handle{handle} {} + + private: + IReactNotificationArgs m_handle; +}; + +template +struct ReactNotificationArgs : ReactNotificationArgsBase { + ReactNotificationArgs(std::nullptr_t = nullptr) noexcept {} + + explicit ReactNotificationArgs(IReactNotificationArgs const &handle) noexcept : ReactNotificationArgsBase{handle} {} + + auto Data() const noexcept { + return ReactPropertyBag::FromObject(Handle() ? Handle().Data() : nullptr); + } +}; + +template +inline constexpr bool IsValidHandlerV = + std::is_invocable_v const &>; + +struct ReactNotificationService { + // Notification data result type is either T or std::optional. + // T is returned for types inherited from IInspectable. + // The std::optional is returned for all other types. + template + using ResultType = std::conditional_t, T, std::optional>; + + // Create a new empty instance of ReactNotificationService. + ReactNotificationService(std::nullptr_t = nullptr) noexcept {} + + // Creates a new instance of ReactNotificationService with the provided handle. + explicit ReactNotificationService(IReactNotificationService const &handle) noexcept : m_handle{handle} {} + + IReactNotificationService const &Handle() const noexcept { + return m_handle; + } + + explicit operator bool() const noexcept { + return m_handle ? true : false; + } + + template , int> = 0> + static ReactNotificationSubscriptionRevoker Subscribe( + IReactNotificationService const &handle, + winrt::auto_revoke_t, + ReactNotificationId const ¬ificationId, + ReactDispatcher const &dispatcher, + THandler &&handler) noexcept { + IReactNotificationSubscription subscription = handle + ? handle.Subscribe( + notificationId.Handle(), + dispatcher.Handle(), + [handler = std::forward(handler)]( + Windows::Foundation::IInspectable const &sender, IReactNotificationArgs const &args) noexcept { + handler(sender, ReactNotificationArgs{args}); + }) + : nullptr; + return ReactNotificationSubscriptionRevoker{subscription}; + } + + template , int> = 0> + static ReactNotificationSubscriptionRevoker Subscribe( + IReactNotificationService const &handle, + winrt::auto_revoke_t, + ReactNotificationId const ¬ificationId, + THandler &&handler) noexcept { + return Subscribe(handle, winrt::auto_revoke, notificationId, nullptr, std::forward(handler)); + } + + template , int> = 0> + static ReactNotificationSubscription Subscribe( + IReactNotificationService const &handle, + ReactNotificationId const ¬ificationId, + ReactDispatcher const &dispatcher, + THandler &&handler) noexcept { + IReactNotificationSubscription subscription = handle + ? handle.Subscribe( + notificationId.Handle(), + dispatcher.Handle(), + [handler = std::forward(handler)]( + Windows::Foundation::IInspectable const &sender, IReactNotificationArgs const &args) noexcept { + handler(sender, ReactNotificationArgs{args}); + }) + : nullptr; + return ReactNotificationSubscription{subscription}; + } + + template , int> = 0> + static ReactNotificationSubscription Subscribe( + IReactNotificationService const &handle, + ReactNotificationId const ¬ificationId, + THandler &&handler) noexcept { + return Subscribe(handle, notificationId, nullptr, std::forward(handler)); + } + + template + static void SendNotification( + IReactNotificationService const &handle, + ReactNotificationId const ¬ificationId, + Windows::Foundation::IInspectable const &sender, + TValue &&value) noexcept { + if (handle) { + handle.SendNotification( + notificationId.Handle(), sender, ReactPropertyBag::ToObject(std::forward(value))); + } + } + + static void SendNotification( + IReactNotificationService const &handle, + ReactNotificationId const ¬ificationId, + Windows::Foundation::IInspectable const &sender) noexcept { + if (handle) { + handle.SendNotification(notificationId.Handle(), sender, nullptr); + } + } + + template + static void SendNotification( + IReactNotificationService const &handle, + ReactNotificationId const ¬ificationId, + TValue &&value) noexcept { + if (handle) { + handle.SendNotification( + notificationId.Handle(), nullptr, ReactPropertyBag::ToObject(std::forward(value))); + } + } + + static void SendNotification( + IReactNotificationService const &handle, + ReactNotificationId const ¬ificationId) noexcept { + if (handle) { + handle.SendNotification(notificationId.Handle(), nullptr, nullptr); + } + } + + template , int> = 0> + ReactNotificationSubscriptionRevoker Subscribe( + winrt::auto_revoke_t, + ReactNotificationId const ¬ificationId, + ReactDispatcher const &dispatcher, + THandler &&handler) const noexcept { + return Subscribe(m_handle, winrt::auto_revoke, notificationId, dispatcher, std::forward(handler)); + } + + template , int> = 0> + ReactNotificationSubscriptionRevoker + Subscribe(winrt::auto_revoke_t, ReactNotificationId const ¬ificationId, THandler &&handler) const noexcept { + return Subscribe(m_handle, winrt::auto_revoke, notificationId, nullptr, std::forward(handler)); + } + + template , int> = 0> + ReactNotificationSubscription Subscribe( + ReactNotificationId const ¬ificationId, + ReactDispatcher const &dispatcher, + THandler &&handler) const noexcept { + return Subscribe(m_handle, notificationId, dispatcher, std::forward(handler)); + } + + template , int> = 0> + ReactNotificationSubscription Subscribe(ReactNotificationId const ¬ificationId, THandler &&handler) const + noexcept { + return Subscribe(m_handle, notificationId, nullptr, std::forward(handler)); + } + + template , int> = 0> + void SendNotification( + ReactNotificationId const ¬ificationId, + Windows::Foundation::IInspectable const &sender, + TValue &&value) const noexcept { + SendNotification(m_handle, notificationId, sender, std::forward(value)); + } + + void SendNotification( + ReactNotificationId const ¬ificationId, + Windows::Foundation::IInspectable const &sender) const noexcept { + SendNotification(m_handle, notificationId, sender); + } + + template , int> = 0> + void SendNotification(ReactNotificationId const ¬ificationId, TValue &&value) const noexcept { + SendNotification(m_handle, notificationId, std::forward(value)); + } + + void SendNotification(ReactNotificationId const ¬ificationId) const noexcept { + SendNotification(m_handle, notificationId); + } + + private: + IReactNotificationService m_handle; +}; + +} // namespace winrt::Microsoft::ReactNative + +#endif // MICROSOFT_REACTNATIVE_REACTNOTIFICATIONSERVICE diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactPropertyBag.h b/vnext/Microsoft.ReactNative.Cxx/ReactPropertyBag.h index 8d461e9fb12..d7a05869c3a 100644 --- a/vnext/Microsoft.ReactNative.Cxx/ReactPropertyBag.h +++ b/vnext/Microsoft.ReactNative.Cxx/ReactPropertyBag.h @@ -2,8 +2,8 @@ // Licensed under the MIT License. #pragma once -#ifndef MICROSOFT_REACTNATIVE_PROPERTYBAG -#define MICROSOFT_REACTNATIVE_PROPERTYBAG +#ifndef MICROSOFT_REACTNATIVE_REACTPROPERTYBAG +#define MICROSOFT_REACTNATIVE_REACTPROPERTYBAG // // ReactPropertyBag is a thread-safe storage of property values. @@ -69,77 +69,89 @@ #include #include +#include "ReactHandleHelper.h" #include "ReactNonAbiValue.h" namespace winrt::Microsoft::ReactNative { -// Encapsulates the IReactPropertyNamespace +// ReactPropertyNamespace encapsulates the IReactPropertyNamespace. +// It represents an atomic property namespace object. struct ReactPropertyNamespace { ReactPropertyNamespace(std::nullptr_t = nullptr) noexcept {} explicit ReactPropertyNamespace(IReactPropertyNamespace const &handle) noexcept : m_handle{handle} {} - explicit ReactPropertyNamespace(param::hstring const &ns) noexcept - : m_handle{ReactPropertyBagHelper::GetNamespace(ns)} {} + explicit ReactPropertyNamespace(param::hstring const &namespaceName) noexcept + : m_handle{ReactPropertyBagHelper::GetNamespace(namespaceName)} {} - static ReactPropertyNamespace Global() noexcept { - return ReactPropertyNamespace{ReactPropertyBagHelper::GlobalNamespace()}; + IReactPropertyNamespace const &Handle() const noexcept { + return m_handle; } - hstring NamespaceName() const noexcept { - return m_handle ? m_handle.NamespaceName() : hstring{}; + explicit operator bool() const noexcept { + return m_handle ? true : false; } - IReactPropertyNamespace const &Handle() const noexcept { - return m_handle; + static ReactPropertyNamespace Global() noexcept { + return ReactPropertyNamespace{ReactPropertyBagHelper::GlobalNamespace()}; } - explicit operator bool() const noexcept { - return static_cast(m_handle); + hstring NamespaceName() const noexcept { + return m_handle ? m_handle.NamespaceName() : hstring{}; } private: IReactPropertyNamespace m_handle; }; -// Encapsulates the IReactPropertyName and the property type -template -struct ReactPropertyId { - using PropertyType = T; +// ReactPropertyName encapsulates the IReactPropertyName. +// It represents an atomic property name object that defines a LocalName +// within the referenced Namespace. +struct ReactPropertyName { + ReactPropertyName(std::nullptr_t = nullptr) noexcept {} - ReactPropertyId(std::nullptr_t = nullptr) noexcept {} + explicit ReactPropertyName(IReactPropertyName const &handle) noexcept : m_handle{handle} {} - explicit ReactPropertyId(IReactPropertyName const &handle) noexcept : m_handle{handle} {} - - explicit ReactPropertyId(hstring const &localName) noexcept + explicit ReactPropertyName(hstring const &localName) noexcept : m_handle{ReactPropertyBagHelper::GetName(nullptr, localName)} {} - ReactPropertyId(ReactPropertyNamespace const &ns, hstring const &localName) noexcept + ReactPropertyName(ReactPropertyNamespace const &ns, hstring const &localName) noexcept : m_handle{ReactPropertyBagHelper::GetName(ns.Handle(), localName)} {} - ReactPropertyId(hstring const &ns, hstring const &localName) noexcept - : m_handle{ReactPropertyBagHelper::GetName(ReactPropertyBagHelper::GetNamespace(ns), localName)} {} + ReactPropertyName(hstring const &namespaceName, hstring const &localName) noexcept + : m_handle{ReactPropertyBagHelper::GetName(ReactPropertyBagHelper::GetNamespace(namespaceName), localName)} {} - hstring NamespaceName() const noexcept { - return m_handle ? m_handle.Namespace().NamespaceName() : hstring{}; + IReactPropertyName const &Handle() const noexcept { + return m_handle; } - hstring LocalName() const noexcept { - return m_handle ? m_handle.LocalName() : hstring{}; + explicit operator bool() const noexcept { + return m_handle ? true : false; } - IReactPropertyName const &Handle() const noexcept { - return m_handle; + ReactPropertyNamespace Namespace() const noexcept { + return ReactPropertyNamespace{m_handle ? m_handle.Namespace() : nullptr}; } - explicit operator bool() const noexcept { - return static_cast(m_handle); + hstring NamespaceName() const noexcept { + return m_handle ? m_handle.Namespace().NamespaceName() : hstring{}; + } + + hstring LocalName() const noexcept { + return m_handle ? m_handle.LocalName() : hstring{}; } private: IReactPropertyName m_handle; }; +// Encapsulates the IReactPropertyName and the property type +template +struct ReactPropertyId : ReactPropertyName { + using PropertyType = T; + using ReactPropertyName::ReactPropertyName; +}; + // ReactPropertyBag is a wrapper for IReactPropertyBag to store strongly-typed properties in a thread-safe way. // Types inherited from IInspectable are stored directly. // Values of other types are boxed with help of winrt::box_value. @@ -159,7 +171,7 @@ struct ReactPropertyBag { // True if handle is not null. explicit operator bool() const noexcept { - return static_cast(m_handle); + return m_handle ? true : false; } // Get ReactPropertyBag handle. @@ -229,37 +241,7 @@ struct ReactPropertyBag { Remove(m_handle, propertyId); } - // True if two ReactPropertyBags have the same handle. - friend bool operator==(const ReactPropertyBag &left, const ReactPropertyBag &right) noexcept { - return left.m_handle == right.m_handle; - } - - // True if two ReactPropertyBags have different handles. - friend bool operator!=(const ReactPropertyBag &left, const ReactPropertyBag &right) noexcept { - return left.m_handle != right.m_handle; - } - - // True if left ReactPropertyBag handle is null. - friend bool operator==(const ReactPropertyBag &left, std::nullptr_t) noexcept { - return !static_cast(left.m_handle); - } - - // True if left ReactPropertyBag handle is not null. - friend bool operator!=(const ReactPropertyBag &left, std::nullptr_t) noexcept { - return static_cast(left.m_handle); - } - - // True if right ReactPropertyBag handle is null. - friend bool operator==(std::nullptr_t, const ReactPropertyBag &right) noexcept { - return !static_cast(right.m_handle); - } - - // True if right ReactPropertyBag handle is not null. - friend bool operator!=(std::nullptr_t, const ReactPropertyBag &right) noexcept { - return static_cast(right.m_handle); - } - - private: + // Box value to an ABI-safe object. template || std::is_same_v, int> = 0> static Windows::Foundation::IInspectable ToObject(TValue const &value) noexcept { // We box WinRT types and return IInspectable-inherited values as-is. @@ -267,6 +249,7 @@ struct ReactPropertyBag { return box_value(value); } + // Box value to an ABI-safe object. template && !std::is_same_v, int> = 0> static Windows::Foundation::IInspectable ToObject(TValue &&value) noexcept { // Create ReactNonAbiValue with newly allocated wrapper for U and pass TValue as an argument to the U @@ -275,11 +258,13 @@ struct ReactPropertyBag { return T{std::in_place, std::forward(value)}; } + // Box value to an ABI-safe object. template static Windows::Foundation::IInspectable ToObject(std::optional const &value) noexcept { return value ? ToObject(*value) : nullptr; } + // Unbox value from an ABI-safe object. template static auto FromObject(Windows::Foundation::IInspectable const &obj) noexcept { // The code mostly borrowed from the winrt::unbox_value_or implementation to return @@ -316,4 +301,4 @@ struct ReactPropertyBag { } // namespace winrt::Microsoft::ReactNative -#endif // MICROSOFT_REACTNATIVE_PROPERTYBAG +#endif // MICROSOFT_REACTNATIVE_REACTPROPERTYBAG diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj index f85641c415e..51638367dc2 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj +++ b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj @@ -108,6 +108,7 @@ + diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ReactNotificationServiceTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNotificationServiceTests.cpp new file mode 100644 index 00000000000..560d6d70b63 --- /dev/null +++ b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNotificationServiceTests.cpp @@ -0,0 +1,252 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "pch.h" + +#include +#include +#include +#include +#include + +using namespace winrt; +using namespace Microsoft::ReactNative; +using namespace Windows::Foundation; +using namespace std::string_literals; + +namespace ReactNativeIntegrationTests { + +TEST_CLASS (ReactNotificationServiceTests) { + TEST_METHOD(Notification_Subscribe) { + auto fooName = ReactPropertyBagHelper::GetName(nullptr, L"Foo"); + IReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + bool isCalled{false}; + rns.Subscribe(fooName, nullptr, [&](IInspectable const &sender, IReactNotificationArgs const &args) noexcept { + isCalled = true; + TestCheck(!sender); + TestCheck(!args.Data()); + TestCheck(!args.Subscription().Dispatcher()); + TestCheckEqual(fooName, args.Subscription().NotificationName()); + TestCheck(args.Subscription().IsSubscribed()); + }); + rns.SendNotification(fooName, nullptr, nullptr); + TestCheck(isCalled); + } + + TEST_METHOD(Notification_Unsubscribe) { + IReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + auto fooName = ReactPropertyBagHelper::GetName(nullptr, L"Foo"); + bool isCalled{false}; + auto subscription = rns.Subscribe( + fooName, nullptr, [&](IInspectable const & /*sender*/, IReactNotificationArgs const & /*args*/) noexcept { + isCalled = true; + }); + rns.SendNotification(fooName, nullptr, nullptr); + TestCheck(isCalled); + + subscription.Unsubscribe(); + TestCheck(!subscription.IsSubscribed()); + + isCalled = false; + rns.SendNotification(fooName, nullptr, nullptr); + TestCheck(!isCalled); + } + + TEST_METHOD(Notification_UnsubscribeInHandler) { + IReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + auto fooName = ReactPropertyBagHelper::GetName(nullptr, L"Foo"); + bool isCalled{false}; + auto subscription = rns.Subscribe( + fooName, nullptr, [&](IInspectable const & /*sender*/, IReactNotificationArgs const &args) noexcept { + isCalled = true; + args.Subscription().Unsubscribe(); + }); + rns.SendNotification(fooName, nullptr, nullptr); + TestCheck(isCalled); + + isCalled = false; + rns.SendNotification(fooName, nullptr, nullptr); + TestCheck(!isCalled); + } + + TEST_METHOD(Notification_SenderAndData) { + IReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + auto fooName = ReactPropertyBagHelper::GetName(nullptr, L"Foo"); + auto mySender = box_value(L"Hello"); + auto myData = box_value(42); + bool isCalled{false}; + rns.Subscribe(fooName, nullptr, [&](IInspectable const &sender, IReactNotificationArgs const &args) noexcept { + isCalled = true; + TestCheckEqual(mySender, sender); + TestCheckEqual(myData, args.Data()); + }); + rns.SendNotification(fooName, mySender, myData); + TestCheck(isCalled); + } + + TEST_METHOD(Notification_InQueue) { + IReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + Mso::ManualResetEvent finishedEvent; + auto fooName = ReactPropertyBagHelper::GetName(nullptr, L"Foo"); + IReactDispatcher dispatcher = ReactDispatcherHelper::CreateSerialDispatcher(); + bool isCalled{false}; + rns.Subscribe( + fooName, dispatcher, [&](IInspectable const & /*sender*/, IReactNotificationArgs const &args) noexcept { + TestCheckEqual(dispatcher, args.Subscription().Dispatcher()); + TestCheck(dispatcher.HasThreadAccess()); + isCalled = true; + finishedEvent.Set(); + }); + rns.SendNotification(fooName, nullptr, nullptr); + finishedEvent.Wait(); + TestCheck(isCalled); + } + + TEST_METHOD(NotificationWrapper_Subscribe) { + ReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + ReactNotificationId fooNotification{L"Foo"}; + bool isCalled{false}; + rns.Subscribe( + fooNotification, [&](IInspectable const & /*sender*/, ReactNotificationArgs const &args) noexcept { + isCalled = true; + TestCheck(!args.Subscription().Dispatcher()); + TestCheckEqual(fooNotification.NotificationName(), args.Subscription().NotificationName()); + TestCheck(args.Subscription().IsSubscribed()); + }); + rns.SendNotification(fooNotification); + TestCheck(isCalled); + } + + TEST_METHOD(NotificationWrapper_Unsubscribe) { + ReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + ReactNotificationId fooNotification{L"Foo"}; + bool isCalled{false}; + auto subscription = rns.Subscribe( + fooNotification, [&](IInspectable const & /*sender*/, ReactNotificationArgs const & /*args*/) noexcept { + isCalled = true; + }); + rns.SendNotification(fooNotification); + TestCheck(isCalled); + + subscription.Unsubscribe(); + TestCheck(!subscription.IsSubscribed()); + + isCalled = false; + rns.SendNotification(fooNotification); + TestCheck(!isCalled); + } + + TEST_METHOD(NotificationWrapper_UnsubscribeInHandler) { + ReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + ReactNotificationId fooNotification{L"Foo"}; + bool isCalled{false}; + auto subscription = rns.Subscribe( + fooNotification, [&](IInspectable const & /*sender*/, ReactNotificationArgs const &args) noexcept { + isCalled = true; + args.Subscription().Unsubscribe(); + }); + rns.SendNotification(fooNotification); + TestCheck(isCalled); + + isCalled = false; + rns.SendNotification(fooNotification); + TestCheck(!isCalled); + } + + TEST_METHOD(NotificationWrapper_Sender) { + ReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + ReactNotificationId fooNotification{L"Foo"}; + auto mySender = box_value(L"Hello"); + bool isCalled{false}; + rns.Subscribe( + fooNotification, [&](IInspectable const &sender, ReactNotificationArgs const & /*args*/) noexcept { + isCalled = true; + TestCheckEqual(mySender, sender); + }); + rns.SendNotification(fooNotification, mySender); + TestCheck(isCalled); + } + + TEST_METHOD(NotificationWrapper_Data) { + ReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + ReactNotificationId fooNotification{L"Foo"}; + bool isCalled{false}; + rns.Subscribe(fooNotification, [&](IInspectable const &sender, ReactNotificationArgs const &args) noexcept { + isCalled = true; + TestCheck(!sender); + TestCheckEqual(42, *args.Data()); + }); + rns.SendNotification(fooNotification, 42); + TestCheck(isCalled); + } + + TEST_METHOD(NotificationWrapper_InQueue) { + ReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + ReactNotificationId> fooNotification{L"Foo"}; + Mso::ManualResetEvent finishedEvent; + ReactDispatcher dispatcher{ReactDispatcher::CreateSerialDispatcher()}; + bool isCalled{false}; + rns.Subscribe( + fooNotification, + dispatcher, + [&](IInspectable const & /*sender*/, + ReactNotificationArgs> const &args) noexcept { + TestCheckEqual("Hello", *args.Data()); + TestCheckEqual(dispatcher, args.Subscription().Dispatcher()); + TestCheck(dispatcher.HasThreadAccess()); + isCalled = true; + finishedEvent.Set(); + }); + rns.SendNotification(fooNotification, "Hello"); + finishedEvent.Wait(); + TestCheck(isCalled); + } + + TEST_METHOD(NotificationWrapper_AutoRevoke) { + ReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + ReactNotificationId fooNotification{L"Foo"}; + bool isCalled{false}; + auto subscription = rns.Subscribe( + winrt::auto_revoke, + fooNotification, + [&](IInspectable const & /*sender*/, ReactNotificationArgs const & /*args*/) noexcept { + isCalled = true; + }); + rns.SendNotification(fooNotification); + TestCheck(isCalled); + + subscription = nullptr; + TestCheck(!subscription.IsSubscribed()); + + isCalled = false; + rns.SendNotification(fooNotification); + TestCheck(!isCalled); + } + + TEST_METHOD(NotificationWrapper_AutoRevoke_InQueue) { + ReactNotificationService rns{ReactNotificationServiceHelper::CreateNotificationService()}; + ReactNotificationId fooNotification{L"Foo"}; + Mso::ManualResetEvent finishedEvent; + ReactDispatcher dispatcher{ReactDispatcher::CreateSerialDispatcher()}; + ReactNotificationSubscription s; + bool isCalled{false}; + auto subscription = rns.Subscribe( + winrt::auto_revoke, + fooNotification, + dispatcher, + [&](IInspectable const & /*sender*/, ReactNotificationArgs const &args) noexcept { + isCalled = true; + s = args.Subscription(); + TestCheck(dispatcher.HasThreadAccess()); + finishedEvent.Set(); + }); + rns.SendNotification(fooNotification); + finishedEvent.Wait(); + TestCheck(isCalled); + + subscription = nullptr; + TestCheck(!s.IsSubscribed()); + } +}; + +} // namespace ReactNativeIntegrationTests diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ReactPropertyBagTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/ReactPropertyBagTests.cpp index d7200d84715..9e1bd2c9f4c 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/ReactPropertyBagTests.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/ReactPropertyBagTests.cpp @@ -265,6 +265,21 @@ TEST_CLASS (ReactPropertyBagTests) { TestCheckEqual(ReactPropertyBagHelper::GlobalNamespace(), ReactPropertyNamespace::Global().Handle()); } + TEST_METHOD(PropertyNamespace_Equality) { + ReactPropertyNamespace ns11{L"Foo"}; + ReactPropertyNamespace ns12{ns11}; + ReactPropertyNamespace ns2{L"Bar"}; + ReactPropertyNamespace ns3; + TestCheckEqual(ns11, ns12); + TestCheckEqual(ns12, ns11); + TestCheck(ns11 != ns2); + TestCheck(ns2 != ns11); + TestCheck(ns2 != nullptr); + TestCheck(nullptr != ns2); + TestCheck(ns3 == nullptr); + TestCheck(nullptr == ns3); + } + TEST_METHOD(PropertyId_ctor_default) { ReactPropertyId name1; TestCheck(!name1); diff --git a/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs b/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs index 08e87dcfaf3..b41ef2d2c80 100644 --- a/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs +++ b/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs @@ -307,6 +307,8 @@ public ReactContextMock(ReactModuleBuilderMock builder) public IReactPropertyBag Properties { get; } = ReactPropertyBagHelper.CreatePropertyBag(); + public IReactNotificationService Notifications { get; } = ReactNotificationServiceHelper.CreateNotificationService(); + public void DispatchEvent(FrameworkElement view, string eventName, JSValueArgWriter eventDataArgWriter) { throw new NotImplementedException(); diff --git a/vnext/Microsoft.ReactNative/IReactContext.cpp b/vnext/Microsoft.ReactNative/IReactContext.cpp index 1032555d5d2..a232d780be6 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.cpp +++ b/vnext/Microsoft.ReactNative/IReactContext.cpp @@ -13,6 +13,10 @@ IReactPropertyBag ReactContext::Properties() noexcept { return m_context->Properties(); } +IReactNotificationService ReactContext::Notifications() noexcept { + return m_context->Notifications(); +} + void ReactContext::DispatchEvent( xaml::FrameworkElement const &view, hstring const &eventName, diff --git a/vnext/Microsoft.ReactNative/IReactContext.h b/vnext/Microsoft.ReactNative/IReactContext.h index 0ea7a200db2..0dd2069a7d2 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.h +++ b/vnext/Microsoft.ReactNative/IReactContext.h @@ -13,6 +13,7 @@ struct ReactContext : winrt::implements { public: // IReactContext IReactPropertyBag Properties() noexcept; + IReactNotificationService Notifications() noexcept; void DispatchEvent( xaml::FrameworkElement const &view, hstring const &eventName, diff --git a/vnext/Microsoft.ReactNative/IReactContext.idl b/vnext/Microsoft.ReactNative/IReactContext.idl index 1b4bdd826e5..312d5148636 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.idl +++ b/vnext/Microsoft.ReactNative/IReactContext.idl @@ -2,17 +2,37 @@ // Licensed under the MIT License. import "IJSValueWriter.idl"; +import "IReactNotificationService.idl"; import "IReactPropertyBag.idl"; #include "NamespaceRedirect.h" namespace Microsoft.ReactNative { + // The IReactContext is given to native modules to communicate with + // other native modules, views, application, and the ReactNative instance. + // It has the same lifetime as the React instance. When the React instance is reloaded or unloaded, + // the IReactContext is destroyed. + // Use the Properties to share native module's data with other components. + // Use the Notifications to exchange events with other components. + // Use CallJSFunction to call JavaScript functions, and EmitJSEvent to raise JavaScript events. [webhosthidden] interface IReactContext { + // Properties shared with the IReactInstanceSettings.Properties. It can be used to share values and state between components. IReactPropertyBag Properties { get; }; + + // Notifications shared with the IReactInstanceSettings.Notifications. They can be used to exchange events between components. + // All subscriptions added to the IReactContext.Notifications are automatically removed after the IReactContext is destroyed. + // The subscriptions added to IReactInstanceSettings.Notifications kept as long as IReactInstanceSettings alive. + IReactNotificationService Notifications { get; }; + + // Dispatch UI event. This method is to be moved to IReactViewContext. void DispatchEvent(XAML_NAMESPACE.FrameworkElement view, String eventName, JSValueArgWriter eventDataArgWriter); + + // Call JavaScript function methodName of moduleName. void CallJSFunction(String moduleName, String methodName, JSValueArgWriter paramsArgWriter); + + // Call JavaScript module event. It is a specialized CallJSFunction call where method name is always 'emit'. void EmitJSEvent(String eventEmitterName, String eventName, JSValueArgWriter paramsArgWriter); } } // namespace Microsoft.ReactNative diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.cpp b/vnext/Microsoft.ReactNative/IReactDispatcher.cpp index 4c394e94825..f8910d9858e 100644 --- a/vnext/Microsoft.ReactNative/IReactDispatcher.cpp +++ b/vnext/Microsoft.ReactNative/IReactDispatcher.cpp @@ -8,7 +8,7 @@ using namespace winrt; using namespace Windows::Foundation; -namespace winrt::Microsoft::ReactNative { +namespace winrt::Microsoft::ReactNative::implementation { ReactDispatcher::ReactDispatcher(Mso::DispatchQueue &&queue) noexcept : m_queue{std::move(queue)} {} @@ -20,6 +20,10 @@ void ReactDispatcher::Post(ReactDispatcherCallback const &callback) noexcept { return m_queue.Post([callback]() noexcept { callback(); }); } +/*static*/ IReactDispatcher ReactDispatcher::CreateSerialDispatcher() noexcept { + return make(Mso::DispatchQueue{}); +} + /*static*/ Mso::DispatchQueue ReactDispatcher::GetUIDispatchQueue(IReactPropertyBag const &properties) noexcept { return GetUIDispatcher(properties).as()->m_queue; } @@ -41,4 +45,4 @@ void ReactDispatcher::Post(ReactDispatcherCallback const &callback) noexcept { ReactPropertyBag{properties}.Set(UIDispatcherProperty(), UIThreadDispatcher()); } -} // namespace winrt::Microsoft::ReactNative +} // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.h b/vnext/Microsoft.ReactNative/IReactDispatcher.h index e71c0526012..0cd85bf4ca8 100644 --- a/vnext/Microsoft.ReactNative/IReactDispatcher.h +++ b/vnext/Microsoft.ReactNative/IReactDispatcher.h @@ -7,7 +7,7 @@ #include #include -namespace winrt::Microsoft::ReactNative { +namespace winrt::Microsoft::ReactNative::implementation { struct ReactDispatcher : implements { ReactDispatcher() = default; @@ -16,6 +16,8 @@ struct ReactDispatcher : implements { bool HasThreadAccess() noexcept; void Post(ReactDispatcherCallback const &callback) noexcept; + static IReactDispatcher CreateSerialDispatcher() noexcept; + static Mso::DispatchQueue GetUIDispatchQueue(IReactPropertyBag const &properties) noexcept; static IReactDispatcher UIThreadDispatcher() noexcept; @@ -27,13 +29,13 @@ struct ReactDispatcher : implements { Mso::DispatchQueue m_queue; }; -} // namespace winrt::Microsoft::ReactNative - -namespace winrt::Microsoft::ReactNative::implementation { - struct ReactDispatcherHelper { ReactDispatcherHelper() = default; + static IReactDispatcher CreateSerialDispatcher() noexcept { + return ReactDispatcher::CreateSerialDispatcher(); + } + static IReactDispatcher UIThreadDispatcher() noexcept { return ReactDispatcher::UIThreadDispatcher(); } diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.idl b/vnext/Microsoft.ReactNative/IReactDispatcher.idl index e5f3d9b6d5f..84dd744f5b7 100644 --- a/vnext/Microsoft.ReactNative/IReactDispatcher.idl +++ b/vnext/Microsoft.ReactNative/IReactDispatcher.idl @@ -23,6 +23,9 @@ namespace Microsoft.ReactNative { [webhosthidden] static runtimeclass ReactDispatcherHelper { + // Creates a new serial dispatcher that uses thread pool to run tasks. + static IReactDispatcher CreateSerialDispatcher(); + // Get or create IReactDispatcher for the current UI thread. static IReactDispatcher UIThreadDispatcher{ get; }; diff --git a/vnext/Microsoft.ReactNative/IReactNotificationService.cpp b/vnext/Microsoft.ReactNative/IReactNotificationService.cpp new file mode 100644 index 00000000000..bb3956c8a58 --- /dev/null +++ b/vnext/Microsoft.ReactNative/IReactNotificationService.cpp @@ -0,0 +1,204 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "pch.h" +#include "IReactNotificationService.h" +#include "ReactNotificationServiceHelper.g.cpp" + +namespace winrt::Microsoft::ReactNative::implementation { + +//============================================================================= +// ReactNotificationSubscription implementation +//============================================================================= + +ReactNotificationSubscription::ReactNotificationSubscription( + weak_ref &¬ificationService, + IReactPropertyName const ¬ificationName, + IReactDispatcher const &dispatcher, + ReactNotificationHandler const &handler) noexcept + : m_notificationService{std::move(notificationService)}, + m_notificationName{notificationName}, + m_dispatcher{dispatcher}, + m_handler{handler} {} + +ReactNotificationSubscription::~ReactNotificationSubscription() noexcept { + Unsubscribe(); +} + +IReactDispatcher ReactNotificationSubscription::Dispatcher() const noexcept { + return m_dispatcher; +} + +IReactPropertyName ReactNotificationSubscription::NotificationName() const noexcept { + return m_notificationName; +} + +bool ReactNotificationSubscription::IsSubscribed() const noexcept { + return m_isSubscribed; +} + +void ReactNotificationSubscription::Unsubscribe() noexcept { + if (m_isSubscribed.exchange(false)) { + if (auto notificationService = m_notificationService.get()) { + notificationService->Unsubscribe(*this); + } + } +} + +void ReactNotificationSubscription::CallHandler( + IInspectable const &sender, + IReactNotificationArgs const &args) noexcept { + if (IsSubscribed()) { + if (m_dispatcher) { + m_dispatcher.Post([ thisPtr = get_strong(), sender, args ]() noexcept { + if (thisPtr->IsSubscribed()) { + thisPtr->m_handler(sender, args); + } + }); + } else { + m_handler(sender, args); + } + } +} + +//============================================================================= +// ReactNotificationService implementation +//============================================================================= + +ReactNotificationService::ReactNotificationService() = default; + +ReactNotificationService::ReactNotificationService(IReactNotificationService const parentNotificationService) noexcept + : m_parentNotificationService{parentNotificationService} {} + +ReactNotificationService::~ReactNotificationService() noexcept { + UnsubscribeAll(); +} + +void ReactNotificationService::ModifySubscriptions( + IReactPropertyName const ¬ificationName, + Mso::FunctorRef const &modifySnapshot) { + // Get the current snapshot under the lock + SubscriptionSnapshotPtr currentSnapshotPtr; + { + std::scoped_lock lock{m_mutex}; + auto it = m_subscriptions.find(notificationName); + if (it != m_subscriptions.end()) { + currentSnapshotPtr = it->second; + } + } + + // Modify the snapshot under the loop until we succeed. + for (;;) { + // Create new snapshot outside of lock. + auto newSnapshot = modifySnapshot(currentSnapshotPtr ? *currentSnapshotPtr : SubscriptionSnapshot()); + + // Try to set the new snapshot under the lock + SubscriptionSnapshotPtr snapshotPtr; + std::scoped_lock lock{m_mutex}; + auto it = m_subscriptions.find(notificationName); + if (it != m_subscriptions.end()) { + snapshotPtr = it->second; + } + + if (currentSnapshotPtr != snapshotPtr) { + // Some other thread already changed the snapshot for our subscription. Do it again. + currentSnapshotPtr = std::move(snapshotPtr); + continue; + } + + if (newSnapshot.empty()) { + if (it != m_subscriptions.end()) { + m_subscriptions.erase(it); + } + } else { + auto newSnapshotPtr = Mso::Make_RefCounted(std::move(newSnapshot)); + if (it != m_subscriptions.end()) { + it->second = std::move(newSnapshotPtr); + } else { + m_subscriptions.try_emplace(notificationName, std::move(newSnapshotPtr)); + } + } + + break; + } +} + +IReactNotificationSubscription ReactNotificationService::Subscribe( + IReactPropertyName const ¬ificationName, + IReactDispatcher const &dispatcher, + ReactNotificationHandler const &handler) noexcept { + auto subscription = make(get_weak(), notificationName, dispatcher, handler); + ModifySubscriptions( + notificationName, [&subscription](std::vector const &snapshot) noexcept { + std::vector newSnapshot(snapshot); + newSnapshot.push_back(subscription); + return newSnapshot; + }); + return subscription; +} + +void ReactNotificationService::Unsubscribe(IReactNotificationSubscription const &subscription) noexcept { + ModifySubscriptions( + subscription.NotificationName(), + [&subscription](std::vector const &snapshot) noexcept { + std::vector newSnapshot(snapshot); + auto it = std::find(newSnapshot.begin(), newSnapshot.end(), subscription); + if (it != newSnapshot.end()) { + newSnapshot.erase(it); + } + return newSnapshot; + }); +} + +void ReactNotificationService::UnsubscribeAll() noexcept { + std::map subscriptions; + { + std::scoped_lock lock{m_mutex}; + subscriptions = std::move(m_subscriptions); + } + + // Unsubscribe outside of lock. + for (auto &namedEntry : subscriptions) { + for (auto &subscription : *namedEntry.second) { + subscription.Unsubscribe(); + } + } +} + +void ReactNotificationService::SendNotification( + IReactPropertyName const ¬ificationName, + IInspectable const &sender, + IInspectable const &data) noexcept { + SubscriptionSnapshotPtr currentSnapshotPtr; + + { + std::scoped_lock lock{m_mutex}; + auto it = m_subscriptions.find(notificationName); + if (it != m_subscriptions.end()) { + currentSnapshotPtr = it->second; + } + } + + // Call notification handlers outside of lock. + if (currentSnapshotPtr) { + for (auto &subscription : *currentSnapshotPtr) { + auto args = make(subscription, data); + get_self(subscription)->CallHandler(sender, args); + } + } + + // Call parent notification service + if (m_parentNotificationService) { + m_parentNotificationService.SendNotification(notificationName, sender, data); + } +} + +//============================================================================= +// ReactNotificationServiceHelper implementation +//============================================================================= + +Microsoft::ReactNative::IReactNotificationService ReactNotificationServiceHelper::CreateNotificationService() noexcept { + return make(); +} + +} // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/IReactNotificationService.h b/vnext/Microsoft.ReactNative/IReactNotificationService.h new file mode 100644 index 00000000000..585d4a47bcf --- /dev/null +++ b/vnext/Microsoft.ReactNative/IReactNotificationService.h @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#include "ReactNotificationServiceHelper.g.h" +#include +#include +#include +#include +#include +#include +#include + +namespace winrt::Microsoft::ReactNative::implementation { + +struct ReactNotificationArgs : implements { + ReactNotificationArgs(IReactNotificationSubscription const &subscription, IInspectable const &data) noexcept + : m_subscription{subscription}, m_data{data} {} + + IReactNotificationSubscription Subscription() const noexcept { + return m_subscription; + } + + IInspectable Data() const noexcept { + return m_data; + } + + private: + IReactNotificationSubscription m_subscription; + IInspectable m_data; +}; + +struct ReactNotificationService : implements { + ReactNotificationService(); + explicit ReactNotificationService(IReactNotificationService const parentNotificationService) noexcept; + ~ReactNotificationService() noexcept; + + IReactNotificationSubscription Subscribe( + IReactPropertyName const ¬ificationName, + IReactDispatcher const &dispatcher, + ReactNotificationHandler const &handler) noexcept; + + void Unsubscribe(IReactNotificationSubscription const &subscription) noexcept; + + void UnsubscribeAll() noexcept; + + void SendNotification( + IReactPropertyName const ¬ificationName, + IInspectable const &sender, + IInspectable const &data) noexcept; + + private: + // We treat subscription snapshots as immutable data. + using SubscriptionSnapshot = std::vector; + using SubscriptionSnapshotPtr = Mso::RefCountedPtr; + + void ModifySubscriptions( + IReactPropertyName const ¬ificationName, + Mso::FunctorRef const &modifySnapshot); + + private: + std::mutex m_mutex; + std::map m_subscriptions; + IReactNotificationService m_parentNotificationService; +}; + +struct ReactNotificationServiceHelper { + ReactNotificationServiceHelper() = default; + + static IReactNotificationService CreateNotificationService() noexcept; +}; + +struct ReactNotificationSubscription : implements { + ReactNotificationSubscription( + weak_ref &¬ificationService, + IReactPropertyName const ¬ificationName, + IReactDispatcher const &dispatcher, + ReactNotificationHandler const &handler) noexcept; + ~ReactNotificationSubscription() noexcept; + + IReactPropertyName NotificationName() const noexcept; + IReactDispatcher Dispatcher() const noexcept; + bool IsSubscribed() const noexcept; + void Unsubscribe() noexcept; + void CallHandler(IInspectable const &sender, IReactNotificationArgs const &args) noexcept; + + private: + weak_ref m_notificationService; + IReactPropertyName m_notificationName; + IReactDispatcher m_dispatcher; + ReactNotificationHandler m_handler; + std::atomic_bool m_isSubscribed{true}; +}; + +} // namespace winrt::Microsoft::ReactNative::implementation + +namespace winrt::Microsoft::ReactNative::factory_implementation { + +struct ReactNotificationServiceHelper + : ReactNotificationServiceHelperT { +}; + +} // namespace winrt::Microsoft::ReactNative::factory_implementation diff --git a/vnext/Microsoft.ReactNative/IReactNotificationService.idl b/vnext/Microsoft.ReactNative/IReactNotificationService.idl new file mode 100644 index 00000000000..d3cda711caf --- /dev/null +++ b/vnext/Microsoft.ReactNative/IReactNotificationService.idl @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import "IReactDispatcher.idl"; +import "IReactPropertyBag.idl"; + +namespace Microsoft.ReactNative { + + // A subscription to a notification. + // The subscription is removed when this object is deleted or the Unsubscribe method is called. + [webhosthidden] + interface IReactNotificationSubscription + { + // Name of the notification. + IReactPropertyName NotificationName { get; }; + + // The IReactDispatcher provided when the notification subscription created. + // All notifications will be handled using this dispatcher. + IReactDispatcher Dispatcher { get; }; + + // True if the subscription is still active. + // This property is checked before notification handler is invoked. + Boolean IsSubscribed { get; }; + + // Remove the subscription. + // Because of the multi-threaded nature of the notifications, the handler can be still called + // after the Unsubscribe method called if the IsSubscribed property is already checked. + // Consider calling the Unsubscribe method and the handler in the same IReactDispatcher + // to ensure that no handler is invoked after the Unsubscribe method call. + void Unsubscribe(); + } + + // Notification args provided to the notification handler. + [webhosthidden] + interface IReactNotificationArgs + { + // The notification subscription that can be used to unsubscribe in the notification handler. + // It also has the name and dispatcher associated with the notification. + IReactNotificationSubscription Subscription { get; }; + + // The data sent with the notification. It can be any WinRT type. + // Consider using IReactPropertyBag for semi-structured data. + // It can be null if notification has no data. + Object Data { get; }; + } + + // Delegate to handle notifications. + // The sender parameter is the object that sent the notification. It can be null. + // The args contain the notification-specific data and the notification subscription. + [webhosthidden] + delegate void ReactNotificationHandler(Object sender, IReactNotificationArgs args); + + // The notification service is used to subscribe to notifications and to send notifications. + [webhosthidden] + interface IReactNotificationService + { + // Subscribe to a notification. + // The notificationName as a property name can belong to a specific namespace. It must be not null. + // The dispatcher is used to call notification handlers. If it is null, then handler is called synchronously. + // The handler is a delegate that can be implemented as a lambda to handle notifications. + // The method returns IReactNotificationSubscription that must be kept alive while the subscription + // is active. The subscription is removed when the IReactNotificationSubscription is destroyed. + IReactNotificationSubscription Subscribe( + IReactPropertyName notificationName, IReactDispatcher dispatcher, ReactNotificationHandler handler); + + // Send the notification with notificationName. + // The sender is the object that sends notification. It can be null. + // The data is the data associated with the notification. It can be null. + // Consider using IReactPropertyBag for sending semi-structured data. It can be created + // using the ReactPropertyBagHelper.CreatePropertyBag method. + void SendNotification(IReactPropertyName notificationName, Object sender, Object data); + } + + // Helper methods for the notification service implementation. + [webhosthidden] + static runtimeclass ReactNotificationServiceHelper + { + // Create new instance of IReactNotificationService + static IReactNotificationService CreateNotificationService(); + } +} // namespace Microsoft.ReactNative diff --git a/vnext/Microsoft.ReactNative/IReactPropertyBag.idl b/vnext/Microsoft.ReactNative/IReactPropertyBag.idl index b9ae64f6aed..cfe85e57d65 100644 --- a/vnext/Microsoft.ReactNative/IReactPropertyBag.idl +++ b/vnext/Microsoft.ReactNative/IReactPropertyBag.idl @@ -10,7 +10,8 @@ namespace Microsoft.ReactNative { // Namespace for the property name. // Use ReactPropertyBagHelper.GetNamespace to get atomic property namespace for a string. [webhosthidden] - interface IReactPropertyNamespace { + interface IReactPropertyNamespace + { // Get String name representation of the property namespace. String NamespaceName { get; }; } @@ -18,7 +19,8 @@ namespace Microsoft.ReactNative { // Name of a property. // Use ReactPropertyBagHelper.GetName to get atomic property name for a string. [webhosthidden] - interface IReactPropertyName { + interface IReactPropertyName + { // The local property name String in context of the property namespace. String LocalName { get; }; @@ -53,7 +55,8 @@ namespace Microsoft.ReactNative { // Helper methods for the property bag implementation. [webhosthidden] - static runtimeclass ReactPropertyBagHelper { + static runtimeclass ReactPropertyBagHelper + { // Return a global namespace that corresponds to an empty string. static IReactPropertyNamespace GlobalNamespace { get; }; diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index 234c8d355fd..ff3899552b7 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -306,6 +306,10 @@ IReactModuleBuilder.idl + + IReactNotificationService.idl + Code + @@ -477,6 +481,10 @@ IReactModuleBuilder.idl + + IReactNotificationService.idl + Code + @@ -555,6 +563,7 @@ + diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters index 3730f7e0db1..4824e58dc1d 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters @@ -266,6 +266,7 @@ + Modules @@ -604,6 +605,7 @@ + Modules @@ -695,6 +697,7 @@ + diff --git a/vnext/Microsoft.ReactNative/ReactHost/React.h b/vnext/Microsoft.ReactNative/ReactHost/React.h index d48786695da..906d7b6c884 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/React.h +++ b/vnext/Microsoft.ReactNative/ReactHost/React.h @@ -72,6 +72,7 @@ struct IReactInstance : IUnknown { MSO_GUID(IReactContext, "a4309a29-8fc5-478e-abea-0ddb9ecc5e40") struct IReactContext : IUnknown { + virtual winrt::Microsoft::ReactNative::IReactNotificationService Notifications() noexcept = 0; virtual winrt::Microsoft::ReactNative::IReactPropertyBag Properties() noexcept = 0; virtual void CallJSFunction(std::string &&module, std::string &&method, folly::dynamic &¶ms) noexcept = 0; virtual void DispatchEvent(int64_t viewTag, std::string &&eventName, folly::dynamic &&eventData) noexcept = 0; @@ -159,6 +160,8 @@ struct ReactOptions { winrt::Microsoft::ReactNative::IReactPropertyBag Properties; + winrt::Microsoft::ReactNative::IReactNotificationService Notifications; + std::shared_ptr ModuleProvider; std::shared_ptr ViewManagerProvider; diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index 6676d038690..151f0141364 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -11,6 +11,7 @@ #include #include "ReactErrorProvider.h" +#include "Microsoft.ReactNative/IReactNotificationService.h" #include "Microsoft.ReactNative/Threading/MessageQueueThreadFactory.h" #include "../../codegen/NativeClipboardSpec.g.h" @@ -59,6 +60,8 @@ std::shared_ptr CreateUIManager2( } // namespace react::uwp +using namespace winrt::Microsoft::ReactNative; + namespace Mso::React { //============================================================================================= @@ -67,13 +70,24 @@ namespace Mso::React { ReactContext::ReactContext( Mso::WeakPtr &&reactInstance, - winrt::Microsoft::ReactNative::IReactPropertyBag const &properties) noexcept - : m_reactInstance{std::move(reactInstance)}, m_properties{properties} {} + IReactPropertyBag const &properties, + IReactNotificationService const ¬ifications) noexcept + : m_reactInstance{std::move(reactInstance)}, m_properties{properties}, m_notifications{notifications} {} + +void ReactContext::Destroy() noexcept { + if (auto notificationService = winrt::get_self(m_notifications)) { + notificationService->UnsubscribeAll(); + } +} -winrt::Microsoft::ReactNative::IReactPropertyBag ReactContext::Properties() noexcept { +IReactPropertyBag ReactContext::Properties() noexcept { return m_properties; } +IReactNotificationService ReactContext::Notifications() noexcept { + return m_notifications; +} + void ReactContext::CallJSFunction(std::string &&module, std::string &&method, folly::dynamic &¶ms) noexcept { if (auto instance = m_reactInstance.GetStrongPtr()) { if (instance->State() == ReactInstanceState::Loaded) { @@ -133,7 +147,10 @@ ReactInstanceWin::ReactInstanceWin( m_whenCreated{std::move(whenCreated)}, m_whenLoaded{std::move(whenLoaded)}, m_updateUI{std::move(updateUI)}, - m_reactContext{Mso::Make(this, options.Properties)}, + m_reactContext{Mso::Make( + this, + options.Properties, + winrt::make(options.Notifications))}, m_legacyInstance{std::make_shared( Mso::WeakPtr{this}, Mso::Copy(options.LegacySettings))} { @@ -459,7 +476,7 @@ void ReactInstanceWin::InitNativeMessageThread() noexcept { void ReactInstanceWin::InitUIMessageThread() noexcept { // Native queue was already given us in constructor. - m_uiQueue = winrt::Microsoft::ReactNative::ReactDispatcher::GetUIDispatchQueue(m_options.Properties); + m_uiQueue = winrt::Microsoft::ReactNative::implementation::ReactDispatcher::GetUIDispatchQueue(m_options.Properties); m_uiMessageThread.Exchange( std::make_shared(m_uiQueue, Mso::MakeWeakMemberFunctor(this, &ReactInstanceWin::OnError))); diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h index 5f2957f8389..42992246e00 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h @@ -41,16 +41,24 @@ class ReactContext final : public Mso::UnknownObject { public: ReactContext( Mso::WeakPtr &&reactInstance, - winrt::Microsoft::ReactNative::IReactPropertyBag const &properties) noexcept; + winrt::Microsoft::ReactNative::IReactPropertyBag const &properties, + winrt::Microsoft::ReactNative::IReactNotificationService const ¬ifications) noexcept; + + // ReactContext may have longer lifespan than ReactInstance. + // The ReactInstance uses the Destroy method to enforce the ReactContext cleaup + // when the ReactInstance is destroyed. + void Destroy() noexcept; public: // IReactContext winrt::Microsoft::ReactNative::IReactPropertyBag Properties() noexcept override; + winrt::Microsoft::ReactNative::IReactNotificationService Notifications() noexcept override; void CallJSFunction(std::string &&module, std::string &&method, folly::dynamic &¶ms) noexcept override; void DispatchEvent(int64_t viewTag, std::string &&eventName, folly::dynamic &&eventData) noexcept override; private: Mso::WeakPtr m_reactInstance; winrt::Microsoft::ReactNative::IReactPropertyBag m_properties; + winrt::Microsoft::ReactNative::IReactNotificationService m_notifications; }; //! ReactInstance implementation for Windows that is managed by ReactHost. diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettings.h b/vnext/Microsoft.ReactNative/ReactInstanceSettings.h index 1e828273511..fcdc1ef1a1b 100644 --- a/vnext/Microsoft.ReactNative/ReactInstanceSettings.h +++ b/vnext/Microsoft.ReactNative/ReactInstanceSettings.h @@ -26,6 +26,8 @@ struct ReactInstanceSettings : ReactInstanceSettingsT { IReactPropertyBag Properties() noexcept; + IReactNotificationService Notifications() noexcept; + hstring MainComponentName() noexcept; void MainComponentName(hstring const &value) noexcept; @@ -85,6 +87,7 @@ struct ReactInstanceSettings : ReactInstanceSettingsT { private: IReactPropertyBag m_properties{ReactPropertyBagHelper::CreatePropertyBag()}; + IReactNotificationService m_notifications{ReactNotificationServiceHelper::CreateNotificationService()}; hstring m_mainComponentName{}; bool m_useDeveloperSupport{REACT_DEFAULT_USE_DEVELOPER_SUPPORT}; hstring m_javaScriptMainModuleName{}; @@ -124,6 +127,10 @@ inline IReactPropertyBag ReactInstanceSettings::Properties() noexcept { return m_properties; } +inline IReactNotificationService ReactInstanceSettings::Notifications() noexcept { + return m_notifications; +} + inline hstring ReactInstanceSettings::MainComponentName() noexcept { return m_mainComponentName; } diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl b/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl index fff4532c2d4..ade42eaf090 100644 --- a/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl +++ b/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl @@ -2,6 +2,7 @@ // Licensed under the MIT License. import "RedBoxHandler.idl"; +import "IReactNotificationService.idl"; import "IReactPropertyBag.idl"; namespace Microsoft.ReactNative { @@ -12,6 +13,7 @@ namespace Microsoft.ReactNative { ReactInstanceSettings(); IReactPropertyBag Properties { get; }; + IReactNotificationService Notifications { get; }; String MainComponentName { get; set; }; Boolean UseDeveloperSupport { get; set; }; String JavaScriptMainModuleName { get; set; }; diff --git a/vnext/Microsoft.ReactNative/ReactNativeHost.cpp b/vnext/Microsoft.ReactNative/ReactNativeHost.cpp index fc2eaa4ec3d..180f5af5678 100644 --- a/vnext/Microsoft.ReactNative/ReactNativeHost.cpp +++ b/vnext/Microsoft.ReactNative/ReactNativeHost.cpp @@ -92,6 +92,7 @@ void ReactNativeHost::ReloadInstance() noexcept { Mso::React::ReactOptions reactOptions{}; reactOptions.Properties = m_instanceSettings.Properties(); + reactOptions.Notifications = m_instanceSettings.Notifications(); reactOptions.DeveloperSettings.IsDevModeEnabled = legacySettings.EnableDeveloperMenu; reactOptions.DeveloperSettings.SourceBundleName = legacySettings.DebugBundlePath; reactOptions.DeveloperSettings.UseWebDebugger = legacySettings.UseWebDebugger;