Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions API/hermes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ add_hermes_library(hermesapi
LINK_OBJLIBS hermesPublic LINK_LIBS jsi ${CDP_DEPS}
)
target_include_directories(hermesapi_obj PUBLIC ..)
target_include_directories(hermesapi_obj PRIVATE
${PROJECT_SOURCE_DIR}/lib/VM
)

# Pass core extensions flag to C++ code.
if(HERMES_ENABLE_CORE_EXTENSIONS)
Expand Down
40 changes: 40 additions & 0 deletions API/hermes/hermes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include <hermes/hermes.h>

#include "JSONValue.h"

#include "llvh/Support/Compiler.h"

#include "hermes/ADT/ManagedChunkedList.h"
Expand Down Expand Up @@ -249,6 +251,7 @@ class HermesRootAPI final : public IHermesRootAPI, public ISetFatalHandler {
namespace {
class HermesRuntimeImpl final : public HermesRuntime,
private IHermesTestHelpers,
private jsi::IJSONValueFactory,
private InstallHermesFatalErrorHandler,
private jsi::Instrumentation
#ifdef JSI_UNSTABLE
Expand Down Expand Up @@ -700,6 +703,13 @@ class HermesRuntimeImpl final : public HermesRuntime,

ICast *castInterface(const jsi::UUID &interfaceUUID) override;

jsi::Value createValueFromJSONTree(
const jsi::JSONValue &value) override;
jsi::Value createValueFromJSONTreeAndConsume(
jsi::JSONValue &value) override;
jsi::JSONValue createJSONTreeFromValue(
const jsi::Value &value) override;

#ifdef JSI_UNSTABLE
std::shared_ptr<jsi::Serialized> serialize(const jsi::Value &value) override;
jsi::Value deserialize(
Expand Down Expand Up @@ -1573,6 +1583,8 @@ jsi::ICast *HermesRuntimeImpl::castInterface(const jsi::UUID &interfaceUUID) {
return static_cast<IHermes *>(this);
} else if (interfaceUUID == IHermesSHUnit::uuid) {
return static_cast<IHermesSHUnit *>(this);
} else if (interfaceUUID == jsi::IJSONValueFactory::uuid) {
return static_cast<jsi::IJSONValueFactory *>(this);
}
#ifdef JSI_UNSTABLE
else if (interfaceUUID == ISerialization::uuid) {
Expand All @@ -1584,6 +1596,34 @@ jsi::ICast *HermesRuntimeImpl::castInterface(const jsi::UUID &interfaceUUID) {
return nullptr;
}

jsi::Value HermesRuntimeImpl::createValueFromJSONTree(
const jsi::JSONValue &value) {
vm::GCScope gcScope(runtime_);
vm::JSONValueMaterializer materializer;
auto res = materializer.materialize(runtime_, value);
checkStatus(res.getStatus());
return valueFromHermesValue(*res);
}

jsi::Value HermesRuntimeImpl::createValueFromJSONTreeAndConsume(
jsi::JSONValue &value) {
vm::GCScope gcScope(runtime_);
vm::JSONValueMaterializer materializer;
auto res = materializer.materializeAndConsume(runtime_, value);
checkStatus(res.getStatus());
return valueFromHermesValue(*res);
}

jsi::JSONValue HermesRuntimeImpl::createJSONTreeFromValue(
const jsi::Value &value) {
vm::GCScope gcScope(runtime_);
vm::PinnedHermesValue numberStorage;
auto res = vm::createJSONValueFromHermesValue(
runtime_, vmHandleFromValue(value, &numberStorage));
checkStatus(res.getStatus());
return std::move(*res);
}

#ifdef JSI_UNSTABLE
std::shared_ptr<jsi::Serialized> HermesRuntimeImpl::serialize(
const jsi::Value &value) {
Expand Down
10 changes: 10 additions & 0 deletions API/jsi/jsi/jsi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,16 @@ MutableBuffer::~MutableBuffer() = default;

PreparedJavaScript::~PreparedJavaScript() = default;

JSONValue JSONValue::createFromValue(IRuntime& runtime, const Value& value) {
auto* factory = static_cast<IJSONValueFactory*>(
runtime.castInterface(IJSONValueFactory::uuid));
if (!factory) {
throw JSINativeException(
"Runtime does not implement jsi::IJSONValueFactory");
}
return factory->createJSONTreeFromValue(value);
}

Value HostObject::get(Runtime&, const PropNameID&) {
return Value();
}
Expand Down
130 changes: 130 additions & 0 deletions API/jsi/jsi/jsi.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#ifndef JSI_EXPORT
Expand Down Expand Up @@ -203,6 +205,108 @@ class JSError;
class TypedArray;
class Uint8Array;

/// A decoded JSON-compatible value tree that native code can use to ask a
/// runtime to materialize a JavaScript value without reparsing JSON text.
///
/// Strings carry their decoded character encoding so runtimes can avoid
/// unnecessary transcoding when the producer already has the runtime's
/// preferred representation.
struct JSI_EXPORT JSONValue {
enum class Kind { Null, Bool, Number, String, Array, Object };
enum class StringEncoding { ASCII, UTF8, UTF16 };

using Object = std::vector<std::pair<std::string, JSONValue>>;

Kind kind{Kind::Null};
bool boolValue{false};
double numberValue{0};
StringEncoding stringEncoding{StringEncoding::ASCII};
std::string stringValue;
std::u16string utf16StringValue;
std::vector<JSONValue> arrayValue;
Object objectValue;

static JSONValue null() {
return {};
}

static JSONValue boolean(bool value) {
JSONValue result;
result.kind = Kind::Bool;
result.boolValue = value;
return result;
}

static JSONValue number(double value) {
JSONValue result;
result.kind = Kind::Number;
result.numberValue = value;
return result;
}

static bool isASCII(std::string_view value) {
for (unsigned char c : value) {
if (c > 0x7f) {
return false;
}
}
return true;
}

static JSONValue asciiString(std::string value) {
JSONValue result;
result.kind = Kind::String;
result.stringEncoding =
isASCII(value) ? StringEncoding::ASCII : StringEncoding::UTF8;
result.stringValue = std::move(value);
return result;
}

static JSONValue utf8String(std::string value) {
if (isASCII(value)) {
return asciiString(std::move(value));
}
JSONValue result;
result.kind = Kind::String;
result.stringEncoding = StringEncoding::UTF8;
result.stringValue = std::move(value);
return result;
}

static JSONValue utf16String(std::u16string value) {
JSONValue result;
result.kind = Kind::String;
result.stringEncoding = StringEncoding::UTF16;
result.utf16StringValue = std::move(value);
return result;
}

static JSONValue string(std::string value) {
return utf8String(std::move(value));
}

static JSONValue array(std::vector<JSONValue> value) {
JSONValue result;
result.kind = Kind::Array;
result.arrayValue = std::move(value);
return result;
}

static JSONValue object(Object value) {
JSONValue result;
result.kind = Kind::Object;
result.objectValue = std::move(value);
return result;
}

/// Create a JSONValue tree from a JavaScript value.
///
/// This walks arrays and own enumerable object properties. Values that do
/// not have a JSONValue representation, such as functions, symbols, bigints,
/// and cyclic objects, throw a JSIException.
static JSONValue createFromValue(IRuntime& runtime, const Value& value);
};

/// A function which has this type can be registered as a function
/// callable from JavaScript using Function::createFromHostFunction().
/// When the function is called, args will point to the arguments, and
Expand Down Expand Up @@ -329,6 +433,32 @@ class JSI_EXPORT ISerialization : public ICast {

#endif // JSI_UNSTABLE

/// Optional interface for runtimes that can efficiently materialize a decoded
/// JSONValue tree into a JavaScript value.
class JSI_EXPORT IJSONValueFactory : public ICast {
public:
static constexpr jsi::UUID uuid{
0x69b0d272,
0x65fa,
0x4c9a,
0x9d83,
0x1c85a5a610ce};

/// Materialize \p value without mutating it.
virtual Value createValueFromJSONTree(const JSONValue& value) = 0;

/// Materialize \p value and allow the runtime to consume string storage.
///
/// After this returns, \p value must be treated as consumed.
virtual Value createValueFromJSONTreeAndConsume(JSONValue& value) = 0;

/// Create a JSONValue tree from a JavaScript value.
virtual JSONValue createJSONTreeFromValue(const Value& value) = 0;

protected:
~IJSONValueFactory() = default;
};

/// An interface that provides various functionalities of the JS runtime.
/// The APIs must not be called from multiple threads concurrently. It is the
/// user's responsibility ensure thread safety when using IRuntime.
Expand Down
1 change: 1 addition & 0 deletions lib/VM/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ set(source_files
JSWeakMapImpl.cpp
JSWeakRef.cpp
JSFinalizationRegistry.cpp
JSONValue.cpp
LimitedStorageProvider.cpp
DecoratedObject.cpp
HostModel.cpp
Expand Down
Loading