diff --git a/change/react-native-windows-2020-05-26-13-47-12-pull_request.json b/change/react-native-windows-2020-05-26-13-47-12-pull_request.json new file mode 100644 index 00000000000..4ee64488681 --- /dev/null +++ b/change/react-native-windows-2020-05-26-13-47-12-pull_request.json @@ -0,0 +1,8 @@ +{ + "type": "none", + "comment": "Create JSI implementation for IJSValueReader and IJSValueWriter", + "packageName": "react-native-windows", + "email": "zihanc@microsoft.com", + "dependentChangeType": "none", + "date": "2020-05-26T20:47:12.811Z" +} diff --git a/vnext/Microsoft.ReactNative/JsiReader.cpp b/vnext/Microsoft.ReactNative/JsiReader.cpp new file mode 100644 index 00000000000..ea56b90547c --- /dev/null +++ b/vnext/Microsoft.ReactNative/JsiReader.cpp @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "pch.h" +#include "JsiReader.h" +#include + +namespace winrt::Microsoft::ReactNative { + +//=========================================================================== +// JsiReader implementation +//=========================================================================== + +JsiReader::JsiReader(facebook::jsi::Runtime &runtime, const facebook::jsi::Value &root) noexcept + : m_runtime(runtime), m_root(root) { + SetValue(root); +} + +JSValueType JsiReader::ValueType() noexcept { + if (m_currentPrimitiveValue) { + if (m_currentPrimitiveValue.value().isString()) { + return JSValueType::String; + } else if (m_currentPrimitiveValue.value().isBool()) { + return JSValueType::Boolean; + } else if (m_currentPrimitiveValue.value().isNumber()) { + double number = m_currentPrimitiveValue.value().getNumber(); + + // unfortunately JSI doesn't differentiate int and double + // here we test if the double value can be converted to int without data loss + // treat it like an int if we succeeded + + if (floor(number) == number) { + return JSValueType::Int64; + } else { + return JSValueType::Double; + } + } + } else if (m_containers.size() > 0) { + return m_containers[m_containers.size() - 1].Type == ContainerType::Object ? JSValueType::Object + : JSValueType::Array; + } + return JSValueType::Null; +} + +bool JsiReader::GetNextObjectProperty(hstring &propertyName) noexcept { + if (m_containers.size() == 0) { + return false; + } + + auto &top = m_containers[m_containers.size() - 1]; + if (top.Type != ContainerType::Object) { + return false; + } + + top.Index++; + if (top.Index < static_cast(top.PropertyNames.value().size(m_runtime))) { + auto propertyId = + top.PropertyNames.value().getValueAtIndex(m_runtime, static_cast(top.Index)).getString(m_runtime); + propertyName = winrt::to_hstring(propertyId.utf8(m_runtime)); + SetValue(top.CurrentObject.value().getProperty(m_runtime, propertyId)); + return true; + } else { + m_containers.pop_back(); + return false; + } +} + +bool JsiReader::GetNextArrayItem() noexcept { + if (m_containers.size() == 0) { + return false; + } + + auto &top = m_containers[m_containers.size() - 1]; + if (top.Type != ContainerType::Array) { + return false; + } + + top.Index++; + if (top.Index < static_cast(top.CurrentArray.value().size(m_runtime))) { + SetValue(top.CurrentArray.value().getValueAtIndex(m_runtime, static_cast(top.Index))); + return true; + } else { + m_containers.pop_back(); + return false; + } +} + +hstring JsiReader::GetString() noexcept { + if (ValueType() != JSValueType::String) { + return {}; + } + return winrt::to_hstring(m_currentPrimitiveValue.value().getString(m_runtime).utf8(m_runtime)); +} + +bool JsiReader::GetBoolean() noexcept { + if (ValueType() != JSValueType::Boolean) { + return false; + } + return m_currentPrimitiveValue.value().getBool(); +} + +int64_t JsiReader::GetInt64() noexcept { + if (ValueType() != JSValueType::Int64) { + return 0; + } + return static_cast(m_currentPrimitiveValue.value().getNumber()); +} + +double JsiReader::GetDouble() noexcept { + auto valueType = ValueType(); + if (valueType != JSValueType::Int64 && valueType != JSValueType::Double) { + return 0; + } + return m_currentPrimitiveValue.value().getNumber(); +} + +void JsiReader::SetValue(const facebook::jsi::Value &value) noexcept { + if (value.isObject()) { + auto obj = value.getObject(m_runtime); + if (obj.isArray(m_runtime)) { + m_containers.push_back(obj.getArray(m_runtime)); + } else { + m_containers.push_back({m_runtime, std::move(obj)}); + } + m_currentPrimitiveValue = std::nullopt; + } else if (value.isString() || value.isBool() || value.isNumber()) { + m_currentPrimitiveValue = {m_runtime, value}; + } else { + m_currentPrimitiveValue = facebook::jsi::Value::null(); + } +} + +} // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/JsiReader.h b/vnext/Microsoft.ReactNative/JsiReader.h new file mode 100644 index 00000000000..5fd279d980e --- /dev/null +++ b/vnext/Microsoft.ReactNative/JsiReader.h @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "jsi/jsi.h" +#include "winrt/Microsoft.ReactNative.h" + +namespace winrt::Microsoft::ReactNative { + +struct JsiReader : implements { + JsiReader(facebook::jsi::Runtime &runtime, const facebook::jsi::Value &root) noexcept; + + public: // IJSValueReader + JSValueType ValueType() noexcept; + bool GetNextObjectProperty(hstring &propertyName) noexcept; + bool GetNextArrayItem() noexcept; + hstring GetString() noexcept; + bool GetBoolean() noexcept; + int64_t GetInt64() noexcept; + double GetDouble() noexcept; + + private: + enum class ContainerType { + Object, + Array, + }; + + struct Container { + ContainerType Type; + std::optional CurrentObject; // valid for object + std::optional PropertyNames; // valid for object + std::optional CurrentArray; // valid for array + int Index = -1; + + Container(facebook::jsi::Runtime &runtime, facebook::jsi::Object &&value) noexcept + : Type(ContainerType::Object), CurrentObject(std::make_optional(std::move(value))) { + PropertyNames = CurrentObject.value().getPropertyNames(runtime); + } + + Container(facebook::jsi::Array &&value) noexcept + : Type(ContainerType::Array), CurrentArray(std::make_optional(std::move(value))) {} + + Container(const Container &) = delete; + Container(Container &&) = default; + }; + + private: + void SetValue(const facebook::jsi::Value &value) noexcept; + + private: + facebook::jsi::Runtime &m_runtime; + const facebook::jsi::Value &m_root; + + // when m_currentPrimitiveValue is not null, the current value is a primitive value + // when m_currentPrimitiveValue is null, the current value is the top value of m_nonPrimitiveValues + std::optional m_currentPrimitiveValue; + std::vector m_containers; +}; + +} // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/JsiWriter.cpp b/vnext/Microsoft.ReactNative/JsiWriter.cpp new file mode 100644 index 00000000000..0d2aee5ec8e --- /dev/null +++ b/vnext/Microsoft.ReactNative/JsiWriter.cpp @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "pch.h" +#include "JsiWriter.h" +#include + +namespace winrt::Microsoft::ReactNative { + +//=========================================================================== +// JsiWriter implementation +//=========================================================================== + +JsiWriter::JsiWriter(facebook::jsi::Runtime &runtime) noexcept : m_runtime(runtime) { + Push({ContainerState::AcceptValueAndFinish}); +} + +facebook::jsi::Value JsiWriter::MoveResult() noexcept { + VerifyElseCrash(m_containers.size() == 0); + return std::move(m_result); +} + +void JsiWriter::WriteNull() noexcept { + WriteValue(facebook::jsi::Value::null()); +} + +void JsiWriter::WriteBoolean(bool value) noexcept { + WriteValue({value}); +} + +void JsiWriter::WriteInt64(int64_t value) noexcept { + WriteValue({static_cast(value)}); +} + +void JsiWriter::WriteDouble(double value) noexcept { + WriteValue({value}); +} + +void JsiWriter::WriteString(const winrt::hstring &value) noexcept { + WriteValue({m_runtime, facebook::jsi::String::createFromUtf8(m_runtime, winrt::to_string(value))}); +} + +void JsiWriter::WriteObjectBegin() noexcept { + // legal to create an object when it is accepting a value + VerifyElseCrash(Top().State != ContainerState::AcceptPropertyName); + Push({ContainerState::AcceptPropertyName, facebook::jsi::Object(m_runtime)}); +} + +void JsiWriter::WritePropertyName(const winrt::hstring &name) noexcept { + // legal to set a property name only when AcceptPropertyName + auto &top = Top(); + VerifyElseCrash(top.State == ContainerState::AcceptPropertyName); + top.State = ContainerState::AcceptPropertyValue; + top.PropertyName = winrt::to_string(name); +} + +void JsiWriter::WriteObjectEnd() noexcept { + // legal to finish an object only when AcceptPropertyName + VerifyElseCrash(Top().State == ContainerState::AcceptPropertyName); + WriteValue(Pop().CurrentObject.value()); +} + +void JsiWriter::WriteArrayBegin() noexcept { + // legal to create an array only when it is accepting a value + VerifyElseCrash(Top().State != ContainerState::AcceptPropertyName); + Push({ContainerState::AcceptArrayElement}); +} + +void JsiWriter::WriteArrayEnd() noexcept { + // legal to finish an array only when AcceptArrayElement + auto &top = Top(); + VerifyElseCrash(top.State == ContainerState::AcceptArrayElement); + + facebook::jsi::Array createdArray(m_runtime, top.CurrentArrayElements.size()); + for (size_t i = 0; i < top.CurrentArrayElements.size(); i++) { + createdArray.setValueAtIndex(m_runtime, i, std::move(top.CurrentArrayElements.at(i))); + } + Pop(); + WriteValue({m_runtime, createdArray}); +} + +void JsiWriter::WriteValue(facebook::jsi::Value &&value) noexcept { + auto &top = Top(); + switch (top.State) { + case ContainerState::AcceptValueAndFinish: { + m_result = std::move(value); + Pop(); + VerifyElseCrash(m_containers.size() == 0); + break; + } + case ContainerState::AcceptArrayElement: { + top.CurrentArrayElements.push_back(std::move(value)); + break; + } + case ContainerState::AcceptPropertyValue: { + auto &createdObject = top.CurrentObject.value(); + createdObject.setProperty(m_runtime, top.PropertyName.c_str(), std::move(value)); + top.State = ContainerState::AcceptPropertyName; + top.PropertyName = {}; + break; + } + default: + VerifyElseCrash(false); + } +} + +JsiWriter::Container &JsiWriter::Top() noexcept { + VerifyElseCrash(m_containers.size() > 0); + return m_containers[m_containers.size() - 1]; +} + +JsiWriter::Container JsiWriter::Pop() noexcept { + auto top = std::move(Top()); + m_containers.pop_back(); + return top; +} + +void JsiWriter::Push(Container &&container) noexcept { + m_containers.push_back(std::move(container)); +} + +/*static*/ facebook::jsi::Value JsiWriter::ToJsiValue( + facebook::jsi::Runtime &runtime, + JSValueArgWriter const &argWriter) noexcept { + if (argWriter) { + IJSValueWriter jsiWriter = winrt::make(runtime); + argWriter(jsiWriter); + return jsiWriter.as()->MoveResult(); + } + + return {}; +} + +} // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/JsiWriter.h b/vnext/Microsoft.ReactNative/JsiWriter.h new file mode 100644 index 00000000000..73e46894736 --- /dev/null +++ b/vnext/Microsoft.ReactNative/JsiWriter.h @@ -0,0 +1,63 @@ +#pragma once +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "jsi/jsi.h" +#include "winrt/Microsoft.ReactNative.h" + +namespace winrt::Microsoft::ReactNative { + +struct JsiWriter : winrt::implements { + JsiWriter(facebook::jsi::Runtime &runtime) noexcept; + facebook::jsi::Value MoveResult() noexcept; + + public: // IJSValueWriter + void WriteNull() noexcept; + void WriteBoolean(bool value) noexcept; + void WriteInt64(int64_t value) noexcept; + void WriteDouble(double value) noexcept; + void WriteString(const winrt::hstring &value) noexcept; + void WriteObjectBegin() noexcept; + void WritePropertyName(const winrt::hstring &name) noexcept; + void WriteObjectEnd() noexcept; + void WriteArrayBegin() noexcept; + void WriteArrayEnd() noexcept; + + public: + static facebook::jsi::Value ToJsiValue(facebook::jsi::Runtime &runtime, JSValueArgWriter const &argWriter) noexcept; + + private: + enum class ContainerState { + AcceptValueAndFinish, + AcceptArrayElement, + AcceptPropertyName, + AcceptPropertyValue, + }; + + struct Container { + ContainerState State; + std::optional CurrentObject; + std::vector CurrentArrayElements; + std::string PropertyName; + + Container(ContainerState state) noexcept : State(state) {} + Container(ContainerState state, facebook::jsi::Object &&value) noexcept + : State(state), CurrentObject(std::move(value)) {} + + Container(const Container &) = delete; + Container(Container &&) = default; + }; + + private: + void WriteValue(facebook::jsi::Value &&value) noexcept; + Container &Top() noexcept; + Container Pop() noexcept; + void Push(Container &&container) noexcept; + + private: + facebook::jsi::Runtime &m_runtime; + facebook::jsi::Value m_result; + std::vector m_containers; +}; + +} // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index 5053446b741..515f3c9fbcc 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -312,6 +312,12 @@ IReactNotificationService.idl Code + + IJSValueReader.idl + + + IJSValueWriter.idl + @@ -487,6 +493,12 @@ IReactNotificationService.idl Code + + IJSValueReader.idl + + + IJSValueWriter.idl +