diff --git a/docs/deprecated.md b/docs/deprecated.md new file mode 100644 index 0000000..7390791 --- /dev/null +++ b/docs/deprecated.md @@ -0,0 +1,62 @@ +# Deprecated TypeScript Annotations in react-native-winrt + +## Overview + +The `rnwinrt` tool generates `/** @deprecated message */` JSDoc annotations for WinRT types +and members that have the `Windows.Foundation.Metadata.DeprecatedAttribute` in their metadata. + +## Usage + +To include deprecated types in the generated output, use the `-deprecatedincluded` flag: + +```bash +rnwinrt.exe -input local -include Windows.Media.PlayTo -tsoutput ./output -deprecatedincluded +``` + +Without `-deprecatedincluded`, deprecated types are excluded entirely from the output. + +## Generated Output Example + +```typescript +/** @deprecated PlayToConnection may be altered or unavailable for releases after Windows 10. Instead, use CastingConnection. */ +class PlayToConnection { + public readonly state: Windows.Media.PlayTo.PlayToConnectionState; + /** @deprecated PlayToConnection may be altered or unavailable ... */ + public addEventListener(type: "statechanged", listener: any): void; +} + +/** @deprecated PlayToConnectionState may be altered or unavailable for releases after Windows 10. Instead, use CastingConnectionState. */ +enum PlayToConnectionState { + /** @deprecated ... */ + disconnected, + /** @deprecated ... */ + connected, + /** @deprecated ... */ + rendering, +} +``` + +## What Gets Annotated + +- **Classes/Interfaces**: The class declaration gets `@deprecated` +- **Enums**: Both the enum type and individual enum values get `@deprecated` +- **Methods**: Instance and static methods get `@deprecated` +- **Properties**: Read-only and read-write properties get `@deprecated` +- **Events**: addEventListener/removeEventListener pairs get `@deprecated` +- **Delegates**: Delegate type aliases get `@deprecated` +- **Structs**: Interface declarations (for value types) get `@deprecated` + +## IDE Support + +TypeScript-aware editors (VS Code, WebStorm, etc.) will show: +- Strikethrough on deprecated symbols +- Warning messages in hover tooltips +- Diagnostics when deprecated APIs are used + +## Verification + +Run the verification script to confirm deprecated annotations are generated correctly: + +```powershell +.\tests\TestArtifacts\verify_deprecated.ps1 -rnwinrtPath .\rnwinrt\x64\Release\rnwinrt.exe +``` diff --git a/rnwinrt/ProjectedValueConverters.g.h b/rnwinrt/ProjectedValueConverters.g.h new file mode 100644 index 0000000..1e4ff26 --- /dev/null +++ b/rnwinrt/ProjectedValueConverters.g.h @@ -0,0 +1,4 @@ +#pragma once + +#include "base.h" + diff --git a/rnwinrt/Projections.g.cpp b/rnwinrt/Projections.g.cpp new file mode 100644 index 0000000..9d60fdd --- /dev/null +++ b/rnwinrt/Projections.g.cpp @@ -0,0 +1,42 @@ +#include "pch.h" + +#include "base.h" + +#include "Windows.g.h" +#include "Windows.Media.PlayTo.g.h" +#include + +namespace rnwinrt +{ + static constexpr const static_namespace_data* const root_namespace_data[] = { + &rnwinrt::namespaces::Windows::data, + }; + + constexpr const span root_namespaces{ root_namespace_data }; + + static constexpr const std::pair global_interface_map_data[] = { + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToManager::data }, // f56a206e-1b77-42ef-8f0d-b949f8d9b260 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToConnectionErrorEventArgs::data }, // bf5eada6-88e6-445f-9d40-d9b9f8939896 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSourceRequest::data }, // f8584665-64f4-44a0-ac0d-468d2b8fda83 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToConnectionTransferredEventArgs::data }, // fae3193a-0683-47d9-8df0-18cbb48984d8 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToConnectionStateChangedEventArgs::data }, // 68c4b50f-0c20-4980-8602-58c62238d423 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::ISourceChangeRequestedEventArgs::data }, // fb3f3a96-7aa6-4a8b-86e7-54f6c6d34f64 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToReceiver::data }, // ac15cf47-a162-4aa6-af1b-3aa35f3b9069 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSource::data }, // 7f138a08-fbb7-4b09-8356-aa5f4e335c31 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::ICurrentTimeChangeRequestedEventArgs::data }, // 99711324-edc7-4bf5-91f6-3c8627db59e5 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IVolumeChangeRequestedEventArgs::data }, // 6f026d5c-cf75-4c2b-913e-6d7c6c329179 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlaybackRateChangeRequestedEventArgs::data }, // 0f5661ae-2c88-4cca-8540-d586095d13a5 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSourceWithPreferredSourceUri::data }, // aab253eb-3301-4dc4-afba-b2f2ed9635a0 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSourceSelectedEventArgs::data }, // 0c9d8511-5202-4dcb-8c67-abda12bb3c12 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSourceRequestedEventArgs::data }, // c5cdc330-29df-4ec6-9da9-9fbdfcfc1b3e + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IMuteChangeRequestedEventArgs::data }, // e4b4f5f6-af1f-4f1e-b437-7da32400e1d4 + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSourceDeferral::data }, // 4100891d-278e-4f29-859b-a9e501053e7d + { winrt::guid_of(), &rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToConnection::data }, // 112fbfc8-f235-4fde-8d41-9bf27c9e9a40 + { winrt::guid_of>>(), &rnwinrt::interfaces::Windows::Foundation::Collections::IIterable::data> }, // fe2f3d47-5d47-5499-8374-430c7cda0204 + { winrt::guid_of>(), &rnwinrt::interfaces::Windows::Foundation::Collections::IMapView::data }, // bb78502a-f79d-54fa-92c9-90c5039fdf7e + { winrt::guid_of>>(), &rnwinrt::interfaces::Windows::Foundation::Collections::IIterator::data> }, // 5db5fa32-707c-5849-a06b-91c8eb9d10e8 + { winrt::guid_of>(), &rnwinrt::interfaces::Windows::Foundation::Collections::IKeyValuePair::data }, // 09335560-6c6b-5a26-9348-97b781132b20 + }; + + constexpr const span> global_interface_map(global_interface_map_data); +} diff --git a/rnwinrt/Windows.Media.PlayTo.g.cpp b/rnwinrt/Windows.Media.PlayTo.g.cpp new file mode 100644 index 0000000..41e0659 --- /dev/null +++ b/rnwinrt/Windows.Media.PlayTo.g.cpp @@ -0,0 +1,776 @@ +#include "pch.h" + +#include "base.h" + +#include "Windows.Media.PlayTo.g.h" + +#include + +namespace rnwinrt::namespaces::Windows::Media::PlayTo +{ + static constexpr const static_projection_data* const children[] = { + &rnwinrt::classes::Windows::Media::PlayTo::CurrentTimeChangeRequestedEventArgs::data, + &rnwinrt::classes::Windows::Media::PlayTo::MuteChangeRequestedEventArgs::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlayToConnection::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlayToConnectionErrorEventArgs::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlayToConnectionStateChangedEventArgs::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlayToConnectionTransferredEventArgs::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlayToManager::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlayToReceiver::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlayToSource::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlayToSourceDeferral::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlayToSourceRequest::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlayToSourceRequestedEventArgs::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlayToSourceSelectedEventArgs::data, + &rnwinrt::classes::Windows::Media::PlayTo::PlaybackRateChangeRequestedEventArgs::data, + &rnwinrt::classes::Windows::Media::PlayTo::SourceChangeRequestedEventArgs::data, + &rnwinrt::classes::Windows::Media::PlayTo::VolumeChangeRequestedEventArgs::data, + &rnwinrt::enums::Windows::Media::PlayTo::PlayToConnectionError::data, + &rnwinrt::enums::Windows::Media::PlayTo::PlayToConnectionState::data, + }; + + constexpr const static_namespace_data data{ "PlayTo"sv, children }; +} + +namespace rnwinrt::enums::Windows::Media::PlayTo::PlayToConnectionError +{ + static constexpr const static_enum_data::value_mapping mappings[] = { + { "none"sv, 0, "0"sv }, + { "deviceNotResponding"sv, 1, "1"sv }, + { "deviceError"sv, 2, "2"sv }, + { "deviceLocked"sv, 3, "3"sv }, + { "protectedPlaybackFailed"sv, 4, "4"sv }, + }; + + constexpr const static_enum_data data{ "PlayToConnectionError"sv, mappings }; +} + +namespace rnwinrt::enums::Windows::Media::PlayTo::PlayToConnectionState +{ + static constexpr const static_enum_data::value_mapping mappings[] = { + { "disconnected"sv, 0, "0"sv }, + { "connected"sv, 1, "1"sv }, + { "rendering"sv, 2, "2"sv }, + }; + + constexpr const static_enum_data data{ "PlayToConnectionState"sv, mappings }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::CurrentTimeChangeRequestedEventArgs +{ + constexpr const static_class_data data{ "CurrentTimeChangeRequestedEventArgs"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::MuteChangeRequestedEventArgs +{ + constexpr const static_class_data data{ "MuteChangeRequestedEventArgs"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlayToConnection +{ + constexpr const static_class_data data{ "PlayToConnection"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlayToConnectionErrorEventArgs +{ + constexpr const static_class_data data{ "PlayToConnectionErrorEventArgs"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlayToConnectionStateChangedEventArgs +{ + constexpr const static_class_data data{ "PlayToConnectionStateChangedEventArgs"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlayToConnectionTransferredEventArgs +{ + constexpr const static_class_data data{ "PlayToConnectionTransferredEventArgs"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlayToManager +{ + static constexpr const static_class_data::function_mapping function_data[] = { + { "getForCurrentView", + []([[maybe_unused]] jsi::Runtime& runtime, const jsi::Value&, [[maybe_unused]] const jsi::Value* args, size_t count) { + if (count == 0) + { + auto result = winrt::Windows::Media::PlayTo::PlayToManager::GetForCurrentView(); + return convert_native_to_value(runtime, result); + } + throw_no_function_overload(runtime, "Windows.Media.PlayTo"sv, "PlayToManager"sv, "getForCurrentView"sv, count); + } + }, + { "showPlayToUI", + []([[maybe_unused]] jsi::Runtime& runtime, const jsi::Value&, [[maybe_unused]] const jsi::Value* args, size_t count) { + if (count == 0) + { + winrt::Windows::Media::PlayTo::PlayToManager::ShowPlayToUI(); + return jsi::Value::undefined(); + } + throw_no_function_overload(runtime, "Windows.Media.PlayTo"sv, "PlayToManager"sv, "showPlayToUI"sv, count); + } + }, + }; + + constexpr const static_class_data data{ "PlayToManager"sv, {}, {}, function_data }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlayToReceiver +{ + static jsi::Value constructor_function(jsi::Runtime& runtime, const jsi::Value&, [[maybe_unused]] const jsi::Value* args, size_t count) + { + if (count == 0) + { + return convert_native_to_value(runtime, winrt::Windows::Media::PlayTo::PlayToReceiver()); + } + throw_no_constructor(runtime, "Windows.Media.PlayTo"sv, "PlayToReceiver"sv, count); + } + + constexpr const static_activatable_class_data data{ "Windows.Media.PlayTo"sv, "PlayToReceiver"sv, constructor_function, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlayToSource +{ + constexpr const static_class_data data{ "PlayToSource"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlayToSourceDeferral +{ + constexpr const static_class_data data{ "PlayToSourceDeferral"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlayToSourceRequest +{ + constexpr const static_class_data data{ "PlayToSourceRequest"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlayToSourceRequestedEventArgs +{ + constexpr const static_class_data data{ "PlayToSourceRequestedEventArgs"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlayToSourceSelectedEventArgs +{ + constexpr const static_class_data data{ "PlayToSourceSelectedEventArgs"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::PlaybackRateChangeRequestedEventArgs +{ + constexpr const static_class_data data{ "PlaybackRateChangeRequestedEventArgs"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::SourceChangeRequestedEventArgs +{ + constexpr const static_class_data data{ "SourceChangeRequestedEventArgs"sv, {}, {}, {} }; +} + +namespace rnwinrt::classes::Windows::Media::PlayTo::VolumeChangeRequestedEventArgs +{ + constexpr const static_class_data data{ "VolumeChangeRequestedEventArgs"sv, {}, {}, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::ICurrentTimeChangeRequestedEventArgs +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "time", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Time()); + }, + nullptr + }, + }; + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IMuteChangeRequestedEventArgs +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "mute", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Mute()); + }, + nullptr + }, + }; + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToConnection +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "state", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().State()); + }, + nullptr + }, + }; + static constexpr const static_interface_data::event_mapping event_data[] = { + { "error", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().Error(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().Error(token); + } + }, + { "statechanged", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().StateChanged(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().StateChanged(token); + } + }, + { "transferred", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().Transferred(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().Transferred(token); + } + }, + }; + + constexpr const static_interface_data data{ winrt::guid_of(), property_data, event_data, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToConnectionErrorEventArgs +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "code", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Code()); + }, + nullptr + }, + { "message", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Message()); + }, + nullptr + }, + }; + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToConnectionStateChangedEventArgs +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "currentState", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().CurrentState()); + }, + nullptr + }, + { "previousState", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().PreviousState()); + }, + nullptr + }, + }; + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToConnectionTransferredEventArgs +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "currentSource", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().CurrentSource()); + }, + nullptr + }, + { "previousSource", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().PreviousSource()); + }, + nullptr + }, + }; + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToManager +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "defaultSourceSelection", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().DefaultSourceSelection()); + }, + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& value) { + thisValue.as().DefaultSourceSelection(convert_value_to_native(runtime, value)); + }, + }, + }; + static constexpr const static_interface_data::event_mapping event_data[] = { + { "sourcerequested", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().SourceRequested(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().SourceRequested(token); + } + }, + { "sourceselected", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().SourceSelected(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().SourceSelected(token); + } + }, + }; + + constexpr const static_interface_data data{ winrt::guid_of(), property_data, event_data, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToReceiver +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "friendlyName", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().FriendlyName()); + }, + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& value) { + thisValue.as().FriendlyName(convert_value_to_native(runtime, value)); + }, + }, + { "properties", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Properties()); + }, + nullptr + }, + { "supportsAudio", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().SupportsAudio()); + }, + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& value) { + thisValue.as().SupportsAudio(convert_value_to_native(runtime, value)); + }, + }, + { "supportsImage", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().SupportsImage()); + }, + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& value) { + thisValue.as().SupportsImage(convert_value_to_native(runtime, value)); + }, + }, + { "supportsVideo", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().SupportsVideo()); + }, + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& value) { + thisValue.as().SupportsVideo(convert_value_to_native(runtime, value)); + }, + }, + }; + static constexpr const static_interface_data::event_mapping event_data[] = { + { "currenttimechangerequested", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().CurrentTimeChangeRequested(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().CurrentTimeChangeRequested(token); + } + }, + { "mutechangerequested", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().MuteChangeRequested(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().MuteChangeRequested(token); + } + }, + { "pauserequested", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().PauseRequested(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().PauseRequested(token); + } + }, + { "playbackratechangerequested", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().PlaybackRateChangeRequested(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().PlaybackRateChangeRequested(token); + } + }, + { "playrequested", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().PlayRequested(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().PlayRequested(token); + } + }, + { "sourcechangerequested", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().SourceChangeRequested(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().SourceChangeRequested(token); + } + }, + { "stoprequested", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().StopRequested(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().StopRequested(token); + } + }, + { "timeupdaterequested", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().TimeUpdateRequested(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().TimeUpdateRequested(token); + } + }, + { "volumechangerequested", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& callback) { + return thisValue.as().VolumeChangeRequested(convert_value_to_native>(runtime, callback)); + }, + [](const winrt::Windows::Foundation::IInspectable& thisValue, winrt::event_token token) { + thisValue.as().VolumeChangeRequested(token); + } + }, + }; + + static constexpr const static_interface_data::function_mapping function_data[] = { + { "notifyDurationChange", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + auto arg0 = convert_value_to_native(runtime, args[0]); + thisValue.as().NotifyDurationChange(arg0); + return jsi::Value::undefined(); + }, + 1, false }, + { "notifyEnded", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + thisValue.as().NotifyEnded(); + return jsi::Value::undefined(); + }, + 0, false }, + { "notifyError", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + thisValue.as().NotifyError(); + return jsi::Value::undefined(); + }, + 0, false }, + { "notifyLoadedMetadata", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + thisValue.as().NotifyLoadedMetadata(); + return jsi::Value::undefined(); + }, + 0, false }, + { "notifyPaused", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + thisValue.as().NotifyPaused(); + return jsi::Value::undefined(); + }, + 0, false }, + { "notifyPlaying", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + thisValue.as().NotifyPlaying(); + return jsi::Value::undefined(); + }, + 0, false }, + { "notifyRateChange", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + auto arg0 = convert_value_to_native(runtime, args[0]); + thisValue.as().NotifyRateChange(arg0); + return jsi::Value::undefined(); + }, + 1, false }, + { "notifySeeked", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + thisValue.as().NotifySeeked(); + return jsi::Value::undefined(); + }, + 0, false }, + { "notifySeeking", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + thisValue.as().NotifySeeking(); + return jsi::Value::undefined(); + }, + 0, false }, + { "notifyStopped", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + thisValue.as().NotifyStopped(); + return jsi::Value::undefined(); + }, + 0, false }, + { "notifyTimeUpdate", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + auto arg0 = convert_value_to_native(runtime, args[0]); + thisValue.as().NotifyTimeUpdate(arg0); + return jsi::Value::undefined(); + }, + 1, false }, + { "notifyVolumeChange", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + auto arg0 = convert_value_to_native(runtime, args[0]); + auto arg1 = convert_value_to_native(runtime, args[1]); + thisValue.as().NotifyVolumeChange(arg0, arg1); + return jsi::Value::undefined(); + }, + 2, false }, + { "startAsync", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + auto result = thisValue.as().StartAsync(); + return convert_native_to_value(runtime, result); + }, + 0, false }, + { "stopAsync", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + auto result = thisValue.as().StopAsync(); + return convert_native_to_value(runtime, result); + }, + 0, false }, + }; + + constexpr const static_interface_data data{ winrt::guid_of(), property_data, event_data, function_data }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSource +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "connection", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Connection()); + }, + nullptr + }, + { "next", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Next()); + }, + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& value) { + thisValue.as().Next(convert_value_to_native(runtime, value)); + }, + }, + }; + static constexpr const static_interface_data::function_mapping function_data[] = { + { "playNext", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + thisValue.as().PlayNext(); + return jsi::Value::undefined(); + }, + 0, false }, + }; + + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, function_data }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSourceDeferral +{ + static constexpr const static_interface_data::function_mapping function_data[] = { + { "complete", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + thisValue.as().Complete(); + return jsi::Value::undefined(); + }, + 0, false }, + }; + + constexpr const static_interface_data data{ winrt::guid_of(), {}, {}, function_data }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSourceRequest +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "deadline", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Deadline()); + }, + nullptr + }, + }; + static constexpr const static_interface_data::function_mapping function_data[] = { + { "displayErrorString", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + auto arg0 = convert_value_to_native(runtime, args[0]); + thisValue.as().DisplayErrorString(arg0); + return jsi::Value::undefined(); + }, + 1, false }, + { "getDeferral", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + auto result = thisValue.as().GetDeferral(); + return convert_native_to_value(runtime, result); + }, + 0, false }, + { "setSource", + []([[maybe_unused]] jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, [[maybe_unused]] const jsi::Value* args) { + auto arg0 = convert_value_to_native(runtime, args[0]); + thisValue.as().SetSource(arg0); + return jsi::Value::undefined(); + }, + 1, false }, + }; + + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, function_data }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSourceRequestedEventArgs +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "sourceRequest", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().SourceRequest()); + }, + nullptr + }, + }; + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSourceSelectedEventArgs +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "friendlyName", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().FriendlyName()); + }, + nullptr + }, + { "icon", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Icon()); + }, + nullptr + }, + { "supportsAudio", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().SupportsAudio()); + }, + nullptr + }, + { "supportsImage", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().SupportsImage()); + }, + nullptr + }, + { "supportsVideo", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().SupportsVideo()); + }, + nullptr + }, + }; + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlayToSourceWithPreferredSourceUri +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "preferredSourceUri", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().PreferredSourceUri()); + }, + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue, const jsi::Value& value) { + thisValue.as().PreferredSourceUri(convert_value_to_native(runtime, value)); + }, + }, + }; + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IPlaybackRateChangeRequestedEventArgs +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "rate", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Rate()); + }, + nullptr + }, + }; + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::ISourceChangeRequestedEventArgs +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "album", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Album()); + }, + nullptr + }, + { "author", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Author()); + }, + nullptr + }, + { "date", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Date()); + }, + nullptr + }, + { "description", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Description()); + }, + nullptr + }, + { "genre", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Genre()); + }, + nullptr + }, + { "properties", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Properties()); + }, + nullptr + }, + { "rating", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Rating()); + }, + nullptr + }, + { "stream", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Stream()); + }, + nullptr + }, + { "thumbnail", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Thumbnail()); + }, + nullptr + }, + { "title", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Title()); + }, + nullptr + }, + }; + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, {} }; +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo::IVolumeChangeRequestedEventArgs +{ + static constexpr const static_interface_data::property_mapping property_data[] = { + { "volume", + [](jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Volume()); + }, + nullptr + }, + }; + constexpr const static_interface_data data{ winrt::guid_of(), property_data, {}, {} }; +} diff --git a/rnwinrt/Windows.Media.PlayTo.g.h b/rnwinrt/Windows.Media.PlayTo.g.h new file mode 100644 index 0000000..e8fcb38 --- /dev/null +++ b/rnwinrt/Windows.Media.PlayTo.g.h @@ -0,0 +1,192 @@ +#pragma once + +#include "base.h" + +namespace rnwinrt::namespaces::Windows::Media::PlayTo +{ + extern const static_namespace_data data; +} + +namespace rnwinrt::enums::Windows::Media::PlayTo +{ + namespace PlayToConnectionError + { + extern const static_enum_data data; + } + + namespace PlayToConnectionState + { + extern const static_enum_data data; + } +} + +namespace rnwinrt::classes::Windows::Media::PlayTo +{ + namespace CurrentTimeChangeRequestedEventArgs + { + extern const static_class_data data; + } + + namespace MuteChangeRequestedEventArgs + { + extern const static_class_data data; + } + + namespace PlayToConnection + { + extern const static_class_data data; + } + + namespace PlayToConnectionErrorEventArgs + { + extern const static_class_data data; + } + + namespace PlayToConnectionStateChangedEventArgs + { + extern const static_class_data data; + } + + namespace PlayToConnectionTransferredEventArgs + { + extern const static_class_data data; + } + + namespace PlayToManager + { + extern const static_class_data data; + } + + namespace PlayToReceiver + { + extern const static_activatable_class_data data; + } + + namespace PlayToSource + { + extern const static_class_data data; + } + + namespace PlayToSourceDeferral + { + extern const static_class_data data; + } + + namespace PlayToSourceRequest + { + extern const static_class_data data; + } + + namespace PlayToSourceRequestedEventArgs + { + extern const static_class_data data; + } + + namespace PlayToSourceSelectedEventArgs + { + extern const static_class_data data; + } + + namespace PlaybackRateChangeRequestedEventArgs + { + extern const static_class_data data; + } + + namespace SourceChangeRequestedEventArgs + { + extern const static_class_data data; + } + + namespace VolumeChangeRequestedEventArgs + { + extern const static_class_data data; + } +} + +namespace rnwinrt::interfaces::Windows::Media::PlayTo +{ + namespace ICurrentTimeChangeRequestedEventArgs + { + extern const static_interface_data data; + } + + namespace IMuteChangeRequestedEventArgs + { + extern const static_interface_data data; + } + + namespace IPlayToConnection + { + extern const static_interface_data data; + } + + namespace IPlayToConnectionErrorEventArgs + { + extern const static_interface_data data; + } + + namespace IPlayToConnectionStateChangedEventArgs + { + extern const static_interface_data data; + } + + namespace IPlayToConnectionTransferredEventArgs + { + extern const static_interface_data data; + } + + namespace IPlayToManager + { + extern const static_interface_data data; + } + + namespace IPlayToReceiver + { + extern const static_interface_data data; + } + + namespace IPlayToSource + { + extern const static_interface_data data; + } + + namespace IPlayToSourceDeferral + { + extern const static_interface_data data; + } + + namespace IPlayToSourceRequest + { + extern const static_interface_data data; + } + + namespace IPlayToSourceRequestedEventArgs + { + extern const static_interface_data data; + } + + namespace IPlayToSourceSelectedEventArgs + { + extern const static_interface_data data; + } + + namespace IPlayToSourceWithPreferredSourceUri + { + extern const static_interface_data data; + } + + namespace IPlaybackRateChangeRequestedEventArgs + { + extern const static_interface_data data; + } + + namespace ISourceChangeRequestedEventArgs + { + extern const static_interface_data data; + } + + namespace IVolumeChangeRequestedEventArgs + { + extern const static_interface_data data; + } +} diff --git a/rnwinrt/Windows.Media.g.cpp b/rnwinrt/Windows.Media.g.cpp new file mode 100644 index 0000000..56da654 --- /dev/null +++ b/rnwinrt/Windows.Media.g.cpp @@ -0,0 +1,15 @@ +#include "pch.h" + +#include "base.h" + +#include "Windows.Media.g.h" +#include "Windows.Media.PlayTo.g.h" + +namespace rnwinrt::namespaces::Windows::Media +{ + static constexpr const static_projection_data* const children[] = { + &rnwinrt::namespaces::Windows::Media::PlayTo::data, + }; + + constexpr const static_namespace_data data{ "Media"sv, children }; +} diff --git a/rnwinrt/Windows.Media.g.h b/rnwinrt/Windows.Media.g.h new file mode 100644 index 0000000..60f068c --- /dev/null +++ b/rnwinrt/Windows.Media.g.h @@ -0,0 +1,8 @@ +#pragma once + +#include "base.h" + +namespace rnwinrt::namespaces::Windows::Media +{ + extern const static_namespace_data data; +} diff --git a/rnwinrt/Windows.g.cpp b/rnwinrt/Windows.g.cpp new file mode 100644 index 0000000..33a046d --- /dev/null +++ b/rnwinrt/Windows.g.cpp @@ -0,0 +1,15 @@ +#include "pch.h" + +#include "base.h" + +#include "Windows.g.h" +#include "Windows.Media.g.h" + +namespace rnwinrt::namespaces::Windows +{ + static constexpr const static_projection_data* const children[] = { + &rnwinrt::namespaces::Windows::Media::data, + }; + + constexpr const static_namespace_data data{ "Windows"sv, children }; +} diff --git a/rnwinrt/Windows.g.h b/rnwinrt/Windows.g.h new file mode 100644 index 0000000..541bed7 --- /dev/null +++ b/rnwinrt/Windows.g.h @@ -0,0 +1,8 @@ +#pragma once + +#include "base.h" + +namespace rnwinrt::namespaces::Windows +{ + extern const static_namespace_data data; +} diff --git a/rnwinrt/base.cpp b/rnwinrt/base.cpp new file mode 100644 index 0000000..d8bd2e2 --- /dev/null +++ b/rnwinrt/base.cpp @@ -0,0 +1,1405 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "base.h" + +#include + +#include +#include + +using namespace rnwinrt; +using namespace std::literals; + +namespace winrt +{ + using namespace Windows::Foundation; + using namespace Windows::Foundation::Numerics; +} + +jsi::String rnwinrt::make_string(jsi::Runtime& runtime, std::wstring_view str) +{ + if (str.empty()) + { + return jsi::String::createFromAscii(runtime, ""); + } + + auto bytes = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, str.data(), static_cast(str.size()), + nullptr /*lpMultiByteStr*/, 0 /*cbMultiByte*/, nullptr /*lpDefaultChar*/, nullptr /*lpUsedDefaultChar*/); + winrt::check_bool(bytes); + + auto buffer = std::make_unique(bytes); + winrt::check_bool(::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, str.data(), static_cast(str.size()), + buffer.get(), bytes, nullptr /*lpDefaultChar*/, nullptr /*lpUsedDefaultChar*/)); + + return jsi::String::createFromUtf8(runtime, reinterpret_cast(buffer.get()), bytes); +} + +std::u16string rnwinrt::string_to_utf16(jsi::Runtime& runtime, const jsi::String& string) +{ + auto str = string.utf8(runtime); + if (str.empty()) + { + return {}; + } + + auto len = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.c_str(), static_cast(str.size()), + nullptr /*lpWideCharStr*/, 0 /*cchWideChar*/); + winrt::check_bool(len); + + std::u16string result(len, 0); + winrt::check_bool(::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.c_str(), + static_cast(str.size()), reinterpret_cast(result.data()), len)); + + return result; +} + +[[noreturn]] __declspec(noinline) void rnwinrt::throw_no_constructor( + jsi::Runtime& runtime, std::string_view typeNamespace, std::string_view typeName, size_t argCount) +{ + auto msg = "TypeError: No constructor overload exists for "s; + msg.append(typeNamespace); + msg += "."; + msg.append(typeName); + msg = msg + " with " + std::to_string(argCount) + " args"; + throw jsi::JSError(runtime, std::move(msg)); +} + +[[noreturn]] __declspec(noinline) void rnwinrt::throw_no_function_overload(jsi::Runtime& runtime, + std::string_view typeNamespace, std::string_view typeName, std::string_view fnName, size_t argCount) +{ + auto msg = "TypeError: No function overload exists for "s; + msg.append(typeNamespace); + msg += "."; + msg.append(typeName); + msg += "."; + msg.append(fnName); + msg = msg + " with " + std::to_string(argCount) + " args"; + throw jsi::JSError(runtime, std::move(msg)); +} + +[[noreturn]] __declspec(noinline) void rnwinrt::throw_invalid_delegate_arg_count( + jsi::Runtime& runtime, std::string_view typeNamespace, std::string_view typeName) +{ + auto msg = "TypeError: Invalid number of arguments to delegate "s; + msg.append(typeNamespace); + msg += "."; + msg.append(typeName); + throw jsi::JSError(runtime, std::move(msg)); +} + +// NOTE: Most lists are sorted, so in theory this could be a binary search-turns to linear search. The only thing +// blocking this are enums, where their values are not currently sorted by name. Note however, that if this happens, +// there can still be duplicates in a list (e.g. function overloads), so this would have to act more like +// std::lower_bound (or std::equal_range) since callers expect the result to be the first. +template +static auto find_by_name(span list, std::string_view name) noexcept +{ + return std::find_if(list.begin(), list.end(), [&](const ThingWithName* ptr) { return ptr->name == name; }); +} + +template +static auto find_by_name(span list, std::string_view name) noexcept +{ + return std::find_if(list.begin(), list.end(), [&](const ThingWithName& thing) { return thing.name == name; }); +} + +jsi::Value object_instance_cache::get_instance(jsi::Runtime& runtime, const winrt::IInspectable& value) +{ + if ((std::chrono::steady_clock::now() - last_cleanup) >= cleanup_interval) + { + cleanup(runtime); + } + + // NOTE: Each interface has its own associated v-table, so two IInspectable pointers to the same object may actually + // be different if they were originally pointers to two different interfaces. Hence the QI here + auto instance = value.as(); + auto key = winrt::get_abi(instance); + if (auto itr = instances.find(key); itr != instances.end()) + { + // NOTE: It is possible for an interface to get deallocated and have its memory address reused for a new object, + // however because we hold strong references to WinRT objects and weak references to the JS objects we create, + // this would imply that the JS object also got GC'd and would fail to resolve below + if (supports_weak_object) + { + if (auto strongValue = std::get<0>(itr->second).lock(runtime); !strongValue.isUndefined()) + { + return strongValue; + } + } + else + { + if (auto hostObj = std::get<1>(itr->second).lock()) + { + return jsi::Value(runtime, jsi::Object::createFromHostObject(runtime, std::move(hostObj))); + } + } + + // Otherwise, the object has been GC'd. Remove it so that we can re-create the object below + instances.erase(itr); + } + + auto hostObj = std::make_shared(instance); + auto obj = jsi::Object::createFromHostObject(runtime, hostObj); + if (supports_weak_object) + { + try + { + instances.emplace(key, jsi::WeakObject(runtime, obj)); + } + catch (std::logic_error&) + { + supports_weak_object = false; + } + } + + if (!supports_weak_object) + { + instances.emplace(key, std::move(hostObj)); + } + + return jsi::Value(runtime, std::move(obj)); +} + +jsi::Value static_namespace_data::create(jsi::Runtime& runtime) const +{ + return jsi::Value(runtime, jsi::Object::createFromHostObject(runtime, std::make_shared(this))); +} + +jsi::Value projected_namespace::get(jsi::Runtime& runtime, const jsi::PropNameID& name) +{ + if (auto itr = find_by_name(m_data->children, name.utf8(runtime)); itr != m_data->children.end()) + { + auto& item = m_children[itr - m_data->children.begin()]; + if (item.isUndefined()) + { + item = (*itr)->create(runtime); + } + + return jsi::Value(runtime, item); + } + + return jsi::Value::undefined(); +} + +void projected_namespace::set(jsi::Runtime& runtime, const jsi::PropNameID& name, const jsi::Value&) +{ + throw jsi::JSError( + runtime, "TypeError: Cannot assign to property '" + name.utf8(runtime) + "' of a projected WinRT namespace"); +} + +std::vector projected_namespace::getPropertyNames(jsi::Runtime& runtime) +{ + std::vector result; + result.reserve(m_children.size()); + for (auto ptr : m_data->children) + { + result.push_back(make_propid(runtime, ptr->name)); + } + + return result; +} + +jsi::Value static_enum_data::create(jsi::Runtime& runtime) const +{ + return jsi::Value(runtime, jsi::Object::createFromHostObject(runtime, std::make_shared(this))); +} + +jsi::Value static_enum_data::get_value(jsi::Runtime& runtime, std::string_view valueName) const +{ + // TODO: It would also be rather simple to ensure that the array is sorted and do a binary search, however the + // number of enum elements is typically pretty small, so that may actually be harmful + auto itr = std::find_if(values.begin(), values.end(), [&](auto& mapping) { return mapping.name == valueName; }); + if (itr != values.end()) + { + return jsi::Value(itr->value); + } + + // Look for a matching value (reverse mapping) + itr = std::find_if(values.begin(), values.end(), [&](auto& mapping) { return mapping.value_as_string == valueName; }); + if (itr != values.end()) + { + return jsi::Value(runtime, make_string(runtime, itr->name)); + } + + return jsi::Value::undefined(); +} + +jsi::Value projected_enum::get(jsi::Runtime& runtime, const jsi::PropNameID& name) +{ + return m_data->get_value(runtime, name.utf8(runtime)); +} + +void projected_enum::set(jsi::Runtime& runtime, const jsi::PropNameID& name, const jsi::Value&) +{ + throw jsi::JSError( + runtime, "TypeError: Cannot assign to property '" + name.utf8(runtime) + "' of a projected WinRT enum"); +} + +std::vector projected_enum::getPropertyNames(jsi::Runtime& runtime) +{ + std::vector result; + result.reserve(m_data->values.size()); + for (auto& mapping : m_data->values) + { + result.push_back(make_propid(runtime, mapping.name)); + // Reverse mapping (to match TypeScript enums) + result.push_back(make_propid(runtime, mapping.value_as_string)); + } + + return result; +} + +jsi::Value static_class_data::create(jsi::Runtime& runtime) const +{ + return jsi::Value( + runtime, jsi::Object::createFromHostObject(runtime, std::make_shared(this))); +} + +static jsi::Value static_add_event_listener(jsi::Runtime& runtime, const jsi::Value* args, size_t count, + const static_class_data* data, event_registration_array& registrations) +{ + if (count < 2) + { + throw jsi::JSError(runtime, "TypeError: addEventListener expects (at least) 2 arguments"); + } + + auto name = args[0].asString(runtime).utf8(runtime); + if (auto itr = find_by_name(data->events, name); itr != data->events.end()) + { + auto token = itr->add(runtime, args[1]); + registrations.add(args[1].asObject(runtime), itr->name.data(), token); + } + + return jsi::Value::undefined(); +} + +static jsi::Value static_remove_event_listener(jsi::Runtime& runtime, const jsi::Value* args, size_t count, + const static_class_data* data, event_registration_array& registrations) +{ + if (count < 2) + { + throw jsi::JSError(runtime, "TypeError: removeEventListener expects (at least) 2 arguments"); + } + + auto name = args[0].asString(runtime).utf8(runtime); + if (auto itr = find_by_name(data->events, name); itr != data->events.end()) + { + auto token = registrations.remove(runtime, args[1].asObject(runtime), itr->name.data()); + itr->remove(token); + } + + return jsi::Value::undefined(); +} + +jsi::Value projected_statics_class::get(jsi::Runtime& runtime, const jsi::PropNameID& id) +{ + auto name = id.utf8(runtime); + + if (auto itr = find_by_name(m_data->properties, name); itr != m_data->properties.end()) + { + return itr->getter(runtime); + } + + auto itr = m_functions.find(name); + if (itr == m_functions.end()) + { + auto dataItr = find_by_name(m_data->functions, name); + if (dataItr != m_data->functions.end()) + { + auto fn = jsi::Function::createFromHostFunction(runtime, id, 0, dataItr->function); + itr = m_functions.emplace(dataItr->name, jsi::Value(runtime, std::move(fn))).first; + } + else if (!m_data->events.empty()) + { + if (name == add_event_name) + { + auto fn = bind_host_function(runtime, id, 2, &projected_statics_class::add_event_listener); + itr = m_functions.emplace(add_event_name, jsi::Value(runtime, std::move(fn))).first; + } + else if (name == remove_event_name) + { + auto fn = bind_host_function(runtime, id, 2, &projected_statics_class::remove_event_listener); + itr = m_functions.emplace(remove_event_name, jsi::Value(runtime, std::move(fn))).first; + } + } + } + + if (itr == m_functions.end()) + { + return jsi::Value::undefined(); + } + + return jsi::Value(runtime, itr->second); +} + +void projected_statics_class::set(jsi::Runtime& runtime, const jsi::PropNameID& id, const jsi::Value& value) +{ + auto name = id.utf8(runtime); + if (auto itr = find_by_name(m_data->properties, name); itr != m_data->properties.end()) + { + // Unlike getters, setters can be null + if (itr->setter) + { + (*itr->setter)(runtime, value); + } + } + + // If no property exists with the given name, then ignore the call rather than throwing. This is more-or-less + // consistent with EdgeHTML WebView. +} + +std::vector projected_statics_class::getPropertyNames(jsi::Runtime& runtime) +{ + std::vector result; + result.reserve(m_data->properties.size() + m_data->functions.size() + (m_data->events.empty() ? 0 : 2)); + + for (auto&& data : m_data->properties) + { + result.push_back(make_propid(runtime, data.name)); + } + + for (auto&& data : m_data->functions) + { + result.push_back(make_propid(runtime, data.name)); + } + + if (!m_data->events.empty()) + { + result.push_back(make_propid(runtime, add_event_name)); + result.push_back(make_propid(runtime, remove_event_name)); + } + + return result; +} + +jsi::Value projected_statics_class::add_event_listener(jsi::Runtime& runtime, const jsi::Value* args, size_t count) +{ + return static_add_event_listener(runtime, args, count, m_data, m_events); +} + +jsi::Value projected_statics_class::remove_event_listener(jsi::Runtime& runtime, const jsi::Value* args, size_t count) +{ + return static_remove_event_listener(runtime, args, count, m_data, m_events); +} + +jsi::Value static_activatable_class_data::create(jsi::Runtime& runtime) const +{ + auto code = "(function() { return "s; + code.append(full_namespace); + code.append("."); + code.append(name); + code.append(".ctor.apply(this, arguments); })"); + auto result = runtime.evaluateJavaScript(std::make_shared(std::move(code)), "Activatable Class") + .asObject(runtime); + + // TODO: param count? Seems to not matter? It would be rather simple to calculate when generating the constructor + // function, but would also be more data... + result.setProperty( + runtime, "ctor", jsi::Function::createFromHostFunction(runtime, make_propid(runtime, name), 0, constructor)); + + // JSI does not allow us to create a 'Function' that is also a 'HostObject' and therefore cannot provide virtual + // get/set functions and instead must attach them to the function object + auto defineProperty = + runtime.global().getPropertyAsFunction(runtime, "Object").getPropertyAsFunction(runtime, "defineProperty"); + for (auto&& prop : properties) + { + jsi::Object propDesc(runtime); + propDesc.setProperty(runtime, "get", + jsi::Function::createFromHostFunction(runtime, make_propid(runtime, "get"), 0, + [getter = prop.getter](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value*, size_t count) { + if (count != 0) + { + throw jsi::JSError(runtime, "TypeError: Property getter expects 0 arguments"); + } + + return getter(runtime); + })); + if (prop.setter) + { + propDesc.setProperty(runtime, "set", + jsi::Function::createFromHostFunction(runtime, make_propid(runtime, "set"), 1, + [setter = prop.setter]( + jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, size_t count) { + if (count != 1) + { + throw jsi::JSError(runtime, "TypeError: Property setter expects 1 argument"); + } + + setter(runtime, args[0]); + return jsi::Value::undefined(); + })); + } + + defineProperty.call(runtime, result, make_string(runtime, prop.name), std::move(propDesc)); + } + + for (auto&& fn : functions) + { + auto propId = make_propid(runtime, fn.name); + result.setProperty(runtime, propId, jsi::Function::createFromHostFunction(runtime, propId, 0, fn.function)); + } + + if (!events.empty()) + { + // NOTE: We need shared state, but there's not really a good way to store that state on the function object that + // we return... Use a unique_ptr here and give ownership to a single lambda. Neither should outlive the object + // we return, so this should be okay + auto evtArray = std::make_unique(); + + auto propId = make_propid(runtime, add_event_name); + result.setProperty(runtime, propId, + jsi::Function::createFromHostFunction(runtime, propId, 0, + [this, evtArray = evtArray.get()](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, + size_t count) { return static_add_event_listener(runtime, args, count, this, *evtArray); })); + + propId = make_propid(runtime, remove_event_name); + result.setProperty(runtime, propId, + jsi::Function::createFromHostFunction(runtime, propId, 0, + move_only_lambda([this, evtArray = std::move(evtArray)]( + jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, size_t count) { + return static_remove_event_listener(runtime, args, count, this, *evtArray); + }))); + } + + return result; +} + +#ifndef _WIN32 +static_assert(false, "Compilation requires a little-endian target"); +#endif + +// NOTE: Must be kept in-sync with the copy in MetadataTypes.h +static int compare_guid(const winrt::guid& lhs, const winrt::guid& rhs) +{ + // NOTE: This method of comparison needs to remain consistant with how we sort the static array + auto lhsPtr = reinterpret_cast(&lhs); + auto rhsPtr = reinterpret_cast(&rhs); + if (auto diff = lhsPtr[0] - rhsPtr[0]) + { + return (diff < 0) ? -1 : 1; + } + + auto diff = lhsPtr[1] - rhsPtr[1]; + return (diff == 0) ? 0 : (diff < 0) ? -1 : 1; +} + +static const static_interface_data* find_interface(const winrt::guid& guid) +{ + auto begin = global_interface_map.begin(); + auto end = global_interface_map.end(); + static constexpr std::ptrdiff_t linear_search_size = 16; // TODO: Find a good value + + while ((end - begin) > linear_search_size) + { + auto mid = begin + (end - begin) / 2; + auto cmp = compare_guid(guid, mid->first); + if (cmp < 0) + { + end = mid; + } + else if (cmp > 0) + { + begin = mid + 1; + } + else + { + return mid->second; + } + } + + for (; begin != end; ++begin) + { + if (begin->first == guid) + { + return begin->second; + } + } + + return nullptr; +} + +projected_object_instance::projected_object_instance(const winrt::IInspectable& instance) : m_instance(instance) +{ + auto iids = winrt::get_interfaces(m_instance); + for (auto&& iid : iids) + { + if (auto iface = find_interface(iid)) + { + m_interfaces.push_back(iface); + } + } +} + +namespace rnwinrt +{ + struct projected_function + { + jsi::Value operator()( + jsi::Runtime& runtime, const jsi::Value& thisVal, const jsi::Value* args, size_t count) const + { + if (count != data->arity) + { + throw jsi::JSError(runtime, "TypeError: Non-overloaded function " + std::string(data->name) + + " expects " + std::to_string(data->arity) + " arguments, but " + + std::to_string(count) + " provided"); + } + + auto obj = thisVal.asObject(runtime).asHostObject(runtime); + return data->function(runtime, obj->m_instance, args); + } + + const static_interface_data::function_mapping* data; + }; + + struct projected_overloaded_function + { + jsi::Value operator()( + jsi::Runtime& runtime, const jsi::Value& thisVal, const jsi::Value* args, size_t count) const + { + for (auto func : data) + { + if (func->arity == count) + { + auto obj = thisVal.asObject(runtime).asHostObject(runtime); + return func->function(runtime, obj->m_instance, args); + } + } + + throw jsi::JSError(runtime, "TypeError: Overloaded function " + std::string(data[0]->name) + + " does not have an overload that expects " + std::to_string(count) + + " arguments"); + } + + // TODO: Figure out a good SSO size (4 might be larger than we need most of the time. Perhaps 2?) + sso_vector data; + }; +} + +jsi::Value projected_object_instance::get(jsi::Runtime& runtime, const jsi::PropNameID& id) +{ + auto name = id.utf8(runtime); + if (auto itr = m_functions.find(name); itr != m_functions.end()) + { + return jsi::Value(runtime, itr->second); + } + + sso_vector functions; + bool hasEvents = false; + for (auto iface : m_interfaces) + { + if (auto itr = find_by_name(iface->properties, name); (itr != iface->properties.end()) && itr->getter) + { + return itr->getter(runtime, m_instance); + } + + if (auto dataItr = find_by_name(iface->functions, name); dataItr != iface->functions.end()) + { + functions.push_back(&*dataItr); + + // NOTE: Functions are sorted, so this should be the first of N consecutive functions with the same name + for (++dataItr; (dataItr != iface->functions.end()) && (dataItr->name == name); ++dataItr) + { + functions.push_back(&*dataItr); + } + } + + hasEvents = hasEvents || !iface->events.empty(); + } + + if (functions.size() > 1) + { + // Make sure there are no conflicts in arity. While this isn't technically 100% necessary, we still at least + // need to validate that default overloads are at the front of the list + // NOTE: The number of overloads should be pretty minimal, so doing this in a "selection sort-like" way should + // be fine + for (size_t i = 0; i < functions.size(); ++i) + { + // NOTE: Even if the target is the default overload, we still want to remove other ones with the same arity + auto tgt = functions[i]; + for (size_t j = i + 1; j < functions.size();) + { + auto test = functions[j]; + if (tgt->arity == test->arity) + { + if (!tgt->is_default_overload && test->is_default_overload) + { + // Use the other one + tgt = test; + functions[i] = test; + } + + functions[j] = functions.back(); + functions.pop_back(); + continue; + } + + ++j; + } + } + } + + if (functions.size() == 1) + { + // Non-overloaded function, or at least not overloaded with different arities + auto fn = + jsi::Function::createFromHostFunction(runtime, id, functions[0]->arity, projected_function{ functions[0] }); + return jsi::Value(runtime, m_functions.emplace(functions[0]->name, std::move(fn)).first->second); + } + else if (!functions.empty()) + { + // TODO: Calculate max arity? Does it matter? + auto functionName = functions[0]->name; + auto fn = jsi::Function::createFromHostFunction( + runtime, id, 0, projected_overloaded_function{ std::move(functions) }); + return jsi::Value(runtime, m_functions.emplace(functionName, std::move(fn)).first->second); + } + + if (hasEvents) + { + if (name == add_event_name) + { + auto fn = bind_host_function(runtime, id, 2, &projected_object_instance::add_event_listener); + return jsi::Value(runtime, m_functions.emplace(add_event_name, std::move(fn)).first->second); + } + else if (name == remove_event_name) + { + auto fn = bind_host_function(runtime, id, 2, &projected_object_instance::remove_event_listener); + return jsi::Value(runtime, m_functions.emplace(remove_event_name, std::move(fn)).first->second); + } + } + + // If we've made it this far, check to see if any interface wants to handle the call (e.g. operator[] etc.) + jsi::Value fallbackValue; + for (auto iface : m_interfaces) + { + if (!iface->runtime_get_property) + continue; + + auto [result, fallback] = iface->runtime_get_property(runtime, m_instance, name); + if (result) + return std::move(*result); + else if (fallback) + fallbackValue = std::move(*fallback); + } + + return fallbackValue; +} + +void projected_object_instance::set(jsi::Runtime& runtime, const jsi::PropNameID& id, const jsi::Value& value) +{ + auto name = id.utf8(runtime); + for (auto iface : m_interfaces) + { + if (auto itr = find_by_name(iface->properties, name); (itr != iface->properties.end()) && itr->setter) + { + itr->setter(runtime, m_instance, value); + return; + } + } + + // If we've made it this far, check to see if any interface wants to handle the call (e.g. operator[] etc.) + for (auto iface : m_interfaces) + { + if (!iface->runtime_set_property) + continue; + + if (iface->runtime_set_property(runtime, m_instance, name, value)) + return; + } + + // If no property exists with the given name, then ignore the call rather than throwing. This is more-or-less + // consistent with EdgeHTML WebView. +} + +std::vector projected_object_instance::getPropertyNames(jsi::Runtime& runtime) +{ + // TODO: Since functions can be overloaded - and we don't collate them on interfaces like we do with classes - we + // may end up with duplicates. Is that okay? + std::vector result; + bool hasEvents = false; + for (auto iface : m_interfaces) + { + for (auto&& prop : iface->properties) + { + result.push_back(make_propid(runtime, prop.name)); + } + + for (auto&& func : iface->functions) + { + result.push_back(make_propid(runtime, func.name)); + } + + hasEvents = hasEvents || !iface->events.empty(); + } + + if (hasEvents) + { + result.push_back(make_propid(runtime, add_event_name)); + result.push_back(make_propid(runtime, remove_event_name)); + } + + return result; +} + +jsi::Value projected_object_instance::add_event_listener(jsi::Runtime& runtime, const jsi::Value* args, size_t count) +{ + if (count < 2) + { + throw jsi::JSError(runtime, "TypeError: addEventListener expects (at least) 2 arguments"); + } + + auto name = args[0].asString(runtime).utf8(runtime); + for (auto iface : m_interfaces) + { + if (auto itr = find_by_name(iface->events, name); itr != iface->events.end()) + { + auto token = itr->add(runtime, m_instance, args[1]); + current_runtime_context()->event_cache.add(m_instance, args[1].asObject(runtime), itr->name.data(), token); + break; + } + } + + return jsi::Value::undefined(); +} + +jsi::Value projected_object_instance::remove_event_listener(jsi::Runtime& runtime, const jsi::Value* args, size_t count) +{ + if (count < 2) + { + throw jsi::JSError(runtime, "TypeError: removeEventListener expects (at least) 2 arguments"); + } + + auto name = args[0].asString(runtime).utf8(runtime); + for (auto iface : m_interfaces) + { + if (auto itr = find_by_name(iface->events, name); itr != iface->events.end()) + { + // TODO: Should we just no-op if the token can't be found? + auto token = current_runtime_context()->event_cache.remove( + runtime, m_instance, args[1].asObject(runtime), itr->name.data()); + itr->remove(m_instance, token); + break; + } + } + + return jsi::Value::undefined(); +} + +bool projected_value_traits::as_native(jsi::Runtime&, const jsi::Value& value) noexcept +{ + if (value.isBool()) + { + return value.getBool(); + } + else if (value.isNumber()) + { + return value.getNumber() != 0; + } + + return !value.isNull() && !value.isUndefined(); +} + +jsi::Value projected_value_traits::as_value(jsi::Runtime& runtime, char16_t value) +{ + char buffer[8]; + auto bytes = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, reinterpret_cast(&value), 1, buffer, + static_cast(std::size(buffer)), nullptr, nullptr); + winrt::check_bool(bytes); + return jsi::String::createFromUtf8(runtime, reinterpret_cast(buffer), bytes); +} + +char16_t projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + auto str = convert_value_to_native(runtime, value); + return str.empty() ? 0 : str[0]; +} + +winrt::hstring projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + auto str = value.asString(runtime).utf8(runtime); + if (str.empty()) + { + return {}; + } + + // MultiByteToWideChar is requesting the size in wide characters required for 'stringUtf8' without null + // termination as WindowsPreallocateStringBuffer will actually allocated 'outputLength + 1' characters and asign + // the last as the null terminator automatically. + + auto len = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.c_str(), static_cast(str.size()), + nullptr /*lpWideCharStr*/, 0 /*cchWideChar*/); + winrt::check_bool(len); + + PWSTR stringBuffer; + HSTRING_BUFFER buffer; + winrt::check_hresult(::WindowsPreallocateStringBuffer(static_cast(len), &stringBuffer, &buffer)); + + // NOTE: WindowsPreallocateStringBuffer will only give back null if the string is empty, however we've already + // covered that case + _Analysis_assume_(buffer != nullptr); + + winrt::hstring result; + try + { + winrt::check_bool(::MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, str.c_str(), static_cast(str.size()), stringBuffer, len)); + winrt::check_hresult(::WindowsPromoteStringBuffer(buffer, reinterpret_cast(winrt::put_abi(result)))); + } + catch (...) + { + ::WindowsDeleteStringBuffer(buffer); + throw; + } + + return result; +} + +// Lengths are not null-terminated. +static constexpr uint32_t uuid_length = 8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12; +static constexpr uint32_t guid_length = 2 + uuid_length; + +jsi::Value projected_value_traits::as_value(jsi::Runtime& runtime, const winrt::guid& value) +{ + // NOTE: 'StringFromGUID2' formats in '{...}' form, but we don't want the curly braces + wchar_t wideBuffer[guid_length + 1]; + winrt::check_hresult(::StringFromGUID2(reinterpret_cast(value), wideBuffer, ARRAYSIZE(wideBuffer))); + + // GUIDS are always ANSI, so there's no need to call 'WideCharToMultiByte' or anything + char buffer[uuid_length]; + std::transform( + wideBuffer + 1, wideBuffer + 1 + uuid_length, buffer, [](wchar_t ch) { return static_cast(ch); }); + + return jsi::Value(runtime, jsi::String::createFromAscii(runtime, buffer, uuid_length)); +} + +winrt::guid projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + auto str = value.asString(runtime).utf8(runtime); + auto strBuffer = str.data(); + if (str.size() == guid_length) + { + ++strBuffer; // Move past the '{' + strBuffer[uuid_length] = 0; // UuidFromString expects null termination + } + else if (str.size() != uuid_length) + { + throw jsi::JSError(runtime, "TypeError: Invalid GUID length"); + } + + winrt::guid result; + if (::UuidFromStringA(reinterpret_cast(strBuffer), reinterpret_cast(winrt::put_abi(result))) != + ERROR_SUCCESS) + { + throw jsi::JSError(runtime, "TypeError: GUID contains unexpected characters"); + } + + return result; +} + +// NOTE: 'DateTime' is a 'FILETIME' value which represents the number of 100 ns "units" since 01/01/1601, whereas the +// Javascript 'Date' class represents the number of milliseconds since 01/01/1970. The delta between the two is +// 11644473600 seconds +static constexpr auto windows_to_unix_epoch_delta = 11644473600s; + +jsi::Value projected_value_traits::as_value(jsi::Runtime& runtime, winrt::DateTime value) +{ + auto unixTime = + std::chrono::duration_cast(value.time_since_epoch() - windows_to_unix_epoch_delta); + return runtime.global() + .getPropertyAsFunction(runtime, "Date") + .callAsConstructor(runtime, static_cast(unixTime.count())); +} + +winrt::DateTime projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + double number; + if (value.isNumber()) + { + number = value.getNumber(); + } + else + { + auto object = value.asObject(runtime); + number = object.getPropertyAsFunction(runtime, "valueOf").callWithThis(runtime, object).asNumber(); + } + + std::chrono::milliseconds unixTime(static_cast(number)); + return winrt::DateTime(unixTime + windows_to_unix_epoch_delta); +} + +jsi::Value projected_value_traits::as_value(jsi::Runtime& runtime, winrt::TimeSpan value) +{ + auto ms = std::chrono::duration_cast(value); + return convert_native_to_value(runtime, ms.count()); +} + +winrt::TimeSpan projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + std::chrono::milliseconds ms(convert_value_to_native(runtime, value)); + return std::chrono::duration_cast(ms); +} + +jsi::Value projected_value_traits::as_value(jsi::Runtime& runtime, winrt::float3x2 value) +{ + jsi::Object result(runtime); + result.setProperty(runtime, "m11", convert_native_to_value(runtime, value.m11)); + result.setProperty(runtime, "m12", convert_native_to_value(runtime, value.m12)); + result.setProperty(runtime, "m21", convert_native_to_value(runtime, value.m21)); + result.setProperty(runtime, "m22", convert_native_to_value(runtime, value.m22)); + result.setProperty(runtime, "m31", convert_native_to_value(runtime, value.m31)); + result.setProperty(runtime, "m32", convert_native_to_value(runtime, value.m32)); + return result; +} + +winrt::float3x2 projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + winrt::float3x2 result{}; + auto obj = value.asObject(runtime); + if (auto field = obj.getProperty(runtime, "m11"); !field.isUndefined()) + result.m11 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m12"); !field.isUndefined()) + result.m12 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m21"); !field.isUndefined()) + result.m21 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m22"); !field.isUndefined()) + result.m22 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m31"); !field.isUndefined()) + result.m31 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m32"); !field.isUndefined()) + result.m32 = convert_value_to_native(runtime, field); + return result; +} + +jsi::Value projected_value_traits::as_value(jsi::Runtime& runtime, winrt::float4x4 value) +{ + jsi::Object result(runtime); + result.setProperty(runtime, "m11", convert_native_to_value(runtime, value.m11)); + result.setProperty(runtime, "m12", convert_native_to_value(runtime, value.m12)); + result.setProperty(runtime, "m13", convert_native_to_value(runtime, value.m13)); + result.setProperty(runtime, "m14", convert_native_to_value(runtime, value.m14)); + result.setProperty(runtime, "m21", convert_native_to_value(runtime, value.m21)); + result.setProperty(runtime, "m22", convert_native_to_value(runtime, value.m22)); + result.setProperty(runtime, "m23", convert_native_to_value(runtime, value.m23)); + result.setProperty(runtime, "m24", convert_native_to_value(runtime, value.m24)); + result.setProperty(runtime, "m31", convert_native_to_value(runtime, value.m31)); + result.setProperty(runtime, "m32", convert_native_to_value(runtime, value.m32)); + result.setProperty(runtime, "m33", convert_native_to_value(runtime, value.m33)); + result.setProperty(runtime, "m34", convert_native_to_value(runtime, value.m34)); + result.setProperty(runtime, "m41", convert_native_to_value(runtime, value.m41)); + result.setProperty(runtime, "m42", convert_native_to_value(runtime, value.m42)); + result.setProperty(runtime, "m43", convert_native_to_value(runtime, value.m43)); + result.setProperty(runtime, "m44", convert_native_to_value(runtime, value.m44)); + return result; +} + +winrt::float4x4 projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + winrt::float4x4 result{}; + auto obj = value.asObject(runtime); + if (auto field = obj.getProperty(runtime, "m11"); !field.isUndefined()) + result.m11 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m12"); !field.isUndefined()) + result.m12 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m13"); !field.isUndefined()) + result.m13 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m14"); !field.isUndefined()) + result.m14 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m21"); !field.isUndefined()) + result.m21 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m22"); !field.isUndefined()) + result.m22 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m23"); !field.isUndefined()) + result.m23 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m24"); !field.isUndefined()) + result.m24 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m31"); !field.isUndefined()) + result.m31 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m32"); !field.isUndefined()) + result.m32 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m33"); !field.isUndefined()) + result.m33 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m34"); !field.isUndefined()) + result.m34 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m41"); !field.isUndefined()) + result.m41 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m42"); !field.isUndefined()) + result.m42 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m43"); !field.isUndefined()) + result.m43 = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "m44"); !field.isUndefined()) + result.m44 = convert_value_to_native(runtime, field); + return result; +} + +jsi::Value projected_value_traits::as_value(jsi::Runtime& runtime, winrt::plane value) +{ + jsi::Object result(runtime); + result.setProperty(runtime, "normal", convert_native_to_value(runtime, value.normal)); + result.setProperty(runtime, "d", convert_native_to_value(runtime, value.d)); + return result; +} + +winrt::plane projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + winrt::plane result{}; + auto obj = value.asObject(runtime); + if (auto field = obj.getProperty(runtime, "normal"); !field.isUndefined()) + result.normal = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "d"); !field.isUndefined()) + result.d = convert_value_to_native(runtime, field); + return result; +} + +jsi::Value projected_value_traits::as_value(jsi::Runtime& runtime, winrt::quaternion value) +{ + jsi::Object result(runtime); + result.setProperty(runtime, "x", convert_native_to_value(runtime, value.x)); + result.setProperty(runtime, "y", convert_native_to_value(runtime, value.y)); + result.setProperty(runtime, "z", convert_native_to_value(runtime, value.z)); + result.setProperty(runtime, "w", convert_native_to_value(runtime, value.w)); + return result; +} + +winrt::quaternion projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + winrt::quaternion result{}; + auto obj = value.asObject(runtime); + if (auto field = obj.getProperty(runtime, "x"); !field.isUndefined()) + result.x = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "y"); !field.isUndefined()) + result.y = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "z"); !field.isUndefined()) + result.z = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "w"); !field.isUndefined()) + result.w = convert_value_to_native(runtime, field); + return result; +} + +jsi::Value projected_value_traits::as_value(jsi::Runtime& runtime, winrt::float2 value) +{ + jsi::Object result(runtime); + result.setProperty(runtime, "x", convert_native_to_value(runtime, value.x)); + result.setProperty(runtime, "y", convert_native_to_value(runtime, value.y)); + return result; +} + +winrt::float2 projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + winrt::float2 result{}; + auto obj = value.asObject(runtime); + if (auto field = obj.getProperty(runtime, "x"); !field.isUndefined()) + result.x = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "y"); !field.isUndefined()) + result.y = convert_value_to_native(runtime, field); + return result; +} + +jsi::Value projected_value_traits::as_value(jsi::Runtime& runtime, winrt::float3 value) +{ + jsi::Object result(runtime); + result.setProperty(runtime, "x", convert_native_to_value(runtime, value.x)); + result.setProperty(runtime, "y", convert_native_to_value(runtime, value.y)); + result.setProperty(runtime, "z", convert_native_to_value(runtime, value.z)); + return result; +} + +winrt::float3 projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + winrt::float3 result{}; + auto obj = value.asObject(runtime); + if (auto field = obj.getProperty(runtime, "x"); !field.isUndefined()) + result.x = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "y"); !field.isUndefined()) + result.y = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "z"); !field.isUndefined()) + result.z = convert_value_to_native(runtime, field); + return result; +} + +jsi::Value projected_value_traits::as_value(jsi::Runtime& runtime, winrt::float4 value) +{ + jsi::Object result(runtime); + result.setProperty(runtime, "x", convert_native_to_value(runtime, value.x)); + result.setProperty(runtime, "y", convert_native_to_value(runtime, value.y)); + result.setProperty(runtime, "z", convert_native_to_value(runtime, value.z)); + result.setProperty(runtime, "w", convert_native_to_value(runtime, value.w)); + return result; +} + +winrt::float4 projected_value_traits::as_native(jsi::Runtime& runtime, const jsi::Value& value) +{ + winrt::float4 result{}; + auto obj = value.asObject(runtime); + if (auto field = obj.getProperty(runtime, "x"); !field.isUndefined()) + result.x = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "y"); !field.isUndefined()) + result.y = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "z"); !field.isUndefined()) + result.z = convert_value_to_native(runtime, field); + if (auto field = obj.getProperty(runtime, "w"); !field.isUndefined()) + result.w = convert_value_to_native(runtime, field); + return result; +} + +jsi::Value rnwinrt::convert_from_property_value(jsi::Runtime& runtime, const winrt::IPropertyValue& value) +{ + switch (value.Type()) + { + case winrt::PropertyType::Empty: + return jsi::Value::undefined(); + case winrt::PropertyType::UInt8: + return convert_native_to_value(runtime, value.GetUInt8()); + case winrt::PropertyType::Int16: + return convert_native_to_value(runtime, value.GetInt16()); + case winrt::PropertyType::UInt16: + return convert_native_to_value(runtime, value.GetUInt16()); + case winrt::PropertyType::Int32: + return convert_native_to_value(runtime, value.GetInt32()); + case winrt::PropertyType::UInt32: + return convert_native_to_value(runtime, value.GetUInt32()); + case winrt::PropertyType::Int64: + return convert_native_to_value(runtime, value.GetInt64()); + case winrt::PropertyType::UInt64: + return convert_native_to_value(runtime, value.GetUInt64()); + case winrt::PropertyType::Single: + return convert_native_to_value(runtime, value.GetSingle()); + case winrt::PropertyType::Double: + return convert_native_to_value(runtime, value.GetDouble()); + case winrt::PropertyType::Char16: + return convert_native_to_value(runtime, value.GetChar16()); + case winrt::PropertyType::Boolean: + return convert_native_to_value(runtime, value.GetBoolean()); + case winrt::PropertyType::String: + return convert_native_to_value(runtime, value.GetString()); + case winrt::PropertyType::Inspectable: + return jsi::Value::undefined(); // IInspectable is just the object itself + case winrt::PropertyType::DateTime: + return convert_native_to_value(runtime, value.GetDateTime()); + case winrt::PropertyType::TimeSpan: + return convert_native_to_value(runtime, value.GetTimeSpan()); + case winrt::PropertyType::Guid: + return convert_native_to_value(runtime, value.GetGuid()); + case winrt::PropertyType::Point: + return convert_native_to_value(runtime, value.GetPoint()); + case winrt::PropertyType::Size: + return convert_native_to_value(runtime, value.GetSize()); + case winrt::PropertyType::Rect: + return convert_native_to_value(runtime, value.GetRect()); + case winrt::PropertyType::OtherType: + return jsi::Value::undefined(); + case winrt::PropertyType::UInt8Array: { + winrt::com_array result; + value.GetUInt8Array(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::Int16Array: { + winrt::com_array result; + value.GetInt16Array(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::UInt16Array: { + winrt::com_array result; + value.GetUInt16Array(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::Int32Array: { + winrt::com_array result; + value.GetInt32Array(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::UInt32Array: { + winrt::com_array result; + value.GetUInt32Array(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::Int64Array: { + winrt::com_array result; + value.GetInt64Array(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::UInt64Array: { + winrt::com_array result; + value.GetUInt64Array(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::SingleArray: { + winrt::com_array result; + value.GetSingleArray(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::DoubleArray: { + winrt::com_array result; + value.GetDoubleArray(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::Char16Array: { + winrt::com_array result; + value.GetChar16Array(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::BooleanArray: { + winrt::com_array result; + value.GetBooleanArray(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::StringArray: { + winrt::com_array result; + value.GetStringArray(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::InspectableArray: { + winrt::com_array result; + value.GetInspectableArray(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::DateTimeArray: { + winrt::com_array result; + value.GetDateTimeArray(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::TimeSpanArray: { + winrt::com_array result; + value.GetTimeSpanArray(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::GuidArray: { + winrt::com_array result; + value.GetGuidArray(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::PointArray: { + winrt::com_array result; + value.GetPointArray(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::SizeArray: { + winrt::com_array result; + value.GetSizeArray(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::RectArray: { + winrt::com_array result; + value.GetRectArray(result); + return convert_native_to_value(runtime, std::move(result)); + } + case winrt::PropertyType::OtherTypeArray: + return jsi::Value::undefined(); + default: + winrt::terminate(); // TODO: Just return undefined? + } +} + +winrt::IInspectable rnwinrt::convert_to_property_value(jsi::Runtime& runtime, const jsi::Value& value) +{ + if (value.isBool()) + { + return winrt::PropertyValue::CreateBoolean(value.getBool()); + } + else if (value.isNumber()) + { + // NOTE: Due to inherent ambiguities between the loosely typed JS 'number' type and strongly typed WinRT types, + // we can't do much better than to take a guess here and preserve the value as a double-precision floating point + // value. It's probably best for consumers to call the 'PropertyValue' static methods directly as needed + return winrt::PropertyValue::CreateDouble(value.getNumber()); + } + else if (value.isString()) + { + return winrt::PropertyValue::CreateString(convert_value_to_native(runtime, value)); + } + else if (value.isObject()) + { + auto obj = value.getObject(runtime); + if (obj.isArray(runtime)) + { + auto array = obj.getArray(runtime); + + // Can't determine type from empty arrays + if (array.size(runtime) > 0) + { + auto elem = array.getValueAtIndex(runtime, 0); + if (elem.isBool()) + { + return winrt::PropertyValue::CreateBooleanArray( + convert_value_to_native>(runtime, value)); + } + else if (elem.isNumber()) + { + return winrt::PropertyValue::CreateDoubleArray( + convert_value_to_native>(runtime, value)); + } + else if (elem.isString()) + { + return winrt::PropertyValue::CreateStringArray( + convert_value_to_native>(runtime, value)); + } + else if (elem.isObject()) + { + auto elemObj = elem.getObject(runtime); + if (elemObj.isHostObject(runtime)) + { + return winrt::PropertyValue::CreateInspectableArray( + convert_value_to_native>(runtime, value)); + } + + auto isPointLike = elemObj.hasProperty(runtime, "x") && elemObj.hasProperty(runtime, "y"); + auto isSizeLike = elemObj.hasProperty(runtime, "width") && elemObj.hasProperty(runtime, "height"); + if (isPointLike && isSizeLike) + { + return winrt::PropertyValue::CreateRectArray( + convert_value_to_native>(runtime, value)); + } + else if (isPointLike) + { + return winrt::PropertyValue::CreatePointArray( + convert_value_to_native>(runtime, value)); + } + else if (isSizeLike) + { + return winrt::PropertyValue::CreateSizeArray( + convert_value_to_native>(runtime, value)); + } + + auto isDateTimeLike = + elemObj.hasProperty(runtime, "getDate") && elemObj.hasProperty(runtime, "setDate") && + elemObj.hasProperty(runtime, "getTime") && elemObj.hasProperty(runtime, "setTime") && + elemObj.hasProperty(runtime, "valueOf"); + if (isDateTimeLike) + { + return winrt::PropertyValue::CreateDateTimeArray( + convert_value_to_native>( + runtime, value)); + } + } + } + } + else + { + auto isPointLike = obj.hasProperty(runtime, "x") && obj.hasProperty(runtime, "y"); + auto isSizeLike = obj.hasProperty(runtime, "width") && obj.hasProperty(runtime, "height"); + if (isPointLike && isSizeLike) + { + return winrt::PropertyValue::CreateRect(convert_value_to_native(runtime, value)); + } + else if (isPointLike) + { + return winrt::PropertyValue::CreatePoint(convert_value_to_native(runtime, value)); + } + else if (isSizeLike) + { + return winrt::PropertyValue::CreateSize(convert_value_to_native(runtime, value)); + } + + auto isDateTimeLike = obj.hasProperty(runtime, "getDate") && obj.hasProperty(runtime, "setDate") && + obj.hasProperty(runtime, "getTime") && obj.hasProperty(runtime, "setTime") && + obj.hasProperty(runtime, "valueOf"); + if (isDateTimeLike) + { + return winrt::PropertyValue::CreateDateTime( + convert_value_to_native(runtime, value)); + } + } + } + + return nullptr; +} diff --git a/rnwinrt/base.h b/rnwinrt/base.h new file mode 100644 index 0000000..d76a374 --- /dev/null +++ b/rnwinrt/base.h @@ -0,0 +1,4632 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX 1 +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Common helpers/types +namespace rnwinrt +{ + // TODO: Switch to std::span when available. For now, this is just a subset that we need + template + struct span + { + constexpr span() = default; + + constexpr span(T* data, std::size_t size) noexcept : m_data(data), m_size(size) + { + } + + template + constexpr span(T (&arr)[Size]) noexcept : m_data(arr), m_size(Size) + { + } + + constexpr T* data() const noexcept + { + return m_data; + } + + constexpr std::size_t size() const noexcept + { + return m_size; + } + + constexpr bool empty() const noexcept + { + return m_size == 0; + } + + constexpr T& operator[](std::size_t index) const noexcept + { + assert(index < m_size); + return m_data[index]; + } + + constexpr T* begin() const noexcept + { + return m_data; + } + + constexpr T* end() const noexcept + { + return m_data + m_size; + } + + private: + T* m_data = nullptr; + std::size_t m_size = 0; + }; + + // NOTE: This is a std::vector-like type that uses a small-size buffer optimization + template + struct sso_vector + { + template + friend struct sso_vector; + + sso_vector() = default; + + sso_vector(const sso_vector& other) + { + copy_construct(other); + } + + template + sso_vector(const sso_vector& other) + { + copy_construct(other); + } + + sso_vector(sso_vector&& other) + { + move_construct(other); + } + + template + sso_vector(sso_vector&& other) + { + move_construct(other); + } + + sso_vector& operator=(const sso_vector& other) + { + if (&other != this) + { + copy_assign(other); + } + + return *this; + } + + template + sso_vector& operator=(const sso_vector& other) + { + copy_assign(other); + return *this; + } + + sso_vector& operator=(sso_vector&& other) + { + if (&other != this) + { + move_assign(other); + } + return *this; + } + + template + sso_vector& operator=(sso_vector&& other) + { + move_assign(other); + return *this; + } + + ~sso_vector() + { + destroy_buffer(); + } + + size_t size() const noexcept + { + return m_size; + } + + bool empty() const noexcept + { + return m_size == 0; + } + + T& operator[](size_t index) noexcept + { + assert(index < m_size); + return data()[index]; + } + + const T& operator[](size_t index) const noexcept + { + assert(index < m_size); + return data()[index]; + } + + T& back() noexcept + { + assert(!empty()); + return data()[m_size - 1]; + } + + const T& back() const noexcept + { + assert(!empty()); + return data()[m_size - 1]; + } + + T* data() noexcept + { + return is_locally_allocated() ? local_data() : m_data.pointer; + } + + const T* data() const noexcept + { + return is_locally_allocated() ? local_data() : m_data.pointer; + } + + void reserve(size_t size) + { + // NOTE: Capacity will never fall below SSO buffer size, so this is always an allocate + if (size > m_capacity) + { + auto ptr = static_cast(::operator new(size * sizeof(T))); + try + { + std::uninitialized_move(begin(), end(), ptr); + } + catch (...) + { + ::operator delete(ptr); + throw; + } + + destroy_buffer(); + m_capacity = size; + m_data.pointer = ptr; + } + } + + void resize(size_t size) + { + reserve(size); + auto ptr = data() + m_size; + while (m_size < size) + { + ::new (ptr) T(); + ++m_size; + ++ptr; + } + } + + void resize(size_t size, const T& value) + { + reserve(size); + auto ptr = data() + m_size; + while (m_size < size) + { + ::new (ptr) T(value); + ++m_size; + ++ptr; + } + } + + void clear() noexcept + { + destroy_buffer(); + m_size = 0; + m_capacity = BufferSize; + } + + void push_back(const T& value) + { + if (m_size == m_capacity) + { + reserve(m_capacity * 2); + } + + ::new (data() + m_size) T(value); + ++m_size; + } + + void push_back(T&& value) + { + if (m_size == m_capacity) + { + reserve(m_capacity * 2); + } + + ::new (data() + m_size) T(std::move(value)); + ++m_size; + } + + template + void emplace_back(Args&&... args) + { + if (m_size == m_capacity) + { + reserve(m_capacity * 2); + } + + ::new (data() + m_size) T(std::forward(args)...); + ++m_size; + } + + void pop_back() noexcept + { + --m_size; + data()[m_size].~T(); + } + + T* begin() noexcept + { + return data(); + } + + const T* begin() const noexcept + { + return data(); + } + + T* end() noexcept + { + return begin() + m_size; + } + + const T* end() const noexcept + { + return begin() + m_size; + } + + const T* cbegin() const noexcept + { + return begin(); + } + + const T* cend() const noexcept + { + return end(); + } + + private: + template + void copy_construct(const sso_vector& other) + { + assert(m_size == 0); + reserve(other.m_size); + std::uninitialized_copy(other.begin(), other.end(), data()); + m_size = other.m_size; + } + + template + void copy_assign(const sso_vector& other) + { + if (other.m_size > m_capacity) + { + destroy_buffer(); + m_size = 0; + m_capacity = BufferSize; + copy_construct(other); + } + else + { + // We either need to copy and construct new elements or copy and destroy old elements + auto assignCount = std::min(m_size, other.m_size); + std::copy(other.begin(), other.begin() + assignCount, begin()); + + if (m_size > other.m_size) + { + std::destroy(begin() + assignCount, end()); + } + else + { + std::uninitialized_copy(other.begin() + assignCount, other.end(), begin() + assignCount); + } + + m_size = other.m_size; + } + } + + template + void move_construct(sso_vector& other) + { + // TODO: We can "steal" the buffer in more scenarios + assert(m_size == 0); + if (other.m_size <= m_capacity) + { + std::uninitialized_move(other.begin(), other.end(), data()); + m_size = other.m_size; + other.destroy_buffer(); + other.m_size = 0; + other.m_capacity = OtherBufferSize; // NOTE: Could be less than our buffer size, so can't assume + } + else if (!other.is_locally_allocated()) + { + m_data.pointer = other.m_data.pointer; + m_size = other.m_size; + m_capacity = other.m_capacity; + other.m_size = 0; + other.m_capacity = OtherBufferSize; + } + else + { + reserve(other.m_size); + std::uninitialized_move(other.begin(), other.end(), data()); + m_size = other.m_size; + other.destroy_buffer(); + other.m_size = 0; // NOTE: Capacity already correct since it's locally allocated + } + } + + template + void move_assign(sso_vector& other) + { + if (other.is_locally_allocated()) + { + // Scenario 1: Other is locally allocated. We need to move elements to our own buffer + if (other.m_size <= m_capacity) + { + // Scenario 1.1: Current buffer is large enough. We need to move assign and then either construct or + // destroy extra elements + auto assignCount = std::min(m_size, other.m_size); + std::move(other.begin(), other.begin() + assignCount, begin()); + + if (m_size > other.m_size) + { + std::destroy(begin() + assignCount, end()); + } + else + { + std::uninitialized_move(other.begin() + assignCount, other.end(), begin() + assignCount); + } + } + else + { + // Scenario 1.2: Current buffer is too small. We need to move elements into a new buffer + destroy_buffer(); + m_size = 0; + m_capacity = BufferSize; + reserve(other.m_size); + std::uninitialized_move(other.begin(), other.end(), data()); + } + + m_size = other.m_size; + other.destroy_buffer(); + other.m_size = 0; // NOTE: Capacity already set to buffer size + } + else if (other.m_capacity <= BufferSize) + { + // Scenario 2: Other is heap allocated, but its capacity is not greater than our buffer size, so + // assuming ownership of the pointer would break assumptions. Keep our own buffer (we know it must be + // large enough), so that if it's already heap allocated, we may save the need to do a future allocation + auto assignCount = std::min(m_size, other.m_size); + std::move(other.begin(), other.begin() + assignCount, begin()); + + if (m_size > other.m_size) + { + std::destroy(begin() + assignCount, end()); + } + else + { + std::uninitialized_move(other.begin() + assignCount, other.end(), begin() + assignCount); + } + + m_size = other.m_size; + other.destroy_buffer(); + other.m_size = 0; + other.m_capacity = OtherBufferSize; + } + else + { + // Scenario 3: Other is heap allocated and we can assume ownership of the buffer + destroy_buffer(); + m_data.pointer = other.m_data.pointer; + m_size = other.m_size; + m_capacity = other.m_capacity; + other.m_size = 0; + other.m_capacity = OtherBufferSize; + } + } + + bool is_locally_allocated() const noexcept + { + return m_capacity == BufferSize; + } + + T* local_data() noexcept + { + return reinterpret_cast(&m_data.buffer); + } + + const T* local_data() const noexcept + { + return reinterpret_cast(&m_data.buffer); + } + + // NOTE: Caller is in charge of setting size/capacity correctly + void destroy_buffer() + { + static_assert(std::is_nothrow_destructible_v); + if (is_locally_allocated()) + { + auto ptr = local_data(); + std::destroy(ptr, ptr + m_size); + } + else + { + auto ptr = m_data.pointer; + std::destroy(ptr, ptr + m_size); + ::operator delete(ptr); + } + } + + size_t m_size = 0; + size_t m_capacity = BufferSize; + union + { + std::aligned_storage_t buffer; + T* pointer; + } m_data{}; + }; + + // Used to capture a lambda whose capture includes non-copyable types in scenarios where a copy constructor is + // needed (e.g. std::function), but is never used in practice. + template + struct move_only_lambda + { + move_only_lambda(TLambda lambda) : m_lambda(std::move(lambda)) + { + } + + move_only_lambda(move_only_lambda&&) = default; + move_only_lambda& operator=(move_only_lambda&&) = default; + + move_only_lambda(const move_only_lambda& other) : m_lambda(std::move(const_cast(other.m_lambda))) + { + winrt::terminate(); + } + + move_only_lambda& operator=(const move_only_lambda&) + { + winrt::terminate(); + } + + template + auto operator()(Args&&... args) + { + return m_lambda(std::forward(args)...); + } + + private: + TLambda m_lambda; + }; + + template + auto bind_this(Return (U::*fn)(Args...), T* pThis) + { + return [fn, pThis](Args... args) { return (pThis->*fn)(std::forward(args)...); }; + } + + template + auto bind_this(Return (U::*fn)(Args...) const, const T* pThis) + { + return [fn, pThis](Args... args) { return (pThis->*fn)(std::forward(args)...); }; + } + + inline std::optional index_from_name(std::string_view name) + { + auto begin = name.data(); + auto end = begin + name.size(); + uint32_t index; + auto [ptr, ec] = std::from_chars(begin, end, index); + if ((ptr == end) && (ec == std::errc{})) + { + return index; + } + + return std::nullopt; + } +} + +// Generic helpers +namespace rnwinrt +{ + namespace jsi = facebook::jsi; + using namespace std::literals; + + using call_function_t = jsi::Value (*)(jsi::Runtime&, const jsi::Value&, const jsi::Value*, size_t); + + using static_get_property_t = jsi::Value (*)(jsi::Runtime&); + using static_set_property_t = void (*)(jsi::Runtime&, const jsi::Value&); + using static_add_event_t = winrt::event_token (*)(jsi::Runtime&, const jsi::Value&); + using static_remove_event_t = void (*)(winrt::event_token); + + using instance_get_property_t = jsi::Value (*)(jsi::Runtime&, const winrt::Windows::Foundation::IInspectable&); + using instance_set_property_t = void (*)( + jsi::Runtime&, const winrt::Windows::Foundation::IInspectable&, const jsi::Value&); + using instance_add_event_t = winrt::event_token (*)( + jsi::Runtime&, const winrt::Windows::Foundation::IInspectable&, const jsi::Value&); + using instance_remove_event_t = void (*)(const winrt::Windows::Foundation::IInspectable&, winrt::event_token); + using instance_call_function_t = jsi::Value (*)( + jsi::Runtime&, const winrt::Windows::Foundation::IInspectable&, const jsi::Value*); + using instance_runtime_get_property_t = std::pair, std::optional> (*)( + jsi::Runtime&, const winrt::Windows::Foundation::IInspectable&, std::string_view); + using instance_runtime_set_property_t = bool (*)( + jsi::Runtime&, const winrt::Windows::Foundation::IInspectable&, std::string_view, const jsi::Value&); + + inline constexpr std::string_view add_event_name = "addEventListener"sv; + inline constexpr std::string_view remove_event_name = "removeEventListener"sv; + + inline jsi::String make_string(jsi::Runtime& runtime, std::string_view str) + { + return jsi::String::createFromUtf8(runtime, reinterpret_cast(str.data()), str.size()); + } + + jsi::String make_string(jsi::Runtime& runtime, std::wstring_view str); + std::u16string string_to_utf16(jsi::Runtime& runtime, const jsi::String& string); + + inline jsi::PropNameID make_propid(jsi::Runtime& runtime, std::string_view str) + { + return jsi::PropNameID::forUtf8(runtime, reinterpret_cast(str.data()), str.size()); + } + + inline jsi::Value make_error(jsi::Runtime& runtime, std::string_view message) + { + jsi::Object result(runtime); + result.setProperty(runtime, "message", make_string(runtime, message)); + return jsi::Value(runtime, result); + } + + inline jsi::Value make_error(jsi::Runtime& runtime, const std::exception& exception) + { + return make_error(runtime, exception.what()); + } + + inline jsi::Value make_error(jsi::Runtime& runtime, const winrt::hresult_error& error) + { + jsi::Object result(runtime); + result.setProperty(runtime, "message", make_string(runtime, error.message())); + result.setProperty(runtime, "number", static_cast(error.code())); + return jsi::Value(runtime, result); + } + + template + jsi::Function bind_host_function(jsi::Runtime& runtime, const jsi::PropNameID& name, unsigned int paramCount, + jsi::Value (T::*fn)(jsi::Runtime&, const jsi::Value*, size_t)) + { + return jsi::Function::createFromHostFunction(runtime, name, paramCount, + [fn](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) { + auto strongThis = thisValue.asObject(runtime).asHostObject(runtime); + return (strongThis.get()->*fn)(runtime, args, count); + }); + } + + template + jsi::Function bind_host_function(jsi::Runtime& runtime, const jsi::PropNameID& name, unsigned int paramCount, + jsi::Value (T::*fn)(jsi::Runtime&, const jsi::Value*, size_t) const) + { + return jsi::Function::createFromHostFunction(runtime, name, paramCount, + [fn](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) { + auto strongThis = thisValue.asObject(runtime).asHostObject(runtime); + return (strongThis.get()->*fn)(runtime, args, count); + }); + } + + template + jsi::Value convert_object_instance_to_value(jsi::Runtime& runtime, const T& value); + + template + T convert_value_to_object_instance(jsi::Runtime& runtime, const jsi::Value& value); + + template + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, const T& value) + { + if constexpr (std::is_base_of_v) + { + return convert_object_instance_to_value(runtime, value); + } + else + { + throw jsi::JSError( + runtime, "TypeError: Conversion from native to JS not implemented for type '"s + typeid(T).name() + + "'. This is likely caused by the type being in a non-projected namespace"); + } + } + + static T as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + if constexpr (std::is_base_of_v) + { + return convert_value_to_object_instance(runtime, value); + } + else + { + throw jsi::JSError( + runtime, "TypeError: Conversion from JS to native not implemented for type '"s + typeid(T).name() + + "'. This is likely caused by the type being in a non-projected namespace"); + } + } + }; + + template + auto convert_native_to_value(jsi::Runtime& runtime, T&& value) + { + return projected_value_traits>>::as_value( + runtime, std::forward(value)); + } + + template + auto convert_value_to_native(jsi::Runtime& runtime, const jsi::Value& value) + { + return projected_value_traits::as_native(runtime, value); + } + + template + struct array_to_native_iterator + { + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = void; + using reference = T; + using iterator_category = std::random_access_iterator_tag; + + array_to_native_iterator(jsi::Runtime& runtime, jsi::Array& array, size_t index = 0) : + m_runtime(runtime), m_array(array), m_index(index) + { + } + + array_to_native_iterator& begin() noexcept + { + return *this; + } + + array_to_native_iterator end() noexcept + { + return array_to_native_iterator(m_runtime, m_array, m_array.length(m_runtime)); + } + + bool operator==(const array_to_native_iterator& other) const noexcept + { + assert(&m_array == &other.m_array); + return m_index == other.m_index; + } + + bool operator!=(const array_to_native_iterator& other) const noexcept + { + return !(*this == other); + } + + bool operator<(const array_to_native_iterator& other) const noexcept + { + assert(&m_array == &other.m_array); + return m_index < other.m_index; + } + + bool operator>(const array_to_native_iterator& other) const noexcept + { + assert(&m_array == &other.m_array); + return m_index > other.m_index; + } + + bool operator<=(const array_to_native_iterator& other) const noexcept + { + return !(*this > other); + } + + bool operator>=(const array_to_native_iterator& other) const noexcept + { + return !(*this < other); + } + + T operator*() + { + return convert_value_to_native(m_runtime, m_array.getValueAtIndex(m_runtime, m_index)); + } + + T operator[](difference_type index) const + { + return convert_value_to_native(m_runtime, m_array.getValueAtIndex(m_runtime, m_index + index)); + } + + array_to_native_iterator& operator++() noexcept + { + ++m_index; + return *this; + } + + array_to_native_iterator operator++(int) noexcept + { + auto result = *this; + ++(*this); + return result; + } + + array_to_native_iterator& operator+=(difference_type count) noexcept + { + m_index += count; + return *this; + } + + array_to_native_iterator operator+(difference_type count) noexcept + { + auto result = *this; + result += count; + return result; + } + + friend array_to_native_iterator operator+(difference_type count, array_to_native_iterator itr) noexcept + { + itr += count; + return itr; + } + + array_to_native_iterator& operator--() noexcept + { + --m_index; + return *this; + } + + array_to_native_iterator operator--(int) noexcept + { + auto result = *this; + --(*this); + return result; + } + + array_to_native_iterator& operator-=(difference_type count) noexcept + { + m_index -= count; + return *this; + } + + array_to_native_iterator operator-(difference_type count) noexcept + { + auto result = *this; + result -= count; + return result; + } + + difference_type operator-(const array_to_native_iterator& other) const noexcept + { + return static_cast(m_index - other.m_index); + } + + private: + jsi::Runtime& m_runtime; + jsi::Array& m_array; + std::size_t m_index; + }; + + namespace details + { + inline void fill_return_struct(jsi::Runtime&, jsi::Object&) + { + } + + template + void fill_return_struct( + jsi::Runtime& runtime, jsi::Object& target, std::string_view name, const T& value, const Args&... args) + { + target.setProperty(runtime, make_propid(runtime, name), convert_native_to_value(runtime, value)); + fill_return_struct(runtime, target, args...); + } + } + + template + jsi::Value make_void_return_struct(jsi::Runtime& runtime, const Args&... args) + { + jsi::Object result(runtime); + details::fill_return_struct(runtime, result, args...); + return jsi::Value(runtime, result); + } + + template + jsi::Value make_return_struct(jsi::Runtime& runtime, const Args&... args) + { + return make_void_return_struct(runtime, "returnValue", args...); + } + + struct pinterface_traits_base + { + static constexpr bool is_async_with_progress = false; + static constexpr bool is_async_with_result = false; + + static constexpr bool is_array_convertible = false; + static constexpr bool is_iterable = false; + static constexpr bool is_vector_view = false; + static constexpr bool is_vector = false; + }; + + template + struct pinterface_traits : pinterface_traits_base + { + }; + + template <> + struct pinterface_traits : pinterface_traits_base + { + // NOTE: Currently don't need anything to differentiate here + }; + + template + struct pinterface_traits> : pinterface_traits_base + { + using progress_type = TProgress; + static constexpr bool is_async_with_progress = true; + }; + + template + struct pinterface_traits> : pinterface_traits_base + { + using result_type = TResult; + static constexpr bool is_async_with_result = true; + }; + + template + struct pinterface_traits> : + pinterface_traits_base + { + using progress_type = TProgress; + using result_type = TResult; + static constexpr bool is_async_with_progress = true; + static constexpr bool is_async_with_result = true; + }; + + template + struct pinterface_traits> : pinterface_traits_base + { + using value_type = T; + static constexpr bool is_array_convertible = true; + static constexpr bool is_iterable = true; + }; + + template + struct pinterface_traits> : pinterface_traits_base + { + using value_type = T; + static constexpr bool is_array_convertible = true; + static constexpr bool is_vector_view = true; + }; + + template + struct pinterface_traits> : pinterface_traits_base + { + using value_type = T; + static constexpr bool is_array_convertible = true; + static constexpr bool is_vector = true; + }; + + struct promise_wrapper + { + static promise_wrapper create(jsi::Runtime& runtime) + { + // NOTE: The promise callback is called immediately, hence the capture by reference + std::optional resolveFn, rejectFn; + auto callback = jsi::Function::createFromHostFunction(runtime, make_propid(runtime, "callback"), 2, + [&](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, size_t count) { + if (count < 2) + { + throw jsi::JSError(runtime, "Promise callback unexpectedly called with insufficient arguments"); + } + + resolveFn = args[0].asObject(runtime).asFunction(runtime); + rejectFn = args[1].asObject(runtime).asFunction(runtime); + return jsi::Value::undefined(); + }); + + auto promise = + runtime.global().getPropertyAsFunction(runtime, "Promise").callAsConstructor(runtime, callback); + assert(resolveFn && rejectFn); + return promise_wrapper(std::move(promise), std::move(*resolveFn), std::move(*rejectFn)); + } + + const jsi::Value& get() const noexcept + { + return m_promise; + } + + void resolve(jsi::Runtime& runtime, const jsi::Value& value) + { + assert(!m_completed); // TODO: Throw/fail fast? + m_completed = true; + m_resolve.call(runtime, value); + } + + void reject(jsi::Runtime& runtime, const jsi::Value& value) + { + assert(!m_completed); // TODO: Throw/fail fast? + m_completed = true; + m_reject.call(runtime, value); + } + + private: + promise_wrapper(jsi::Value promise, jsi::Function resolveFn, jsi::Function rejectFn) : + m_promise(std::move(promise)), m_resolve(std::move(resolveFn)), m_reject(std::move(rejectFn)) + { + } + + jsi::Value m_promise; + bool m_completed = false; + jsi::Function m_resolve; + jsi::Function m_reject; + }; + + inline bool to_boolean(jsi::Runtime& runtime, const jsi::Value& value) + { + // Common case first + if (value.isBool()) + return value.getBool(); + if (value.isUndefined() || value.isNull()) + return false; + if (value.isSymbol() || value.isObject()) + return true; + if (value.isNumber()) + return value.getNumber() != 0; + + assert(value.isString()); + return !value.getString(runtime).utf8(runtime).empty(); + } + + inline double to_number(jsi::Runtime& runtime, const jsi::Value& value) + { + if (value.isNumber()) + return value.getNumber(); + if (value.isUndefined()) + return std::numeric_limits::quiet_NaN(); + if (value.isNull()) + return 0; + if (value.isBool()) + return value.getBool() ? 1 : 0; + + if (value.isString()) + { + // This is probably not 100% correct, but it should be good enough + auto str = value.getString(runtime).utf8(runtime); + double result; + auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result); + if (ec != std::errc{}) + return std::numeric_limits::quiet_NaN(); + + return result; + } + + // Symbol & Object (TODO: Object shouldn't be included here) + throw jsi::JSError(runtime, "TypeError: Cannot convert value to number"); + } + + inline double to_integer_or_infinity(jsi::Runtime& runtime, const jsi::Value& value) + { + auto result = to_number(runtime, value); + if (std::isnan(result) || (result == 0.0) || (result == -0.0)) + return 0; + if ((result == std::numeric_limits::infinity()) || (result == -std::numeric_limits::infinity())) + return result; + + auto integer = std::floor(std::abs(result)); + return (result < 0) ? -integer : integer; + } + + // Exception methods, to help reduce binary size + [[noreturn]] __declspec(noinline) void throw_no_constructor( + jsi::Runtime& runtime, std::string_view typeNamespace, std::string_view typeName, size_t argCount); + + [[noreturn]] __declspec(noinline) void throw_no_function_overload(jsi::Runtime& runtime, + std::string_view typeNamespace, std::string_view typeName, std::string_view fnName, size_t argCount); + + [[noreturn]] __declspec(noinline) void throw_invalid_delegate_arg_count( + jsi::Runtime& runtime, std::string_view typeNamespace, std::string_view typeName); +} + +// JS thread context data +namespace rnwinrt +{ + struct runtime_context; + + // A shared, strong reference to the context, for (safe) use off the JS thread + struct shared_runtime_context + { + runtime_context* pointer = nullptr; + + shared_runtime_context() = default; + shared_runtime_context(runtime_context* ptr); + + shared_runtime_context(const shared_runtime_context& other) : shared_runtime_context(other.pointer) + { + } + + shared_runtime_context(shared_runtime_context&& other) noexcept : pointer(other.pointer) + { + other.pointer = nullptr; + } + + ~shared_runtime_context(); + + shared_runtime_context& operator=(const shared_runtime_context& other); + + shared_runtime_context& operator=(shared_runtime_context&& other) + { + std::swap(pointer, other.pointer); + return *this; + } + + runtime_context* operator->() noexcept + { + return pointer; + } + + const runtime_context* operator->() const noexcept + { + return pointer; + } + + runtime_context& operator*() noexcept + { + return *pointer; + } + + const runtime_context& operator*() const noexcept + { + return *pointer; + } + }; + + // TODO: Figure out a good interval for performing cleanup + static constexpr auto cleanup_interval = 5min; + + struct object_instance_cache + { + // Maps an IInspectable pointer to the HostObject that represents that object. Note that it is possible for a + // WinRT object to get destroyed and have its memory location re-used for a later object. This is okay since the + // HostObject holds a strong reference to the WinRT object and therefore the WinRT object getting destroyed + // would therefore imply that the JS object also got destroyed, meaning we won't accidentally re-use the same + // HostObject after its underlying object got destroyed + std::unordered_map>> instances; + + // TODO: This is kind of a hack/workaround for V8, which does not appear to have WeakObject support per + // V8Runtime::createWeakObject/V8Runtime::lockWeakObject + // (https://github.com/microsoft/v8-jsi/blob/master/src/V8JsiRuntime.cpp) + bool supports_weak_object = true; + + // TODO: Ideally this would be a periodic thing (on a timer), but for now we'll manually trigger cleanup + // occasionally when we're querying for object instances + std::chrono::steady_clock::time_point last_cleanup = std::chrono::steady_clock::now(); + + jsi::Value get_instance(jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& value); + + void cleanup(jsi::Runtime& runtime) + { + last_cleanup = std::chrono::steady_clock::now(); + for (auto itr = instances.begin(); itr != instances.end();) + { + if (supports_weak_object) + { + if (auto value = std::get<0>(itr->second).lock(runtime); !value.isUndefined()) // Object still alive + { + ++itr; + } + else // Object was destroyed + { + itr = instances.erase(itr); + } + } + else + { + if (std::get<1>(itr->second).lock()) // Object still alive + { + ++itr; + } + else // Object was destroyed + { + itr = instances.erase(itr); + } + } + } + } + }; + + // A reusable mapping of (delegate, event name) -> event_token for a single + struct event_registration_array + { + // NOTE: It's possible to re-use the same callback for multiple events on the same object (especially since + // we're talking about JS here), so we include the event name in the mapping here. Since event names are all + // stored as static data, we primarily use it as an integer id and perform pointer comparison. + struct registration_data + { + jsi::Object object; + const char* event_name; + winrt::event_token token; + }; + + sso_vector registrations; + + bool empty() const noexcept + { + return registrations.empty(); + } + + void add(jsi::Object object, const char* eventName, winrt::event_token token) + { + registrations.push_back({ std::move(object), eventName, token }); + } + + winrt::event_token remove(jsi::Runtime& runtime, const jsi::Object& object, const char* eventName) + { + for (auto& data : registrations) + { + if ((data.event_name == eventName) && jsi::Object::strictEquals(runtime, data.object, object)) + { + auto result = data.token; + data = std::move(registrations.back()); + registrations.pop_back(); + return result; + } + } + + return {}; + } + }; + + struct object_event_cache + { + // NOTE: Since we currently hold strong references to the function objects being used as delegates, and just + // cleanup in general, we hold a weak reference to the WinRT object and periodically try and clean the map up. + // Note that some objects don't support weak references, in which case we just forgo this cleanup and instead + // rely on the consumer to clean up registrations. + struct instance_data + { + winrt::weak_ref weak_ref; + event_registration_array registrations; + }; + + std::unordered_map events; + + // TODO: Ideally this would be a periodic thing (on a timer), but for now we'll manually trigger cleanup + // occasionally when adding tokens + std::chrono::steady_clock::time_point last_cleanup = std::chrono::steady_clock::now(); + + void add(const winrt::Windows::Foundation::IInspectable& instance, jsi::Object object, const char* eventName, + winrt::event_token token) + { + if ((std::chrono::steady_clock::now() - last_cleanup) >= cleanup_interval) + { + cleanup(); + } + + auto ptr = winrt::get_abi(instance); + auto& data = events[ptr]; + if (data.weak_ref && !data.weak_ref.get()) + { + data = {}; // Object address was re-used; clean up the registrations + } + + if (data.registrations.empty()) + { + // NOTE: C++/WinRT has no 'try_make_weak_ref' or equivalent, so doing it manually here... + assert(!data.weak_ref); + if (auto src = instance.try_as<::IWeakReferenceSource>()) + { + winrt::check_hresult( + src->GetWeakReference(reinterpret_cast<::IWeakReference**>(data.weak_ref.put()))); + } + } + + data.registrations.add(std::move(object), eventName, token); + } + + winrt::event_token remove(jsi::Runtime& runtime, const winrt::Windows::Foundation::IInspectable& instance, + const jsi::Object& object, const char* eventName) + { + auto ptr = winrt::get_abi(instance); + if (auto itr = events.find(ptr); itr != events.end()) + { + auto result = itr->second.registrations.remove(runtime, object, eventName); + if (itr->second.registrations.empty()) + { + events.erase(itr); + } + + return result; + } + + return {}; + } + + void cleanup() + { + last_cleanup = std::chrono::steady_clock::now(); + for (auto itr = events.begin(); itr != events.end();) + { + if (itr->second.weak_ref.get()) // Object still alive + { + ++itr; + } + else // Object was destroyed + { + itr = events.erase(itr); + } + } + } + }; + + // React's CallInvoker type does not have a way to communicate failure back to us (i.e. if the provided function + // object won't run). So we instead capture an object that can track the lifetime of the function object so that we + // can determine if the function will never be called + template + struct lifetime_tracker + { + Func callback; + std::atomic_int ref_count{ 0 }; + + lifetime_tracker(Func callback) : callback(std::move(callback)) + { + } + + // Should copy *references* to this type, not the type itself + lifetime_tracker(const lifetime_tracker&) = delete; + lifetime_tracker& operator=(const lifetime_tracker&) = delete; + + struct reference + { + lifetime_tracker* target; + + reference(lifetime_tracker* target) noexcept : target(target) + { + target->ref_count.fetch_add(1, std::memory_order_relaxed); + } + + reference(const reference& other) noexcept : target(other.target) + { + if (target) + { + target->ref_count.fetch_add(1, std::memory_order_relaxed); + } + } + + reference(reference&& other) noexcept : target(other.target) + { + other.target = nullptr; + } + + ~reference() + { + reset(); + } + + reference& operator=(const reference& other) noexcept + { + if (this != &other) + { + reset(); + + target = other.target; + if (target) + { + target->ref_count.fetch_add(1, std::memory_order_relaxed); + } + } + + return *this; + } + + reference& operator=(reference&& other) noexcept + { + std::swap(target, other.target); + } + + void reset() + { + auto ptr = std::exchange(target, nullptr); + if (ptr) + { + auto count = ptr->ref_count.fetch_sub(1, std::memory_order_acq_rel); + if (count == 1) // Returns the previous value + { + ptr->callback(); + } + } + } + }; + + // Should only be called once; begins the reference tracking + reference begin() noexcept + { + assert(ref_count.load(std::memory_order_relaxed) == 0); + return reference(this); + } + }; + + struct runtime_context + { + jsi::Runtime& runtime; + std::thread::id thread_id = std::this_thread::get_id(); + std::function)> call_invoker; + + // TODO: Currently each of these objects handles its own periodic cleanup. It would be great if we could do this + // on a more well-defined basis for both (e.g. on a timer) + object_instance_cache instance_cache; + object_event_cache event_cache; + + runtime_context(jsi::Runtime& runtime, std::function)> callInvoker) : + runtime(runtime), call_invoker(callInvoker) + { + } + + void call(std::function fn) const + { + if (thread_id == std::this_thread::get_id()) + { + fn(); + } + else + { + call_invoker(std::move(fn)); + } + } + + void call_async(std::function fn) const + { + call_invoker(std::move(fn)); + } + + void call_sync(std::function fn) const + { + if (thread_id == std::this_thread::get_id()) + { + fn(); + } + else + { + winrt::handle event(::CreateEventW(nullptr, true, false, nullptr)); + if (!event.get()) + { + winrt::throw_last_error(); + } + + lifetime_tracker tracker([&] { ::SetEvent(event.get()); }); + + std::exception_ptr exception; + bool invoked = false; + call_invoker([&, ref = tracker.begin()]() mutable { + // Force the completion of the event once the callback completes so we don't need to wait for the + // lambda to be destroyed if for some reason it isn't immediate. Note that this sets the callback + // pointer to null, so there's no dangling reference anywhere + auto forceComplete = std::move(ref); + assert(tracker.ref_count.load(std::memory_order_relaxed) == 1); + + try + { + fn(); + } + catch (...) + { + exception = std::current_exception(); + } + + invoked = true; + }); + + if (::WaitForSingleObject(event.get(), INFINITE) != WAIT_OBJECT_0) + { + winrt::terminate(); + } + + if (exception) + { + std::rethrow_exception(exception); + } + else if (!invoked) + { + throw winrt::hresult_error(JSCRIPT_E_CANTEXECUTE); + } + } + } + + // For the most part, these pointers should only be accessed from the JS thread and therefore the reference + // count does not need to be bumped, but for things like asynchronous operations, we may need to take strong + // references to ensure the object does not get destroyed during use + std::atomic_uint32_t ref_count{ 1 }; + + [[nodiscard]] shared_runtime_context add_reference() + { + assert(thread_id == std::this_thread::get_id()); + assert(ref_count.load() >= 1); + return shared_runtime_context{ this }; // NOTE: Bumps ref count + } + + void release() + { + if (--ref_count == 0) + { + delete this; + } + } + }; + + inline shared_runtime_context::shared_runtime_context(runtime_context* ptr) : pointer(ptr) + { + if (pointer) + { + ++pointer->ref_count; + } + } + + inline shared_runtime_context& shared_runtime_context::operator=(const shared_runtime_context& other) + { + if (this != &other) + { + if (pointer) + { + pointer->release(); + } + + pointer = other.pointer; + if (pointer) + { + ++pointer->ref_count; + } + } + + return *this; + } + + inline shared_runtime_context::~shared_runtime_context() + { + if (pointer) + { + pointer->release(); + } + } + + runtime_context* current_runtime_context(); +} + +// Types used to store static data +namespace rnwinrt +{ + // NOTE: All of these instances are intended to go into the .text section and hold no state that needs to be free'd, + // hence the lack of a virtual destructor + struct static_projection_data + { + constexpr static_projection_data(std::string_view name) : name(name) + { + } + + virtual jsi::Value create(jsi::Runtime& runtime) const = 0; + + std::string_view name; // E.g. 'Baz' for the type/namespace/etc. 'Foo.Bar.Baz' + }; + + struct static_namespace_data final : static_projection_data + { + constexpr static_namespace_data(std::string_view name, span children) : + static_projection_data(name), children(children) + { + } + + virtual jsi::Value create(jsi::Runtime& runtime) const override; + + span children; + }; + + struct static_enum_data final : static_projection_data + { + // Enums are either signed or unsigned 32-bit values in Windows metadata. All of which are representable as + // 64-bit floating point values (i.e. "number") + struct value_mapping + { + std::string_view name; + double value; + std::string_view value_as_string; + }; + + constexpr static_enum_data(std::string_view name, span values) : + static_projection_data(name), values(values) + { + } + + virtual jsi::Value create(jsi::Runtime& runtime) const override; + + jsi::Value get_value(jsi::Runtime& runtime, std::string_view valueName) const; + + span values; + }; + + extern const span root_namespaces; + + struct static_class_data : static_projection_data + { + struct property_mapping + { + std::string_view name; + static_get_property_t getter; + static_set_property_t setter; + }; + + struct event_mapping + { + std::string_view name; + static_add_event_t add; + static_remove_event_t remove; + }; + + struct function_mapping + { + std::string_view name; + call_function_t function; + }; + + constexpr static_class_data(std::string_view name, span properties, + span events, span functions) : + static_projection_data(name), + properties(properties), events(events), functions(functions) + { + } + + virtual jsi::Value create(jsi::Runtime& runtime) const override; + + span properties; + span events; + span functions; + }; + + struct static_activatable_class_data final : static_class_data + { + constexpr static_activatable_class_data(std::string_view ns, std::string_view name, call_function_t constructor, + span properties, span events, + span functions) : + static_class_data(name, properties, events, functions), + full_namespace(ns), constructor(constructor) + { + } + + virtual jsi::Value create(jsi::Runtime& runtime) const override; + + std::string_view full_namespace; + call_function_t constructor; + }; + + // NOTE: We don't need to "create" objects from interfaces - we create objects and populate the interfaces that the + // object supports - hence the fact that we don't derive from 'static_projection_data' here. + struct static_interface_data + { + struct property_mapping + { + // NOTE: Unlike classes, the getter *can* be null + std::string_view name; + instance_get_property_t getter; + instance_set_property_t setter; + }; + + struct event_mapping + { + std::string_view name; + instance_add_event_t add; + instance_remove_event_t remove; + }; + + struct function_mapping + { + std::string_view name; + instance_call_function_t function; + unsigned int arity; + bool is_default_overload; + }; + + constexpr static_interface_data(const winrt::guid& guid, span properties, + span events, span functions, + instance_runtime_get_property_t runtimeGetProperty = nullptr, + instance_runtime_set_property_t runtimeSetProperty = nullptr) : + guid(guid), + properties(properties), events(events), functions(functions), runtime_get_property(runtimeGetProperty), + runtime_set_property(runtimeSetProperty) + { + } + + winrt::guid guid; + span properties; + span events; + span functions; + + // Some projected types want the ability to expose functions beyond what's + instance_runtime_get_property_t runtime_get_property; + instance_runtime_set_property_t runtime_set_property; + }; + + extern const span> global_interface_map; +} + +// Types used for object instances, etc. +namespace rnwinrt +{ + struct projected_namespace final : public jsi::HostObject + { + projected_namespace(const static_namespace_data* data) : m_data(data) + { + m_children.resize(data->children.size()); + } + + // HostObject functions + virtual jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override; + virtual void set(jsi::Runtime& runtime, const jsi::PropNameID& name, const jsi::Value& value) override; + virtual std::vector getPropertyNames(jsi::Runtime& runtime) override; + + private: + const static_namespace_data* m_data; + std::vector m_children; + }; + + struct projected_enum final : public jsi::HostObject + { + projected_enum(const static_enum_data* data) : m_data(data) + { + } + + // HostObject functions + virtual jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override; + virtual void set(jsi::Runtime& runtime, const jsi::PropNameID& name, const jsi::Value& value) override; + virtual std::vector getPropertyNames(jsi::Runtime& runtime) override; + + private: + const static_enum_data* m_data; + }; + + struct projected_statics_class final : public jsi::HostObject + { + projected_statics_class(const static_class_data* data) : m_data(data) + { + } + + // HostObject functions + virtual jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override; + virtual void set(jsi::Runtime& runtime, const jsi::PropNameID& name, const jsi::Value& value) override; + virtual std::vector getPropertyNames(jsi::Runtime& runtime) override; + + private: + jsi::Value add_event_listener(jsi::Runtime& runtime, const jsi::Value* args, size_t count); + jsi::Value remove_event_listener(jsi::Runtime& runtime, const jsi::Value* args, size_t count); + + const static_class_data* m_data; + std::unordered_map m_functions; + event_registration_array m_events; + }; + + struct projected_function; + struct projected_overloaded_function; + + struct projected_object_instance : public jsi::HostObject + { + friend struct projected_function; + friend struct projected_overloaded_function; + + projected_object_instance(const winrt::Windows::Foundation::IInspectable& instance); + + // HostObject functions + virtual jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override; + virtual void set(jsi::Runtime& runtime, const jsi::PropNameID& name, const jsi::Value& value) override; + virtual std::vector getPropertyNames(jsi::Runtime& runtime) override; + + const winrt::Windows::Foundation::IInspectable& instance() const noexcept + { + return m_instance; + } + + private: + jsi::Value add_event_listener(jsi::Runtime& runtime, const jsi::Value* args, size_t count); + jsi::Value remove_event_listener(jsi::Runtime& runtime, const jsi::Value* args, size_t count); + + winrt::Windows::Foundation::IInspectable m_instance; + sso_vector m_interfaces; + std::unordered_map m_functions; + }; + + template + struct projected_async_instance : + public jsi::HostObject, + public std::enable_shared_from_this> + { + private: + using traits = pinterface_traits; + + struct tag_t + { + }; + + public: + static std::shared_ptr create(IFace instance) + { + auto result = std::make_shared(std::move(instance), tag_t{}); + result->initialize(); + + if constexpr (traits::is_async_with_progress) // IAsync*WithProgress + { + result->m_instance.Progress( + [weakThis = std::weak_ptr{ result }, ctxt = current_runtime_context()->add_reference()]( + const auto&, const auto& progress) { + ctxt->call([progress, weakThis]() { + if (auto strongThis = weakThis.lock()) + { + auto& runtime = current_runtime_context()->runtime; + strongThis->on_progress(runtime, convert_native_to_value(runtime, progress)); + } + }); + }); + } + + return result; + } + + projected_async_instance(IFace instance, tag_t) : m_instance(std::move(instance)) + { + } + + // HostObject functions + virtual jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& id) override + { + static constexpr const struct get_function_t get_functions[] = { + { "cancel"sv, 0, &projected_async_instance::on_cancel }, + { "catch"sv, 1, &projected_async_instance::on_catch }, + { "done"sv, 3, &projected_async_instance::on_done }, + { "finally"sv, 3, &projected_async_instance::on_finally }, + { "then"sv, 3, &projected_async_instance::on_then }, + }; + + return get_impl(runtime, id, get_functions); + } + + virtual void set(jsi::Runtime& runtime, const jsi::PropNameID& name, const jsi::Value&) override + { + throw jsi::JSError(runtime, "TypeError: Cannot assign to property '" + name.utf8(runtime) + + "' of a projected WinRT AsyncOperation"); + } + + virtual std::vector getPropertyNames(jsi::Runtime& runtime) override + { + std::vector result; + result.reserve(6); + + result.push_back(make_propid(runtime, "cancel"sv)); + result.push_back(make_propid(runtime, "catch"sv)); + result.push_back(make_propid(runtime, "done"sv)); + result.push_back(make_propid(runtime, "finally"sv)); + result.push_back(make_propid(runtime, "then"sv)); + result.push_back(make_propid(runtime, "operation"sv)); + + return result; + } + + private: + struct get_function_t + { + std::string_view name; + int arg_count; + jsi::Value (projected_async_instance::*function)(jsi::Runtime&, const jsi::Value*, size_t); + }; + + jsi::Value get_impl(jsi::Runtime& runtime, const jsi::PropNameID& id, span getFns) + { + auto name = id.utf8(runtime); + auto itr = std::find_if(getFns.begin(), getFns.end(), [&](auto&& pair) { return pair.name == name; }); + if (itr != getFns.end()) + { + return jsi::Value(runtime, bind_host_function(runtime, id, itr->arg_count, itr->function)); + } + else if (name == "operation"sv) + { + return current_runtime_context()->instance_cache.get_instance(runtime, m_instance); + } + + return jsi::Value::undefined(); + } + + struct continuation + { + enum + { + then_type, // Covers both .then and .catch + finally_type, + done_type, + } kind; + std::optional promise; + jsi::Value data[3]; // Contents depend on 'kind' + }; + + enum class state + { + pending, + resolved, + rejected, + }; + + winrt::fire_and_forget initialize() + { + auto strongThis = this->shared_from_this(); + auto ctxt = current_runtime_context()->add_reference(); + auto& runtime = ctxt->runtime; + auto inst = m_instance; + try + { + if constexpr (!traits::is_async_with_result) // IAsyncAction* + { + co_await inst; + ctxt->call( + [strongThis, &runtime]() { strongThis->on_completed(runtime, jsi::Value::undefined(), true); }); + } + else // IAsyncOperation* + { + auto result = co_await inst; + ctxt->call([strongThis, &runtime, result = std::move(result)]() { + strongThis->on_completed(runtime, convert_native_to_value(runtime, result), true); + }); + } + } + catch (winrt::hresult_error& err) + { + ctxt->call([strongThis, &runtime, err = std::move(err)]() { + strongThis->on_completed(runtime, make_error(runtime, err), false); + }); + } + catch (std::exception& err) + { + ctxt->call([strongThis, &runtime, err = std::move(err)]() { + strongThis->on_completed(runtime, make_error(runtime, err), false); + }); + } + } + + jsi::Value on_cancel(jsi::Runtime&, const jsi::Value*, size_t) + { + m_instance.Cancel(); + return jsi::Value::undefined(); + } + + jsi::Value on_catch(jsi::Runtime& runtime, const jsi::Value* args, size_t count) + { + // Prototype: catch(rejectCallback) -> Promise + // NOTE: Equivalent to 'then(null, rejectCallback)' + auto undefined = jsi::Value::undefined(); + return handle_then(runtime, undefined, count >= 1 ? args[0] : undefined); + } + + using handle_continuation_t = void (projected_async_instance::*)(jsi::Runtime&, continuation&&); + + __declspec(noinline) jsi::Value on_done_impl( + jsi::Runtime& runtime, const jsi::Value* args, size_t count, handle_continuation_t handleContinuation) + { + // Prototype: done(resolveCallback, rejectCallback, progressCallback) + continuation c{ continuation::done_type, {}, + { count >= 1 ? jsi::Value(runtime, args[0]) : jsi::Value::undefined(), + count >= 2 ? jsi::Value(runtime, args[1]) : jsi::Value::undefined(), + count >= 3 ? jsi::Value(runtime, args[2]) : jsi::Value::undefined() } }; + (this->*handleContinuation)(runtime, std::move(c)); + + return jsi::Value::undefined(); + } + + jsi::Value on_done(jsi::Runtime& runtime, const jsi::Value* args, size_t count) + { + return on_done_impl(runtime, args, count, &projected_async_instance::handle_continuation); + } + + __declspec(noinline) jsi::Value on_finally_impl( + jsi::Runtime& runtime, const jsi::Value* args, size_t count, handle_continuation_t handleContinuation) + { + // Prototype: finally(callback) -> Promise + continuation c{ continuation::finally_type, promise_wrapper::create(runtime), + { count >= 1 ? jsi::Value(runtime, args[0]) : jsi::Value::undefined() } }; + + auto result = jsi::Value(runtime, c.promise->get()); + (this->*handleContinuation)(runtime, std::move(c)); + + return result; + } + + jsi::Value on_finally(jsi::Runtime& runtime, const jsi::Value* args, size_t count) + { + return on_finally_impl(runtime, args, count, &projected_async_instance::handle_continuation); + } + + jsi::Value on_then(jsi::Runtime& runtime, const jsi::Value* args, size_t count) + { + // Prototype: then(resolvedCallback, rejectedCallback) + auto undefined = jsi::Value::undefined(); + return handle_then(runtime, count >= 1 ? args[0] : undefined, count >= 2 ? args[1] : undefined); + } + + __declspec(noinline) jsi::Value handle_then_impl(jsi::Runtime& runtime, const jsi::Value& resolvedHandler, + const jsi::Value& rejectedHandler, handle_continuation_t handleContinuation) + { + continuation c{ continuation::then_type, promise_wrapper::create(runtime), + { jsi::Value(runtime, resolvedHandler), jsi::Value(runtime, rejectedHandler) } }; + + auto result = jsi::Value(runtime, c.promise->get()); + (this->*handleContinuation)(runtime, std::move(c)); + + return result; + } + + jsi::Value handle_then( + jsi::Runtime& runtime, const jsi::Value& resolvedHandler, const jsi::Value& rejectedHandler) + { + return handle_then_impl( + runtime, resolvedHandler, rejectedHandler, &projected_async_instance::handle_continuation); + } + + __declspec(noinline) void handle_continuation_impl(jsi::Runtime& runtime, continuation&& c, + std::function (*makeCallback)(projected_async_instance*, jsi::Runtime&, continuation&&)) + { + if (m_state != state::pending) + { + // NOTE: Still expected to be async + // NOTE: The callback will occur on the same thread, implying that the 'Runtime' instance will still be + // alive and valid, hence the ref is safe + current_runtime_context()->call_async(makeCallback(this, runtime, std::move(c))); + } + else + { + m_continuations.push_back(std::move(c)); + } + } + + void handle_continuation(jsi::Runtime& runtime, continuation&& c) + { + handle_continuation_impl(runtime, std::move(c), + [](projected_async_instance* pThis, jsi::Runtime& runtime, continuation&& c) -> std::function { + return move_only_lambda( + [&runtime, strongThis = pThis->shared_from_this(), cont = std::move(c)]() mutable { + strongThis->dispatch_continuation(runtime, cont); + }); + }); + } + + void on_progress(jsi::Runtime& runtime, const jsi::Value& progress) + { + for (auto&& cont : m_continuations) + { + if (cont.kind != continuation::done_type) + continue; + + if (!cont.data[2].isObject()) + continue; + + cont.data[2].getObject(runtime).asFunction(runtime).call(runtime, progress); + } + } + + void on_completed(jsi::Runtime& runtime, jsi::Value&& value, bool resolved) + { + assert(m_state == state::pending); + m_state = resolved ? state::resolved : state::rejected; + m_result = std::move(value); + + for (auto& cont : m_continuations) + { + dispatch_continuation(runtime, cont); + } + } + + void dispatch_continuation(jsi::Runtime& runtime, continuation& cont) + { + assert(m_state != state::pending); + std::optional continuationResult; + bool resolved = m_state == state::resolved; + + switch (cont.kind) + { + case continuation::then_type: + case continuation::done_type: // Effectively 'then', but with progress and no promise + if (resolved) + { + if (cont.data[0].isObject()) + { + try + { + continuationResult = + cont.data[0].getObject(runtime).asFunction(runtime).call(runtime, m_result); + } + catch (const jsi::JSError& err) + { + resolved = false; + continuationResult = jsi::Value(runtime, err.value()); + } + } + } + // NOTE: If the resolve handler of a 'Promise.then' throws, it is *not* passed on to the reject handler + // that was passed in the same '.then' call. We mimic that behavior here, hence the 'else' instead of + // re-inspecting the value of 'resolved' + else // rejected + { + if (cont.data[1].isObject()) + { + try + { + continuationResult = + cont.data[1].getObject(runtime).asFunction(runtime).call(runtime, m_result); + resolved = true; + } + catch (const jsi::JSError& err) + { + // NOTE: 'resolved' already false... + continuationResult = jsi::Value(runtime, err.value()); + } + } + } + break; + + case continuation::finally_type: + // NOTE: Finally does not change the value, nor the resolved state, unless there's an exception + if (cont.data[0].isObject()) + { + try + { + cont.data[0].getObject(runtime).asFunction(runtime).call(runtime, m_result); + } + catch (const jsi::JSError& err) + { + resolved = false; + continuationResult = jsi::Value(runtime, err.value()); + } + } + break; + } + + auto& effectiveResult = continuationResult ? *continuationResult : m_result; + if (cont.promise) + { + if (resolved) + { + cont.promise->resolve(runtime, effectiveResult); + } + else + { + cont.promise->reject(runtime, effectiveResult); + } + } + else if (!resolved) + { + // Failure in 'done' scenario - throw unhandled errors instead of swallowing + throw jsi::JSError(runtime, jsi::Value(runtime, effectiveResult)); + } + } + + IFace m_instance; + sso_vector m_continuations; + state m_state = state::pending; + jsi::Value m_result; + }; +} + +// Collections wrappers +namespace rnwinrt +{ + // Implementations for passing arrays as various iterable types + template + struct array_iterator : + winrt::implements, winrt::Windows::Foundation::Collections::IIterator> + { + array_iterator(D* target) : target(target->get_strong()) + { + } + + winrt::hstring GetRuntimeClassName() const + { + return L"JsArrayIterator"; + } + + T Current() + { + return target->GetAt(index); + } + + bool HasCurrent() + { + return index < target->Size(); + } + + std::uint32_t GetMany(winrt::array_view const& items) + { + auto count = target->GetMany(index, items); + index += count; + return count; + } + + bool MoveNext() + { + auto size = target->Size(); + if (index < size) + { + ++index; + } + + return index < size; + } + + winrt::com_ptr target; + std::uint32_t index = 0; + }; + + template + struct array_vector_base + { + array_vector_base(jsi::Runtime& runtime, jsi::Array array) : runtime(runtime), array(std::move(array)) + { + } + + void CheckThread() + { + if (thread_id != std::this_thread::get_id()) + { + throw winrt::hresult_wrong_thread{}; + } + } + + // IIterable functions + winrt::Windows::Foundation::Collections::IIterator First() + { + return winrt::make>(static_cast(this)); + } + + // IVectorView functions + std::uint32_t Size() + { + CheckThread(); + return static_cast(array.size(runtime)); + } + + T GetAt(std::uint32_t index) + { + auto size = Size(); // NOTE: Checks thread access + if (index >= size) + { + throw winrt::hresult_out_of_bounds(); + } + + return convert_value_to_native(runtime, array.getValueAtIndex(runtime, index)); + } + + std::uint32_t GetMany(std::uint32_t startIndex, winrt::array_view items) + { + auto size = Size(); // NOTE: Checks thread access + auto count = std::min(size - startIndex, items.size()); + for (uint32_t i = 0; i < count; ++i) + { + items[i] = convert_value_to_native(runtime, array.getValueAtIndex(runtime, startIndex + i)); + } + + return count; + } + + bool IndexOf(T const& value, std::uint32_t& index) + { + auto size = Size(); // NOTE: Checks thread access + for (uint32_t i = 0; i < size; ++i) + { + if (value == convert_value_to_native(runtime, array.getValueAtIndex(runtime, i))) + { + index = i; + return true; + } + } + + return false; + } + + // IVector functions, with the exception of 'GetView' + void Append(T const& value) + { + // NOTE: JSI doesn't currently seem to allow modification of arrays from native + CheckThread(); + auto pushFn = array.getPropertyAsFunction(runtime, "push"); + pushFn.callWithThis(runtime, array, convert_native_to_value(runtime, value)); + } + + void Clear() + { + // NOTE: JSI doesn't currently seem to allow modification of arrays from native + CheckThread(); + array.setProperty(runtime, "length", 0); + } + + void InsertAt(std::uint32_t index, T const& value) + { + // NOTE: JSI doesn't currently seem to allow modification of arrays from native + CheckThread(); + auto spliceFn = array.getPropertyAsFunction(runtime, "splice"); + spliceFn.callWithThis( + runtime, array, static_cast(index), 0, convert_native_to_value(runtime, value)); + } + + void RemoveAt(std::uint32_t index) + { + // NOTE: JSI doesn't currently seem to allow modification of arrays from native + CheckThread(); + auto spliceFn = array.getPropertyAsFunction(runtime, "splice"); + spliceFn.callWithThis(runtime, array, static_cast(index), 1); + } + + void RemoveAtEnd() + { + // NOTE: JSI doesn't currently seem to allow modification of arrays from native + CheckThread(); + auto popFn = array.getPropertyAsFunction(runtime, "pop"); + popFn.callWithThis(runtime, array); + } + + void ReplaceAll(winrt::array_view const& items) + { + // NOTE: JSI doesn't currently seem to allow modification of arrays from native + CheckThread(); + array.setProperty(runtime, "length", static_cast(items.size())); + + for (uint32_t i = 0; i < items.size(); ++i) + { + array.setValueAtIndex(runtime, i, convert_native_to_value(runtime, items[i])); + } + } + + void SetAt(std::uint32_t index, T const& value) + { + auto size = Size(); // NOTE: Checks thread access + if (index >= size) + { + throw winrt::hresult_out_of_bounds(); + } + + array.setValueAtIndex(runtime, index, convert_native_to_value(runtime, value)); + } + + jsi::Runtime& runtime; + jsi::Array array; + std::thread::id thread_id = std::this_thread::get_id(); + }; + + template + struct array_iterable : + winrt::implements, winrt::Windows::Foundation::Collections::IIterable>, + array_vector_base, T> + { + using array_vector_base, T>::array_vector_base; + + winrt::hstring GetRuntimeClassName() const + { + return L"JsArrayIterable"; + } + }; + + template + struct array_vector_view : + winrt::implements, winrt::Windows::Foundation::Collections::IVectorView, + winrt::Windows::Foundation::Collections::IIterable>, + array_vector_base, T> + { + using array_vector_base, T>::array_vector_base; + + winrt::hstring GetRuntimeClassName() const + { + return L"JsArrayVectorView"; + } + }; + + template + struct array_vector : + winrt::implements, winrt::Windows::Foundation::Collections::IVector, + winrt::Windows::Foundation::Collections::IIterable>, + array_vector_base, T> + { + using array_vector_base, T>::array_vector_base; + + winrt::hstring GetRuntimeClassName() const + { + return L"JsArrayVector"; + } + + winrt::Windows::Foundation::Collections::IVectorView GetView() + { + return winrt::make>(this->runtime, this->array.getArray(this->runtime)); + } + }; +} + +// Value converters +namespace rnwinrt +{ + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime&, bool value) noexcept + { + return jsi::Value(value); + } + + static bool as_native(jsi::Runtime&, const jsi::Value& value) noexcept; + }; + + template + struct projected_value_traits>> + { + static jsi::Value as_value(jsi::Runtime&, T value) noexcept + { + return jsi::Value(static_cast(value)); + } + + static T as_native(jsi::Runtime&, const jsi::Value& value) + { + return static_cast(value.asNumber()); + } + }; + + template + struct projected_value_traits>> + { + static jsi::Value as_value(jsi::Runtime&, T value) noexcept + { + return jsi::Value(static_cast(static_cast>(value))); + } + + static T as_native(jsi::Runtime&, const jsi::Value& value) + { + return static_cast(static_cast>(value.asNumber())); + } + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, char16_t value); + static char16_t as_native(jsi::Runtime& runtime, const jsi::Value& value); + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, const winrt::hstring& value) + { + return make_string(runtime, static_cast(value)); + } + + static winrt::hstring as_native(jsi::Runtime& runtime, const jsi::Value& value); + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, const winrt::guid& value); + static winrt::guid as_native(jsi::Runtime& runtime, const jsi::Value& value); + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, winrt::hresult value) + { + return projected_value_traits::as_value(runtime, static_cast(value)); + } + + static winrt::hresult as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + return winrt::hresult(projected_value_traits::as_native(runtime, value)); + } + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, winrt::event_token value) + { + jsi::Object result(runtime); + result.setProperty(runtime, "value", convert_native_to_value(runtime, value.value)); + return result; + } + + static winrt::event_token as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + winrt::event_token result{}; + auto obj = value.asObject(runtime); + if (auto field = obj.getProperty(runtime, "value"); !field.isUndefined()) + result.value = convert_value_to_native(runtime, field); + return result; + } + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, winrt::Windows::Foundation::DateTime value); + static winrt::Windows::Foundation::DateTime as_native(jsi::Runtime& runtime, const jsi::Value& value); + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, winrt::Windows::Foundation::TimeSpan value); + static winrt::Windows::Foundation::TimeSpan as_native(jsi::Runtime& runtime, const jsi::Value& value); + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, winrt::Windows::Foundation::Numerics::float3x2 value); + static winrt::Windows::Foundation::Numerics::float3x2 as_native(jsi::Runtime& runtime, const jsi::Value& value); + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, winrt::Windows::Foundation::Numerics::float4x4 value); + static winrt::Windows::Foundation::Numerics::float4x4 as_native(jsi::Runtime& runtime, const jsi::Value& value); + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, winrt::Windows::Foundation::Numerics::plane value); + static winrt::Windows::Foundation::Numerics::plane as_native(jsi::Runtime& runtime, const jsi::Value& value); + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, winrt::Windows::Foundation::Numerics::quaternion value); + static winrt::Windows::Foundation::Numerics::quaternion as_native( + jsi::Runtime& runtime, const jsi::Value& value); + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, winrt::Windows::Foundation::Numerics::float2 value); + static winrt::Windows::Foundation::Numerics::float2 as_native(jsi::Runtime& runtime, const jsi::Value& value); + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, winrt::Windows::Foundation::Numerics::float3 value); + static winrt::Windows::Foundation::Numerics::float3 as_native(jsi::Runtime& runtime, const jsi::Value& value); + }; + + template <> + struct projected_value_traits + { + static jsi::Value as_value(jsi::Runtime& runtime, winrt::Windows::Foundation::Numerics::float4 value); + static winrt::Windows::Foundation::Numerics::float4 as_native(jsi::Runtime& runtime, const jsi::Value& value); + }; + + template + struct value_fill_array_wrapper + { + value_fill_array_wrapper(jsi::Runtime& runtime, const winrt::array_view& array) : + m_runtime(runtime), m_nativeArray(array), m_jsArray(runtime, array.size()) + { + } + + ~value_fill_array_wrapper() noexcept(false) + { + for (uint32_t i = 0; i < m_nativeArray.size(); ++i) + { + auto value = m_jsArray.getValueAtIndex(m_runtime, i); + if (!value.isUndefined()) + { + m_nativeArray[i] = convert_value_to_native(m_runtime, value); + } + } + } + + // NOTE: Cannot rely on an 'operator jsi::Value()' because of how JSI makes function calls + jsi::Value value() + { + return jsi::Value(m_runtime, m_jsArray); + } + + private: + jsi::Runtime& m_runtime; + winrt::array_view m_nativeArray; + jsi::Array m_jsArray; + }; + + template + struct native_fill_array_wrapper + { + native_fill_array_wrapper(jsi::Runtime& runtime, const jsi::Value& value) : + m_runtime(runtime), m_jsArray(value.asObject(runtime).asArray(runtime)) + { + auto size = m_jsArray.size(runtime); + if constexpr (std::is_base_of_v) + { + m_nativeArray.resize(size, nullptr); + } + else + { + m_nativeArray.resize(size); + } + } + + ~native_fill_array_wrapper() noexcept(false) + { + for (std::size_t i = 0; i < m_nativeArray.size(); ++i) + { + m_jsArray.setValueAtIndex(m_runtime, i, convert_native_to_value(m_runtime, m_nativeArray[i])); + } + } + + operator winrt::array_view() + { + return winrt::array_view(m_nativeArray.data(), static_cast(m_nativeArray.size())); + } + + private: + jsi::Runtime& m_runtime; + jsi::Array m_jsArray; + sso_vector m_nativeArray; // NOTE: Because std::vector makes everyone sad... + }; + + template + struct projected_value_traits> + { + // NOTE: Non-const 'T' - this is specific to 'fill array' scenarios + static value_fill_array_wrapper as_value(jsi::Runtime& runtime, const winrt::array_view& value) + { + return value_fill_array_wrapper(runtime, value); + } + + static native_fill_array_wrapper as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + return native_fill_array_wrapper(runtime, value); + } + }; + + template + struct pass_array_wrapper + { + pass_array_wrapper(jsi::Runtime& runtime, const jsi::Value& value) + { + auto array = value.asObject(runtime).asArray(runtime); + auto size = array.size(runtime); + m_data.reserve(size); + for (std::size_t i = 0; i < size; ++i) + { + m_data.push_back(convert_value_to_native(runtime, array.getValueAtIndex(runtime, i))); + } + } + + operator winrt::array_view() + { + return winrt::array_view(m_data.data(), static_cast(m_data.size())); + } + + private: + sso_vector m_data; // NOTE: Because std::vector makes everyone sad... + }; + + template + struct projected_value_traits> + { + // NOTE: Const 'T' - this is specific to 'pass array' scenarios + static jsi::Value as_value(jsi::Runtime& runtime, const winrt::array_view& value) + { + // Array is immutable; we can convert to a JS array and call it a day + jsi::Array result(runtime, value.size()); + for (uint32_t i = 0; i < value.size(); ++i) + { + result.setValueAtIndex(runtime, i, convert_native_to_value(runtime, value[i])); + } + + return result; + } + + static pass_array_wrapper as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + return pass_array_wrapper(runtime, value); + } + }; + + template + struct projected_value_traits> + { + static jsi::Value as_value(jsi::Runtime& runtime, const winrt::com_array& value) + { + auto result = jsi::Array(runtime, value.size()); + for (std::uint32_t i = 0; i < value.size(); ++i) + { + result.setValueAtIndex(runtime, i, convert_native_to_value(runtime, value[i])); + } + + return result; + } + + static winrt::com_array as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + auto array = value.asObject(runtime).asArray(runtime); + array_to_native_iterator range(runtime, array); + return winrt::com_array(range.begin(), range.end()); + } + }; + + jsi::Value convert_from_property_value( + jsi::Runtime& runtime, const winrt::Windows::Foundation::IPropertyValue& value); + winrt::Windows::Foundation::IInspectable convert_to_property_value(jsi::Runtime& runtime, const jsi::Value& value); + + template + jsi::Value convert_object_instance_to_value(jsi::Runtime& runtime, const T& value) + { + if (!value) + { + return jsi::Value::null(); + } + + if constexpr (std::is_same_v) + { + if (auto result = convert_from_property_value(runtime, value); !result.isUndefined()) + { + return result; + } + } + else if constexpr (std::is_same_v) + { + if (auto propVal = value.try_as()) + { + if (auto result = convert_from_property_value(runtime, propVal); !result.isUndefined()) + { + return result; + } + } + } + + return current_runtime_context()->instance_cache.get_instance(runtime, value); + } + + template + __declspec(noinline) std::optional convert_value_to_object_instance_impl(jsi::Runtime& runtime, + const jsi::Value& value, T (*asTargetType)(const winrt::Windows::Foundation::IInspectable&)) + { + if (value.isNull() || value.isUndefined()) + { + return nullptr; + } + + if (value.isObject()) + { + auto obj = value.getObject(runtime); + if (obj.isHostObject(runtime)) + { + return asTargetType(obj.getHostObject(runtime)->instance()); + } + } + + return std::nullopt; + } + + template + T convert_value_to_object_instance(jsi::Runtime& runtime, const jsi::Value& value) + { + if (auto result = convert_value_to_object_instance_impl( + runtime, value, [](const winrt::Windows::Foundation::IInspectable& insp) { + if constexpr (std::is_same_v) + { + return insp; + } + else + { + return insp.as(); + } + })) + { + return std::move(*result); + } + + if constexpr (std::is_same_v || + std::is_same_v) + { + if (auto result = convert_to_property_value(runtime, value)) + { + if constexpr (std::is_same_v) + { + return result; + } + else + { + return result.as(); + } + } + } + + if constexpr (pinterface_traits::is_array_convertible) + { + if (value.isObject()) + { + auto obj = value.getObject(runtime); + if (obj.isArray(runtime)) + { + using elem_type = typename pinterface_traits::value_type; + if constexpr (pinterface_traits::is_iterable) + { + return winrt::make>(runtime, obj.getArray(runtime)); + } + else if constexpr (pinterface_traits::is_vector_view) + { + return winrt::make>(runtime, obj.getArray(runtime)); + } + else // is_vector + { + static_assert(pinterface_traits::is_vector); + return winrt::make>(runtime, obj.getArray(runtime)); + } + } + } + } + + // TODO: Also IMap/IMapView? + + throw jsi::JSError(runtime, "TypeError: Cannot derive a WinRT interface for the JS value"); + } + + template + struct projected_value_traits> + { + static jsi::Value as_value(jsi::Runtime& runtime, const winrt::Windows::Foundation::IReference& value) + { + if (!value) + { + return jsi::Value::null(); + } + + return convert_native_to_value(runtime, value.Value()); + } + + static winrt::Windows::Foundation::IReference as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + if (value.isNull() || value.isUndefined()) + { + return nullptr; + } + + return winrt::box_value(convert_value_to_native(runtime, value)) + .as>(); + } + }; + + template + struct projected_value_traits> + { + static jsi::Value as_value(jsi::Runtime& runtime, const winrt::Windows::Foundation::IReferenceArray& value) + { + if (!value) + { + return jsi::Value(nullptr); + } + + return convert_native_to_value(runtime, value.Value()); + } + + static winrt::Windows::Foundation::IReferenceArray from_value(jsi::Runtime& runtime, const jsi::Value& value) + { + if (value.isNull() || value.isUndefined()) + { + return nullptr; + } + + // It doesn't seem like C++/WinRT actually provides an implementation of IReferenceArray like it does for + // IReference nor does it even map the basic array types supported by Windows::Foundation::PropertyValue. + // It can be done but since the public SDK doesn't actually make use of it, perhaps it it is not necessary + // to implement. + throw jsi::JSError(runtime, + "TypeError: Conversion to native reference array to JS not implemented for "s + typeid(T).name()); + } + }; + + template <> + struct projected_value_traits + { + using interface_type = winrt::Windows::Foundation::IAsyncAction; + static jsi::Value as_value(jsi::Runtime& runtime, const interface_type& value) + { + return jsi::Value(runtime, + jsi::Object::createFromHostObject(runtime, projected_async_instance::create(value))); + } + + static interface_type as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + // Assume that the caller got the value from calling 'operation' on the projected_async_instance + return convert_value_to_object_instance(runtime, value); + } + }; + + template + struct projected_value_traits> + { + using interface_type = winrt::Windows::Foundation::IAsyncActionWithProgress; + static jsi::Value as_value(jsi::Runtime& runtime, const interface_type& value) + { + return jsi::Value(runtime, + jsi::Object::createFromHostObject(runtime, projected_async_instance::create(value))); + } + + static interface_type as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + // Assume that the caller got the value from calling 'operation' on the projected_async_instance + return convert_value_to_object_instance(runtime, value); + } + }; + + template + struct projected_value_traits> + { + using interface_type = winrt::Windows::Foundation::IAsyncOperation; + static jsi::Value as_value(jsi::Runtime& runtime, const interface_type& value) + { + return jsi::Value(runtime, + jsi::Object::createFromHostObject(runtime, projected_async_instance::create(value))); + } + + static interface_type as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + // Assume that the caller got the value from calling 'operation' on the projected_async_instance + return convert_value_to_object_instance(runtime, value); + } + }; + + template + struct projected_value_traits> + { + using interface_type = winrt::Windows::Foundation::IAsyncOperationWithProgress; + static jsi::Value as_value(jsi::Runtime& runtime, const interface_type& value) + { + return jsi::Value(runtime, + jsi::Object::createFromHostObject(runtime, projected_async_instance::create(value))); + } + + static interface_type as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + // Assume that the caller got the value from calling 'operation' on the projected_async_instance + return convert_value_to_object_instance(runtime, value); + } + }; + + template + struct projected_value_traits> + { + static jsi::Value as_value(jsi::Runtime& runtime, const winrt::Windows::Foundation::EventHandler& value) + { + return jsi::Function::createFromHostFunction(runtime, make_propid(runtime, "EventHandler"), 2, + [value](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, size_t count) { + if (count != 2) + { + throw_invalid_delegate_arg_count(runtime, "Windows.Foundation"sv, "EventHandler"); + } + + auto arg0 = convert_value_to_native(runtime, args[0]); + auto arg1 = convert_value_to_native(runtime, args[1]); + value(arg0, arg1); + return jsi::Value::undefined(); + }); + } + + static winrt::Windows::Foundation::EventHandler as_native(jsi::Runtime& runtime, const jsi::Value& value) + { + return + [ctxt = current_runtime_context()->add_reference(), fn = value.asObject(runtime).asFunction(runtime)]( + const winrt::Windows::Foundation::IInspectable& sender, const T& args) { + // TODO: Do we need to call synchronously? One reason might be to propagate errors, but typically + // event sources don't care about those. + ctxt->call_sync([&]() { + fn.call(ctxt->runtime, convert_native_to_value(ctxt->runtime, sender), + convert_native_to_value(ctxt->runtime, args)); + }); + }; + } + }; + + template + struct projected_value_traits> + { + static jsi::Value as_value( + jsi::Runtime& runtime, const winrt::Windows::Foundation::TypedEventHandler& value) + { + return jsi::Function::createFromHostFunction(runtime, make_propid(runtime, "TypedEventHandler"), 2, + [value](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, size_t count) { + if (count != 2) + { + throw_invalid_delegate_arg_count(runtime, "Windows.Foundation"sv, "TypedEventHandler"); + } + + auto arg0 = convert_value_to_native(runtime, args[0]); + auto arg1 = convert_value_to_native(runtime, args[1]); + value(arg0, arg1); + return jsi::Value::undefined(); + }); + } + + static winrt::Windows::Foundation::TypedEventHandler as_native( + jsi::Runtime& runtime, const jsi::Value& value) + { + return [ctxt = current_runtime_context()->add_reference(), + fn = value.asObject(runtime).asFunction(runtime)](const TSender& sender, const TResult& args) { + ctxt->call_sync([&]() { + fn.call(ctxt->runtime, convert_native_to_value(ctxt->runtime, sender), + convert_native_to_value(ctxt->runtime, args)); + }); + }; + } + }; + + template + struct projected_value_traits> + { + static jsi::Value as_value( + jsi::Runtime& runtime, const winrt::Windows::Foundation::Collections::MapChangedEventHandler& value) + { + return jsi::Function::createFromHostFunction(runtime, make_propid(runtime, "MapChangedEventHandler"), 2, + [value](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, size_t count) { + if (count != 2) + { + throw_invalid_delegate_arg_count( + runtime, "Windows.Foundation.Collections"sv, "MapChangedEventHandler"); + } + + auto arg0 = convert_value_to_native>( + runtime, args[0]); + auto arg1 = + convert_value_to_native>( + runtime, args[1]); + value(arg0, arg1); + return jsi::Value::undefined(); + }); + } + + static winrt::Windows::Foundation::Collections::MapChangedEventHandler as_native( + jsi::Runtime& runtime, const jsi::Value& value) + { + return + [ctxt = current_runtime_context()->add_reference(), fn = value.asObject(runtime).asFunction(runtime)]( + const winrt::Windows::Foundation::Collections::IObservableMap& sender, + const winrt::Windows::Foundation::Collections::IMapChangedEventArgs& args) { + ctxt->call_sync([&]() { + fn.call(ctxt->runtime, convert_native_to_value(ctxt->runtime, sender), + convert_native_to_value(ctxt->runtime, args)); + }); + }; + } + }; + + template + struct projected_value_traits> + { + static jsi::Value as_value( + jsi::Runtime& runtime, const winrt::Windows::Foundation::Collections::VectorChangedEventHandler& value) + { + return jsi::Function::createFromHostFunction(runtime, make_propid(runtime, "VectorChangedEventHandler"), 2, + [value](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, size_t count) { + if (count != 2) + { + throw_invalid_delegate_arg_count( + runtime, "Windows.Foundation.Collections"sv, "VectorChangedEventHandler"); + } + + auto arg0 = convert_value_to_native>( + runtime, args[0]); + auto arg1 = + convert_value_to_native( + runtime, args[1]); + value(arg0, arg1); + return jsi::Value::undefined(); + }); + } + + static winrt::Windows::Foundation::Collections::VectorChangedEventHandler as_native( + jsi::Runtime& runtime, const jsi::Value& value) + { + return + [ctxt = current_runtime_context()->add_reference(), fn = value.asObject(runtime).asFunction(runtime)]( + const winrt::Windows::Foundation::Collections::IObservableVector& sender, + const winrt::Windows::Foundation::Collections::IVectorChangedEventArgs& args) { + ctxt->call_sync([&]() { + fn.call(ctxt->runtime, convert_native_to_value(ctxt->runtime, sender), + convert_native_to_value(ctxt->runtime, args)); + }); + }; + } + }; +} + +// Static data for generic types +namespace rnwinrt +{ + namespace interfaces::Windows::Foundation::Collections + { + using winrt::Windows::Foundation::IInspectable; + + namespace IIterable + { + template + struct interface_data + { + using native_type = winrt::Windows::Foundation::Collections::IIterable; + + static constexpr const static_interface_data::function_mapping functions[] = { + { "first", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value*) { + return convert_native_to_value(runtime, thisValue.as().First()); + } }, + }; + }; + + template + struct data_t + { + using iface = interface_data; + static constexpr const static_interface_data value{ winrt::guid_of(), {}, + {}, iface::functions }; + }; + + template + constexpr const static_interface_data& data = data_t::value; + } + + namespace IIterator + { + template + struct interface_data + { + using native_type = winrt::Windows::Foundation::Collections::IIterator; + + static constexpr const static_interface_data::property_mapping properties[] = { + { "current", + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Current()); + }, + nullptr }, + { "hasCurrent", + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().HasCurrent()); + }, + nullptr }, + }; + + static constexpr const static_interface_data::function_mapping functions[] = { + { "getMany", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto items = convert_value_to_native>(runtime, args[0]); + return convert_native_to_value(runtime, thisValue.as().GetMany(items)); + }, + 1, false }, + { "moveNext", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value*) { + return convert_native_to_value(runtime, thisValue.as().MoveNext()); + }, + 0, false }, + }; + }; + + template + struct data_t + { + using iface = interface_data; + static constexpr const static_interface_data value{ winrt::guid_of(), + iface::properties, {}, iface::functions }; + }; + + template + constexpr const static_interface_data& data = data_t::value; + } + + namespace IKeyValuePair + { + template + struct interface_data + { + using native_type = winrt::Windows::Foundation::Collections::IKeyValuePair; + + static constexpr const static_interface_data::property_mapping properties[] = { + { "key", + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Key()); + }, + nullptr }, + { "value", + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Value()); + }, + nullptr }, + }; + }; + + template + struct data_t + { + using iface = interface_data; + static constexpr const static_interface_data value{ winrt::guid_of(), + iface::properties, {}, {} }; + }; + + template + constexpr const static_interface_data& data = data_t::value; + } + + namespace IMap + { + template + struct interface_data + { + using native_type = winrt::Windows::Foundation::Collections::IMap; + + static constexpr const static_interface_data::property_mapping properties[] = { + { "size", + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Size()); + }, + nullptr }, + }; + + static constexpr const static_interface_data::function_mapping functions[] = { + { "clear", + [](jsi::Runtime&, const IInspectable& thisValue, const jsi::Value*) { + thisValue.as().Clear(); + return jsi::Value::undefined(); + }, + 0, false }, + { "getView", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value*) { + return convert_native_to_value(runtime, thisValue.as().GetView()); + }, + 0, false }, + { "hasKey", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto key = convert_value_to_native(runtime, args[0]); + return convert_native_to_value(runtime, thisValue.as().HasKey(key)); + }, + 1, false }, + { "insert", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto key = convert_value_to_native(runtime, args[0]); + auto value = convert_value_to_native(runtime, args[1]); + return convert_native_to_value(runtime, thisValue.as().Insert(key, value)); + }, + 2, false }, + { "lookup", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto key = convert_value_to_native(runtime, args[0]); + return convert_native_to_value(runtime, thisValue.as().Lookup(key)); + }, + 1, false }, + { "remove", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto key = convert_value_to_native(runtime, args[0]); + thisValue.as().Remove(key); + return jsi::Value::undefined(); + }, + 1, false }, + }; + + static std::pair, std::optional> runtime_get_property( + jsi::Runtime& runtime, const IInspectable& thisValue, std::string_view name) + { + // TODO: Should we also include 'Char' and 'Guid' as well? + if constexpr (std::is_same_v) + { + // If the "property" is any other string, then that translates to a 'Lookup' call + auto map = thisValue.as(); + auto key = winrt::to_hstring(name); + if (map.HasKey(key)) + { + return { convert_native_to_value(runtime, map.Lookup(key)), std::nullopt }; + } + } + + return { std::nullopt, std::nullopt }; + }; + + static bool runtime_set_property(jsi::Runtime& runtime, const IInspectable& thisValue, + std::string_view name, const jsi::Value& value) + { + if constexpr (std::is_same_v) + { + auto map = thisValue.as(); + auto key = winrt::to_hstring(name); + map.Insert(key, convert_value_to_native(runtime, value)); + return true; + } + else + return false; + } + }; + + template + struct data_t + { + using iface = interface_data; + static constexpr const static_interface_data value{ winrt::guid_of(), + iface::properties, {}, iface::functions, &iface::runtime_get_property, + &iface::runtime_set_property }; + }; + + template + constexpr const static_interface_data& data = data_t::value; + } + + namespace IMapChangedEventArgs + { + template + struct interface_data + { + using native_type = winrt::Windows::Foundation::Collections::IMapChangedEventArgs; + + static constexpr const static_interface_data::property_mapping properties[] = { + { "collectionChange", + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().CollectionChange()); + }, + nullptr }, + { "key", + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Key()); + }, + nullptr }, + }; + }; + + template + struct data_t + { + using iface = interface_data; + static constexpr const static_interface_data value{ winrt::guid_of(), + iface::properties, {}, {} }; + }; + + template + constexpr const static_interface_data& data = data_t::value; + } + + namespace IMapView + { + template + struct interface_data + { + using native_type = winrt::Windows::Foundation::Collections::IMapView; + + static constexpr const static_interface_data::property_mapping properties[] = { + { "size", + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Size()); + }, + nullptr }, + }; + + static constexpr const static_interface_data::function_mapping functions[] = { + { "hasKey", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto key = convert_value_to_native(runtime, args[0]); + return convert_native_to_value(runtime, thisValue.as().HasKey(key)); + }, + 1, false }, + { "lookup", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto key = convert_value_to_native(runtime, args[0]); + return convert_native_to_value(runtime, thisValue.as().Lookup(key)); + }, + 1, false }, + { "split", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value*) { + winrt::Windows::Foundation::Collections::IMapView first; + winrt::Windows::Foundation::Collections::IMapView second; + thisValue.as().Split(first, second); + return make_void_return_struct(runtime, "first", first, "second", second); + }, + 0, false }, + }; + + static std::pair, std::optional> runtime_get_property( + jsi::Runtime& runtime, const IInspectable& thisValue, std::string_view name) + { + // TODO: Should we also include 'Char' and 'Guid' as well? + if constexpr (std::is_same_v) + { + // If the "property" is any other string, then that translates to a 'Lookup' call + auto map = thisValue.as(); + auto key = winrt::to_hstring(name); + if (map.HasKey(key)) + { + return { convert_native_to_value(runtime, map.Lookup(key)), std::nullopt }; + } + } + + return { std::nullopt, std::nullopt }; + }; + }; + + template + struct data_t + { + using iface = interface_data; + static constexpr const static_interface_data value{ winrt::guid_of(), + iface::properties, {}, iface::functions, &iface::runtime_get_property }; + }; + + template + constexpr const static_interface_data& data = data_t::value; + } + + namespace IObservableMap + { + template + struct interface_data + { + using native_type = winrt::Windows::Foundation::Collections::IObservableMap; + + static constexpr const static_interface_data::event_mapping events[] = { + { "mapchanged", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value& callback) { + auto handler = convert_value_to_native< + winrt::Windows::Foundation::Collections::MapChangedEventHandler>( + runtime, callback); + return thisValue.as().MapChanged(handler); + }, + [](const IInspectable& thisValue, winrt::event_token token) { + thisValue.as().MapChanged(token); + } }, + }; + }; + + template + struct data_t + { + using iface = interface_data; + static constexpr const static_interface_data value{ winrt::guid_of(), {}, + iface::events, {} }; + }; + + template + constexpr const static_interface_data& data = data_t::value; + } + + namespace IObservableVector + { + template + struct interface_data + { + using native_type = winrt::Windows::Foundation::Collections::IObservableVector; + + static constexpr const static_interface_data::event_mapping events[] = { + { "vectorchanged", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value& callback) { + auto handler = convert_value_to_native< + winrt::Windows::Foundation::Collections::VectorChangedEventHandler>( + runtime, callback); + return thisValue.as().VectorChanged(handler); + }, + [](const IInspectable& thisValue, winrt::event_token token) { + thisValue.as().VectorChanged(token); + } }, + }; + }; + + template + struct data_t + { + using iface = interface_data; + static constexpr const static_interface_data value{ winrt::guid_of(), {}, + iface::events, {} }; + }; + + template + constexpr const static_interface_data& data = data_t::value; + } + + inline std::optional fwd_array_prototype(jsi::Runtime& runtime, std::string_view name) + { + auto arrayClass = runtime.global().getPropertyAsObject(runtime, "Array"); + auto arrayProto = arrayClass.getPropertyAsObject(runtime, "prototype"); + + // NOTE: 'name' is constructed from a std::string + assert(name.data()[name.size()] == 0); + auto result = arrayProto.getProperty(runtime, name.data()); + if (!result.isUndefined()) + return result; + + return std::nullopt; + } + + inline uint32_t vector_index_from_arg(jsi::Runtime& runtime, const jsi::Value* args, size_t count, size_t index, + uint32_t size, uint32_t defaultValue = 0) + { + if (index >= count) + return defaultValue; + + auto value = to_integer_or_infinity(runtime, args[index]); + if (value == -std::numeric_limits::infinity()) + return 0; + + if (value < 0) + { + value = std::max(0.0, size + value); + } + else + { + value = std::min(value, static_cast(size)); + } + + assert(static_cast(static_cast(value)) == value); + return static_cast(value); + } + + inline jsi::Function callback_from_arg( + jsi::Runtime& runtime, const jsi::Value* args, size_t count, size_t index = 0) + { + if (index >= count) + { + throw jsi::JSError(runtime, "TypeError: undefined is not a function"); + } + + return args[index].asObject(runtime).asFunction(runtime); + } + + inline std::optional callback_this_arg( + jsi::Runtime& runtime, const jsi::Value* args, size_t count, size_t index = 1) + { + if ((index < count) && args[index].isObject()) + { + return args[index].getObject(runtime); + } + + return std::nullopt; + } + + // NOTE: From Array.prototype.concat + template + inline __declspec(noinline) jsi::Value + vector_concat_impl(jsi::Runtime& runtime, const TVector& vector, const jsi::Value* args, size_t count, + winrt::Windows::Foundation::Collections::IVector (*vectorCast)( + const winrt::Windows::Foundation::IInspectable&), + winrt::Windows::Foundation::Collections::IVectorView (*vectorViewCast)( + const winrt::Windows::Foundation::IInspectable&)) + { + using namespace winrt::Windows::Foundation::Collections; + + // TODO: We could in theory try and pre-calculate the final size, but it's not quite clear that would be + // worth it + jsi::Array result(runtime, vector.Size()); + + size_t i = 0; + for (auto&& value : vector) + { + result.setValueAtIndex(runtime, i++, convert_native_to_value(runtime, value)); + } + + auto pushFn = result.getPropertyAsFunction(runtime, "push"); + for (size_t argIndex = 0; argIndex < count; ++argIndex) + { + auto& arg = args[argIndex]; + + // TODO: By the JS standard, we should check to see if the argument responds to + // 'Symbol.isConcatSpreadable', however this poses two problems. The first is that JSI does not offer us + // the ability to check this, and the second is that JSI does not allow us to advertise 'IVector*' types + // as 'isConcatSpreadable'. As a workaround - at least until JSI has the functionality we need - we'll + // assume 'isConcatSpreadable' to be true if (1) the argument is an array, or (2) the argument is an + // 'IVector*'. Note that we'll be missing out on non-array/non-vector spreadable types as well as + // 'IVector*' types. + // Also note that this problem exists in the reverse direction - when a vector is used as an argument to + // an 'Array.prototype.concat' call since we'll be unable to use our logic below during that call. + // Again, the ideal solution would be for JSI to give us the functionality we require + if (!arg.isObject()) + { + pushFn.callWithThis(runtime, result, arg); + } + else if (auto obj = arg.getObject(runtime); obj.isArray(runtime)) + { + auto arr = obj.getArray(runtime); + auto arrLen = arr.length(runtime); + for (size_t j = 0; j < arrLen; ++j) + { + pushFn.callWithThis(runtime, result, arr.getValueAtIndex(runtime, j)); + } + } + else if (obj.isHostObject(runtime)) + { + auto hostObj = obj.getHostObject(runtime); + if (auto v = vectorCast(hostObj->instance())) + { + for (auto&& value : v) + { + pushFn.callWithThis(runtime, result, convert_native_to_value(runtime, value)); + } + } + else if (auto view = vectorViewCast(hostObj->instance())) + { + for (auto&& value : v) + { + pushFn.callWithThis(runtime, result, convert_native_to_value(runtime, value)); + } + } + // TODO: IIterable? + else + { + pushFn.callWithThis(runtime, result, arg); + } + } + else + { + pushFn.callWithThis(runtime, result, arg); + } + } + + return result; + } + + template + inline jsi::Value vector_concat( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVectorView to get COMDAT folded together along with IVector* for + // similar enough 'U' (e.g. classes/interfaces get folded together) + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Foundation::Collections; + using value_type = typename pinterface_traits::value_type; + return vector_concat_impl( + runtime, convert_value_to_native(runtime, thisValue), args, count, + [](const IInspectable& insp) { return insp.try_as>(); }, + [](const IInspectable& insp) { return insp.try_as>(); }); + } + + // NOTE: From Array.prototype.every + template + inline __declspec(noinline) jsi::Value vector_every_impl(jsi::Runtime& runtime, const jsi::Value& thisValue, + const TVector& vector, const jsi::Value* args, size_t count) + { + auto fn = callback_from_arg(runtime, args, count); + auto thisArg = callback_this_arg(runtime, args, count); + + double index = 0; + for (auto&& value : vector) + { + jsi::Value result; + if (thisArg) + { + result = + fn.callWithThis(runtime, *thisArg, convert_native_to_value(runtime, value), index++, thisValue); + } + else + { + result = fn.call(runtime, convert_native_to_value(runtime, value), index++, thisValue); + } + + if (!to_boolean(runtime, result)) + return false; + } + + return true; + } + + template + inline jsi::Value vector_every( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVectorView to get COMDAT folded together along with IVector* for + // similar enough 'U' (e.g. classes/interfaces get folded together) + return vector_every_impl( + runtime, thisValue, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.filter + template + inline __declspec(noinline) jsi::Value vector_filter_impl(jsi::Runtime& runtime, const jsi::Value& thisValue, + const TVector& vector, const jsi::Value* args, size_t count) + { + auto fn = callback_from_arg(runtime, args, count); + auto thisArg = callback_this_arg(runtime, args, count); + + jsi::Array result(runtime, 0); + auto pushFn = result.getPropertyAsFunction(runtime, "push"); + + double index = 0; + for (auto&& value : vector) + { + jsi::Value includeResult; + if (thisArg) + { + includeResult = + fn.callWithThis(runtime, *thisArg, convert_native_to_value(runtime, value), index++, thisValue); + } + else + { + includeResult = fn.call(runtime, convert_native_to_value(runtime, value), index++, thisValue); + } + + if (to_boolean(runtime, includeResult)) + { + pushFn.callWithThis(runtime, result, convert_native_to_value(runtime, value)); + } + } + + return result; + } + + template + inline jsi::Value vector_filter( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVectorView to get COMDAT folded together along with IVector* for + // similar enough 'U' (e.g. classes/interfaces get folded together) + return vector_filter_impl( + runtime, thisValue, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.forEach + template + inline __declspec(noinline) jsi::Value vector_forEach_impl(jsi::Runtime& runtime, const jsi::Value& thisValue, + const TVector& vector, const jsi::Value* args, size_t count) + { + auto fn = callback_from_arg(runtime, args, count); + auto thisArg = callback_this_arg(runtime, args, count); + + double index = 0; + for (auto&& value : vector) + { + if (thisArg) + { + fn.callWithThis(runtime, *thisArg, convert_native_to_value(runtime, value), index++, thisValue); + } + else + { + fn.call(runtime, convert_native_to_value(runtime, value), index++, thisValue); + } + } + + return jsi::Value::undefined(); + } + + template + inline jsi::Value vector_forEach( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVectorView to get COMDAT folded together along with IVector* for + // similar enough 'U' (e.g. classes/interfaces get folded together) + return vector_forEach_impl( + runtime, thisValue, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.lastIndexOf + template + inline __declspec(noinline) jsi::Value + vector_lastIndexOf_impl(jsi::Runtime& runtime, const TVector& vector, const jsi::Value* args, size_t count) + { + auto size = vector.Size(); + if ((size == 0) || (count < 1)) + return -1; + + int64_t fromIndex = size - 1; + if (count >= 2) + { + auto val = to_integer_or_infinity(runtime, args[1]); + if (val == -std::numeric_limits::infinity()) + return -1; + + if (val >= 0) + { + fromIndex = static_cast(std::min(static_cast(fromIndex), val)); + } + else + { + fromIndex = static_cast(size + val); + } + } + + for (; fromIndex >= 0; --fromIndex) + { + if (jsi::Value::strictEquals(runtime, args[0], + convert_native_to_value(runtime, vector.GetAt(static_cast(fromIndex))))) + return static_cast(fromIndex); + } + + return -1; + } + + template + inline jsi::Value vector_lastIndexOf( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVectorView to get COMDAT folded together along with IVector* for + // similar enough 'U' (e.g. classes/interfaces get folded together) + return vector_lastIndexOf_impl(runtime, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.map + template + inline __declspec(noinline) jsi::Value vector_map_impl(jsi::Runtime& runtime, const jsi::Value& thisValue, + const TVector& vector, const jsi::Value* args, size_t count) + { + auto fn = callback_from_arg(runtime, args, count); + auto thisArg = callback_this_arg(runtime, args, count); + + jsi::Array result(runtime, vector.Size()); + uint32_t index = 0; + for (auto&& value : vector) + { + jsi::Value mapResult; + if (thisArg) + { + mapResult = fn.callWithThis(runtime, *thisArg, convert_native_to_value(runtime, value), + static_cast(index), thisValue); + } + else + { + mapResult = fn.call( + runtime, convert_native_to_value(runtime, value), static_cast(index), thisValue); + } + + result.setValueAtIndex(runtime, index, std::move(mapResult)); + ++index; + } + + return result; + } + + template + inline jsi::Value vector_map( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVectorView to get COMDAT folded together along with IVector* for + // similar enough 'U' (e.g. classes/interfaces get folded together) + return vector_map_impl( + runtime, thisValue, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.reduce + template + inline __declspec(noinline) jsi::Value vector_reduce_impl(jsi::Runtime& runtime, const jsi::Value& thisValue, + const TVector& vector, const jsi::Value* args, size_t count) + { + auto size = vector.Size(); + auto fn = callback_from_arg(runtime, args, count); + + if ((size == 0) && (count < 2)) + { + throw jsi::JSError(runtime, "TypeError: Reduce of empty vector with no initial value"); + } + + jsi::Value accum; + uint32_t i = 0; + if (count >= 2) + { + accum = jsi::Value(runtime, args[1]); + } + else + { + accum = convert_native_to_value(runtime, vector.GetAt(i++)); + } + + for (; i < size; ++i) + { + accum = fn.call(runtime, accum, convert_native_to_value(runtime, vector.GetAt(i)), + static_cast(i), thisValue); + } + + return accum; + } + + template + inline jsi::Value vector_reduce( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVectorView to get COMDAT folded together along with IVector* for + // similar enough 'U' (e.g. classes/interfaces get folded together) + return vector_reduce_impl( + runtime, thisValue, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.reduceRight + template + inline __declspec(noinline) jsi::Value vector_reduceRight_impl(jsi::Runtime& runtime, + const jsi::Value& thisValue, const TVector& vector, const jsi::Value* args, size_t count) + { + auto size = vector.Size(); + auto fn = callback_from_arg(runtime, args, count); + + if ((size == 0) && (count < 2)) + { + throw jsi::JSError(runtime, "TypeError: Reduce of empty vector with no initial value"); + } + + jsi::Value accum; + uint32_t i = size; + if (count >= 2) + { + accum = jsi::Value(runtime, args[1]); + } + else + { + accum = convert_native_to_value(runtime, vector.GetAt(--i)); + } + + while (i-- > 0) + { + accum = fn.call(runtime, accum, convert_native_to_value(runtime, vector.GetAt(i)), + static_cast(i), thisValue); + } + + return accum; + } + + template + inline jsi::Value vector_reduceRight( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVectorView to get COMDAT folded together along with IVector* for + // similar enough 'U' (e.g. classes/interfaces get folded together) + return vector_reduceRight_impl( + runtime, thisValue, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.slice + template + inline __declspec(noinline) jsi::Value + vector_slice_impl(jsi::Runtime& runtime, const TVector& vector, const jsi::Value* args, size_t count) + { + auto size = vector.Size(); + auto start = vector_index_from_arg(runtime, args, count, 0, size); + auto end = vector_index_from_arg(runtime, args, count, 1, size, size); + if (start > end) + end = start; + + auto copySize = end - start; + jsi::Array result(runtime, copySize); + for (size_t i = 0; start < end; ++i, ++start) + { + result.setValueAtIndex(runtime, i, convert_native_to_value(runtime, vector.GetAt(start))); + } + + return result; + } + + template + inline jsi::Value vector_slice( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVectorView to get COMDAT folded together along with IVector* for + // similar enough 'U' (e.g. classes/interfaces get folded together) + return vector_slice_impl(runtime, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.some + template + inline __declspec(noinline) jsi::Value vector_some_impl(jsi::Runtime& runtime, const jsi::Value& thisValue, + const TVector& vector, const jsi::Value* args, size_t count) + { + auto fn = callback_from_arg(runtime, args, count); + auto thisArg = callback_this_arg(runtime, args, count); + + double index = 0; + for (auto&& value : vector) + { + jsi::Value result; + if (thisArg) + { + result = + fn.callWithThis(runtime, *thisArg, convert_native_to_value(runtime, value), index++, thisValue); + } + else + { + result = fn.call(runtime, convert_native_to_value(runtime, value), index++, thisValue); + } + + if (to_boolean(runtime, result)) + return true; + } + + return false; + } + + template + inline jsi::Value vector_some( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVectorView to get COMDAT folded together along with IVector* for + // similar enough 'U' (e.g. classes/interfaces get folded together) + return vector_some_impl( + runtime, thisValue, convert_value_to_native(runtime, thisValue), args, count); + } + + namespace IVector + { + template + struct interface_data + { + using native_type = winrt::Windows::Foundation::Collections::IVector; + + static constexpr const static_interface_data::property_mapping properties[] = { + { "length", // NOTE: From Array.prototype + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Size()); + }, + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value& value) { + // Following Chakra's behavior, setting the length can only be used to remove elements + auto vector = thisValue.as(); + auto currLen = vector.Size(); + auto newLen = convert_value_to_native(runtime, value); + if (newLen > currLen) + { + throw jsi::JSError(runtime, "TypeError: Cannot assign 'length' to IVector that is " + "greater than its current length"); + } + else if (newLen == 0) + { + vector.Clear(); + } + else + { + for (; currLen > newLen; --currLen) + { + vector.RemoveAtEnd(); + } + } + } }, + { "size", + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Size()); + }, + nullptr }, + }; + + static constexpr const static_interface_data::function_mapping functions[] = { + { "append", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto value = convert_value_to_native(runtime, args[0]); + thisValue.as().Append(value); + return jsi::Value::undefined(); + }, + 1, false }, + { "clear", + [](jsi::Runtime&, const IInspectable& thisValue, const jsi::Value*) { + thisValue.as().Clear(); + return jsi::Value::undefined(); + }, + 0, false }, + { "getAt", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto index = convert_value_to_native(runtime, args[0]); + return convert_native_to_value(runtime, thisValue.as().GetAt(index)); + }, + 1, false }, + { "getMany", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto startIndex = convert_value_to_native(runtime, args[0]); + auto items = convert_value_to_native>(runtime, args[1]); + return convert_native_to_value( + runtime, thisValue.as().GetMany(startIndex, items)); + }, + 2, false }, + { "getView", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value*) { + return convert_native_to_value(runtime, thisValue.as().GetView()); + }, + 0, false }, + { "indexOf", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto value = convert_value_to_native(runtime, args[0]); + uint32_t index; + auto returnValue = thisValue.as().IndexOf(value, index); + return make_return_struct(runtime, returnValue, "index", index); + }, + 1, false }, + { "insertAt", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto index = convert_value_to_native(runtime, args[0]); + auto value = convert_value_to_native(runtime, args[1]); + thisValue.as().InsertAt(index, value); + return jsi::Value::undefined(); + }, + 2, false }, + { "removeAt", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto index = convert_value_to_native(runtime, args[0]); + thisValue.as().RemoveAt(index); + return jsi::Value::undefined(); + }, + 1, false }, + { "removeAtEnd", + [](jsi::Runtime&, const IInspectable& thisValue, const jsi::Value*) { + thisValue.as().RemoveAtEnd(); + return jsi::Value::undefined(); + }, + 0, false }, + { "replaceAll", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto items = convert_value_to_native>(runtime, args[0]); + thisValue.as().ReplaceAll(items); + return jsi::Value::undefined(); + }, + 1, false }, + { "setAt", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto index = convert_value_to_native(runtime, args[0]); + auto value = convert_value_to_native(runtime, args[1]); + thisValue.as().SetAt(index, value); + return jsi::Value::undefined(); + }, + 2, false }, + }; + + using convert_item_t = T (*)(jsi::Runtime&, const jsi::Value&); + + static convert_item_t item_converter() + { + // NOTE: The optimizer seems to be very agressive at optimizing away function pointers to static + // member functions, but a lot less agressive at optimizing away function pointers created from + // lambdas, hence why we return a lambda here. + return [](jsi::Runtime& runtime, const jsi::Value& value) { + return convert_value_to_native(runtime, value); + }; + } + + // NOTE: From Array.prototype.copyWithin + static __declspec(noinline) jsi::Value copyWithin_impl(jsi::Runtime& runtime, + const jsi::Value& thisValue, const native_type& vector, const jsi::Value* args, size_t count) + { + auto size = vector.Size(); + auto target = vector_index_from_arg(runtime, args, count, 0, size); + auto start = vector_index_from_arg(runtime, args, count, 1, size); + auto end = vector_index_from_arg(runtime, args, count, 2, size, size); + + if (end > start) + { + if (target < start) // Forward copy + { + // Because we're copying to earlier in the list, the [begin, end) pair define the length + while (start < end) + { + vector.SetAt(target++, vector.GetAt(start++)); + } + } + else // Reverse copy + { + // Because we're copying later in the list, either [begin, end) or [target, size) define the + // length + auto copyCount = std::min(end - start, size - target); + while (copyCount-- != 0) + { + vector.SetAt(target + copyCount, vector.GetAt(start + copyCount)); + } + } + } + + return jsi::Value(runtime, thisValue); + } + + static jsi::Value copyWithin( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVector to get COMDAT folded together for a similar enough + // 'U' (e.g. classes/interfaces get folded together) + return copyWithin_impl( + runtime, thisValue, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.pop + static __declspec(noinline) jsi::Value + pop_impl(jsi::Runtime& runtime, const native_type& vector, const jsi::Value*, size_t) + { + auto size = vector.Size(); + if (size == 0) + return jsi::Value::undefined(); + + auto result = convert_native_to_value(runtime, vector.GetAt(size - 1)); + vector.RemoveAtEnd(); + return result; + } + + static jsi::Value pop( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVector to get COMDAT folded together for a similar enough + // 'U' (e.g. classes/interfaces get folded together) + return pop_impl(runtime, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.push + static __declspec(noinline) jsi::Value push_impl(jsi::Runtime& runtime, const native_type& vector, + const jsi::Value* args, size_t count, convert_item_t converter) + { + // TODO: Why do we have to define this function? Based off the specification, we should satisfy all + // requements for Array.prototype.push to work + for (size_t i = 0; i < count; ++i) + { + vector.Append(converter(runtime, args[i])); + } + + return static_cast(vector.Size()); + } + + static jsi::Value push( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVector to get COMDAT folded together for a similar enough + // 'U' (e.g. classes/interfaces get folded together) + return push_impl(runtime, convert_value_to_native(runtime, thisValue), args, count, + item_converter()); + } + + // NOTE: From Array.prototype.reverse + static __declspec(noinline) jsi::Value reverse_impl(jsi::Runtime& runtime, const jsi::Value& thisValue, + const native_type& vector, const jsi::Value*, size_t) + { + auto size = vector.Size(); + uint32_t left = 0; + uint32_t right = (size != 0) ? size - 1 : 0; + for (; left < right; ++left, --right) + { + auto temp = vector.GetAt(right); + vector.SetAt(right, vector.GetAt(left)); + vector.SetAt(left, temp); + } + + return jsi::Value(runtime, thisValue); + } + + static jsi::Value reverse( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVector to get COMDAT folded together for a similar enough + // 'U' (e.g. classes/interfaces get folded together) + return reverse_impl( + runtime, thisValue, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.shift + static __declspec(noinline) jsi::Value + shift_impl(jsi::Runtime& runtime, const native_type& vector, const jsi::Value*, size_t) + { + auto size = vector.Size(); + if (size == 0) + return jsi::Value::undefined(); + + auto result = convert_native_to_value(runtime, vector.GetAt(0)); + for (uint32_t i = 1; i < size; ++i) + { + vector.SetAt(i - 1, vector.GetAt(i)); + } + + vector.RemoveAtEnd(); + return result; + } + + static jsi::Value shift( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVector to get COMDAT folded together for a similar enough + // 'U' (e.g. classes/interfaces get folded together) + return shift_impl(runtime, convert_value_to_native(runtime, thisValue), args, count); + } + + // NOTE: From Array.prototype.sort + static __declspec(noinline) jsi::Value sort_impl(jsi::Runtime& runtime, const jsi::Value& thisValue, + const native_type& vector, const jsi::Value* args, size_t count, convert_item_t converter) + { + auto size = vector.Size(); + if (count >= 1) + { + auto compareFn = args[0].asObject(runtime).asFunction(runtime); + + std::vector values; + values.reserve(size); + for (auto&& value : vector) + { + values.push_back(convert_native_to_value(runtime, value)); + } + + std::stable_sort(values.begin(), values.end(), [&](auto&& lhs, auto&& rhs) { + return to_number(runtime, compareFn.call(runtime, lhs, rhs)) < 0; + }); + + for (uint32_t i = 0; i < size; ++i) + { + vector.SetAt(i, converter(runtime, values[i])); + } + } + else + { + // Default is to sort 'toString' representations + std::vector> values; + values.reserve(size); + for (auto value : vector) + { + values.emplace_back( + string_to_utf16(runtime, convert_native_to_value(runtime, value).toString(runtime)), + std::move(value)); + } + + std::stable_sort(values.begin(), values.end(), + [&](auto&& lhs, auto&& rhs) { return lhs.first < rhs.first; }); + + for (uint32_t i = 0; i < size; ++i) + { + vector.SetAt(i, values[i].second); + } + } + + return jsi::Value(runtime, thisValue); + } + + static jsi::Value sort( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + return sort_impl(runtime, thisValue, convert_value_to_native(runtime, thisValue), args, + count, item_converter()); + } + + // NOTE: From Array.prototype.splice + static __declspec(noinline) jsi::Value splice_impl(jsi::Runtime& runtime, const native_type& vector, + const jsi::Value* args, size_t count, convert_item_t converter) + { + auto size = vector.Size(); + auto start = vector_index_from_arg(runtime, args, count, 0, size); + + uint32_t deleteCount = + (count >= 2) ? static_cast(std::clamp(to_integer_or_infinity(runtime, args[1]), 0.0, + static_cast(size - start))) : + (count >= 1) ? (size - start) : + 0; + + uint32_t insertCount = (count >= 3) ? static_cast(count - 2) : 0; + auto assignCount = std::min(deleteCount, insertCount); + deleteCount -= assignCount; + insertCount -= assignCount; + + jsi::Array result(runtime, static_cast(deleteCount) + assignCount); + uint32_t i = 0; + while (assignCount-- > 0) + { + result.setValueAtIndex(runtime, i, convert_native_to_value(runtime, vector.GetAt(start))); + vector.SetAt(start, converter(runtime, args[2 + i])); + ++i; + ++start; + } + + while (deleteCount-- > 0) + { + assert(insertCount == 0); + result.setValueAtIndex(runtime, i, convert_native_to_value(runtime, vector.GetAt(start))); + vector.RemoveAt(start); + ++i; + } + assert(i == result.length(runtime)); + + while (insertCount-- > 0) + { + vector.InsertAt(start, converter(runtime, args[2 + i])); + ++i; + ++start; + } + + return result; + } + + static jsi::Value splice( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVector to get COMDAT folded together for a similar enough + // 'U' (e.g. classes/interfaces get folded together) + return splice_impl(runtime, convert_value_to_native(runtime, thisValue), args, count, + item_converter()); + } + + // NOTE: From Array.prototype.unshift + static __declspec(noinline) jsi::Value unshift_impl(jsi::Runtime& runtime, const native_type& vector, + const jsi::Value* args, size_t count, convert_item_t converter) + { + for (size_t i = count; i-- > 0;) + { + vector.InsertAt(0, converter(runtime, args[i])); + } + + return static_cast(vector.Size()); + } + + static jsi::Value unshift( + jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count) + { + // NOTE: This causes IVector and IVector to get COMDAT folded together for a similar enough + // 'U' (e.g. classes/interfaces get folded together) + return unshift_impl(runtime, convert_value_to_native(runtime, thisValue), args, count, + item_converter()); + } + + static constexpr const struct array_proto_functions_t + { + std::string_view name; + call_function_t function; + int arg_count; + } array_proto_functions[] = { + { "concat"sv, &vector_concat, 1 }, // NOTE: Cannot use Array.prototype.concat since we + // cannot satisfy 'IsConcatSpreadable' + { "copyWithin"sv, ©Within, 2 }, // NOTE: Cannot use Array.prototype.copyWithin since we cannot + // satisfy 'HasProperty' + // NOTE: Forward to Array.prototype.entries since we satisfy 'CreateArrayIterator' requirements + { "every"sv, &vector_every, 1 }, // NOTE: Cannot use Array.prototype.every since we + // cannot satisfy 'HasProperty' + // NOTE: Forward to Array.prototype.fill since we satisfy all requirements + { "filter"sv, &vector_filter, 1 }, // NOTE: Cannot use Array.prototype.filter since we + // cannot satisfy 'HasProperty' + // NOTE: Forward to Array.prototype.find since we satisfy all requirements + // NOTE: Forward to Array.prototype.findIndex since we satisfy all requirements + // TODO: flat & flatMap + { "forEach"sv, &vector_forEach, 1 }, // NOTE: Cannot use Array.prototype.forEach since + // we cannot satisfy 'HasProperty' + // NOTE: Forward to Array.prototype.includes since we satisfy all requirements + // NOTE: 'indexOf' is a function that exists on IVector, so that takes precedence + // NOTE: Forward to Array.prototype.join since we satisfy all requirements + // NOTE: Forward to Array.prototype.keys since we satisfy all requirements + { "lastIndexOf"sv, &vector_lastIndexOf, + 1 }, // NOTE: Cannot use Array.prototype.lastIndexOf since we + // cannot satisfy 'HasProperty' + { "map"sv, &vector_map, 1 }, // NOTE: Cannot use Array.prototype.map since we cannot + // satisfy 'HasProperty' + { "pop"sv, &pop, + 0 }, // NOTE: Cannot use Array.prototype.pop since we cannot satisfy 'DeletePropertyOrThrow' + { "push"sv, &push, + 1 }, // TODO: It's not actually clear why we can't just forward to Array.prototype.push + { "reduce"sv, &vector_reduce, 1 }, // NOTE: Cannot use Array.prototype.reduce since we + // cannot satisfy 'HasProperty' + { "reduceRight"sv, &vector_reduceRight, + 1 }, // NOTE: Cannot use Array.prototype.reduceRight since we + // cannot satisfy 'HasProperty' + { "reverse"sv, &reverse, + 0 }, // NOTE: Cannot use Array.prototype.reverse since we cannot satisfy 'HasProperty' + { "shift"sv, &shift, + 0 }, // NOTE: Cannot use Array.prototype.shift since we cannot satisfy 'HasProperty' + { "slice"sv, &vector_slice, 2 }, // NOTE: Cannot use Array.prototype.slice since we + // cannot satisfy 'HasProperty' + { "some"sv, &vector_some, 1 }, // NOTE: Cannot use Array.prototype.some since we cannot + // satisfy 'HasProperty' + { "sort"sv, &sort, + 1 }, // NOTE: Cannot use Array.prototype.sort since we cannot satisfy 'HasProperty' + { "splice"sv, &splice, + 2 }, // NOTE: Cannot use Array.prototype.splice since we cannot satisfy 'HasProperty' + // NOTE: Forward to Array.prototype.toLocaleString since we satisfy all requirements + // NOTE: Forward to Array.prototype.toString since we satisfy all requirements + { "unshift"sv, &unshift, + 1 }, // NOTE: Cannot use Array.prototype.unshift since we cannot satisfy 'HasProperty' + // NOTE: Forward to Array.prototype.values since we satisfy 'CreateArrayIterator' requirements + }; + + static std::pair, std::optional> runtime_get_property_impl( + jsi::Runtime& runtime, const IInspectable& thisValue, std::string_view name, + span protoFunctions, native_type (*queryThis)(const IInspectable&)) + { + // If the "property" is a number, then that translates to a 'GetAt' call + if (auto index = index_from_name(name)) + { + auto vector = queryThis(thisValue); + if (*index >= vector.Size()) + { + return { jsi::Value::undefined(), std::nullopt }; + } + + return { convert_native_to_value(runtime, vector.GetAt(*index)), std::nullopt }; + } + + auto itr = std::find_if( + protoFunctions.begin(), protoFunctions.end(), [&](auto& pair) { return pair.name == name; }); + if (itr != protoFunctions.end()) + { + return { jsi::Function::createFromHostFunction( + runtime, make_propid(runtime, name), itr->arg_count, itr->function), + std::nullopt }; + } + + return { std::nullopt, fwd_array_prototype(runtime, name) }; + } + + static std::pair, std::optional> runtime_get_property( + jsi::Runtime& runtime, const IInspectable& thisValue, std::string_view name) + { + return runtime_get_property_impl(runtime, thisValue, name, array_proto_functions, + [](const IInspectable& thisVal) { return thisVal.as(); }); + } + + static bool runtime_set_property(jsi::Runtime& runtime, const IInspectable& thisValue, + std::string_view name, const jsi::Value& value) + { + // If the "property" is a number, then that translates to a 'SetAt' call + if (auto index = index_from_name(name)) + { + auto vector = thisValue.as(); + if (*index == vector.Size()) + { + // Following Chakra's behavior, assigning to one-past the end appends + vector.Append(convert_value_to_native(runtime, value)); + } + else + { + vector.SetAt(*index, convert_value_to_native(runtime, value)); + } + + return true; + } + + return false; + } + }; + + template + struct data_t + { + using iface = interface_data; + static constexpr const static_interface_data value{ winrt::guid_of(), + iface::properties, {}, iface::functions, &iface::runtime_get_property, + &iface::runtime_set_property }; + }; + + template + constexpr const static_interface_data& data = data_t::value; + } + + namespace IVectorView + { + template + struct interface_data + { + using native_type = winrt::Windows::Foundation::Collections::IVectorView; + + static constexpr const static_interface_data::property_mapping properties[] = { + { "length", // NOTE: From Array.prototype + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Size()); + }, + nullptr }, + { "size", + [](jsi::Runtime& runtime, const IInspectable& thisValue) { + return convert_native_to_value(runtime, thisValue.as().Size()); + }, + nullptr }, + }; + + static constexpr const static_interface_data::function_mapping functions[] = { + { "getAt", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto index = convert_value_to_native(runtime, args[0]); + return convert_native_to_value(runtime, thisValue.as().GetAt(index)); + }, + 1, false }, + { "getMany", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto startIndex = convert_value_to_native(runtime, args[0]); + auto items = convert_value_to_native>(runtime, args[1]); + return convert_native_to_value( + runtime, thisValue.as().GetMany(startIndex, items)); + }, + 2, false }, + { "indexOf", + [](jsi::Runtime& runtime, const IInspectable& thisValue, const jsi::Value* args) { + auto value = convert_value_to_native(runtime, args[0]); + uint32_t index; + auto returnValue = thisValue.as().IndexOf(value, index); + return make_return_struct(runtime, returnValue, "index", index); + }, + 1, false }, + }; + + static constexpr const struct array_proto_functions_t + { + std::string_view name; + call_function_t function; + int arg_count; + } array_proto_functions[] = { + { "concat"sv, &vector_concat, 1 }, // NOTE: Cannot use Array.prototype.concat since we + // cannot satisfy 'IsConcatSpreadable' + // NOTE: Array.prototype.copyWithin is a modify operation. Let this fall through + // NOTE: Forward to Array.prototype.entries since we satisfy 'CreateArrayIterator' requirements + { "every"sv, &vector_every, 1 }, // NOTE: Cannot use Array.prototype.every since we + // cannot satisfy 'HasProperty' + // NOTE: Forward to Array.prototype.fill since we satisfy all requirements + { "filter"sv, &vector_filter, 1 }, // NOTE: Cannot use Array.prototype.filter since we + // cannot satisfy 'HasProperty' + // NOTE: Forward to Array.prototype.find since we satisfy all requirements + // NOTE: Forward to Array.prototype.findIndex since we satisfy all requirements + // TODO: flat & flatMap + { "forEach"sv, &vector_forEach, 1 }, // NOTE: Cannot use Array.prototype.forEach since + // we cannot satisfy 'HasProperty' + // NOTE: Forward to Array.prototype.includes since we satisfy all requirements + // NOTE: 'indexOf' is a function that exists on IVector, so that takes precedence + // NOTE: Forward to Array.prototype.join since we satisfy all requirements + // NOTE: Forward to Array.prototype.keys since we satisfy all requirements + { "lastIndexOf"sv, &vector_lastIndexOf, + 1 }, // NOTE: Cannot use Array.prototype.lastIndexOf since we + // cannot satisfy 'HasProperty' + { "map"sv, &vector_map, 1 }, // NOTE: Cannot use Array.prototype.map since we cannot + // satisfy 'HasProperty' + // NOTE: Array.prototype.pop is a modify operation. Let this fall through + // NOTE: Array.prototype.push is a modify operation. Let this fall through + { "reduce"sv, &vector_reduce, 1 }, // NOTE: Cannot use Array.prototype.reduce since we + // cannot satisfy 'HasProperty' + { "reduceRight"sv, &vector_reduceRight, + 1 }, // NOTE: Cannot use Array.prototype.reduceRight since we + // cannot satisfy 'HasProperty' + // NOTE: Array.prototype.reverse is a modify operation. Let this fall through + // NOTE: Array.prototype.shift is a modify operation. Let this fall through + { "slice"sv, &vector_slice, 2 }, // NOTE: Cannot use Array.prototype.slice since we + // cannot satisfy 'HasProperty' + { "some"sv, &vector_some, 1 }, // NOTE: Cannot use Array.prototype.some since we cannot + // satisfy 'HasProperty' + // NOTE: Array.prototype.sort is a modify operation. Let this fall through + // NOTE: Array.prototype.splic is a modify operation. Let this fall through + // NOTE: Forward to Array.prototype.toLocaleString since we satisfy all requirements + // NOTE: Forward to Array.prototype.toString since we satisfy all requirements + // NOTE: Array.prototype.unshift is a modify operation. Let this fall through + // NOTE: Forward to Array.prototype.values since we satisfy 'CreateArrayIterator' requirements + }; + + static std::pair, std::optional> runtime_get_property_impl( + jsi::Runtime& runtime, const IInspectable& thisValue, std::string_view name, + span protoFunctions, native_type (*queryThis)(const IInspectable&)) + { + // If the "property" is a number, then that translates to a 'GetAt' call + if (auto index = index_from_name(name)) + { + auto vector = queryThis(thisValue); + if (*index >= vector.Size()) + { + return { jsi::Value::undefined(), std::nullopt }; + } + + return { convert_native_to_value(runtime, vector.GetAt(*index)), std::nullopt }; + } + + auto itr = std::find_if( + protoFunctions.begin(), protoFunctions.end(), [&](auto& pair) { return pair.name == name; }); + if (itr != protoFunctions.end()) + { + return { jsi::Function::createFromHostFunction( + runtime, make_propid(runtime, name), itr->arg_count, itr->function), + std::nullopt }; + } + + return { std::nullopt, fwd_array_prototype(runtime, name) }; + } + + static std::pair, std::optional> runtime_get_property( + jsi::Runtime& runtime, const IInspectable& thisValue, std::string_view name) + { + return runtime_get_property_impl(runtime, thisValue, name, array_proto_functions, + [](const IInspectable& thisVal) { return thisVal.as(); }); + } + }; + + template + struct data_t + { + using iface = interface_data; + static constexpr const static_interface_data value{ winrt::guid_of(), + iface::properties, {}, iface::functions, &iface::runtime_get_property }; + }; + + template + constexpr const static_interface_data& data = data_t::value; + } + } +} + +#include "ProjectedValueConverters.g.h" diff --git a/rnwinrt/rnwinrt/MetadataHelpers.h b/rnwinrt/rnwinrt/MetadataHelpers.h index 0ffb5c0..efcb1f9 100644 --- a/rnwinrt/rnwinrt/MetadataHelpers.h +++ b/rnwinrt/rnwinrt/MetadataHelpers.h @@ -73,6 +73,47 @@ inline bool is_deprecated(const T& row) return has_attribute(row, metadata_namespace, "DeprecatedAttribute"sv); } +template +inline std::string_view get_deprecated_message(const T& row) +{ + using namespace std::literals; + auto attr = winmd::reader::get_attribute(row, metadata_namespace, "DeprecatedAttribute"sv); + if (attr) + { + auto sig = attr.Value(); + auto const& fixedArgs = sig.FixedArgs(); + if (fixedArgs.size() >= 1) + { + auto const& elemSig = std::get(fixedArgs[0].value); + if (std::holds_alternative(elemSig.value)) + { + return std::get(elemSig.value); + } + } + } + return {}; +} + +template +inline bool is_removed(const T& row) +{ + using namespace std::literals; + auto attr = winmd::reader::get_attribute(row, metadata_namespace, "DeprecatedAttribute"sv); + if (attr) + { + auto sig = attr.Value(); + auto const& fixedArgs = sig.FixedArgs(); + if (fixedArgs.size() >= 2) + { + // DeprecationType enum: Deprecate=0, Remove=1 + auto const& elemSig = std::get(fixedArgs[1].value); + auto const& enumVal = std::get(elemSig.value); + return std::visit([](auto v) -> bool { return static_cast(v) == 1; }, enumVal.value); + } + } + return false; +} + template inline bool is_web_host_hidden(const T& row) { diff --git a/rnwinrt/rnwinrt/Settings.cpp b/rnwinrt/rnwinrt/Settings.cpp index f0ccf89..21860ba 100644 --- a/rnwinrt/rnwinrt/Settings.cpp +++ b/rnwinrt/rnwinrt/Settings.cpp @@ -75,6 +75,11 @@ bool is_type_allowed(const Settings& settings, const TypeDef& typeDef) return false; } + if (is_removed(typeDef)) + { + return false; + } + if (!settings.IncludeDeprecated && is_deprecated(typeDef)) { return false; diff --git a/rnwinrt/rnwinrt/TypescriptWriter.h b/rnwinrt/rnwinrt/TypescriptWriter.h index bdf7d47..c997d2a 100644 --- a/rnwinrt/rnwinrt/TypescriptWriter.h +++ b/rnwinrt/rnwinrt/TypescriptWriter.h @@ -63,8 +63,30 @@ class TypescriptWriter } } + void WriteDeprecatedJsdoc(TextWriter& textWriter, std::string_view message) + { + if (!message.empty()) + { + textWriter.WriteIndentedLine("/** ^@deprecated "sv); + textWriter.Write(message); + textWriter.Write(" */"sv); + } + else + { + textWriter.WriteIndentedLine("/** ^@deprecated */"sv); + } + } + void WriteDelegate(winmd::reader::TypeDef const& type, TextWriter& textWriter) { + if (is_removed(type)) + { + return; + } + if (is_deprecated(type)) + { + WriteDeprecatedJsdoc(textWriter, get_deprecated_message(type)); + } textWriter.WriteIndentedLine( "type % = %", [&]() { WriteGenericTypeName(type, textWriter); }, [&]() { @@ -81,8 +103,17 @@ class TypescriptWriter void WriteEnum(winmd::reader::TypeDef const& type, TextWriter& textWriter) { + if (is_removed(type)) + { + return; + } + if (is_deprecated(type)) + { + WriteDeprecatedJsdoc(textWriter, get_deprecated_message(type)); + } textWriter.WriteIndentedLine("enum % {%}"sv, type.TypeName(), [&]() { textWriter.AddIndent(); + bool parent_deprecated = is_deprecated(type); for (auto field : type.FieldList()) { if (field.Name() == "value__") @@ -90,6 +121,16 @@ class TypescriptWriter continue; }; + if (is_removed(field)) + { + continue; + } + + if (is_deprecated(field) || parent_deprecated) + { + auto msg = is_deprecated(field) ? get_deprecated_message(field) : get_deprecated_message(type); + WriteDeprecatedJsdoc(textWriter, msg); + } textWriter.WriteIndentedLine("% = %,"sv, TextWriter::ToCamelCase(std::string(field.Name())), [&]() { auto value = field.Constant(); switch (value.Type()) @@ -119,6 +160,14 @@ class TypescriptWriter { return; } + if (is_removed(type)) + { + return; + } + if (is_deprecated(type)) + { + WriteDeprecatedJsdoc(textWriter, get_deprecated_message(type)); + } textWriter.WriteIndentedLine( "%% %%% {%}"sv, [&]() // Format: ['abstract?' 'class/interface' 'name' 'extends baseclass' 'implements interface1, @@ -224,6 +273,14 @@ class TypescriptWriter // Fields: for (auto&& field : type.FieldList()) { + if (is_removed(field)) + { + continue; + } + if (is_deprecated(field)) + { + WriteDeprecatedJsdoc(textWriter, get_deprecated_message(field)); + } textWriter.WriteIndentedLine( "%%: ", [&]() { @@ -238,6 +295,16 @@ class TypescriptWriter // Properties: for (auto&& prop : type.PropertyList()) { + // MIDL places DeprecatedAttribute on getter method, not Property row + auto getter = prop.MethodSemantic().first.Method(); + if (is_removed(getter)) + { + continue; + } + if (is_deprecated(getter)) + { + WriteDeprecatedJsdoc(textWriter, get_deprecated_message(getter)); + } textWriter.WriteIndentedLine(); WriteAccess(prop.MethodSemantic().first.Method().Flags().Access(), textWriter, category != winmd::reader::category::class_type); @@ -262,8 +329,14 @@ class TypescriptWriter { if (!is_method_allowed(settings, method)) continue; + else if (is_removed(method)) + continue; else if (!method.SpecialName() || (method.Name() == ".ctor"sv)) { + if (is_deprecated(method)) + { + WriteDeprecatedJsdoc(textWriter, get_deprecated_message(method)); + } textWriter.WriteIndentedLine(); WriteMethod(method, type, textWriter); } @@ -278,6 +351,14 @@ class TypescriptWriter // Event Listeners: for (auto&& method : eventListeners) { + if (is_removed(method)) + { + continue; + } + if (is_deprecated(method)) + { + WriteDeprecatedJsdoc(textWriter, get_deprecated_message(method)); + } WriteEventListener(method, type, textWriter); } diff --git a/rnwinrt/rnwinrt/react/ReactFileGenerator.cpp b/rnwinrt/rnwinrt/react/ReactFileGenerator.cpp index a0535d4..fe650f8 100644 --- a/rnwinrt/rnwinrt/react/ReactFileGenerator.cpp +++ b/rnwinrt/rnwinrt/react/ReactFileGenerator.cpp @@ -226,6 +226,9 @@ namespace rnwinrt::classes::% for (auto& classDef : ns.class_children) { + if (is_removed(classDef->type_def)) + continue; + writer.write_fmt(R"^-^( namespace % { @@ -857,6 +860,9 @@ namespace rnwinrt::namespaces::% // Static class data for (auto& classData : ns.class_children) { + if (is_removed(classData->type_def)) + continue; + write_rnwinrt_class_projection_data(writer, *classData); } diff --git a/tests/TestArtifacts/verify_deprecated.ps1 b/tests/TestArtifacts/verify_deprecated.ps1 new file mode 100644 index 0000000..9de838c --- /dev/null +++ b/tests/TestArtifacts/verify_deprecated.ps1 @@ -0,0 +1,135 @@ +<# +.SYNOPSIS + Verifies that @deprecated JSDoc annotations are correctly generated for deprecated WinRT types. + +.DESCRIPTION + Runs rnwinrt.exe against system WinMD with -deprecatedincluded flag and verifies that + deprecated types and members have /** @deprecated ... */ JSDoc annotations in the + generated TypeScript declaration files. + +.PARAMETER rnwinrtPath + Path to rnwinrt.exe. Defaults to the x64 Release build location. +#> +[CmdLetBinding()] +Param( + [string] $rnwinrtPath +) + +$ErrorActionPreference = "Stop" +$passed = 0 +$failed = 0 + +function Test-Check { + param([string]$Name, [bool]$Condition) + if ($Condition) { + Write-Host " PASS: $Name" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: $Name" -ForegroundColor Red + $script:failed++ + } +} + +# Resolve rnwinrt path +if (-not $rnwinrtPath) { + # Try common locations relative to script + $candidates = @( + (Join-Path $PSScriptRoot "..\..\rnwinrt\x64\Release\rnwinrt.exe"), + (Join-Path (Get-Location) "rnwinrt\x64\Release\rnwinrt.exe") + ) + foreach ($c in $candidates) { + if (Test-Path $c) { $rnwinrtPath = $c; break } + } + if (-not $rnwinrtPath) { + throw "Cannot find rnwinrt.exe. Pass -rnwinrtPath explicitly." + } +} +$rnwinrtPath = (Resolve-Path $rnwinrtPath).Path +Write-Host "Using rnwinrt: $rnwinrtPath" + +# Create temp output directory +$outDir = Join-Path $env:TEMP "rnwinrt_deprecated_verify_$(Get-Random)" +New-Item -ItemType Directory -Path $outDir -Force | Out-Null + +try { + Write-Host "`nGenerating TypeScript for Windows.Media.PlayTo (deprecated namespace)..." + & $rnwinrtPath -input local -include Windows.Media.PlayTo -tsoutput $outDir -deprecatedincluded + if ($LASTEXITCODE -ne 0) { + throw "rnwinrt.exe failed with exit code $LASTEXITCODE" + } + + $playToFile = Join-Path $outDir "Windows.Media.PlayTo.d.ts" + if (-not (Test-Path $playToFile)) { + throw "Expected output file not generated: $playToFile" + } + + $content = Get-Content $playToFile -Raw + + Write-Host "`n=== Deprecated Type Annotations ===" + + # 1. PlayToConnection class should have @deprecated + Test-Check "PlayToConnection class has @deprecated" ` + ($content -match '(?s)/\*\* @deprecated PlayToConnection.*?\*/\s*class PlayToConnection') + + # 2. PlayToManager class should have @deprecated + Test-Check "PlayToManager class has @deprecated" ` + ($content -match '(?s)/\*\* @deprecated PlayToManager.*?\*/\s*class PlayToManager') + + # 3. PlayToSource class should have @deprecated + Test-Check "PlayToSource class has @deprecated" ` + ($content -match '(?s)/\*\* @deprecated PlayToSource.*?\*/\s*class PlayToSource') + + # 4. PlayToSourceDeferral class should have @deprecated + Test-Check "PlayToSourceDeferral class has @deprecated" ` + ($content -match '(?s)/\*\* @deprecated PlayToSourceDeferral.*?\*/\s*class PlayToSourceDeferral') + + # 5. PlayToSourceRequest class should have @deprecated + Test-Check "PlayToSourceRequest class has @deprecated" ` + ($content -match '(?s)/\*\* @deprecated PlayToSourceRequest.*?\*/\s*class PlayToSourceRequest') + + Write-Host "`n=== Deprecated Enum Annotations ===" + + # 6. PlayToConnectionState enum should have deprecated values + Test-Check "PlayToConnectionState has @deprecated enum values" ` + ($content -match '(?s)/\*\* @deprecated PlayToConnectionState.*?\*/\s*(disconnected|connected|rendering)') + + # 7. PlayToConnectionError enum should have deprecated values + Test-Check "PlayToConnectionError has @deprecated enum values" ` + ($content -match '(?s)/\*\* @deprecated PlayToConnectionError.*?\*/\s*(none|deviceNotResponding|deviceError)') + + Write-Host "`n=== Deprecated Method/Property Annotations ===" + + # 8. Methods on deprecated classes should have @deprecated + $deprecatedCount = ([regex]::Matches($content, '/\*\* @deprecated')).Count + Test-Check "Multiple @deprecated annotations generated (found $deprecatedCount)" ` + ($deprecatedCount -ge 20) + + # 9. Non-deprecated types should NOT have @deprecated + # CurrentTimeChangeRequestedEventArgs is NOT deprecated + $nonDeprecatedSection = $content | Select-String -Pattern 'class CurrentTimeChangeRequestedEventArgs' -Context 2 + if ($nonDeprecatedSection) { + $contextText = $nonDeprecatedSection.Context.PreContext -join "`n" + Test-Check "Non-deprecated CurrentTimeChangeRequestedEventArgs lacks @deprecated" ` + ($contextText -notmatch '@deprecated') + } else { + Test-Check "Non-deprecated CurrentTimeChangeRequestedEventArgs lacks @deprecated" $true + } + + Write-Host "`n=== TypeScript Validity ===" + + # 10. File should be valid TypeScript structure (starts with declare namespace) + Test-Check "Output is valid TypeScript declaration structure" ` + ($content -match 'declare namespace Windows\.Media\.PlayTo') + + Write-Host "`n=== Results ===" + Write-Host "$passed passed, $failed failed out of $($passed + $failed) checks" + + if ($failed -gt 0) { + throw "$failed check(s) failed" + } + + Write-Host "`nAll checks passed!" -ForegroundColor Green +} +finally { + Remove-Item -Recurse -Force $outDir -ErrorAction SilentlyContinue +} diff --git a/tests/verify_removed.ps1 b/tests/verify_removed.ps1 new file mode 100644 index 0000000..5a27641 --- /dev/null +++ b/tests/verify_removed.ps1 @@ -0,0 +1,223 @@ +# Verify deprecated and removed API support in react-native-winrt +# This script validates the TypeScript generation behavior using both the +# DeprecationTest WinMD and source-code verification. +# It tests two modes: +# 1. Without -deprecatedincluded: deprecated and removed items excluded +# 2. With -deprecatedincluded: all items present with @deprecated JSDoc annotations + +param( + [string]$rnwinrtPath, + [string]$WinMDPath = "G:\WinRT_DeprecationTesting\WinMD\DeprecationTest.winmd" +) + +$ErrorActionPreference = "Stop" +$script:passed = 0 +$script:failed = 0 + +function Assert-True { + param([bool]$Condition, [string]$Message) + if ($Condition) { + Write-Host " PASS: $Message" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: $Message" -ForegroundColor Red + $script:failed++ + } +} + +# === Source code verification === +Write-Host "`n=== Source code: helper functions ===" -ForegroundColor Cyan + +$helpersFile = Join-Path "rnwinrt\rnwinrt" "MetadataHelpers.h" +if (Test-Path $helpersFile) { + $helpersContent = Get-Content $helpersFile -Raw + Assert-True ($helpersContent -match "is_removed") "is_removed() helper exists" + Assert-True ($helpersContent -match "is_deprecated") "is_deprecated() helper exists" + Assert-True ($helpersContent -match "get_deprecated_message") "get_deprecated_message() helper exists" +} + +$writerFile = Join-Path "rnwinrt\rnwinrt" "TypescriptWriter.h" +if (Test-Path $writerFile) { + $writerContent = Get-Content $writerFile -Raw + Assert-True ($writerContent -match "is_removed") "TypescriptWriter has is_removed() checks" + Assert-True ($writerContent -match "WriteDeprecatedJsdoc") "TypescriptWriter has WriteDeprecatedJsdoc()" +} + +Write-Host "`n=== Source code: type filtering ===" -ForegroundColor Cyan + +$settingsFile = Join-Path "rnwinrt\rnwinrt" "Settings.cpp" +if (Test-Path $settingsFile) { + $settingsContent = Get-Content $settingsFile -Raw + Assert-True ($settingsContent -match "is_removed\(typeDef\)") ` + "Settings.cpp is_type_allowed() checks is_removed()" +} + +$reactGenFile = Join-Path "rnwinrt\rnwinrt" "react\ReactFileGenerator.cpp" +if (Test-Path $reactGenFile) { + $reactGenContent = Get-Content $reactGenFile -Raw + Assert-True ($reactGenContent -match "is_removed") ` + "ReactFileGenerator.cpp has is_removed() guard checks" +} + +# === WinMD-based generation verification === +if (!(Test-Path $WinMDPath)) { + Write-Host "SKIP: WinMD not found at $WinMDPath (WinMD tests skipped)" -ForegroundColor Yellow + Write-Host "`n=== Summary ===" -ForegroundColor Cyan + Write-Host "Passed: $($script:passed)" -ForegroundColor Green + if ($script:failed -gt 0) { Write-Host "Failed: $($script:failed)" -ForegroundColor Red; exit 1 } + else { Write-Host "All checks passed!" -ForegroundColor Green } + exit 0 +} + +# Resolve rnwinrt path +if (-not $rnwinrtPath) { + $candidates = @( + (Join-Path $PSScriptRoot "..\rnwinrt\x64\Release\rnwinrt.exe"), + (Join-Path (Get-Location) "rnwinrt\x64\Release\rnwinrt.exe") + ) + foreach ($c in $candidates) { + if (Test-Path $c) { $rnwinrtPath = $c; break } + } + if (-not $rnwinrtPath) { + Write-Host "SKIP: rnwinrt.exe not found (WinMD tests skipped)" -ForegroundColor Yellow + Write-Host "`n=== Summary ===" -ForegroundColor Cyan + Write-Host "Passed: $($script:passed)" -ForegroundColor Green + if ($script:failed -gt 0) { Write-Host "Failed: $($script:failed)" -ForegroundColor Red; exit 1 } + else { Write-Host "All checks passed!" -ForegroundColor Green } + exit 0 + } +} +$rnwinrtPath = (Resolve-Path $rnwinrtPath).Path +Write-Host "`nUsing rnwinrt: $rnwinrtPath" + +# --- Mode 1: Without -deprecatedincluded (removed items excluded) --- +$outDir1 = Join-Path $env:TEMP "rnwinrt_verify_noincl_$(Get-Random)" +New-Item -ItemType Directory -Path $outDir1 -Force | Out-Null + +Write-Host "`nGenerating TypeScript WITHOUT -deprecatedincluded..." +& $rnwinrtPath -input $WinMDPath -input local -include DeprecationTest -include Windows.Foundation -tsoutput $outDir1 2>&1 | Out-Null +$content1 = Get-Content (Join-Path $outDir1 "DeprecationTest.d.ts") -Raw + +Write-Host "`n=== Mode 1 (no deprecated): Removed types excluded ===" -ForegroundColor Cyan +Assert-True ($content1 -notmatch 'class RemovedClass') "RemovedClass excluded" +Assert-True ($content1 -notmatch 'enum RemovedEnum') "RemovedEnum excluded" +Assert-True ($content1 -notmatch 'interface RemovedStruct') "RemovedStruct excluded" +Assert-True ($content1 -notmatch 'RemovedDelegate') "RemovedDelegate excluded" +Assert-True ($content1 -notmatch 'IRemovedInterface') "IRemovedInterface excluded" + +Write-Host "`n=== Mode 1 (no deprecated): Normal types present ===" -ForegroundColor Cyan +Assert-True ($content1 -match 'class TestComponent') "TestComponent present" +Assert-True ($content1 -match 'enum NormalEnum') "NormalEnum present" +Assert-True ($content1 -match 'interface NormalStruct') "NormalStruct present" +Assert-True ($content1 -match 'INormalInterface') "INormalInterface present" + +Write-Host "`n=== Mode 1 (no deprecated): Removed methods on TestComponent excluded ===" -ForegroundColor Cyan +Assert-True ($content1 -notmatch 'getEpsilon') "getEpsilon (removed) excluded" +Assert-True ($content1 -notmatch 'getZeta') "getZeta (removed) excluded" +Assert-True ($content1 -match 'getAlpha') "getAlpha (normal) present" +Assert-True ($content1 -match 'getBeta') "getBeta (normal) present" + +Write-Host "`n=== Mode 1 (no deprecated): Removed static methods excluded ===" -ForegroundColor Cyan +Assert-True ($content1 -match 'staticMethod') "staticMethod (normal) present" +Assert-True ($content1 -notmatch 'staticRemovedMethod') "staticRemovedMethod excluded" + +Write-Host "`n=== Mode 1 (no deprecated): Removed properties excluded ===" -ForegroundColor Cyan +Assert-True ($content1 -match 'normalProp') "normalProp (normal) present" +Assert-True ($content1 -notmatch 'removedProp') "removedProp excluded" +Assert-True ($content1 -match 'writableProp') "writableProp (normal) present" +Assert-True ($content1 -notmatch 'writableRemovedProp') "writableRemovedProp excluded" + +Write-Host "`n=== Mode 1 (no deprecated): Removed static properties excluded ===" -ForegroundColor Cyan +Assert-True ($content1 -match 'staticProp') "staticProp (normal) present" +Assert-True ($content1 -notmatch 'staticRemovedProp') "staticRemovedProp excluded" + +Write-Host "`n=== Mode 1 (no deprecated): Removed events excluded ===" -ForegroundColor Cyan +Assert-True ($content1 -match 'normalevent') "normalEvent present" +Assert-True ($content1 -notmatch 'removedevent') "removedEvent excluded" + +Write-Host "`n=== Mode 1 (no deprecated): Removed constructors excluded ===" -ForegroundColor Cyan +Assert-True ($content1 -match 'constructor\(\)') "Default constructor present" +Assert-True ($content1 -notmatch 'constructor\(name: string, config: number\)') "Removed constructor excluded" + +# --- C++ bridge output verification (ReactFileGenerator.cpp coverage) --- +$cppOutDir = Join-Path $env:TEMP "rnwinrt_verify_cpp_$(Get-Random)" +New-Item -ItemType Directory -Path $cppOutDir -Force | Out-Null + +Write-Host "`nGenerating C++ bridge code WITHOUT -deprecatedincluded..." +& $rnwinrtPath -input $WinMDPath -input local -include DeprecationTest -include Windows.Foundation -output $cppOutDir 2>&1 | Out-Null + +$gHFile = Join-Path $cppOutDir "rnwinrt\DeprecationTest.g.h" +$gCppFile = Join-Path $cppOutDir "rnwinrt\DeprecationTest.g.cpp" + +Write-Host "`n=== C++ bridge: Removed types excluded ===" -ForegroundColor Cyan +if (Test-Path $gHFile) { + $gHContent = Get-Content $gHFile -Raw + Assert-True ($gHContent -notmatch 'RemovedClass') "RemovedClass excluded from C++ bridge header" + Assert-True ($gHContent -notmatch 'RemovedEnum') "RemovedEnum excluded from C++ bridge header" + Assert-True ($gHContent -match 'TestComponent') "TestComponent present in C++ bridge header" +} else { + Write-Host " SKIP: DeprecationTest.g.h not generated" -ForegroundColor Yellow +} + +if (Test-Path $gCppFile) { + $gCppContent = Get-Content $gCppFile -Raw + Assert-True ($gCppContent -notmatch 'RemovedClass') "RemovedClass excluded from C++ bridge source" + Assert-True ($gCppContent -match 'TestComponent') "TestComponent present in C++ bridge source" +} else { + Write-Host " SKIP: DeprecationTest.g.cpp not generated" -ForegroundColor Yellow +} + +Remove-Item -Recurse -Force $cppOutDir -ErrorAction SilentlyContinue + +# --- Mode 2: With -deprecatedincluded (all items present with annotations) --- +$outDir2 = Join-Path $env:TEMP "rnwinrt_verify_incl_$(Get-Random)" +New-Item -ItemType Directory -Path $outDir2 -Force | Out-Null + +Write-Host "`nGenerating TypeScript WITH -deprecatedincluded..." +& $rnwinrtPath -input $WinMDPath -input local -include DeprecationTest -include Windows.Foundation -tsoutput $outDir2 -deprecatedincluded 2>&1 | Out-Null +$content2 = Get-Content (Join-Path $outDir2 "DeprecationTest.d.ts") -Raw + +Write-Host "`n=== Mode 2 (deprecated included): Removed types still excluded (removed > deprecated) ===" -ForegroundColor Cyan +Assert-True ($content2 -notmatch 'class RemovedClass') ` + "RemovedClass still excluded (removed types are always hidden)" +Assert-True ($content2 -notmatch 'enum RemovedEnum') ` + "RemovedEnum still excluded (removed types are always hidden)" + +Write-Host "`n=== Mode 2 (deprecated included): Deprecated types have @deprecated ===" -ForegroundColor Cyan +Assert-True ($content2 -match '(?s)/\*\*\s*@deprecated\s+DeprecatedClass.*?\*/\s*class DeprecatedClass') ` + "DeprecatedClass has @deprecated annotation" +Assert-True ($content2 -match '(?s)/\*\*\s*@deprecated\s+DeprecatedEnum.*?\*/\s*enum DeprecatedEnum') ` + "DeprecatedEnum has @deprecated annotation" + +Write-Host "`n=== Mode 2 (deprecated included): Deprecated methods on TestComponent ===" -ForegroundColor Cyan +Assert-True ($content2 -match '(?s)/\*\*\s*@deprecated\s+GetGamma.*?\*/\s*public getGamma') ` + "getGamma has @deprecated annotation" + +Write-Host "`n=== Mode 2 (deprecated included): Removed members still excluded ===" -ForegroundColor Cyan +Assert-True ($content2 -notmatch 'getEpsilon') ` + "getEpsilon (removed) excluded even with -deprecatedincluded" +Assert-True ($content2 -notmatch 'staticRemovedMethod') ` + "staticRemovedMethod excluded even with -deprecatedincluded" +Assert-True ($content2 -notmatch 'removedevent') ` + "removedEvent excluded even with -deprecatedincluded" + +Write-Host "`n=== Mode 2 (deprecated included): Deprecated statics annotated ===" -ForegroundColor Cyan +Assert-True ($content2 -match '(?s)/\*\*\s*@deprecated\s+StaticDeprecatedMethod.*?\*/\s*public static staticDeprecatedMethod') ` + "staticDeprecatedMethod has @deprecated" + +Write-Host "`n=== Mode 2 (deprecated included): Deprecated events annotated ===" -ForegroundColor Cyan +Assert-True ($content2 -match '(?s)/\*\*\s*@deprecated\s+DeprecatedEvent.*?\*/\s*public addEventListener\(type: "deprecatedevent"') ` + "DeprecatedEvent has @deprecated" + +# Cleanup +Remove-Item -Recurse -Force $outDir1 -ErrorAction SilentlyContinue +Remove-Item -Recurse -Force $outDir2 -ErrorAction SilentlyContinue + +Write-Host "`n=== Summary ===" -ForegroundColor Cyan +Write-Host "Passed: $($script:passed)" -ForegroundColor Green +if ($script:failed -gt 0) { + Write-Host "Failed: $($script:failed)" -ForegroundColor Red + exit 1 +} else { + Write-Host "All checks passed!" -ForegroundColor Green +}