diff --git a/NativeScript/CMakeLists.txt b/NativeScript/CMakeLists.txt index a4a781895..88ad7ee31 100644 --- a/NativeScript/CMakeLists.txt +++ b/NativeScript/CMakeLists.txt @@ -237,7 +237,7 @@ include_directories( ) set(FFI_SHARED_SOURCE_FILES - ffi/objc/shared/Tasks.cpp + jsi/shared/Tasks.cpp ) set(FFI_NAPI_SOURCE_FILES @@ -274,26 +274,32 @@ set(FFI_HERMES_ENGINE_SOURCE_FILES set(FFI_V8_ENGINE_SOURCE_FILES ${FFI_ENGINE_SHARED_SOURCE_FILES} + # Platform-neutral engine layer, shared with the Android runtime. + jsi/v8/V8HostObjects.cpp + jsi/v8/V8Runtime.cpp + jsi/v8/V8Value.cpp + # Apple-only ObjC bridge on top of it. ffi/objc/v8/NativeApiV8.mm - ffi/objc/v8/NativeApiV8HostObjects.mm - ffi/objc/v8/NativeApiV8Runtime.mm - ffi/objc/v8/NativeApiV8Value.mm ) set(FFI_JSC_ENGINE_SOURCE_FILES ${FFI_ENGINE_SHARED_SOURCE_FILES} + # Platform-neutral engine layer, shared with the Android runtime. + jsi/jsc/JSCHostObjects.cpp + jsi/jsc/JSCRuntime.cpp + jsi/jsc/JSCValue.cpp + # Apple-only ObjC bridge on top of it. ffi/objc/jsc/NativeApiJSC.mm - ffi/objc/jsc/NativeApiJSCHostObjects.mm - ffi/objc/jsc/NativeApiJSCRuntime.mm - ffi/objc/jsc/NativeApiJSCValue.mm ) set(FFI_QUICKJS_ENGINE_SOURCE_FILES ${FFI_ENGINE_SHARED_SOURCE_FILES} - ffi/objc/quickjs/NativeApiQuickJSHostObjects.mm + # Platform-neutral engine layer, shared with the Android runtime. + jsi/quickjs/QuickJSHostObjects.cpp + jsi/quickjs/QuickJSRuntime.cpp + jsi/quickjs/QuickJSValue.cpp + # Apple-only ObjC bridge on top of it. ffi/objc/quickjs/NativeApiQuickJS.mm - ffi/objc/quickjs/NativeApiQuickJSRuntime.mm - ffi/objc/quickjs/NativeApiQuickJSValue.mm ) set(SOURCE_FILES diff --git a/NativeScript/ffi/objc/hermes/NativeApiJsi.h b/NativeScript/ffi/objc/hermes/NativeApiJsi.h index e98ba0431..b37401f30 100644 --- a/NativeScript/ffi/objc/hermes/NativeApiJsi.h +++ b/NativeScript/ffi/objc/hermes/NativeApiJsi.h @@ -1,26 +1,13 @@ -#ifndef NATIVE_API_JSI_H -#define NATIVE_API_JSI_H +#ifndef NATIVE_API_JSI_H_FFI_FORWARD +#define NATIVE_API_JSI_H_FFI_FORWARD -#include +// Moved to jsi/hermes/NativeApiJsi.h. Hermes is the one backend that exposes +// the real facebook::jsi API rather than reimplementing the nativescript +// engine shape, and this declaration is already platform-neutral, so Android's +// Hermes backend can share it verbatim. +// +// This forwarding header keeps the Apple include paths unchanged. -#include "ffi/objc/shared/NativeApiBackendConfig.h" +#include "jsi/hermes/NativeApiJsi.h" -namespace nativescript { - -using NativeApiJsiScheduler = NativeApiBackendScheduler; -using NativeApiJsiConfig = NativeApiBackendConfig; - -facebook::jsi::Object CreateNativeApiJSI( - facebook::jsi::Runtime& runtime, - const NativeApiJsiConfig& config = NativeApiJsiConfig{}); - -void InstallNativeApiJSI( - facebook::jsi::Runtime& runtime, - const NativeApiJsiConfig& config = NativeApiJsiConfig{}); - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiJSI( - facebook::jsi::Runtime* runtime, const char* metadataPath); - -#endif // NATIVE_API_JSI_H +#endif // NATIVE_API_JSI_H_FFI_FORWARD diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h index c81f46e93..4c595b1c5 100644 --- a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h @@ -1,11 +1,19 @@ #ifndef NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H #define NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H +// Apple-side entry point for the JavaScriptCore engine layer. +// +// The engine layer itself -- the `nativescript::engine` classes wrapping JSC +// -- moved to jsi/jsc/JSCRuntime.h so the Android runtime can compile it too. +// What stays here is exactly what the Apple ObjC bridge needs on top of it and +// Android has no use for: Foundation, the Objective-C runtime, the Mach-O +// metadata section lookup, and the metadata reader. +// +// Apple sources include this path, unchanged, and get both halves. + #ifdef TARGET_ENGINE_JSC #import -#include -#include #include #include #include @@ -13,30 +21,12 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include "Metadata.h" #include "MetadataReader.h" #include "ffi.h" +#include "jsi/jsc/JSCRuntime.h" + @protocol NativeApiClassBuilderProtocol @end @@ -44,860 +34,6 @@ extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; #endif -namespace nativescript { -namespace engine { - -class Runtime; -class Value; -class Object; -class Function; -class Array; -class String; -class BigInt; -class ArrayBuffer; - -class JSError : public std::runtime_error { - public: - JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} - explicit JSError(const std::string& message) : std::runtime_error(message) {} -}; - -class StringBuffer { - public: - explicit StringBuffer(std::string value) : value_(std::move(value)) {} - const char* data() const { return value_.data(); } - size_t size() const { return value_.size(); } - - private: - std::string value_; -}; - -class MutableBuffer { - public: - virtual ~MutableBuffer() = default; - virtual size_t size() const = 0; - virtual uint8_t* data() = 0; -}; - -class PropNameID { - public: - PropNameID() = default; - explicit PropNameID(std::string value) : value_(std::move(value)) {} - - static PropNameID forAscii(Runtime&, const char* value) { - return PropNameID(value != nullptr ? value : ""); - } - - static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } - - std::string utf8(Runtime&) const { return value_; } - - private: - std::string value_; -}; - -class HostObject { - public: - virtual ~HostObject() = default; - virtual Value get(Runtime& runtime, const PropNameID& name); - virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); - virtual std::vector getPropertyNames(Runtime& runtime); -}; - -using HostFunctionType = std::function; - -namespace jscengine { - -inline std::string stringToUtf8(JSStringRef string) { - if (string == nullptr) { - return {}; - } - size_t capacity = JSStringGetMaximumUTF8CStringSize(string); - std::string result(capacity, '\0'); - size_t written = JSStringGetUTF8CString(string, result.data(), capacity); - if (written == 0) { - return {}; - } - result.resize(written - 1); - return result; -} - -inline JSStringRef makeJSString(const std::string& value) { - NSString* string = [[NSString alloc] initWithBytes:value.data() - length:value.size() - encoding:NSUTF8StringEncoding]; - if (string == nil) { - return JSStringCreateWithUTF8CString(value.c_str()); - } - - NSUInteger length = [string length]; - std::vector characters(length); - if (length > 0) { - [string getCharacters:characters.data() range:NSMakeRange(0, length)]; - } - [string release]; - return JSStringCreateWithCharacters(characters.data(), length); -} - -inline JSStringRef makeJSString(const char* value) { - return JSStringCreateWithUTF8CString(value != nullptr ? value : ""); -} - -inline std::string valueToUtf8(JSContextRef context, JSValueRef value) { - if (value == nullptr) { - return {}; - } - JSValueRef exception = nullptr; - JSStringRef string = JSValueToStringCopy(context, value, &exception); - if (string == nullptr || exception != nullptr) { - if (string != nullptr) { - JSStringRelease(string); - } - return {}; - } - std::string result = stringToUtf8(string); - JSStringRelease(string); - return result; -} - -inline JSValueRef makeError(JSContextRef context, const std::string& message) { - JSStringRef string = makeJSString(message); - JSValueRef argument = JSValueMakeString(context, string); - JSStringRelease(string); - JSValueRef exception = nullptr; - JSObjectRef error = JSObjectMakeError(context, 1, &argument, &exception); - if (error != nullptr && exception == nullptr) { - return error; - } - return argument; -} - -inline void setException(JSContextRef context, JSValueRef* exception, const std::exception& error) { - if (exception != nullptr) { - *exception = makeError(context, error.what()); - } -} - -struct RuntimeState { - explicit RuntimeState(JSGlobalContextRef context) : context(context) {} - - ~RuntimeState() { - if (hostClass != nullptr) { - JSClassRelease(hostClass); - } - if (functionClass != nullptr) { - JSClassRelease(functionClass); - } - if (selectorGroupFunctionClass != nullptr) { - JSClassRelease(selectorGroupFunctionClass); - } - } - - JSGlobalContextRef context = nullptr; - JSClassRef hostClass = nullptr; - JSClassRef functionClass = nullptr; - JSClassRef selectorGroupFunctionClass = nullptr; -}; - -struct ValueStorage { - enum class Kind { - Undefined, - Null, - Bool, - Number, - JSC, - JSCBorrowed, - }; - - explicit ValueStorage(Kind kind) : kind(kind) {} - - ~ValueStorage() { - if (kind == Kind::JSC && context != nullptr && value != nullptr) { - JSValueUnprotect(context, value); - } - } - - Kind kind = Kind::Undefined; - bool boolValue = false; - double numberValue = 0; - JSGlobalContextRef context = nullptr; - JSValueRef value = nullptr; -}; - -template -const void* hostObjectTypeToken() { - static int token = 0; - return &token; -} - -struct HostObjectHolder { - HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, - const void* typeToken, bool nativeInstance = false) - : state(std::move(state)), - hostObject(std::move(hostObject)), - typeToken(typeToken), - nativeInstance(nativeInstance) {} - - std::shared_ptr state; - std::shared_ptr hostObject; - const void* typeToken = nullptr; - // Set for wrappers around an ObjC instance, whose JS prototype is allowed to - // shadow a native property. Recorded as a flag rather than derived from - // typeToken: the wrapper class is defined inside an anonymous namespace in - // the engine translation unit, so a type token taken anywhere else names a - // different type and never compares equal. - bool nativeInstance = false; -}; - -struct FunctionHolder { - FunctionHolder(std::shared_ptr state, HostFunctionType callback) - : state(std::move(state)), callback(std::move(callback)) {} - - std::shared_ptr state; - HostFunctionType callback; -}; - -struct ArrayBufferHolder { - explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} - - std::shared_ptr buffer; -}; - -void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function); - -} // namespace jscengine - -class Runtime { - public: - explicit Runtime(JSGlobalContextRef context) - : state_(std::make_shared(context)) {} - - explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} - - JSGlobalContextRef context() const { return state_->context; } - std::shared_ptr state() const { return state_; } - - Object global(); - Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); - void drainMicrotasks() {} - - private: - std::shared_ptr state_; -}; - -class String { - public: - String() = default; - String(Runtime& runtime, JSStringRef string); - - static String createFromUtf8(Runtime& runtime, const char* value) { - JSStringRef string = jscengine::makeJSString(value); - String result(runtime, string); - JSStringRelease(string); - return result; - } - - static String createFromUtf8(Runtime& runtime, const std::string& value) { - JSStringRef string = jscengine::makeJSString(value); - String result(runtime, string); - JSStringRelease(string); - return result; - } - - static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { - std::string text(reinterpret_cast(value), length); - return createFromUtf8(runtime, text); - } - - std::string utf8(Runtime& runtime) const; - JSValueRef local(Runtime& runtime) const { return storage_->value; } - operator Value() const; - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class Value { - public: - Value() : kind_(jscengine::ValueStorage::Kind::Undefined) {} - - Value(bool value) : kind_(jscengine::ValueStorage::Kind::Bool), boolValue_(value) {} - - Value(double value) : kind_(jscengine::ValueStorage::Kind::Number), numberValue_(value) {} - - Value(int value) : Value(static_cast(value)) {} - Value(uint32_t value) : Value(static_cast(value)) {} - - Value(Runtime& runtime, const Value& value) { - if (value.kind_ == jscengine::ValueStorage::Kind::JSCBorrowed) { - // Promote borrowed to owned - storage_ = std::make_shared(jscengine::ValueStorage::Kind::JSC); - storage_->context = runtime.context(); - storage_->value = value.borrowedValue_ != nullptr ? value.borrowedValue_ - : JSValueMakeUndefined(runtime.context()); - JSValueProtect(runtime.context(), storage_->value); - kind_ = jscengine::ValueStorage::Kind::JSC; - return; - } - kind_ = value.kind_; - boolValue_ = value.boolValue_; - numberValue_ = value.numberValue_; - borrowedContext_ = value.borrowedContext_; - borrowedValue_ = value.borrowedValue_; - storage_ = value.storage_; - } - Value(Runtime& runtime, Value&& value) - : kind_(value.kind_), - boolValue_(value.boolValue_), - numberValue_(value.numberValue_), - borrowedContext_(value.borrowedContext_), - borrowedValue_(value.borrowedValue_), - storage_(std::move(value.storage_)) {} - Value(Runtime& runtime, const String& value); - Value(Runtime& runtime, const Object& object); - Value(Runtime& runtime, const Function& function); - Value(Runtime& runtime, const Array& array); - Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); - Value(Runtime& runtime, const BigInt& bigint); - Value(Runtime& runtime, JSValueRef value) - : kind_(jscengine::ValueStorage::Kind::JSC), - storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { - storage_->context = runtime.context(); - storage_->value = value != nullptr ? value : JSValueMakeUndefined(runtime.context()); - JSValueProtect(runtime.context(), storage_->value); - } - - static Value borrowed(Runtime& runtime, JSValueRef value) { - Value result; - result.kind_ = jscengine::ValueStorage::Kind::JSCBorrowed; - result.borrowedContext_ = runtime.context(); - result.borrowedValue_ = value != nullptr ? value : JSValueMakeUndefined(runtime.context()); - return result; - } - - static Value undefined() { return Value(); } - static Value null() { - Value value; - value.kind_ = jscengine::ValueStorage::Kind::Null; - return value; - } - - static bool strictEquals(Runtime& runtime, const Value& lhs, - const Value& rhs) { - return JSValueIsStrictEqual(runtime.context(), lhs.local(runtime), - rhs.local(runtime)); - } - - bool isUndefined() const { - return kind_ == jscengine::ValueStorage::Kind::Undefined || - (isJSC() && JSValueIsUndefined(jscContext(), jscValue())); - } - bool isNull() const { - return kind_ == jscengine::ValueStorage::Kind::Null || - (isJSC() && JSValueIsNull(jscContext(), jscValue())); - } - bool isBool() const { - return kind_ == jscengine::ValueStorage::Kind::Bool || - (isJSC() && JSValueIsBoolean(jscContext(), jscValue())); - } - bool getBool() const { - if (kind_ == jscengine::ValueStorage::Kind::Bool) { - return boolValue_; - } - return isJSC() && JSValueToBoolean(jscContext(), jscValue()); - } - bool isNumber() const { - return kind_ == jscengine::ValueStorage::Kind::Number || - (isJSC() && JSValueIsNumber(jscContext(), jscValue())); - } - double getNumber() const { - if (kind_ == jscengine::ValueStorage::Kind::Number) { - return numberValue_; - } - return isJSC() ? JSValueToNumber(jscContext(), jscValue(), nullptr) : 0; - } - - bool isObject() const { return isJSC() && JSValueIsObject(jscContext(), jscValue()); } - bool isString() const { return isJSC() && JSValueIsString(jscContext(), jscValue()); } - bool isBigInt() const { - if (!isJSC()) { - return false; - } - if (__builtin_available(macOS 15.0, iOS 18.0, *)) { - return JSValueIsBigInt(jscContext(), jscValue()); - } - return false; - } - bool isSymbol() const { return isJSC() && JSValueIsSymbol(jscContext(), jscValue()); } - - Object asObject(Runtime& runtime) const; - String asString(Runtime& runtime) const; - BigInt getBigInt(Runtime& runtime) const; - - JSValueRef local(Runtime& runtime) const { - switch (kind_) { - case jscengine::ValueStorage::Kind::Undefined: - return JSValueMakeUndefined(runtime.context()); - case jscengine::ValueStorage::Kind::Null: - return JSValueMakeNull(runtime.context()); - case jscengine::ValueStorage::Kind::Bool: - return JSValueMakeBoolean(runtime.context(), boolValue_); - case jscengine::ValueStorage::Kind::Number: - return JSValueMakeNumber(runtime.context(), numberValue_); - case jscengine::ValueStorage::Kind::JSC: - return storage_->value; - case jscengine::ValueStorage::Kind::JSCBorrowed: - return borrowedValue_; - } - } - - // Access the shared storage (for Object/Function/Array interop) - std::shared_ptr storage() const { return storage_; } - - static Value fromStorage(std::shared_ptr s) { - Value v; - v.kind_ = s->kind; - v.boolValue_ = s->boolValue; - v.numberValue_ = s->numberValue; - v.storage_ = std::move(s); - return v; - } - - private: - friend class Runtime; - friend class Object; - friend class String; - friend class BigInt; - friend class ArrayBuffer; - friend class Function; - friend class Array; - - bool isJSC() const { - return kind_ == jscengine::ValueStorage::Kind::JSC || - kind_ == jscengine::ValueStorage::Kind::JSCBorrowed; - } - JSContextRef jscContext() const { - return kind_ == jscengine::ValueStorage::Kind::JSCBorrowed ? borrowedContext_ - : storage_->context; - } - JSValueRef jscValue() const { - return kind_ == jscengine::ValueStorage::Kind::JSCBorrowed ? borrowedValue_ : storage_->value; - } - - jscengine::ValueStorage::Kind kind_ = jscengine::ValueStorage::Kind::Undefined; - bool boolValue_ = false; - double numberValue_ = 0; - JSGlobalContextRef borrowedContext_ = nullptr; - JSValueRef borrowedValue_ = nullptr; - std::shared_ptr storage_; -}; - -class Object { - public: - Object() = default; - explicit Object(Runtime& runtime) - : storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { - storage_->context = runtime.context(); - storage_->value = JSObjectMake(runtime.context(), nullptr, nullptr); - JSValueProtect(runtime.context(), storage_->value); - } - - static Object fromValueStorage(std::shared_ptr storage) { - Object object; - object.storage_ = std::move(storage); - return object; - } - - template - static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { - auto baseHost = std::static_pointer_cast(std::move(host)); - return createFromHostObjectWithToken(runtime, std::move(baseHost), - jscengine::hostObjectTypeToken()); - } - - // The wrapper for an ObjC instance. Same object as createFromHostObject - // builds, but marked so property reads defer to the JS prototype chain when - // it carries the name -- V8 gets that from its kNonMasking template; JSC's - // class callback runs before the prototype is consulted, so it has to ask. - template - static Object createNativeInstanceHostObject(Runtime& runtime, std::shared_ptr host) { - auto baseHost = std::static_pointer_cast(std::move(host)); - return createFromHostObjectWithToken(runtime, std::move(baseHost), - jscengine::hostObjectTypeToken(), - /* nativeInstance */ true); - } - - Value getProperty(Runtime& runtime, const char* name) const { - JSStringRef property = jscengine::makeJSString(name); - JSValueRef exception = nullptr; - JSValueRef result = - JSObjectGetProperty(runtime.context(), local(runtime), property, &exception); - JSStringRelease(property); - if (exception != nullptr) { - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Value getProperty(Runtime& runtime, const std::string& name) const { - return getProperty(runtime, name.c_str()); - } - - Value getProperty(Runtime& runtime, const Value& key) const { - JSValueRef exception = nullptr; - JSValueRef result = JSObjectGetPropertyForKey(runtime.context(), local(runtime), - key.local(runtime), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Object getPropertyAsObject(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime); - } - - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; - - void setProperty(Runtime& runtime, const char* name, const Value& value) { - JSStringRef property = jscengine::makeJSString(name); - JSValueRef exception = nullptr; - JSObjectSetProperty(runtime.context(), local(runtime), property, value.local(runtime), - kJSPropertyAttributeNone, &exception); - JSStringRelease(property); - if (exception != nullptr) { - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - } - - void setProperty(Runtime& runtime, const char* name, const String& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Object& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Function& value); - void setProperty(Runtime& runtime, const char* name, const Array& value); - void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); - void setProperty(Runtime& runtime, const char* name, bool value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const char* name, double value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const std::string& name, const Value& value) { - setProperty(runtime, name.c_str(), value); - } - void setProperty(Runtime& runtime, const Value& key, const Value& value) { - JSValueRef exception = nullptr; - JSObjectSetPropertyForKey(runtime.context(), local(runtime), key.local(runtime), - value.local(runtime), kJSPropertyAttributeNone, &exception); - if (exception != nullptr) { - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - } - - bool hasProperty(Runtime& runtime, const char* name) const { - JSStringRef property = jscengine::makeJSString(name); - bool result = JSObjectHasProperty(runtime.context(), local(runtime), property); - JSStringRelease(property); - return result; - } - - bool isFunction(Runtime& runtime) const { - return JSObjectIsFunction(runtime.context(), local(runtime)); - } - - bool isArray(Runtime& runtime) const { - JSStringRef name = jscengine::makeJSString("Array"); - JSValueRef constructorValue = JSObjectGetProperty( - runtime.context(), JSContextGetGlobalObject(runtime.context()), name, nullptr); - JSStringRelease(name); - if (constructorValue == nullptr || !JSValueIsObject(runtime.context(), constructorValue)) { - return false; - } - JSObjectRef constructor = JSValueToObject(runtime.context(), constructorValue, nullptr); - JSValueRef exception = nullptr; - bool result = - JSValueIsInstanceOfConstructor(runtime.context(), local(runtime), constructor, &exception); - return exception == nullptr && result; - } - - bool isArrayBuffer(Runtime& runtime) const { - JSValueRef exception = nullptr; - JSTypedArrayType type = - JSValueGetTypedArrayType(runtime.context(), storage_->value, &exception); - return exception == nullptr && type == kJSTypedArrayTypeArrayBuffer; - } - - Function asFunction(Runtime& runtime) const; - Array getArray(Runtime& runtime) const; - ArrayBuffer getArrayBuffer(Runtime& runtime) const; - Array getPropertyNames(Runtime& runtime) const; - - template - bool isHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - return holder != nullptr && holder->typeToken == jscengine::hostObjectTypeToken(); - } - - template - std::shared_ptr getHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - if (holder == nullptr || holder->typeToken != jscengine::hostObjectTypeToken()) { - return nullptr; - } - return std::static_pointer_cast(holder->hostObject); - } - - JSObjectRef local(Runtime& runtime) const { - return reinterpret_cast(const_cast(storage_->value)); - } - - operator Value() const { return Value::fromStorage(storage_); } - - protected: - friend class Value; - friend class Runtime; - friend class Function; - friend class Array; - friend class ArrayBuffer; - - explicit Object(std::shared_ptr storage) - : storage_(std::move(storage)) {} - - static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken, - bool nativeInstance = false); - - jscengine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { - return static_cast(JSObjectGetPrivate(local(runtime))); - } - - std::shared_ptr storage_; -}; - -class Function : public Object { - public: - Function() = default; - explicit Function(Object object) : Object(std::move(object.storage_)) {} - - static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback); - - Value call(Runtime& runtime, const Value* args, size_t count) const { - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValueRef exception = nullptr; - JSValueRef result = JSObjectCallAsFunction( - runtime.context(), local(runtime), JSContextGetGlobalObject(runtime.context()), argv.size(), - argv.empty() ? nullptr : argv.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Value call(Runtime& runtime) const { - return call(runtime, static_cast(nullptr), 0); - } - Value call(Runtime& runtime, std::nullptr_t, size_t) const { - return call(runtime, static_cast(nullptr), 0); - } - template - Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { - return call(runtime, static_cast(args), count); - } - template - Value call(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return call(runtime, static_cast(argv), sizeof...(Args)); - } - - Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, - size_t count = 0) const { - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValueRef exception = nullptr; - JSValueRef result = - JSObjectCallAsFunction(runtime.context(), local(runtime), thisObject.local(runtime), - argv.size(), argv.empty() ? nullptr : argv.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValueRef exception = nullptr; - JSValueRef result = JSObjectCallAsConstructor(runtime.context(), local(runtime), argv.size(), - argv.empty() ? nullptr : argv.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { - return callAsConstructor(runtime, static_cast(nullptr), 0); - } - template - Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { - return callAsConstructor(runtime, static_cast(args), count); - } - template - Value callAsConstructor(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); - } -}; - -class Array : public Object { - public: - explicit Array(Runtime& runtime, size_t size) - : Object(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { - std::vector initial(size, JSValueMakeUndefined(runtime.context())); - JSValueRef exception = nullptr; - storage_->context = runtime.context(); - storage_->value = - JSObjectMakeArray(runtime.context(), initial.size(), initial.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - JSValueProtect(runtime.context(), storage_->value); - } - - explicit Array(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { - Value length = getProperty(runtime, "length"); - return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; - } - - Value getValueAtIndex(Runtime& runtime, size_t index) const { - JSValueRef exception = nullptr; - JSValueRef result = JSObjectGetPropertyAtIndex(runtime.context(), local(runtime), - static_cast(index), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { - JSValueRef exception = nullptr; - JSObjectSetPropertyAtIndex(runtime.context(), local(runtime), static_cast(index), - value.local(runtime), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - } - void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { - setValueAtIndex(runtime, index, Value(runtime, value)); - } -}; - -class BigInt { - public: - BigInt() = default; - BigInt(Runtime& runtime, JSValueRef value) - : storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { - storage_->context = runtime.context(); - storage_->value = value; - JSValueProtect(runtime.context(), storage_->value); - } - - static BigInt fromInt64(Runtime& runtime, int64_t value) { - JSValueRef exception = nullptr; - JSValueRef result = nullptr; - if (__builtin_available(macOS 15.0, iOS 18.0, *)) { - result = JSBigIntCreateWithInt64(runtime.context(), value, &exception); - } - if (result == nullptr || exception != nullptr) { - result = JSValueMakeNumber(runtime.context(), static_cast(value)); - } - return BigInt(runtime, result); - } - - static BigInt fromUint64(Runtime& runtime, uint64_t value) { - JSValueRef exception = nullptr; - JSValueRef result = nullptr; - if (__builtin_available(macOS 15.0, iOS 18.0, *)) { - result = JSBigIntCreateWithUInt64(runtime.context(), value, &exception); - } - if (result == nullptr || exception != nullptr) { - result = JSValueMakeNumber(runtime.context(), static_cast(value)); - } - return BigInt(runtime, result); - } - - String toString(Runtime& runtime, int) const { - JSValueRef exception = nullptr; - JSStringRef string = JSValueToStringCopy(runtime.context(), local(runtime), &exception); - if (string == nullptr || exception != nullptr) { - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - String result(runtime, string); - JSStringRelease(string); - return result; - } - - JSValueRef local(Runtime& runtime) const { return storage_->value; } - - operator Value() const { return Value::fromStorage(storage_); } - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class ArrayBuffer : public Object { - public: - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) - : Object(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { - auto* holder = new jscengine::ArrayBufferHolder(std::move(buffer)); - JSValueRef exception = nullptr; - storage_->context = runtime.context(); - storage_->value = JSObjectMakeArrayBufferWithBytesNoCopy( - runtime.context(), holder->buffer->data(), holder->buffer->size(), - [](void*, void* deallocatorContext) { - delete static_cast(deallocatorContext); - }, - holder, &exception); - if (exception != nullptr) { - delete holder; - throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); - } - JSValueProtect(runtime.context(), storage_->value); - } - - explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { - JSValueRef exception = nullptr; - return JSObjectGetArrayBufferByteLength(runtime.context(), local(runtime), &exception); - } - - uint8_t* data(Runtime& runtime) const { - JSValueRef exception = nullptr; - return static_cast( - JSObjectGetArrayBufferBytesPtr(runtime.context(), local(runtime), &exception)); - } -}; -} // namespace engine -} // namespace nativescript - #endif // TARGET_ENGINE_JSC #endif // NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h index f3daf70aa..9f945f8e0 100644 --- a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h @@ -1,6 +1,16 @@ #ifndef NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H #define NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H +// Apple-side entry point for the QuickJS engine layer. +// +// The engine layer itself -- the `nativescript::engine` classes wrapping +// QuickJS -- moved to jsi/quickjs/QuickJSRuntime.h so the Android runtime can +// compile it too. What stays here is exactly what the Apple ObjC bridge needs +// on top of it and Android has no use for: Foundation, the Objective-C +// runtime, the Mach-O metadata section lookup, and the metadata reader. +// +// Apple sources include this path, unchanged, and get both halves. + #ifdef TARGET_ENGINE_QUICKJS #import @@ -11,30 +21,11 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include "Metadata.h" #include "MetadataReader.h" #include "ffi.h" -#include "quickjs.h" + +#include "jsi/quickjs/QuickJSRuntime.h" @protocol NativeApiClassBuilderProtocol @end @@ -43,763 +34,6 @@ extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; #endif -namespace nativescript { -namespace engine { - -class Runtime; -class Value; -class Object; -class Function; -class Array; -class String; -class BigInt; -class ArrayBuffer; - -class JSError : public std::runtime_error { - public: - JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} - explicit JSError(const std::string& message) : std::runtime_error(message) {} -}; - -class StringBuffer { - public: - explicit StringBuffer(std::string value) : value_(std::move(value)) {} - const char* data() const { return value_.data(); } - size_t size() const { return value_.size(); } - - private: - std::string value_; -}; - -class MutableBuffer { - public: - virtual ~MutableBuffer() = default; - virtual size_t size() const = 0; - virtual uint8_t* data() = 0; -}; - -class PropNameID { - public: - PropNameID() = default; - explicit PropNameID(std::string value) : value_(std::move(value)) {} - static PropNameID forAscii(Runtime&, const char* value) { - return PropNameID(value != nullptr ? value : ""); - } - static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } - std::string utf8(Runtime&) const { return value_; } - - private: - std::string value_; -}; - -class HostObject { - public: - virtual ~HostObject() = default; - virtual Value get(Runtime& runtime, const PropNameID& name); - virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); - virtual std::vector getPropertyNames(Runtime& runtime); -}; - -using HostFunctionType = std::function; - -namespace quickjsengine { - -template -const void* hostObjectTypeToken() { - static int token = 0; - return &token; -} - -struct RuntimeState { - explicit RuntimeState(JSContext* context) : context(context) {} - JSContext* context = nullptr; - bool hostClassRegistered = false; - bool functionClassRegistered = false; - bool selectorGroupDataClassRegistered = false; -}; - -extern JSClassID gHostClassId; -extern JSClassID gFunctionClassId; - -std::shared_ptr stateForContext(JSContext* context); - -struct ValueStorage { - enum class Kind { - Undefined, - Null, - Bool, - Number, - QuickJS, - QuickJSBorrowed, - }; - - explicit ValueStorage(Kind kind) : kind(kind) {} - ~ValueStorage() { - if (kind == Kind::QuickJS && context != nullptr && !JS_IsUninitialized(value)) { - JS_FreeValue(context, value); - } - } - - Kind kind = Kind::Undefined; - bool boolValue = false; - double numberValue = 0; - JSContext* context = nullptr; - JSValue value = JS_UNINITIALIZED; -}; - -struct HostObjectHolder { - HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, - const void* typeToken) - : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} - std::shared_ptr state; - std::shared_ptr hostObject; - const void* typeToken = nullptr; -}; - -struct FunctionHolder { - FunctionHolder(std::shared_ptr state, HostFunctionType callback) - : state(std::move(state)), callback(std::move(callback)) {} - std::shared_ptr state; - HostFunctionType callback; -}; - -struct ArrayBufferHolder { - explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} - std::shared_ptr buffer; -}; - -inline std::string valueToUtf8(JSContext* context, JSValueConst value) { - size_t length = 0; - const char* cString = JS_ToCStringLen(context, &length, value); - if (cString == nullptr) { - return {}; - } - std::string result(cString, length); - JS_FreeCString(context, cString); - return result; -} - -inline std::string currentExceptionMessage(JSContext* context) { - JSValue exception = JS_GetException(context); - std::string message = valueToUtf8(context, exception); - JS_FreeValue(context, exception); - return message.empty() ? std::string("QuickJS function call failed.") - : message; -} - -inline std::string atomToUtf8(JSContext* context, JSAtom atom) { - const char* cString = JS_AtomToCString(context, atom); - if (cString == nullptr) { - return {}; - } - std::string result(cString); - JS_FreeCString(context, cString); - return result; -} - -inline JSValue throwError(JSContext* context, const std::exception& error) { - return JS_ThrowTypeError(context, "%s", error.what()); -} - -void ensureClasses(Runtime& runtime); - -} // namespace quickjsengine - -class Runtime { - public: - explicit Runtime(JSContext* context) : state_(quickjsengine::stateForContext(context)) {} - explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} - JSContext* context() const { return state_->context; } - std::shared_ptr state() const { return state_; } - Object global(); - Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); - void drainMicrotasks() { - JSContext* ctx = context(); - JSRuntime* rt = JS_GetRuntime(ctx); - JSContext* jobCtx = nullptr; - while (JS_ExecutePendingJob(rt, &jobCtx) > 0) { - } - } - - private: - std::shared_ptr state_; -}; - -class String { - public: - String() = default; - String(Runtime& runtime, JSValue value); - static String createFromUtf8(Runtime& runtime, const char* value) { - return String(runtime, JS_NewString(runtime.context(), value != nullptr ? value : "")); - } - static String createFromUtf8(Runtime& runtime, const std::string& value) { - return String(runtime, JS_NewStringLen(runtime.context(), value.data(), value.size())); - } - static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { - return String(runtime, - JS_NewStringLen(runtime.context(), reinterpret_cast(value), length)); - } - std::string utf8(Runtime& runtime) const; - JSValue local(Runtime& runtime) const; - operator Value() const; - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class Value { - public: - Value() : kind_(quickjsengine::ValueStorage::Kind::Undefined) {} - - Value(bool value) : kind_(quickjsengine::ValueStorage::Kind::Bool), boolValue_(value) {} - - Value(double value) : kind_(quickjsengine::ValueStorage::Kind::Number), numberValue_(value) {} - - Value(int value) : Value(static_cast(value)) {} - Value(uint32_t value) : Value(static_cast(value)) {} - - Value(Runtime& runtime, const Value& value) { - if (value.kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed) { - // Promote borrowed to owned - storage_ = std::make_shared( - quickjsengine::ValueStorage::Kind::QuickJS); - storage_->context = runtime.context(); - storage_->value = JS_DupValue(runtime.context(), value.borrowedValue_); - kind_ = quickjsengine::ValueStorage::Kind::QuickJS; - return; - } - kind_ = value.kind_; - boolValue_ = value.boolValue_; - numberValue_ = value.numberValue_; - borrowedContext_ = value.borrowedContext_; - borrowedValue_ = value.borrowedValue_; - storage_ = value.storage_; - } - Value(Runtime& runtime, Value&& value) - : kind_(value.kind_), - boolValue_(value.boolValue_), - numberValue_(value.numberValue_), - borrowedContext_(value.borrowedContext_), - borrowedValue_(value.borrowedValue_), - storage_(std::move(value.storage_)) {} - Value(Runtime& runtime, const String& value) : storage_(value.storage_) { - kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; - } - Value(Runtime& runtime, const Object& object); - Value(Runtime& runtime, const Function& function); - Value(Runtime& runtime, const Array& array); - Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); - Value(Runtime& runtime, const BigInt& bigint); - Value(Runtime& runtime, JSValue value) - : kind_(quickjsengine::ValueStorage::Kind::QuickJS), - storage_(std::make_shared( - quickjsengine::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_DupValue(runtime.context(), value); - } - - static Value borrowed(Runtime& runtime, JSValueConst value) { - Value result; - result.kind_ = quickjsengine::ValueStorage::Kind::QuickJSBorrowed; - result.borrowedContext_ = runtime.context(); - result.borrowedValue_ = value; - return result; - } - - static Value undefined() { return Value(); } - static Value null() { - Value value; - value.kind_ = quickjsengine::ValueStorage::Kind::Null; - return value; - } - static bool strictEquals(Runtime& runtime, const Value& lhs, - const Value& rhs) { - JSValue lhsValue = lhs.local(runtime); - JSValue rhsValue = rhs.local(runtime); - bool equal = JS_IsStrictEqual(runtime.context(), lhsValue, rhsValue); - JS_FreeValue(runtime.context(), lhsValue); - JS_FreeValue(runtime.context(), rhsValue); - return equal; - } - bool isUndefined() const { - if (kind_ == quickjsengine::ValueStorage::Kind::Undefined) { - return true; - } - return isQuickJS() && JS_IsUndefined(jsValue()); - } - bool isNull() const { - if (kind_ == quickjsengine::ValueStorage::Kind::Null) { - return true; - } - return isQuickJS() && JS_IsNull(jsValue()); - } - bool isBool() const { - if (kind_ == quickjsengine::ValueStorage::Kind::Bool) { - return true; - } - return isQuickJS() && JS_IsBool(jsValue()); - } - bool getBool() const { - if (kind_ == quickjsengine::ValueStorage::Kind::Bool) { - return boolValue_; - } - if (isQuickJS()) { - return JS_ToBool(jsContext(), jsValue()) != 0; - } - return false; - } - bool isNumber() const { - if (kind_ == quickjsengine::ValueStorage::Kind::Number) { - return true; - } - return isQuickJS() && JS_IsNumber(jsValue()); - } - double getNumber() const { - if (kind_ == quickjsengine::ValueStorage::Kind::Number) { - return numberValue_; - } - if (isQuickJS()) { - double value = 0; - JS_ToFloat64(jsContext(), &value, jsValue()); - return value; - } - return 0; - } - bool isObject() const { return isQuickJS() && JS_IsObject(jsValue()); } - bool isString() const { return isQuickJS() && JS_IsString(jsValue()); } - bool isBigInt() const { return isQuickJS() && JS_IsBigInt(jsValue()); } - bool isSymbol() const { return isQuickJS() && JS_IsSymbol(jsValue()); } - - Object asObject(Runtime& runtime) const; - String asString(Runtime& runtime) const; - BigInt getBigInt(Runtime& runtime) const; - - JSValue local(Runtime& runtime) const { - switch (kind_) { - case quickjsengine::ValueStorage::Kind::Undefined: - return JS_UNDEFINED; - case quickjsengine::ValueStorage::Kind::Null: - return JS_NULL; - case quickjsengine::ValueStorage::Kind::Bool: - return JS_NewBool(runtime.context(), boolValue_); - case quickjsengine::ValueStorage::Kind::Number: - return JS_NewFloat64(runtime.context(), numberValue_); - case quickjsengine::ValueStorage::Kind::QuickJS: - case quickjsengine::ValueStorage::Kind::QuickJSBorrowed: - return JS_DupValue(runtime.context(), jsValue()); - } - } - - // Access the shared storage (for Object/Function/Array interop) - std::shared_ptr storage() const { return storage_; } - - static Value fromStorage(std::shared_ptr s) { - Value v; - v.kind_ = s->kind; - v.boolValue_ = s->boolValue; - v.numberValue_ = s->numberValue; - v.storage_ = std::move(s); - return v; - } - - private: - friend class Runtime; - friend class Object; - friend class String; - friend class BigInt; - friend class ArrayBuffer; - friend class Function; - friend class Array; - - bool isQuickJS() const { - return kind_ == quickjsengine::ValueStorage::Kind::QuickJS || - kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed; - } - JSContext* jsContext() const { - return kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed ? borrowedContext_ - : storage_->context; - } - JSValue jsValue() const { - return kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed ? borrowedValue_ - : storage_->value; - } - - quickjsengine::ValueStorage::Kind kind_ = quickjsengine::ValueStorage::Kind::Undefined; - bool boolValue_ = false; - double numberValue_ = 0; - JSContext* borrowedContext_ = nullptr; - JSValue borrowedValue_ = JS_UNINITIALIZED; - std::shared_ptr storage_; -}; - -class Object { - public: - Object() = default; - explicit Object(Runtime& runtime) - : storage_(std::make_shared( - quickjsengine::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_NewObject(runtime.context()); - } - static Object fromValueStorage(std::shared_ptr storage) { - Object object; - object.storage_ = std::move(storage); - return object; - } - template - static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { - auto baseHost = std::static_pointer_cast(std::move(host)); - return createFromHostObjectWithToken(runtime, std::move(baseHost), - quickjsengine::hostObjectTypeToken()); - } - - Value getProperty(Runtime& runtime, const char* name) const { - JSValue object = local(runtime); - JSValue result = JS_GetPropertyStr(runtime.context(), object, name != nullptr ? name : ""); - JS_FreeValue(runtime.context(), object); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS property get failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value getProperty(Runtime& runtime, const std::string& name) const { - return getProperty(runtime, name.c_str()); - } - Value getProperty(Runtime& runtime, const Value& key) const { - JSValue object = local(runtime); - JSValue keyValue = key.local(runtime); - JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); - JS_FreeValue(runtime.context(), keyValue); - JSValue result = - atom == JS_ATOM_NULL ? JS_UNDEFINED : JS_GetProperty(runtime.context(), object, atom); - if (atom != JS_ATOM_NULL) { - JS_FreeAtom(runtime.context(), atom); - } - JS_FreeValue(runtime.context(), object); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS property get failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Object getPropertyAsObject(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime); - } - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; - - void setProperty(Runtime& runtime, const char* name, const Value& value) { - JSValue object = local(runtime); - JSValue localValue = value.local(runtime); - int status = - JS_SetPropertyStr(runtime.context(), object, name != nullptr ? name : "", localValue); - JS_FreeValue(runtime.context(), object); - if (status < 0) { - throw JSError(runtime, "QuickJS property set failed."); - } - } - void setProperty(Runtime& runtime, const char* name, const String& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Object& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Function& value); - void setProperty(Runtime& runtime, const char* name, const Array& value); - void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); - void setProperty(Runtime& runtime, const char* name, bool value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const char* name, double value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const std::string& name, const Value& value) { - setProperty(runtime, name.c_str(), value); - } - void setProperty(Runtime& runtime, const Value& key, const Value& value) { - JSValue object = local(runtime); - JSValue keyValue = key.local(runtime); - JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); - JS_FreeValue(runtime.context(), keyValue); - JSValue localValue = value.local(runtime); - int status = - atom == JS_ATOM_NULL ? -1 : JS_SetProperty(runtime.context(), object, atom, localValue); - if (atom != JS_ATOM_NULL) { - JS_FreeAtom(runtime.context(), atom); - } - JS_FreeValue(runtime.context(), object); - if (status < 0) { - throw JSError(runtime, "QuickJS property set failed."); - } - } - bool hasProperty(Runtime& runtime, const char* name) const { - JSValue object = local(runtime); - JSAtom atom = JS_NewAtom(runtime.context(), name != nullptr ? name : ""); - int result = JS_HasProperty(runtime.context(), object, atom); - JS_FreeAtom(runtime.context(), atom); - JS_FreeValue(runtime.context(), object); - return result > 0; - } - bool isFunction(Runtime& runtime) const { - JSValue object = local(runtime); - bool result = JS_IsFunction(runtime.context(), object); - JS_FreeValue(runtime.context(), object); - return result; - } - bool isArray(Runtime& runtime) const { - JSValue object = local(runtime); - int result = JS_IsArray(object); - JS_FreeValue(runtime.context(), object); - return result > 0; - } - bool isArrayBuffer(Runtime& runtime) const { - JSValue object = local(runtime); - bool result = JS_IsArrayBuffer(object); - JS_FreeValue(runtime.context(), object); - return result; - } - Function asFunction(Runtime& runtime) const; - Array getArray(Runtime& runtime) const; - ArrayBuffer getArrayBuffer(Runtime& runtime) const; - Array getPropertyNames(Runtime& runtime) const; - - template - bool isHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - return holder != nullptr && holder->typeToken == quickjsengine::hostObjectTypeToken(); - } - template - std::shared_ptr getHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - if (holder == nullptr || holder->typeToken != quickjsengine::hostObjectTypeToken()) { - return nullptr; - } - return std::static_pointer_cast(holder->hostObject); - } - JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } - operator Value() const { return Value::fromStorage(storage_); } - - protected: - friend class Value; - friend class Runtime; - friend class Function; - friend class Array; - friend class ArrayBuffer; - explicit Object(std::shared_ptr storage) - : storage_(std::move(storage)) {} - static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken); - quickjsengine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const; - std::shared_ptr storage_; -}; - -class Function : public Object { - public: - Function() = default; - explicit Function(Object object) : Object(std::move(object.storage_)) {} - static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback); - Value call(Runtime& runtime, const Value* args, size_t count) const { - JSValue function = local(runtime); - JSValue global = JS_GetGlobalObject(runtime.context()); - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValue result = JS_Call(runtime.context(), function, global, static_cast(argv.size()), - argv.empty() ? nullptr : argv.data()); - for (auto& arg : argv) { - JS_FreeValue(runtime.context(), arg); - } - JS_FreeValue(runtime.context(), global); - JS_FreeValue(runtime.context(), function); - if (JS_IsException(result)) { - throw JSError(runtime, quickjsengine::currentExceptionMessage(runtime.context())); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value call(Runtime& runtime) const { - return call(runtime, static_cast(nullptr), 0); - } - Value call(Runtime& runtime, std::nullptr_t, size_t) const { - return call(runtime, static_cast(nullptr), 0); - } - template - Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { - return call(runtime, static_cast(args), count); - } - template - Value call(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return call(runtime, static_cast(argv), sizeof...(Args)); - } - Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, - size_t count = 0) const { - JSValue function = local(runtime); - JSValue thisValue = thisObject.local(runtime); - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValue result = JS_Call(runtime.context(), function, thisValue, static_cast(argv.size()), - argv.empty() ? nullptr : argv.data()); - for (auto& arg : argv) { - JS_FreeValue(runtime.context(), arg); - } - JS_FreeValue(runtime.context(), thisValue); - JS_FreeValue(runtime.context(), function); - if (JS_IsException(result)) { - throw JSError(runtime, quickjsengine::currentExceptionMessage(runtime.context())); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { - JSValue function = local(runtime); - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValue result = JS_CallConstructor(runtime.context(), function, static_cast(argv.size()), - argv.empty() ? nullptr : argv.data()); - for (auto& arg : argv) { - JS_FreeValue(runtime.context(), arg); - } - JS_FreeValue(runtime.context(), function); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS constructor call failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { - return callAsConstructor(runtime, static_cast(nullptr), 0); - } - template - Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { - return callAsConstructor(runtime, static_cast(args), count); - } - template - Value callAsConstructor(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); - } -}; - -class Array : public Object { - public: - explicit Array(Runtime& runtime, size_t size) - : Object(std::make_shared( - quickjsengine::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_NewArray(runtime.context()); - JS_SetPropertyStr(runtime.context(), storage_->value, "length", - JS_NewUint32(runtime.context(), static_cast(size))); - } - explicit Array(Object object) : Object(std::move(object.storage_)) {} - size_t size(Runtime& runtime) const { - Value length = getProperty(runtime, "length"); - return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; - } - Value getValueAtIndex(Runtime& runtime, size_t index) const { - JSValue object = local(runtime); - JSValue result = JS_GetPropertyUint32(runtime.context(), object, static_cast(index)); - JS_FreeValue(runtime.context(), object); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS array get failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { - JSValue object = local(runtime); - JSValue localValue = value.local(runtime); - int status = - JS_SetPropertyUint32(runtime.context(), object, static_cast(index), localValue); - JS_FreeValue(runtime.context(), object); - if (status < 0) { - throw JSError(runtime, "QuickJS array set failed."); - } - } - void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { - setValueAtIndex(runtime, index, Value(runtime, value)); - } -}; - -class BigInt { - public: - BigInt() = default; - BigInt(Runtime& runtime, JSValue value) - : storage_(std::make_shared( - quickjsengine::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_DupValue(runtime.context(), value); - } - static BigInt fromInt64(Runtime& runtime, int64_t value) { - JSValue result = JS_NewBigInt64(runtime.context(), value); - BigInt bigint(runtime, result); - JS_FreeValue(runtime.context(), result); - return bigint; - } - static BigInt fromUint64(Runtime& runtime, uint64_t value) { - JSValue result = JS_NewBigUint64(runtime.context(), value); - BigInt bigint(runtime, result); - JS_FreeValue(runtime.context(), result); - return bigint; - } - String toString(Runtime& runtime, int) const; - JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } - operator Value() const { return Value::fromStorage(storage_); } - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class ArrayBuffer : public Object { - public: - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) - : Object(std::make_shared( - quickjsengine::ValueStorage::Kind::QuickJS)) { - auto* holder = new quickjsengine::ArrayBufferHolder(std::move(buffer)); - storage_->context = runtime.context(); - storage_->value = JS_NewArrayBuffer( - runtime.context(), holder->buffer->data(), holder->buffer->size(), - [](JSRuntime*, void* opaque, void*) { - delete static_cast(opaque); - }, - holder, false); - } - explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} - size_t size(Runtime& runtime) const { - JSValue object = local(runtime); - size_t size = 0; - JS_GetArrayBuffer(runtime.context(), &size, object); - JS_FreeValue(runtime.context(), object); - return size; - } - uint8_t* data(Runtime& runtime) const { - JSValue object = local(runtime); - size_t size = 0; - uint8_t* data = JS_GetArrayBuffer(runtime.context(), &size, object); - JS_FreeValue(runtime.context(), object); - return data; - } -}; -} // namespace engine -} // namespace nativescript - #endif // TARGET_ENGINE_QUICKJS #endif // NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H diff --git a/NativeScript/ffi/objc/shared/NativeApiBackendConfig.h b/NativeScript/ffi/objc/shared/NativeApiBackendConfig.h index a6c2044e8..bc229fd2f 100644 --- a/NativeScript/ffi/objc/shared/NativeApiBackendConfig.h +++ b/NativeScript/ffi/objc/shared/NativeApiBackendConfig.h @@ -1,32 +1,8 @@ -#ifndef NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H -#define NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H +#ifndef NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_FORWARD_H +#define NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_FORWARD_H -#include -#include +// Moved to the shared JSI layer; this forwarding header keeps existing +// Apple include paths working unchanged. +#include "jsi/shared/NativeApiBackendConfig.h" -namespace nativescript { - -class NativeApiBackendScheduler { - public: - virtual ~NativeApiBackendScheduler() = default; - virtual void invokeOnJS(std::function task) = 0; - virtual void invokeOnUI(std::function task) = 0; -}; - -struct NativeApiBackendConfig { - const char* metadataPath = nullptr; - const void* metadataPtr = nullptr; - const char* globalName = "__nativeScriptNativeApi"; - std::shared_ptr scheduler = nullptr; - std::function)> nativeInvocationInvoker = nullptr; - std::function)> nativeCallbackInvoker = nullptr; - std::function)> runtimeCallbackInvoker = nullptr; - std::function)> jsThreadCallbackInvoker = nullptr; - std::function)> jsThreadAsyncCallbackInvoker = nullptr; - bool invokeCallbacksOnNativeCallerThread = false; - bool installGlobalSymbols = false; -}; - -} // namespace nativescript - -#endif // NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H +#endif // NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_FORWARD_H diff --git a/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h b/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h index c941006fe..4a2577ab7 100644 --- a/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h +++ b/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h @@ -1,80 +1,14 @@ #ifndef NATIVESCRIPT_FFI_SHARED_PREPARED_SIGNATURE_DISPATCH_H #define NATIVESCRIPT_FFI_SHARED_PREPARED_SIGNATURE_DISPATCH_H -#include "SignatureDispatchCore.h" - -#ifndef NS_GSD_BACKEND_PREPARED -#define NS_GSD_BACKEND_PREPARED 0 -#endif - -#ifndef NS_GSD_BACKEND_HERMES -#define NS_GSD_BACKEND_HERMES 0 -#endif - -#ifndef NS_GSD_BACKEND_NAPI -#define NS_GSD_BACKEND_NAPI 0 -#endif - -#ifndef NS_HAS_GENERATED_SIGNATURE_DISPATCH -#define NS_HAS_GENERATED_SIGNATURE_DISPATCH 0 -#endif - -#define NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH \ - (NS_GSD_BACKEND_HERMES || NS_GSD_BACKEND_NAPI || NS_GSD_BACKEND_PREPARED) - -#if NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH && \ - !NS_HAS_GENERATED_SIGNATURE_DISPATCH -#error GeneratedSignatureDispatch.inc did not enable this generated signature dispatch backend. -#endif - -#if !NS_HAS_GENERATED_SIGNATURE_DISPATCH -namespace nativescript { -inline constexpr ObjCDispatchEntry kGeneratedObjCDispatchEntries[] = { - {0, nullptr}}; -inline constexpr CFunctionDispatchEntry kGeneratedCFunctionDispatchEntries[] = { - {0, nullptr}}; -inline constexpr BlockDispatchEntry kGeneratedBlockDispatchEntries[] = { - {0, nullptr}}; -} // namespace nativescript -#endif - -namespace nativescript { - -inline ObjCPreparedInvoker lookupObjCPreparedInvoker(uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedObjCDispatchEntries, dispatchId); -} - -inline CFunctionPreparedInvoker lookupCFunctionPreparedInvoker( - uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedCFunctionDispatchEntries, dispatchId); -} - -inline BlockPreparedInvoker lookupBlockPreparedInvoker(uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedBlockDispatchEntries, dispatchId); -} - -inline bool isPreparedGeneratedDispatchRequired() { -#if NS_HAS_GENERATED_SIGNATURE_DISPATCH && \ - (NS_GSD_BACKEND_PREPARED || NS_GSD_BACKEND_HERMES) - return isGeneratedDispatchEnabled(); -#else - return false; -#endif -} - -} // namespace nativescript +// Moved to jsi/shared/PreparedSignatureDispatch.h so the Android runtime can +// use it. This forwarding header keeps the Apple include paths unchanged. +// +// SignatureDispatchCore.h is included first because callers of this header +// have always got it transitively, and it is what computes the dispatch ids +// from the Objective-C metadata. + +#include "ffi/objc/shared/SignatureDispatchCore.h" +#include "jsi/shared/PreparedSignatureDispatch.h" #endif // NATIVESCRIPT_FFI_SHARED_PREPARED_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/objc/shared/SignatureDispatchCore.h b/NativeScript/ffi/objc/shared/SignatureDispatchCore.h index 229347a74..c53fd26dc 100644 --- a/NativeScript/ffi/objc/shared/SignatureDispatchCore.h +++ b/NativeScript/ffi/objc/shared/SignatureDispatchCore.h @@ -10,95 +10,17 @@ #include "Metadata.h" #include "MetadataReader.h" +// The platform-neutral half of GSD (FNV-1a hashing, call kinds, dispatch-entry +// types and table lookup) lives in the shared JSI layer so the Android runtime +// can use the same mechanism. Everything below is Objective-C metadata specific +// and stays here. +#include "jsi/shared/SignatureHashing.h" + namespace nativescript { -enum class SignatureCallKind : uint8_t { - ObjCMethod = 1, - CFunction = 2, - BlockInvoke = 3, -}; - -using ObjCPreparedInvoker = void (*)(void* fnptr, void** avalues, - void* rvalue); -using CFunctionPreparedInvoker = void (*)(void* fnptr, void** avalues, - void* rvalue); -using BlockPreparedInvoker = void (*)(void* fnptr, void** avalues, - void* rvalue); - -struct ObjCDispatchEntry { - uint64_t dispatchId; - ObjCPreparedInvoker invoker; -}; - -struct CFunctionDispatchEntry { - uint64_t dispatchId; - CFunctionPreparedInvoker invoker; -}; - -struct BlockDispatchEntry { - uint64_t dispatchId; - BlockPreparedInvoker invoker; -}; - -inline constexpr uint64_t kSignatureHashOffsetBasis = 14695981039346656037ull; -inline constexpr uint64_t kSignatureHashPrime = 1099511628211ull; inline constexpr metagen::MDSectionOffset kNullMetadataSectionOffset = static_cast(0xFFFFFFFFu >> 1); -inline uint64_t hashBytesFnv1a(const void* data, size_t size, - uint64_t seed = kSignatureHashOffsetBasis) { - const auto* bytes = static_cast(data); - uint64_t hash = seed; - for (size_t i = 0; i < size; i++) { - hash ^= static_cast(bytes[i]); - hash *= kSignatureHashPrime; - } - return hash; -} - -inline uint64_t composeSignatureDispatchId(uint64_t signatureHash, - SignatureCallKind kind, - uint8_t flags) { - const uint8_t kindByte = static_cast(kind); - uint64_t hash = hashBytesFnv1a(&kindByte, sizeof(kindByte)); - hash = hashBytesFnv1a(&flags, sizeof(flags), hash); - return hashBytesFnv1a(&signatureHash, sizeof(signatureHash), hash); -} - -template -inline Invoker lookupDispatchInvoker(const Entry (&entries)[N], - uint64_t dispatchId) { - if (dispatchId == 0 || N <= 1) { - return nullptr; - } - - size_t low = 1; - size_t high = N; - while (low < high) { - const size_t mid = low + ((high - low) >> 1); - const uint64_t midId = entries[mid].dispatchId; - if (midId < dispatchId) { - low = mid + 1; - } else { - high = mid; - } - } - - if (low < N && entries[low].dispatchId == dispatchId) { - return entries[low].invoker; - } - return nullptr; -} - -inline bool isGeneratedDispatchEnabled() { - static const bool enabled = []() { - const char* disableFlag = std::getenv("NS_DISABLE_GSD"); - return disableFlag == nullptr || disableFlag[0] == '\0' || - (disableFlag[0] == '0' && disableFlag[1] == '\0'); - }(); - return enabled; -} - namespace signature_dispatch_detail { inline metagen::MDTypeKind canonicalizeSignatureTypeKind( diff --git a/NativeScript/ffi/objc/shared/Tasks.h b/NativeScript/ffi/objc/shared/Tasks.h index cd3b4a68c..8b9c8a2d3 100644 --- a/NativeScript/ffi/objc/shared/Tasks.h +++ b/NativeScript/ffi/objc/shared/Tasks.h @@ -1,20 +1,8 @@ -#ifndef Tasks_h -#define Tasks_h +#ifndef NATIVESCRIPT_FFI_SHARED_TASKS_FORWARD_H +#define NATIVESCRIPT_FFI_SHARED_TASKS_FORWARD_H -#include -#include +// Moved to the shared JSI layer; this forwarding header keeps existing +// Apple include paths working unchanged. +#include "jsi/shared/Tasks.h" -namespace nativescript { - -class Tasks { -public: - static void Register(std::function task); - static void Drain(); - static void ClearTasks(); -private: - static std::vector> tasks_; -}; - -} - -#endif /* Tasks_h */ +#endif // NATIVESCRIPT_FFI_SHARED_TASKS_FORWARD_H diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm b/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm index 97ef47828..1b410907b 100644 --- a/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm +++ b/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm @@ -124,17 +124,19 @@ Class v8NativeClassArgument(Runtime& runtime, v8::Local value) { } auto* state = runtime.rawState(); if (state != nullptr && value->IsObject()) { - if (state->nativeClassArgumentLast.nativeClass != Nil && + // The cache holds the Class opaquely so the engine layer stays platform + // neutral; cast back on the way out. + if (state->nativeClassArgumentLast.nativeClass != nullptr && !state->nativeClassArgumentLast.value.IsEmpty() && state->nativeClassArgumentLast.value.Get(runtime.isolate()) == value) { - return state->nativeClassArgumentLast.nativeClass; + return (Class)state->nativeClassArgumentLast.nativeClass; } for (auto& entry : state->nativeClassArgumentCache) { - if (entry.nativeClass != Nil && !entry.value.IsEmpty() && + if (entry.nativeClass != nullptr && !entry.value.IsEmpty() && entry.value.Get(runtime.isolate()) == value) { state->nativeClassArgumentLast.value.Reset(runtime.isolate(), value); state->nativeClassArgumentLast.nativeClass = entry.nativeClass; - return entry.nativeClass; + return (Class)entry.nativeClass; } } } @@ -194,13 +196,13 @@ bool readV8EngineSelectorArgument(Runtime& runtime, v8::Local value, !state->nativeSelectorArgumentLast.value.IsEmpty() && state->nativeSelectorArgumentLast.value.Get(runtime.isolate()) == value) { - *result = state->nativeSelectorArgumentLast.selector; + *result = (SEL)state->nativeSelectorArgumentLast.selector; return true; } for (auto& entry : state->nativeSelectorArgumentCache) { if (entry.selector != nullptr && !entry.value.IsEmpty() && entry.value.Get(runtime.isolate()) == value) { - *result = entry.selector; + *result = (SEL)entry.selector; state->nativeSelectorArgumentLast.value.Reset(runtime.isolate(), value); state->nativeSelectorArgumentLast.selector = entry.selector; return true; diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h index a7f2c32b0..67c59063d 100644 --- a/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h +++ b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h @@ -1,6 +1,16 @@ #ifndef NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H #define NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H +// Apple-side entry point for the V8 engine layer. +// +// The engine layer itself -- the `nativescript::engine` classes wrapping V8 -- +// moved to jsi/v8/V8Runtime.h so the Android runtime can compile it too. What +// stays here is exactly what the Apple ObjC bridge needs on top of it and +// Android has no use for: Foundation, the Objective-C runtime, the Mach-O +// metadata section lookup, and the metadata reader. +// +// Apple sources include this path, unchanged, and get both halves. + #ifdef TARGET_ENGINE_V8 #import @@ -11,30 +21,11 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include "Metadata.h" #include "MetadataReader.h" #include "ffi.h" -#include "v8.h" + +#include "jsi/v8/V8Runtime.h" @protocol NativeApiClassBuilderProtocol @end @@ -43,777 +34,6 @@ extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; #endif -namespace nativescript { -namespace engine { - -class Runtime; -class Value; -class Object; -class Function; -class Array; -class String; -class BigInt; -class ArrayBuffer; - -class JSError : public std::runtime_error { - public: - JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} - explicit JSError(const std::string& message) : std::runtime_error(message) {} -}; - -class StringBuffer { - public: - explicit StringBuffer(std::string value) : value_(std::move(value)) {} - const char* data() const { return value_.data(); } - size_t size() const { return value_.size(); } - - private: - std::string value_; -}; - -class MutableBuffer { - public: - virtual ~MutableBuffer() = default; - virtual size_t size() const = 0; - virtual uint8_t* data() = 0; -}; - -class PropNameID { - public: - PropNameID() = default; - explicit PropNameID(std::string value) : value_(std::move(value)) {} - - static PropNameID forAscii(Runtime&, const char* value) { - return PropNameID(value != nullptr ? value : ""); - } - - static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } - - std::string utf8(Runtime&) const { return value_; } - - private: - std::string value_; -}; - -class HostObject { - public: - virtual ~HostObject() = default; - virtual Value get(Runtime& runtime, const PropNameID& name); - virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); - virtual std::vector getPropertyNames(Runtime& runtime); -}; - -using HostFunctionType = std::function; - -namespace v8engine { - -struct RuntimeState { - explicit RuntimeState(v8::Isolate* isolate, v8::Local context) : isolate(isolate) { - this->context.Reset(isolate, context); - } - - ~RuntimeState() { - nativeClassArgumentLast.value.Reset(); - nativeClassArgumentLast.nativeClass = Nil; - for (auto& entry : nativeClassArgumentCache) { - entry.value.Reset(); - entry.nativeClass = Nil; - } - nativeSelectorArgumentLast.value.Reset(); - nativeSelectorArgumentLast.selector = nullptr; - for (auto& entry : nativeSelectorArgumentCache) { - entry.value.Reset(); - entry.selector = nullptr; - } - context.Reset(); - } - - v8::Local localContext() const { - v8::Local ctx = context.Get(isolate); - return ctx.IsEmpty() ? isolate->GetCurrentContext() : ctx; - } - - v8::Isolate* isolate = nullptr; - v8::Global context; - v8::Global hostObjectTemplate; - v8::Global nativeObjectTemplate; // kNonMasking for instances - std::vector> retainedNativeData; - struct NativeClassArgumentCacheEntry { - v8::Global value; - Class nativeClass = Nil; - }; - NativeClassArgumentCacheEntry nativeClassArgumentLast; - NativeClassArgumentCacheEntry nativeClassArgumentCache[4]; - size_t nativeClassArgumentCacheNext = 0; - struct NativeSelectorArgumentCacheEntry { - v8::Global value; - SEL selector = nullptr; - }; - NativeSelectorArgumentCacheEntry nativeSelectorArgumentLast; - NativeSelectorArgumentCacheEntry nativeSelectorArgumentCache[4]; - size_t nativeSelectorArgumentCacheNext = 0; -}; - -struct ValueStorage { - enum class Kind : uint8_t { - Undefined, - Null, - Bool, - Number, - V8, - V8Borrowed, - }; - - explicit ValueStorage(Kind kind) : kind(kind) {} - - ~ValueStorage() { value.Reset(); } - - Kind kind = Kind::Undefined; - bool boolValue = false; - double numberValue = 0; - v8::Global value; - v8::Local borrowedValue; -}; - -template -const void* hostObjectTypeToken() { - static int token = 0; - return &token; -} - -struct HostObjectHolder { - HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, - const void* typeToken) - : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} - - ~HostObjectHolder() { object.Reset(); } - - std::shared_ptr state; - std::shared_ptr hostObject; - const void* typeToken = nullptr; - v8::Global object; -}; - -struct FunctionHolder { - FunctionHolder(std::shared_ptr state, HostFunctionType callback) - : state(std::move(state)), callback(std::move(callback)) {} - - ~FunctionHolder() { function.Reset(); } - - std::shared_ptr state; - HostFunctionType callback; - v8::Global function; -}; - -struct ArrayBufferHolder { - explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} - - std::shared_ptr buffer; - v8::Global object; -}; - -inline v8::Local makeV8String(v8::Isolate* isolate, const std::string& value) { - return v8::String::NewFromUtf8(isolate, value.c_str(), v8::NewStringType::kNormal, - static_cast(value.size())) - .ToLocalChecked(); -} - -inline std::string toUtf8(v8::Isolate* isolate, v8::Local value) { - if (value.IsEmpty()) { - return {}; - } - v8::String::Utf8Value utf8(isolate, value); - return *utf8 != nullptr ? std::string(*utf8, utf8.length()) : std::string(); -} - -inline std::string propertyNameToUtf8(v8::Isolate* isolate, v8::Local property) { - if (property->IsSymbol() && - property.As()->StrictEquals(v8::Symbol::GetIterator(isolate))) { - return "Symbol.iterator"; - } - return toUtf8(isolate, property); -} - -inline std::string currentExceptionMessage(v8::Isolate* isolate, v8::TryCatch& tryCatch) { - if (tryCatch.HasCaught()) { - return toUtf8(isolate, tryCatch.Exception()); - } - return "NativeScript V8 engine operation failed."; -} - -inline void throwV8Exception(v8::Isolate* isolate, const std::exception& exception) { - isolate->ThrowException(v8::Exception::Error(makeV8String(isolate, exception.what()))); -} - -} // namespace v8engine - -class Runtime { - public: - Runtime(v8::Isolate* isolate, v8::Local context) - : state_(std::make_shared(isolate, context)) {} - - explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} - - v8::Isolate* isolate() const { return state_->isolate; } - v8::Local context() const { return state_->localContext(); } - v8engine::RuntimeState* rawState() const { return state_.get(); } - std::shared_ptr state() const { return state_; } - - Object global(); - - Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); - - void drainMicrotasks() { isolate()->PerformMicrotaskCheckpoint(); } - - private: - std::shared_ptr state_; -}; - -class String { - public: - String() = default; - String(Runtime& runtime, v8::Local value); - - static String createFromUtf8(Runtime& runtime, const char* value) { - return String(runtime, - v8engine::makeV8String(runtime.isolate(), value != nullptr ? value : "")); - } - - static String createFromUtf8(Runtime& runtime, const std::string& value) { - return String(runtime, v8engine::makeV8String(runtime.isolate(), value)); - } - - static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { - return String(runtime, v8::String::NewFromUtf8( - runtime.isolate(), - reinterpret_cast( - value != nullptr ? value : reinterpret_cast("")), - v8::NewStringType::kNormal, static_cast(length)) - .ToLocalChecked()); - } - - std::string utf8(Runtime& runtime) const { - return v8engine::toUtf8(runtime.isolate(), local(runtime)); - } - - v8::Local local(Runtime& runtime) const { - return storage_->value.Get(runtime.isolate()).As(); - } - - operator Value() const; - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class Value { - public: - Value() : kind_(v8engine::ValueStorage::Kind::Undefined) {} - - Value(bool value) : kind_(v8engine::ValueStorage::Kind::Bool), boolValue_(value) {} - - Value(double value) : kind_(v8engine::ValueStorage::Kind::Number), numberValue_(value) {} - - Value(int value) : Value(static_cast(value)) {} - Value(uint32_t value) : Value(static_cast(value)) {} - - Value(Runtime& runtime, const Value& value) { - if (value.kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { - // Promote borrowed to owned - storage_ = - std::make_shared(v8engine::ValueStorage::Kind::V8); - storage_->value.Reset(runtime.isolate(), value.borrowedValue_); - kind_ = v8engine::ValueStorage::Kind::V8; - return; - } - kind_ = value.kind_; - boolValue_ = value.boolValue_; - numberValue_ = value.numberValue_; - borrowedValue_ = value.borrowedValue_; - storage_ = value.storage_; - } - Value(Runtime& runtime, Value&& value) - : kind_(value.kind_), - boolValue_(value.boolValue_), - numberValue_(value.numberValue_), - borrowedValue_(value.borrowedValue_), - storage_(std::move(value.storage_)) {} - Value(Runtime& runtime, const String& value); - Value(Runtime& runtime, const Object& object); - Value(Runtime& runtime, const Function& function); - Value(Runtime& runtime, const Array& array); - Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); - Value(Runtime& runtime, const BigInt& bigint); - - static Value undefined() { return Value(); } - - static Value null() { - Value value; - value.kind_ = v8engine::ValueStorage::Kind::Null; - return value; - } - - static bool strictEquals(Runtime& runtime, const Value& lhs, - const Value& rhs) { - return lhs.local(runtime)->StrictEquals(rhs.local(runtime)); - } - - bool isUndefined() const; - bool isNull() const; - bool isBool() const; - bool getBool() const; - bool isNumber() const; - double getNumber() const; - - bool isObject() const; - bool isString() const; - bool isBigInt() const; - bool isSymbol() const; - - Object asObject(Runtime& runtime) const; - String asString(Runtime& runtime) const; - BigInt getBigInt(Runtime& runtime) const; - - v8::Local local(Runtime& runtime) const { - v8::Isolate* isolate = runtime.isolate(); - switch (kind_) { - case v8engine::ValueStorage::Kind::Undefined: - return v8::Undefined(isolate); - case v8engine::ValueStorage::Kind::Null: - return v8::Null(isolate); - case v8engine::ValueStorage::Kind::Bool: - return v8::Boolean::New(isolate, boolValue_); - case v8engine::ValueStorage::Kind::Number: - return v8::Number::New(isolate, numberValue_); - case v8engine::ValueStorage::Kind::V8: - return storage_->value.Get(isolate); - case v8engine::ValueStorage::Kind::V8Borrowed: - return borrowedValue_; - } - } - - Value(Runtime& runtime, v8::Local value) - : kind_(v8engine::ValueStorage::Kind::V8), - storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), value); - } - - static Value borrowed(Runtime&, v8::Local value) { - Value result; - result.kind_ = v8engine::ValueStorage::Kind::V8Borrowed; - result.borrowedValue_ = value; - return result; - } - - // Access the shared storage (for Object/Function/Array interop) - std::shared_ptr storage() const { return storage_; } - - static Value fromStorage(std::shared_ptr s) { - Value v; - v.kind_ = s->kind; - v.boolValue_ = s->boolValue; - v.numberValue_ = s->numberValue; - v.borrowedValue_ = s->borrowedValue; - v.storage_ = std::move(s); - return v; - } - - private: - friend class Runtime; - friend class Object; - friend class String; - friend class BigInt; - friend class ArrayBuffer; - friend class Function; - friend class Array; - - v8engine::ValueStorage::Kind kind_ = v8engine::ValueStorage::Kind::Undefined; - bool boolValue_ = false; - double numberValue_ = 0; - v8::Local borrowedValue_; - std::shared_ptr storage_; -}; - -class Object { - public: - Object() = default; - explicit Object(Runtime& runtime) - : storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), v8::Object::New(runtime.isolate())); - } - - static Object fromValueStorage(std::shared_ptr storage) { - Object object; - object.storage_ = std::move(storage); - return object; - } - - template - static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { - auto baseHost = std::static_pointer_cast(std::move(host)); - return createFromHostObjectWithToken(runtime, std::move(baseHost), - v8engine::hostObjectTypeToken()); - } - - // Create a native object instance using kNonMasking template for fast - // prototype-based property access. - template - static Object createNativeInstanceHostObject(Runtime& runtime, std::shared_ptr host) { - auto baseHost = std::static_pointer_cast(std::move(host)); - return createNativeInstanceWithToken(runtime, std::move(baseHost), - v8engine::hostObjectTypeToken()); - } - - static Object createNativeInstanceWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken); - - Value getProperty(Runtime& runtime, const char* name) const { - return getProperty(runtime, - v8engine::makeV8String(runtime.isolate(), name != nullptr ? name : "")); - } - - Value getProperty(Runtime& runtime, const std::string& name) const { - return getProperty(runtime, name.c_str()); - } - - Value getProperty(Runtime& runtime, const Value& key) const { - return getProperty(runtime, key.local(runtime)); - } - - Value getProperty(Runtime& runtime, v8::Local key) const { - v8::TryCatch tryCatch(runtime.isolate()); - v8::Local result; - if (!local(runtime)->Get(runtime.context(), key).ToLocal(&result)) { - throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Object getPropertyAsObject(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime); - } - - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; - - void setProperty(Runtime& runtime, const char* name, const Value& value) { - setProperty(runtime, v8engine::makeV8String(runtime.isolate(), name != nullptr ? name : ""), - value); - } - - void setProperty(Runtime& runtime, const char* name, const String& value) { - setProperty(runtime, name, Value(runtime, value)); - } - - void setProperty(Runtime& runtime, const char* name, const Object& value) { - setProperty(runtime, name, Value(runtime, value)); - } - - void setProperty(Runtime& runtime, const char* name, const Function& value); - void setProperty(Runtime& runtime, const char* name, const Array& value); - void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); - void setProperty(Runtime& runtime, const char* name, bool value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const char* name, double value) { - setProperty(runtime, name, Value(value)); - } - - void setProperty(Runtime& runtime, const std::string& name, const Value& value) { - setProperty(runtime, name.c_str(), value); - } - - void setProperty(Runtime& runtime, const Value& key, const Value& value) { - setProperty(runtime, key.local(runtime), value); - } - - void setProperty(Runtime& runtime, v8::Local key, const Value& value) { - v8::TryCatch tryCatch(runtime.isolate()); - if (!local(runtime)->Set(runtime.context(), key, value.local(runtime)).FromMaybe(false)) { - throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - } - - bool hasProperty(Runtime& runtime, const char* name) const { - v8::TryCatch tryCatch(runtime.isolate()); - return local(runtime) - ->Has(runtime.context(), - v8engine::makeV8String(runtime.isolate(), name != nullptr ? name : "")) - .FromMaybe(false); - } - - bool isFunction(Runtime& runtime) const { return local(runtime)->IsFunction(); } - bool isArray(Runtime& runtime) const { return local(runtime)->IsArray(); } - bool isArrayBuffer(Runtime& runtime) const { return local(runtime)->IsArrayBuffer(); } - - Function asFunction(Runtime& runtime) const; - Array getArray(Runtime& runtime) const; - ArrayBuffer getArrayBuffer(Runtime& runtime) const; - Array getPropertyNames(Runtime& runtime) const; - - template - bool isHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - return holder != nullptr && holder->typeToken == v8engine::hostObjectTypeToken(); - } - - template - std::shared_ptr getHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - if (holder == nullptr || holder->typeToken != v8engine::hostObjectTypeToken()) { - return nullptr; - } - return std::static_pointer_cast(holder->hostObject); - } - - v8::Local local(Runtime& runtime) const { - if (storage_->kind == v8engine::ValueStorage::Kind::V8Borrowed) { - return storage_->borrowedValue.As(); - } - return storage_->value.Get(runtime.isolate()).As(); - } - - operator Value() const { - return Value::fromStorage(storage_); - } - - protected: - friend class Value; - friend class Runtime; - friend class Function; - friend class Array; - friend class ArrayBuffer; - - explicit Object(std::shared_ptr storage) : storage_(std::move(storage)) {} - - static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken); - - v8engine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { - v8::Local object = local(runtime); - if (object->InternalFieldCount() < 1) { - return nullptr; - } - return static_cast(object->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); - } - - std::shared_ptr storage_; -}; - -class Function : public Object { - public: - Function() = default; - explicit Function(Object object) : Object(std::move(object.storage_)) {} - - static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback); - - Value call(Runtime& runtime, const Value* args, size_t count) const { - v8::TryCatch tryCatch(runtime.isolate()); - std::vector> argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - v8::Local result; - if (!local(runtime) - .As() - ->Call(runtime.context(), runtime.context()->Global(), static_cast(argv.size()), - argv.data()) - .ToLocal(&result)) { - throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Value call(Runtime& runtime) const { - return call(runtime, static_cast(nullptr), 0); - } - - Value call(Runtime& runtime, std::nullptr_t, size_t) const { - return call(runtime, static_cast(nullptr), 0); - } - - template - Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { - return call(runtime, static_cast(args), count); - } - - template - Value call(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return call(runtime, static_cast(argv), sizeof...(Args)); - } - - Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, - size_t count = 0) const { - v8::TryCatch tryCatch(runtime.isolate()); - std::vector> argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - v8::Local result; - if (!local(runtime) - .As() - ->Call(runtime.context(), thisObject.local(runtime), static_cast(argv.size()), - argv.data()) - .ToLocal(&result)) { - throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { - v8::TryCatch tryCatch(runtime.isolate()); - std::vector> argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - v8::Local result; - if (!local(runtime) - .As() - ->NewInstance(runtime.context(), static_cast(argv.size()), argv.data()) - .ToLocal(&result)) { - throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { - return callAsConstructor(runtime, static_cast(nullptr), 0); - } - - template - Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { - return callAsConstructor(runtime, static_cast(args), count); - } - - template - Value callAsConstructor(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); - } - - operator Value() const { - return Value::fromStorage(storage_); - } -}; - -class Array : public Object { - public: - explicit Array(Runtime& runtime, size_t size) - : Object(std::make_shared(v8engine::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), - v8::Array::New(runtime.isolate(), static_cast(size))); - } - - explicit Array(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { return local(runtime).As()->Length(); } - - Value getValueAtIndex(Runtime& runtime, size_t index) const { - v8::TryCatch tryCatch(runtime.isolate()); - v8::Local result; - if (!local(runtime)->Get(runtime.context(), static_cast(index)).ToLocal(&result)) { - throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { - v8::TryCatch tryCatch(runtime.isolate()); - if (!local(runtime) - ->Set(runtime.context(), static_cast(index), value.local(runtime)) - .FromMaybe(false)) { - throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - } - - void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { - setValueAtIndex(runtime, index, Value(runtime, value)); - } - - operator Value() const { - return Value::fromStorage(storage_); - } -}; - -class BigInt { - public: - BigInt() = default; - BigInt(Runtime& runtime, v8::Local value) - : storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), value); - } - - static BigInt fromInt64(Runtime& runtime, int64_t value) { - return BigInt(runtime, v8::BigInt::New(runtime.isolate(), value)); - } - - static BigInt fromUint64(Runtime& runtime, uint64_t value) { - return BigInt(runtime, v8::BigInt::NewFromUnsigned(runtime.isolate(), value)); - } - - String toString(Runtime& runtime, int radix) const { - v8::TryCatch tryCatch(runtime.isolate()); - v8::Local result; - (void)radix; - if (!local(runtime)->ToString(runtime.context()).ToLocal(&result)) { - throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return String(runtime, result); - } - - v8::Local local(Runtime& runtime) const { - return storage_->value.Get(runtime.isolate()).As(); - } - - operator Value() const { - return Value::fromStorage(storage_); - } - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class ArrayBuffer : public Object { - public: - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) - : Object(std::make_shared(v8engine::ValueStorage::Kind::V8)) { - auto holder = new v8engine::ArrayBufferHolder(std::move(buffer)); - auto backingStore = v8::ArrayBuffer::NewBackingStore( - holder->buffer->data(), holder->buffer->size(), - [](void*, size_t, void* deleterData) { - auto* holder = static_cast(deleterData); - holder->object.Reset(); - delete holder; - }, - holder); - v8::Local arrayBuffer = - v8::ArrayBuffer::New(runtime.isolate(), std::move(backingStore)); - storage_->value.Reset(runtime.isolate(), arrayBuffer); - holder->object.Reset(runtime.isolate(), arrayBuffer); - } - - explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { return local(runtime).As()->ByteLength(); } - - uint8_t* data(Runtime& runtime) const { - auto backingStore = local(runtime).As()->GetBackingStore(); - return static_cast(backingStore->Data()); - } - - operator Value() const { - return Value::fromStorage(storage_); - } -}; -} // namespace engine -} // namespace nativescript - #endif // TARGET_ENGINE_V8 #endif // NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H diff --git a/NativeScript/jsi/hermes/NativeApiJsi.h b/NativeScript/jsi/hermes/NativeApiJsi.h new file mode 100644 index 000000000..b22773e79 --- /dev/null +++ b/NativeScript/jsi/hermes/NativeApiJsi.h @@ -0,0 +1,26 @@ +#ifndef NATIVE_API_JSI_H +#define NATIVE_API_JSI_H + +#include + +#include "jsi/shared/NativeApiBackendConfig.h" + +namespace nativescript { + +using NativeApiJsiScheduler = NativeApiBackendScheduler; +using NativeApiJsiConfig = NativeApiBackendConfig; + +facebook::jsi::Object CreateNativeApiJSI( + facebook::jsi::Runtime& runtime, + const NativeApiJsiConfig& config = NativeApiJsiConfig{}); + +void InstallNativeApiJSI( + facebook::jsi::Runtime& runtime, + const NativeApiJsiConfig& config = NativeApiJsiConfig{}); + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApiJSI( + facebook::jsi::Runtime* runtime, const char* metadataPath); + +#endif // NATIVE_API_JSI_H diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCHostObjects.mm b/NativeScript/jsi/jsc/JSCHostObjects.cpp similarity index 99% rename from NativeScript/ffi/objc/jsc/NativeApiJSCHostObjects.mm rename to NativeScript/jsi/jsc/JSCHostObjects.cpp index 3caee72cf..eeb8ed28a 100644 --- a/NativeScript/ffi/objc/jsc/NativeApiJSCHostObjects.mm +++ b/NativeScript/jsi/jsc/JSCHostObjects.cpp @@ -1,5 +1,5 @@ -#include "NativeApiJSCRuntime.h" -#include "../shared/NativeApiStackValueArray.h" +#include "jsi/jsc/JSCRuntime.h" +#include "jsi/shared/NativeApiStackValueArray.h" #ifdef TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.mm b/NativeScript/jsi/jsc/JSCRuntime.cpp similarity index 96% rename from NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.mm rename to NativeScript/jsi/jsc/JSCRuntime.cpp index da5aa2ce8..c46e12882 100644 --- a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.mm +++ b/NativeScript/jsi/jsc/JSCRuntime.cpp @@ -1,4 +1,4 @@ -#include "NativeApiJSCRuntime.h" +#include "jsi/jsc/JSCRuntime.h" #ifdef TARGET_ENGINE_JSC diff --git a/NativeScript/jsi/jsc/JSCRuntime.h b/NativeScript/jsi/jsc/JSCRuntime.h new file mode 100644 index 000000000..9ad585db9 --- /dev/null +++ b/NativeScript/jsi/jsc/JSCRuntime.h @@ -0,0 +1,956 @@ +#ifndef NATIVESCRIPT_JSI_JSC_JSC_RUNTIME_H +#define NATIVESCRIPT_JSI_JSC_JSC_RUNTIME_H + +// JavaScriptCore behind the JSI-shaped `nativescript::engine` API. +// +// This header is platform-neutral: it depends on JavaScriptCore and the C++ +// standard library, and on nothing from Foundation, the Objective-C runtime, +// or the Apple metadata format. Android compiles it as-is. +// +// The Apple bridge's extra baggage lives in +// ffi/objc/jsc/NativeApiJSCRuntime.h, which includes this file. Apple sources +// keep including that path unchanged. + +#ifdef TARGET_ENGINE_JSC + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nativescript { +namespace engine { + +class Runtime; +class Value; +class Object; +class Function; +class Array; +class String; +class BigInt; +class ArrayBuffer; + +class JSError : public std::runtime_error { + public: + JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} + explicit JSError(const std::string& message) : std::runtime_error(message) {} +}; + +class StringBuffer { + public: + explicit StringBuffer(std::string value) : value_(std::move(value)) {} + const char* data() const { return value_.data(); } + size_t size() const { return value_.size(); } + + private: + std::string value_; +}; + +class MutableBuffer { + public: + virtual ~MutableBuffer() = default; + virtual size_t size() const = 0; + virtual uint8_t* data() = 0; +}; + +class PropNameID { + public: + PropNameID() = default; + explicit PropNameID(std::string value) : value_(std::move(value)) {} + + static PropNameID forAscii(Runtime&, const char* value) { + return PropNameID(value != nullptr ? value : ""); + } + + static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } + + std::string utf8(Runtime&) const { return value_; } + + private: + std::string value_; +}; + +class HostObject { + public: + virtual ~HostObject() = default; + virtual Value get(Runtime& runtime, const PropNameID& name); + virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); +}; + +using HostFunctionType = std::function; + +namespace jscengine { + +inline std::string stringToUtf8(JSStringRef string) { + if (string == nullptr) { + return {}; + } + size_t capacity = JSStringGetMaximumUTF8CStringSize(string); + std::string result(capacity, '\0'); + size_t written = JSStringGetUTF8CString(string, result.data(), capacity); + if (written == 0) { + return {}; + } + result.resize(written - 1); + return result; +} + +// Decode strict UTF-8 into UTF-16, returning false on any malformed sequence. +// +// This used to go through NSString, which is why it was the last thing keeping +// the JSC engine layer tied to Foundation. JSStringCreateWithUTF8CString is +// not a substitute on its own: it takes a NUL-terminated C string, so it +// truncates at an embedded U+0000, which is a legal JavaScript character. +inline bool decodeUtf8ToUtf16(const std::string& value, + std::vector& out) { + out.clear(); + out.reserve(value.size()); + + const auto* bytes = reinterpret_cast(value.data()); + const size_t size = value.size(); + + for (size_t i = 0; i < size;) { + const unsigned char lead = bytes[i]; + uint32_t codePoint = 0; + size_t extra = 0; + + if (lead < 0x80) { + codePoint = lead; + } else if ((lead & 0xE0) == 0xC0) { + codePoint = lead & 0x1Fu; + extra = 1; + } else if ((lead & 0xF0) == 0xE0) { + codePoint = lead & 0x0Fu; + extra = 2; + } else if ((lead & 0xF8) == 0xF0) { + codePoint = lead & 0x07u; + extra = 3; + } else { + return false; // continuation byte or 5/6-byte form + } + + if (i + extra >= size) { + return false; // truncated sequence (holds trivially when extra == 0) + } + + for (size_t j = 1; j <= extra; j++) { + const unsigned char continuation = bytes[i + j]; + if ((continuation & 0xC0) != 0x80) { + return false; + } + codePoint = (codePoint << 6) | (continuation & 0x3Fu); + } + + // Reject overlong forms, surrogates encoded directly, and out-of-range. + static constexpr uint32_t kMinForLength[4] = {0x0, 0x80, 0x800, 0x10000}; + if (codePoint < kMinForLength[extra] || codePoint > 0x10FFFF || + (codePoint >= 0xD800 && codePoint <= 0xDFFF)) { + return false; + } + + if (codePoint <= 0xFFFF) { + out.push_back(static_cast(codePoint)); + } else { + codePoint -= 0x10000; + out.push_back(static_cast(0xD800 + (codePoint >> 10))); + out.push_back(static_cast(0xDC00 + (codePoint & 0x3FF))); + } + + i += extra + 1; + } + + return true; +} + +inline JSStringRef makeJSString(const std::string& value) { + std::vector characters; + if (!decodeUtf8ToUtf16(value, characters)) { + // Malformed UTF-8; fall back to whatever JSC makes of it, matching the + // behaviour of the previous NSString-returns-nil path. + return JSStringCreateWithUTF8CString(value.c_str()); + } + return JSStringCreateWithCharacters(characters.data(), characters.size()); +} + +inline JSStringRef makeJSString(const char* value) { + return JSStringCreateWithUTF8CString(value != nullptr ? value : ""); +} + +inline std::string valueToUtf8(JSContextRef context, JSValueRef value) { + if (value == nullptr) { + return {}; + } + JSValueRef exception = nullptr; + JSStringRef string = JSValueToStringCopy(context, value, &exception); + if (string == nullptr || exception != nullptr) { + if (string != nullptr) { + JSStringRelease(string); + } + return {}; + } + std::string result = stringToUtf8(string); + JSStringRelease(string); + return result; +} + +inline JSValueRef makeError(JSContextRef context, const std::string& message) { + JSStringRef string = makeJSString(message); + JSValueRef argument = JSValueMakeString(context, string); + JSStringRelease(string); + JSValueRef exception = nullptr; + JSObjectRef error = JSObjectMakeError(context, 1, &argument, &exception); + if (error != nullptr && exception == nullptr) { + return error; + } + return argument; +} + +inline void setException(JSContextRef context, JSValueRef* exception, const std::exception& error) { + if (exception != nullptr) { + *exception = makeError(context, error.what()); + } +} + +struct RuntimeState { + explicit RuntimeState(JSGlobalContextRef context) : context(context) {} + + ~RuntimeState() { + if (hostClass != nullptr) { + JSClassRelease(hostClass); + } + if (functionClass != nullptr) { + JSClassRelease(functionClass); + } + if (selectorGroupFunctionClass != nullptr) { + JSClassRelease(selectorGroupFunctionClass); + } + } + + JSGlobalContextRef context = nullptr; + JSClassRef hostClass = nullptr; + JSClassRef functionClass = nullptr; + JSClassRef selectorGroupFunctionClass = nullptr; +}; + +struct ValueStorage { + enum class Kind { + Undefined, + Null, + Bool, + Number, + JSC, + JSCBorrowed, + }; + + explicit ValueStorage(Kind kind) : kind(kind) {} + + ~ValueStorage() { + if (kind == Kind::JSC && context != nullptr && value != nullptr) { + JSValueUnprotect(context, value); + } + } + + Kind kind = Kind::Undefined; + bool boolValue = false; + double numberValue = 0; + JSGlobalContextRef context = nullptr; + JSValueRef value = nullptr; +}; + +template +const void* hostObjectTypeToken() { + static int token = 0; + return &token; +} + +struct HostObjectHolder { + HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, + const void* typeToken, bool nativeInstance = false) + : state(std::move(state)), + hostObject(std::move(hostObject)), + typeToken(typeToken), + nativeInstance(nativeInstance) {} + + std::shared_ptr state; + std::shared_ptr hostObject; + const void* typeToken = nullptr; + // Set for wrappers around an ObjC instance, whose JS prototype is allowed to + // shadow a native property. Recorded as a flag rather than derived from + // typeToken: the wrapper class is defined inside an anonymous namespace in + // the engine translation unit, so a type token taken anywhere else names a + // different type and never compares equal. + bool nativeInstance = false; +}; + +struct FunctionHolder { + FunctionHolder(std::shared_ptr state, HostFunctionType callback) + : state(std::move(state)), callback(std::move(callback)) {} + + std::shared_ptr state; + HostFunctionType callback; +}; + +struct ArrayBufferHolder { + explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} + + std::shared_ptr buffer; +}; + +void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function); + +} // namespace jscengine + +class Runtime { + public: + explicit Runtime(JSGlobalContextRef context) + : state_(std::make_shared(context)) {} + + explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} + + JSGlobalContextRef context() const { return state_->context; } + std::shared_ptr state() const { return state_; } + + Object global(); + Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); + void drainMicrotasks() {} + + private: + std::shared_ptr state_; +}; + +class String { + public: + String() = default; + String(Runtime& runtime, JSStringRef string); + + static String createFromUtf8(Runtime& runtime, const char* value) { + JSStringRef string = jscengine::makeJSString(value); + String result(runtime, string); + JSStringRelease(string); + return result; + } + + static String createFromUtf8(Runtime& runtime, const std::string& value) { + JSStringRef string = jscengine::makeJSString(value); + String result(runtime, string); + JSStringRelease(string); + return result; + } + + static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { + std::string text(reinterpret_cast(value), length); + return createFromUtf8(runtime, text); + } + + std::string utf8(Runtime& runtime) const; + JSValueRef local(Runtime& runtime) const { return storage_->value; } + operator Value() const; + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class Value { + public: + Value() : kind_(jscengine::ValueStorage::Kind::Undefined) {} + + Value(bool value) : kind_(jscengine::ValueStorage::Kind::Bool), boolValue_(value) {} + + Value(double value) : kind_(jscengine::ValueStorage::Kind::Number), numberValue_(value) {} + + Value(int value) : Value(static_cast(value)) {} + Value(uint32_t value) : Value(static_cast(value)) {} + + Value(Runtime& runtime, const Value& value) { + if (value.kind_ == jscengine::ValueStorage::Kind::JSCBorrowed) { + // Promote borrowed to owned + storage_ = std::make_shared(jscengine::ValueStorage::Kind::JSC); + storage_->context = runtime.context(); + storage_->value = value.borrowedValue_ != nullptr ? value.borrowedValue_ + : JSValueMakeUndefined(runtime.context()); + JSValueProtect(runtime.context(), storage_->value); + kind_ = jscengine::ValueStorage::Kind::JSC; + return; + } + kind_ = value.kind_; + boolValue_ = value.boolValue_; + numberValue_ = value.numberValue_; + borrowedContext_ = value.borrowedContext_; + borrowedValue_ = value.borrowedValue_; + storage_ = value.storage_; + } + Value(Runtime& runtime, Value&& value) + : kind_(value.kind_), + boolValue_(value.boolValue_), + numberValue_(value.numberValue_), + borrowedContext_(value.borrowedContext_), + borrowedValue_(value.borrowedValue_), + storage_(std::move(value.storage_)) {} + Value(Runtime& runtime, const String& value); + Value(Runtime& runtime, const Object& object); + Value(Runtime& runtime, const Function& function); + Value(Runtime& runtime, const Array& array); + Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); + Value(Runtime& runtime, const BigInt& bigint); + Value(Runtime& runtime, JSValueRef value) + : kind_(jscengine::ValueStorage::Kind::JSC), + storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + storage_->context = runtime.context(); + storage_->value = value != nullptr ? value : JSValueMakeUndefined(runtime.context()); + JSValueProtect(runtime.context(), storage_->value); + } + + static Value borrowed(Runtime& runtime, JSValueRef value) { + Value result; + result.kind_ = jscengine::ValueStorage::Kind::JSCBorrowed; + result.borrowedContext_ = runtime.context(); + result.borrowedValue_ = value != nullptr ? value : JSValueMakeUndefined(runtime.context()); + return result; + } + + static Value undefined() { return Value(); } + static Value null() { + Value value; + value.kind_ = jscengine::ValueStorage::Kind::Null; + return value; + } + + static bool strictEquals(Runtime& runtime, const Value& lhs, + const Value& rhs) { + return JSValueIsStrictEqual(runtime.context(), lhs.local(runtime), + rhs.local(runtime)); + } + + bool isUndefined() const { + return kind_ == jscengine::ValueStorage::Kind::Undefined || + (isJSC() && JSValueIsUndefined(jscContext(), jscValue())); + } + bool isNull() const { + return kind_ == jscengine::ValueStorage::Kind::Null || + (isJSC() && JSValueIsNull(jscContext(), jscValue())); + } + bool isBool() const { + return kind_ == jscengine::ValueStorage::Kind::Bool || + (isJSC() && JSValueIsBoolean(jscContext(), jscValue())); + } + bool getBool() const { + if (kind_ == jscengine::ValueStorage::Kind::Bool) { + return boolValue_; + } + return isJSC() && JSValueToBoolean(jscContext(), jscValue()); + } + bool isNumber() const { + return kind_ == jscengine::ValueStorage::Kind::Number || + (isJSC() && JSValueIsNumber(jscContext(), jscValue())); + } + double getNumber() const { + if (kind_ == jscengine::ValueStorage::Kind::Number) { + return numberValue_; + } + return isJSC() ? JSValueToNumber(jscContext(), jscValue(), nullptr) : 0; + } + + bool isObject() const { return isJSC() && JSValueIsObject(jscContext(), jscValue()); } + bool isString() const { return isJSC() && JSValueIsString(jscContext(), jscValue()); } + bool isBigInt() const { + if (!isJSC()) { + return false; + } + if (__builtin_available(macOS 15.0, iOS 18.0, *)) { + return JSValueIsBigInt(jscContext(), jscValue()); + } + return false; + } + bool isSymbol() const { return isJSC() && JSValueIsSymbol(jscContext(), jscValue()); } + + Object asObject(Runtime& runtime) const; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + JSValueRef local(Runtime& runtime) const { + switch (kind_) { + case jscengine::ValueStorage::Kind::Undefined: + return JSValueMakeUndefined(runtime.context()); + case jscengine::ValueStorage::Kind::Null: + return JSValueMakeNull(runtime.context()); + case jscengine::ValueStorage::Kind::Bool: + return JSValueMakeBoolean(runtime.context(), boolValue_); + case jscengine::ValueStorage::Kind::Number: + return JSValueMakeNumber(runtime.context(), numberValue_); + case jscengine::ValueStorage::Kind::JSC: + return storage_->value; + case jscengine::ValueStorage::Kind::JSCBorrowed: + return borrowedValue_; + } + } + + // Access the shared storage (for Object/Function/Array interop) + std::shared_ptr storage() const { return storage_; } + + static Value fromStorage(std::shared_ptr s) { + Value v; + v.kind_ = s->kind; + v.boolValue_ = s->boolValue; + v.numberValue_ = s->numberValue; + v.storage_ = std::move(s); + return v; + } + + private: + friend class Runtime; + friend class Object; + friend class String; + friend class BigInt; + friend class ArrayBuffer; + friend class Function; + friend class Array; + + bool isJSC() const { + return kind_ == jscengine::ValueStorage::Kind::JSC || + kind_ == jscengine::ValueStorage::Kind::JSCBorrowed; + } + JSContextRef jscContext() const { + return kind_ == jscengine::ValueStorage::Kind::JSCBorrowed ? borrowedContext_ + : storage_->context; + } + JSValueRef jscValue() const { + return kind_ == jscengine::ValueStorage::Kind::JSCBorrowed ? borrowedValue_ : storage_->value; + } + + jscengine::ValueStorage::Kind kind_ = jscengine::ValueStorage::Kind::Undefined; + bool boolValue_ = false; + double numberValue_ = 0; + JSGlobalContextRef borrowedContext_ = nullptr; + JSValueRef borrowedValue_ = nullptr; + std::shared_ptr storage_; +}; + +class Object { + public: + Object() = default; + explicit Object(Runtime& runtime) + : storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + storage_->context = runtime.context(); + storage_->value = JSObjectMake(runtime.context(), nullptr, nullptr); + JSValueProtect(runtime.context(), storage_->value); + } + + static Object fromValueStorage(std::shared_ptr storage) { + Object object; + object.storage_ = std::move(storage); + return object; + } + + template + static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken(runtime, std::move(baseHost), + jscengine::hostObjectTypeToken()); + } + + // The wrapper for an ObjC instance. Same object as createFromHostObject + // builds, but marked so property reads defer to the JS prototype chain when + // it carries the name -- V8 gets that from its kNonMasking template; JSC's + // class callback runs before the prototype is consulted, so it has to ask. + template + static Object createNativeInstanceHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken(runtime, std::move(baseHost), + jscengine::hostObjectTypeToken(), + /* nativeInstance */ true); + } + + Value getProperty(Runtime& runtime, const char* name) const { + JSStringRef property = jscengine::makeJSString(name); + JSValueRef exception = nullptr; + JSValueRef result = + JSObjectGetProperty(runtime.context(), local(runtime), property, &exception); + JSStringRelease(property); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + + Value getProperty(Runtime& runtime, const std::string& name) const { + return getProperty(runtime, name.c_str()); + } + + Value getProperty(Runtime& runtime, const Value& key) const { + JSValueRef exception = nullptr; + JSValueRef result = JSObjectGetPropertyForKey(runtime.context(), local(runtime), + key.local(runtime), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + + Object getPropertyAsObject(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime); + } + + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + void setProperty(Runtime& runtime, const char* name, const Value& value) { + JSStringRef property = jscengine::makeJSString(name); + JSValueRef exception = nullptr; + JSObjectSetProperty(runtime.context(), local(runtime), property, value.local(runtime), + kJSPropertyAttributeNone, &exception); + JSStringRelease(property); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + } + + void setProperty(Runtime& runtime, const char* name, const String& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Object& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Function& value); + void setProperty(Runtime& runtime, const char* name, const Array& value); + void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); + void setProperty(Runtime& runtime, const char* name, bool value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const char* name, double value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const std::string& name, const Value& value) { + setProperty(runtime, name.c_str(), value); + } + void setProperty(Runtime& runtime, const Value& key, const Value& value) { + JSValueRef exception = nullptr; + JSObjectSetPropertyForKey(runtime.context(), local(runtime), key.local(runtime), + value.local(runtime), kJSPropertyAttributeNone, &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + } + + bool hasProperty(Runtime& runtime, const char* name) const { + JSStringRef property = jscengine::makeJSString(name); + bool result = JSObjectHasProperty(runtime.context(), local(runtime), property); + JSStringRelease(property); + return result; + } + + bool isFunction(Runtime& runtime) const { + return JSObjectIsFunction(runtime.context(), local(runtime)); + } + + bool isArray(Runtime& runtime) const { + JSStringRef name = jscengine::makeJSString("Array"); + JSValueRef constructorValue = JSObjectGetProperty( + runtime.context(), JSContextGetGlobalObject(runtime.context()), name, nullptr); + JSStringRelease(name); + if (constructorValue == nullptr || !JSValueIsObject(runtime.context(), constructorValue)) { + return false; + } + JSObjectRef constructor = JSValueToObject(runtime.context(), constructorValue, nullptr); + JSValueRef exception = nullptr; + bool result = + JSValueIsInstanceOfConstructor(runtime.context(), local(runtime), constructor, &exception); + return exception == nullptr && result; + } + + bool isArrayBuffer(Runtime& runtime) const { + JSValueRef exception = nullptr; + JSTypedArrayType type = + JSValueGetTypedArrayType(runtime.context(), storage_->value, &exception); + return exception == nullptr && type == kJSTypedArrayTypeArrayBuffer; + } + + Function asFunction(Runtime& runtime) const; + Array getArray(Runtime& runtime) const; + ArrayBuffer getArrayBuffer(Runtime& runtime) const; + Array getPropertyNames(Runtime& runtime) const; + + template + bool isHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + return holder != nullptr && holder->typeToken == jscengine::hostObjectTypeToken(); + } + + template + std::shared_ptr getHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + if (holder == nullptr || holder->typeToken != jscengine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); + } + + JSObjectRef local(Runtime& runtime) const { + return reinterpret_cast(const_cast(storage_->value)); + } + + operator Value() const { return Value::fromStorage(storage_); } + + protected: + friend class Value; + friend class Runtime; + friend class Function; + friend class Array; + friend class ArrayBuffer; + + explicit Object(std::shared_ptr storage) + : storage_(std::move(storage)) {} + + static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken, + bool nativeInstance = false); + + jscengine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { + return static_cast(JSObjectGetPrivate(local(runtime))); + } + + std::shared_ptr storage_; +}; + +class Function : public Object { + public: + Function() = default; + explicit Function(Object object) : Object(std::move(object.storage_)) {} + + static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback); + + Value call(Runtime& runtime, const Value* args, size_t count) const { + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValueRef exception = nullptr; + JSValueRef result = JSObjectCallAsFunction( + runtime.context(), local(runtime), JSContextGetGlobalObject(runtime.context()), argv.size(), + argv.empty() ? nullptr : argv.data(), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + + Value call(Runtime& runtime) const { + return call(runtime, static_cast(nullptr), 0); + } + Value call(Runtime& runtime, std::nullptr_t, size_t) const { + return call(runtime, static_cast(nullptr), 0); + } + template + Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { + return call(runtime, static_cast(args), count); + } + template + Value call(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return call(runtime, static_cast(argv), sizeof...(Args)); + } + + Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, + size_t count = 0) const { + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValueRef exception = nullptr; + JSValueRef result = + JSObjectCallAsFunction(runtime.context(), local(runtime), thisObject.local(runtime), + argv.size(), argv.empty() ? nullptr : argv.data(), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + + Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValueRef exception = nullptr; + JSValueRef result = JSObjectCallAsConstructor(runtime.context(), local(runtime), argv.size(), + argv.empty() ? nullptr : argv.data(), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { + return callAsConstructor(runtime, static_cast(nullptr), 0); + } + template + Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { + return callAsConstructor(runtime, static_cast(args), count); + } + template + Value callAsConstructor(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); + } +}; + +class Array : public Object { + public: + explicit Array(Runtime& runtime, size_t size) + : Object(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + std::vector initial(size, JSValueMakeUndefined(runtime.context())); + JSValueRef exception = nullptr; + storage_->context = runtime.context(); + storage_->value = + JSObjectMakeArray(runtime.context(), initial.size(), initial.data(), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + JSValueProtect(runtime.context(), storage_->value); + } + + explicit Array(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { + Value length = getProperty(runtime, "length"); + return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; + } + + Value getValueAtIndex(Runtime& runtime, size_t index) const { + JSValueRef exception = nullptr; + JSValueRef result = JSObjectGetPropertyAtIndex(runtime.context(), local(runtime), + static_cast(index), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + + void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { + JSValueRef exception = nullptr; + JSObjectSetPropertyAtIndex(runtime.context(), local(runtime), static_cast(index), + value.local(runtime), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + } + void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { + setValueAtIndex(runtime, index, Value(runtime, value)); + } +}; + +class BigInt { + public: + BigInt() = default; + BigInt(Runtime& runtime, JSValueRef value) + : storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + storage_->context = runtime.context(); + storage_->value = value; + JSValueProtect(runtime.context(), storage_->value); + } + + static BigInt fromInt64(Runtime& runtime, int64_t value) { + JSValueRef exception = nullptr; + JSValueRef result = nullptr; + if (__builtin_available(macOS 15.0, iOS 18.0, *)) { + result = JSBigIntCreateWithInt64(runtime.context(), value, &exception); + } + if (result == nullptr || exception != nullptr) { + result = JSValueMakeNumber(runtime.context(), static_cast(value)); + } + return BigInt(runtime, result); + } + + static BigInt fromUint64(Runtime& runtime, uint64_t value) { + JSValueRef exception = nullptr; + JSValueRef result = nullptr; + if (__builtin_available(macOS 15.0, iOS 18.0, *)) { + result = JSBigIntCreateWithUInt64(runtime.context(), value, &exception); + } + if (result == nullptr || exception != nullptr) { + result = JSValueMakeNumber(runtime.context(), static_cast(value)); + } + return BigInt(runtime, result); + } + + String toString(Runtime& runtime, int) const { + JSValueRef exception = nullptr; + JSStringRef string = JSValueToStringCopy(runtime.context(), local(runtime), &exception); + if (string == nullptr || exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + String result(runtime, string); + JSStringRelease(string); + return result; + } + + JSValueRef local(Runtime& runtime) const { return storage_->value; } + + operator Value() const { return Value::fromStorage(storage_); } + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class ArrayBuffer : public Object { + public: + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + : Object(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + auto* holder = new jscengine::ArrayBufferHolder(std::move(buffer)); + JSValueRef exception = nullptr; + storage_->context = runtime.context(); + storage_->value = JSObjectMakeArrayBufferWithBytesNoCopy( + runtime.context(), holder->buffer->data(), holder->buffer->size(), + [](void*, void* deallocatorContext) { + delete static_cast(deallocatorContext); + }, + holder, &exception); + if (exception != nullptr) { + delete holder; + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + JSValueProtect(runtime.context(), storage_->value); + } + + explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { + JSValueRef exception = nullptr; + return JSObjectGetArrayBufferByteLength(runtime.context(), local(runtime), &exception); + } + + uint8_t* data(Runtime& runtime) const { + JSValueRef exception = nullptr; + return static_cast( + JSObjectGetArrayBufferBytesPtr(runtime.context(), local(runtime), &exception)); + } +}; +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_JSC + +#endif // NATIVESCRIPT_JSI_JSC_JSC_RUNTIME_H diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCValue.mm b/NativeScript/jsi/jsc/JSCValue.cpp similarity index 99% rename from NativeScript/ffi/objc/jsc/NativeApiJSCValue.mm rename to NativeScript/jsi/jsc/JSCValue.cpp index f66913761..5db45d4a5 100644 --- a/NativeScript/ffi/objc/jsc/NativeApiJSCValue.mm +++ b/NativeScript/jsi/jsc/JSCValue.cpp @@ -1,4 +1,4 @@ -#include "NativeApiJSCRuntime.h" +#include "jsi/jsc/JSCRuntime.h" #ifdef TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSHostObjects.mm b/NativeScript/jsi/quickjs/QuickJSHostObjects.cpp similarity index 99% rename from NativeScript/ffi/objc/quickjs/NativeApiQuickJSHostObjects.mm rename to NativeScript/jsi/quickjs/QuickJSHostObjects.cpp index 15532e17f..6b1c3561a 100644 --- a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSHostObjects.mm +++ b/NativeScript/jsi/quickjs/QuickJSHostObjects.cpp @@ -1,4 +1,4 @@ -#include "NativeApiQuickJSRuntime.h" +#include "jsi/quickjs/QuickJSRuntime.h" #include "../shared/NativeApiStackValueArray.h" #ifdef TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.mm b/NativeScript/jsi/quickjs/QuickJSRuntime.cpp similarity index 96% rename from NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.mm rename to NativeScript/jsi/quickjs/QuickJSRuntime.cpp index d38eb3ac6..efdba2427 100644 --- a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.mm +++ b/NativeScript/jsi/quickjs/QuickJSRuntime.cpp @@ -1,4 +1,4 @@ -#include "NativeApiQuickJSRuntime.h" +#include "jsi/quickjs/QuickJSRuntime.h" #ifdef TARGET_ENGINE_QUICKJS diff --git a/NativeScript/jsi/quickjs/QuickJSRuntime.h b/NativeScript/jsi/quickjs/QuickJSRuntime.h new file mode 100644 index 000000000..0a56ee904 --- /dev/null +++ b/NativeScript/jsi/quickjs/QuickJSRuntime.h @@ -0,0 +1,799 @@ +#ifndef NATIVESCRIPT_JSI_QUICKJS_QUICKJS_RUNTIME_H +#define NATIVESCRIPT_JSI_QUICKJS_QUICKJS_RUNTIME_H + +// QuickJS behind the JSI-shaped `nativescript::engine` API. +// +// This header is platform-neutral: it depends on QuickJS and the C++ standard +// library, and on nothing from Foundation, the Objective-C runtime, or the +// Apple metadata format. Android compiles it as-is. +// +// The Apple bridge's extra baggage -- Foundation, , the Mach-O +// metadata section lookup, and the NativeApiClassBuilderProtocol forward +// declaration -- lives in ffi/objc/quickjs/NativeApiQuickJSRuntime.h, which +// includes this file. Apple sources keep including that path unchanged. + +#ifdef TARGET_ENGINE_QUICKJS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "quickjs.h" + +namespace nativescript { +namespace engine { + +class Runtime; +class Value; +class Object; +class Function; +class Array; +class String; +class BigInt; +class ArrayBuffer; + +class JSError : public std::runtime_error { + public: + JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} + explicit JSError(const std::string& message) : std::runtime_error(message) {} +}; + +class StringBuffer { + public: + explicit StringBuffer(std::string value) : value_(std::move(value)) {} + const char* data() const { return value_.data(); } + size_t size() const { return value_.size(); } + + private: + std::string value_; +}; + +class MutableBuffer { + public: + virtual ~MutableBuffer() = default; + virtual size_t size() const = 0; + virtual uint8_t* data() = 0; +}; + +class PropNameID { + public: + PropNameID() = default; + explicit PropNameID(std::string value) : value_(std::move(value)) {} + static PropNameID forAscii(Runtime&, const char* value) { + return PropNameID(value != nullptr ? value : ""); + } + static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } + std::string utf8(Runtime&) const { return value_; } + + private: + std::string value_; +}; + +class HostObject { + public: + virtual ~HostObject() = default; + virtual Value get(Runtime& runtime, const PropNameID& name); + virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); +}; + +using HostFunctionType = std::function; + +namespace quickjsengine { + +template +const void* hostObjectTypeToken() { + static int token = 0; + return &token; +} + +struct RuntimeState { + explicit RuntimeState(JSContext* context) : context(context) {} + JSContext* context = nullptr; + bool hostClassRegistered = false; + bool functionClassRegistered = false; + bool selectorGroupDataClassRegistered = false; +}; + +extern JSClassID gHostClassId; +extern JSClassID gFunctionClassId; + +std::shared_ptr stateForContext(JSContext* context); + +struct ValueStorage { + enum class Kind { + Undefined, + Null, + Bool, + Number, + QuickJS, + QuickJSBorrowed, + }; + + explicit ValueStorage(Kind kind) : kind(kind) {} + ~ValueStorage() { + if (kind == Kind::QuickJS && context != nullptr && !JS_IsUninitialized(value)) { + JS_FreeValue(context, value); + } + } + + Kind kind = Kind::Undefined; + bool boolValue = false; + double numberValue = 0; + JSContext* context = nullptr; + JSValue value = JS_UNINITIALIZED; +}; + +struct HostObjectHolder { + HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, + const void* typeToken) + : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} + std::shared_ptr state; + std::shared_ptr hostObject; + const void* typeToken = nullptr; +}; + +struct FunctionHolder { + FunctionHolder(std::shared_ptr state, HostFunctionType callback) + : state(std::move(state)), callback(std::move(callback)) {} + std::shared_ptr state; + HostFunctionType callback; +}; + +struct ArrayBufferHolder { + explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} + std::shared_ptr buffer; +}; + +inline std::string valueToUtf8(JSContext* context, JSValueConst value) { + size_t length = 0; + const char* cString = JS_ToCStringLen(context, &length, value); + if (cString == nullptr) { + return {}; + } + std::string result(cString, length); + JS_FreeCString(context, cString); + return result; +} + +inline std::string currentExceptionMessage(JSContext* context) { + JSValue exception = JS_GetException(context); + std::string message = valueToUtf8(context, exception); + JS_FreeValue(context, exception); + return message.empty() ? std::string("QuickJS function call failed.") + : message; +} + +inline std::string atomToUtf8(JSContext* context, JSAtom atom) { + const char* cString = JS_AtomToCString(context, atom); + if (cString == nullptr) { + return {}; + } + std::string result(cString); + JS_FreeCString(context, cString); + return result; +} + +inline JSValue throwError(JSContext* context, const std::exception& error) { + return JS_ThrowTypeError(context, "%s", error.what()); +} + +void ensureClasses(Runtime& runtime); + +} // namespace quickjsengine + +class Runtime { + public: + explicit Runtime(JSContext* context) : state_(quickjsengine::stateForContext(context)) {} + explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} + JSContext* context() const { return state_->context; } + std::shared_ptr state() const { return state_; } + Object global(); + Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); + void drainMicrotasks() { + JSContext* ctx = context(); + JSRuntime* rt = JS_GetRuntime(ctx); + JSContext* jobCtx = nullptr; + while (JS_ExecutePendingJob(rt, &jobCtx) > 0) { + } + } + + private: + std::shared_ptr state_; +}; + +class String { + public: + String() = default; + String(Runtime& runtime, JSValue value); + static String createFromUtf8(Runtime& runtime, const char* value) { + return String(runtime, JS_NewString(runtime.context(), value != nullptr ? value : "")); + } + static String createFromUtf8(Runtime& runtime, const std::string& value) { + return String(runtime, JS_NewStringLen(runtime.context(), value.data(), value.size())); + } + static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { + return String(runtime, + JS_NewStringLen(runtime.context(), reinterpret_cast(value), length)); + } + std::string utf8(Runtime& runtime) const; + JSValue local(Runtime& runtime) const; + operator Value() const; + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class Value { + public: + Value() : kind_(quickjsengine::ValueStorage::Kind::Undefined) {} + + Value(bool value) : kind_(quickjsengine::ValueStorage::Kind::Bool), boolValue_(value) {} + + Value(double value) : kind_(quickjsengine::ValueStorage::Kind::Number), numberValue_(value) {} + + Value(int value) : Value(static_cast(value)) {} + Value(uint32_t value) : Value(static_cast(value)) {} + + Value(Runtime& runtime, const Value& value) { + if (value.kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed) { + // Promote borrowed to owned + storage_ = std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS); + storage_->context = runtime.context(); + storage_->value = JS_DupValue(runtime.context(), value.borrowedValue_); + kind_ = quickjsengine::ValueStorage::Kind::QuickJS; + return; + } + kind_ = value.kind_; + boolValue_ = value.boolValue_; + numberValue_ = value.numberValue_; + borrowedContext_ = value.borrowedContext_; + borrowedValue_ = value.borrowedValue_; + storage_ = value.storage_; + } + Value(Runtime& runtime, Value&& value) + : kind_(value.kind_), + boolValue_(value.boolValue_), + numberValue_(value.numberValue_), + borrowedContext_(value.borrowedContext_), + borrowedValue_(value.borrowedValue_), + storage_(std::move(value.storage_)) {} + Value(Runtime& runtime, const String& value) : storage_(value.storage_) { + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; + } + Value(Runtime& runtime, const Object& object); + Value(Runtime& runtime, const Function& function); + Value(Runtime& runtime, const Array& array); + Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); + Value(Runtime& runtime, const BigInt& bigint); + Value(Runtime& runtime, JSValue value) + : kind_(quickjsengine::ValueStorage::Kind::QuickJS), + storage_(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_DupValue(runtime.context(), value); + } + + static Value borrowed(Runtime& runtime, JSValueConst value) { + Value result; + result.kind_ = quickjsengine::ValueStorage::Kind::QuickJSBorrowed; + result.borrowedContext_ = runtime.context(); + result.borrowedValue_ = value; + return result; + } + + static Value undefined() { return Value(); } + static Value null() { + Value value; + value.kind_ = quickjsengine::ValueStorage::Kind::Null; + return value; + } + static bool strictEquals(Runtime& runtime, const Value& lhs, + const Value& rhs) { + JSValue lhsValue = lhs.local(runtime); + JSValue rhsValue = rhs.local(runtime); + bool equal = JS_IsStrictEqual(runtime.context(), lhsValue, rhsValue); + JS_FreeValue(runtime.context(), lhsValue); + JS_FreeValue(runtime.context(), rhsValue); + return equal; + } + bool isUndefined() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Undefined) { + return true; + } + return isQuickJS() && JS_IsUndefined(jsValue()); + } + bool isNull() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Null) { + return true; + } + return isQuickJS() && JS_IsNull(jsValue()); + } + bool isBool() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Bool) { + return true; + } + return isQuickJS() && JS_IsBool(jsValue()); + } + bool getBool() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Bool) { + return boolValue_; + } + if (isQuickJS()) { + return JS_ToBool(jsContext(), jsValue()) != 0; + } + return false; + } + bool isNumber() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Number) { + return true; + } + return isQuickJS() && JS_IsNumber(jsValue()); + } + double getNumber() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Number) { + return numberValue_; + } + if (isQuickJS()) { + double value = 0; + JS_ToFloat64(jsContext(), &value, jsValue()); + return value; + } + return 0; + } + bool isObject() const { return isQuickJS() && JS_IsObject(jsValue()); } + bool isString() const { return isQuickJS() && JS_IsString(jsValue()); } + bool isBigInt() const { return isQuickJS() && JS_IsBigInt(jsValue()); } + bool isSymbol() const { return isQuickJS() && JS_IsSymbol(jsValue()); } + + Object asObject(Runtime& runtime) const; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + JSValue local(Runtime& runtime) const { + switch (kind_) { + case quickjsengine::ValueStorage::Kind::Undefined: + return JS_UNDEFINED; + case quickjsengine::ValueStorage::Kind::Null: + return JS_NULL; + case quickjsengine::ValueStorage::Kind::Bool: + return JS_NewBool(runtime.context(), boolValue_); + case quickjsengine::ValueStorage::Kind::Number: + return JS_NewFloat64(runtime.context(), numberValue_); + case quickjsengine::ValueStorage::Kind::QuickJS: + case quickjsengine::ValueStorage::Kind::QuickJSBorrowed: + return JS_DupValue(runtime.context(), jsValue()); + } + } + + // Access the shared storage (for Object/Function/Array interop) + std::shared_ptr storage() const { return storage_; } + + static Value fromStorage(std::shared_ptr s) { + Value v; + v.kind_ = s->kind; + v.boolValue_ = s->boolValue; + v.numberValue_ = s->numberValue; + v.storage_ = std::move(s); + return v; + } + + private: + friend class Runtime; + friend class Object; + friend class String; + friend class BigInt; + friend class ArrayBuffer; + friend class Function; + friend class Array; + + bool isQuickJS() const { + return kind_ == quickjsengine::ValueStorage::Kind::QuickJS || + kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed; + } + JSContext* jsContext() const { + return kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed ? borrowedContext_ + : storage_->context; + } + JSValue jsValue() const { + return kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed ? borrowedValue_ + : storage_->value; + } + + quickjsengine::ValueStorage::Kind kind_ = quickjsengine::ValueStorage::Kind::Undefined; + bool boolValue_ = false; + double numberValue_ = 0; + JSContext* borrowedContext_ = nullptr; + JSValue borrowedValue_ = JS_UNINITIALIZED; + std::shared_ptr storage_; +}; + +class Object { + public: + Object() = default; + explicit Object(Runtime& runtime) + : storage_(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_NewObject(runtime.context()); + } + static Object fromValueStorage(std::shared_ptr storage) { + Object object; + object.storage_ = std::move(storage); + return object; + } + template + static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken(runtime, std::move(baseHost), + quickjsengine::hostObjectTypeToken()); + } + + Value getProperty(Runtime& runtime, const char* name) const { + JSValue object = local(runtime); + JSValue result = JS_GetPropertyStr(runtime.context(), object, name != nullptr ? name : ""); + JS_FreeValue(runtime.context(), object); + if (JS_IsException(result)) { + throw JSError(runtime, "QuickJS property get failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value getProperty(Runtime& runtime, const std::string& name) const { + return getProperty(runtime, name.c_str()); + } + Value getProperty(Runtime& runtime, const Value& key) const { + JSValue object = local(runtime); + JSValue keyValue = key.local(runtime); + JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); + JS_FreeValue(runtime.context(), keyValue); + JSValue result = + atom == JS_ATOM_NULL ? JS_UNDEFINED : JS_GetProperty(runtime.context(), object, atom); + if (atom != JS_ATOM_NULL) { + JS_FreeAtom(runtime.context(), atom); + } + JS_FreeValue(runtime.context(), object); + if (JS_IsException(result)) { + throw JSError(runtime, "QuickJS property get failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Object getPropertyAsObject(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime); + } + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + void setProperty(Runtime& runtime, const char* name, const Value& value) { + JSValue object = local(runtime); + JSValue localValue = value.local(runtime); + int status = + JS_SetPropertyStr(runtime.context(), object, name != nullptr ? name : "", localValue); + JS_FreeValue(runtime.context(), object); + if (status < 0) { + throw JSError(runtime, "QuickJS property set failed."); + } + } + void setProperty(Runtime& runtime, const char* name, const String& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Object& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Function& value); + void setProperty(Runtime& runtime, const char* name, const Array& value); + void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); + void setProperty(Runtime& runtime, const char* name, bool value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const char* name, double value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const std::string& name, const Value& value) { + setProperty(runtime, name.c_str(), value); + } + void setProperty(Runtime& runtime, const Value& key, const Value& value) { + JSValue object = local(runtime); + JSValue keyValue = key.local(runtime); + JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); + JS_FreeValue(runtime.context(), keyValue); + JSValue localValue = value.local(runtime); + int status = + atom == JS_ATOM_NULL ? -1 : JS_SetProperty(runtime.context(), object, atom, localValue); + if (atom != JS_ATOM_NULL) { + JS_FreeAtom(runtime.context(), atom); + } + JS_FreeValue(runtime.context(), object); + if (status < 0) { + throw JSError(runtime, "QuickJS property set failed."); + } + } + bool hasProperty(Runtime& runtime, const char* name) const { + JSValue object = local(runtime); + JSAtom atom = JS_NewAtom(runtime.context(), name != nullptr ? name : ""); + int result = JS_HasProperty(runtime.context(), object, atom); + JS_FreeAtom(runtime.context(), atom); + JS_FreeValue(runtime.context(), object); + return result > 0; + } + bool isFunction(Runtime& runtime) const { + JSValue object = local(runtime); + bool result = JS_IsFunction(runtime.context(), object); + JS_FreeValue(runtime.context(), object); + return result; + } + bool isArray(Runtime& runtime) const { + JSValue object = local(runtime); + int result = JS_IsArray(object); + JS_FreeValue(runtime.context(), object); + return result > 0; + } + bool isArrayBuffer(Runtime& runtime) const { + JSValue object = local(runtime); + bool result = JS_IsArrayBuffer(object); + JS_FreeValue(runtime.context(), object); + return result; + } + Function asFunction(Runtime& runtime) const; + Array getArray(Runtime& runtime) const; + ArrayBuffer getArrayBuffer(Runtime& runtime) const; + Array getPropertyNames(Runtime& runtime) const; + + template + bool isHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + return holder != nullptr && holder->typeToken == quickjsengine::hostObjectTypeToken(); + } + template + std::shared_ptr getHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + if (holder == nullptr || holder->typeToken != quickjsengine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); + } + JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } + operator Value() const { return Value::fromStorage(storage_); } + + protected: + friend class Value; + friend class Runtime; + friend class Function; + friend class Array; + friend class ArrayBuffer; + explicit Object(std::shared_ptr storage) + : storage_(std::move(storage)) {} + static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken); + quickjsengine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const; + std::shared_ptr storage_; +}; + +class Function : public Object { + public: + Function() = default; + explicit Function(Object object) : Object(std::move(object.storage_)) {} + static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback); + Value call(Runtime& runtime, const Value* args, size_t count) const { + JSValue function = local(runtime); + JSValue global = JS_GetGlobalObject(runtime.context()); + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValue result = JS_Call(runtime.context(), function, global, static_cast(argv.size()), + argv.empty() ? nullptr : argv.data()); + for (auto& arg : argv) { + JS_FreeValue(runtime.context(), arg); + } + JS_FreeValue(runtime.context(), global); + JS_FreeValue(runtime.context(), function); + if (JS_IsException(result)) { + throw JSError(runtime, quickjsengine::currentExceptionMessage(runtime.context())); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value call(Runtime& runtime) const { + return call(runtime, static_cast(nullptr), 0); + } + Value call(Runtime& runtime, std::nullptr_t, size_t) const { + return call(runtime, static_cast(nullptr), 0); + } + template + Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { + return call(runtime, static_cast(args), count); + } + template + Value call(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return call(runtime, static_cast(argv), sizeof...(Args)); + } + Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, + size_t count = 0) const { + JSValue function = local(runtime); + JSValue thisValue = thisObject.local(runtime); + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValue result = JS_Call(runtime.context(), function, thisValue, static_cast(argv.size()), + argv.empty() ? nullptr : argv.data()); + for (auto& arg : argv) { + JS_FreeValue(runtime.context(), arg); + } + JS_FreeValue(runtime.context(), thisValue); + JS_FreeValue(runtime.context(), function); + if (JS_IsException(result)) { + throw JSError(runtime, quickjsengine::currentExceptionMessage(runtime.context())); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { + JSValue function = local(runtime); + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValue result = JS_CallConstructor(runtime.context(), function, static_cast(argv.size()), + argv.empty() ? nullptr : argv.data()); + for (auto& arg : argv) { + JS_FreeValue(runtime.context(), arg); + } + JS_FreeValue(runtime.context(), function); + if (JS_IsException(result)) { + throw JSError(runtime, "QuickJS constructor call failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { + return callAsConstructor(runtime, static_cast(nullptr), 0); + } + template + Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { + return callAsConstructor(runtime, static_cast(args), count); + } + template + Value callAsConstructor(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); + } +}; + +class Array : public Object { + public: + explicit Array(Runtime& runtime, size_t size) + : Object(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_NewArray(runtime.context()); + JS_SetPropertyStr(runtime.context(), storage_->value, "length", + JS_NewUint32(runtime.context(), static_cast(size))); + } + explicit Array(Object object) : Object(std::move(object.storage_)) {} + size_t size(Runtime& runtime) const { + Value length = getProperty(runtime, "length"); + return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; + } + Value getValueAtIndex(Runtime& runtime, size_t index) const { + JSValue object = local(runtime); + JSValue result = JS_GetPropertyUint32(runtime.context(), object, static_cast(index)); + JS_FreeValue(runtime.context(), object); + if (JS_IsException(result)) { + throw JSError(runtime, "QuickJS array get failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { + JSValue object = local(runtime); + JSValue localValue = value.local(runtime); + int status = + JS_SetPropertyUint32(runtime.context(), object, static_cast(index), localValue); + JS_FreeValue(runtime.context(), object); + if (status < 0) { + throw JSError(runtime, "QuickJS array set failed."); + } + } + void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { + setValueAtIndex(runtime, index, Value(runtime, value)); + } +}; + +class BigInt { + public: + BigInt() = default; + BigInt(Runtime& runtime, JSValue value) + : storage_(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_DupValue(runtime.context(), value); + } + static BigInt fromInt64(Runtime& runtime, int64_t value) { + JSValue result = JS_NewBigInt64(runtime.context(), value); + BigInt bigint(runtime, result); + JS_FreeValue(runtime.context(), result); + return bigint; + } + static BigInt fromUint64(Runtime& runtime, uint64_t value) { + JSValue result = JS_NewBigUint64(runtime.context(), value); + BigInt bigint(runtime, result); + JS_FreeValue(runtime.context(), result); + return bigint; + } + String toString(Runtime& runtime, int) const; + JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } + operator Value() const { return Value::fromStorage(storage_); } + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class ArrayBuffer : public Object { + public: + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + : Object(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + auto* holder = new quickjsengine::ArrayBufferHolder(std::move(buffer)); + storage_->context = runtime.context(); + storage_->value = JS_NewArrayBuffer( + runtime.context(), holder->buffer->data(), holder->buffer->size(), + [](JSRuntime*, void* opaque, void*) { + delete static_cast(opaque); + }, + holder, false); + } + explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} + size_t size(Runtime& runtime) const { + JSValue object = local(runtime); + size_t size = 0; + JS_GetArrayBuffer(runtime.context(), &size, object); + JS_FreeValue(runtime.context(), object); + return size; + } + uint8_t* data(Runtime& runtime) const { + JSValue object = local(runtime); + size_t size = 0; + uint8_t* data = JS_GetArrayBuffer(runtime.context(), &size, object); + JS_FreeValue(runtime.context(), object); + return data; + } +}; +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_QUICKJS + +#endif // NATIVESCRIPT_JSI_QUICKJS_QUICKJS_RUNTIME_H diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSValue.mm b/NativeScript/jsi/quickjs/QuickJSValue.cpp similarity index 99% rename from NativeScript/ffi/objc/quickjs/NativeApiQuickJSValue.mm rename to NativeScript/jsi/quickjs/QuickJSValue.cpp index bb6852019..8bfb7ebcc 100644 --- a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSValue.mm +++ b/NativeScript/jsi/quickjs/QuickJSValue.cpp @@ -1,4 +1,4 @@ -#include "NativeApiQuickJSRuntime.h" +#include "jsi/quickjs/QuickJSRuntime.h" #ifdef TARGET_ENGINE_QUICKJS diff --git a/NativeScript/jsi/shared/NativeApiBackendConfig.h b/NativeScript/jsi/shared/NativeApiBackendConfig.h new file mode 100644 index 000000000..a6c2044e8 --- /dev/null +++ b/NativeScript/jsi/shared/NativeApiBackendConfig.h @@ -0,0 +1,32 @@ +#ifndef NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H +#define NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H + +#include +#include + +namespace nativescript { + +class NativeApiBackendScheduler { + public: + virtual ~NativeApiBackendScheduler() = default; + virtual void invokeOnJS(std::function task) = 0; + virtual void invokeOnUI(std::function task) = 0; +}; + +struct NativeApiBackendConfig { + const char* metadataPath = nullptr; + const void* metadataPtr = nullptr; + const char* globalName = "__nativeScriptNativeApi"; + std::shared_ptr scheduler = nullptr; + std::function)> nativeInvocationInvoker = nullptr; + std::function)> nativeCallbackInvoker = nullptr; + std::function)> runtimeCallbackInvoker = nullptr; + std::function)> jsThreadCallbackInvoker = nullptr; + std::function)> jsThreadAsyncCallbackInvoker = nullptr; + bool invokeCallbacksOnNativeCallerThread = false; + bool installGlobalSymbols = false; +}; + +} // namespace nativescript + +#endif // NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H diff --git a/NativeScript/ffi/objc/shared/NativeApiStackValueArray.h b/NativeScript/jsi/shared/NativeApiStackValueArray.h similarity index 100% rename from NativeScript/ffi/objc/shared/NativeApiStackValueArray.h rename to NativeScript/jsi/shared/NativeApiStackValueArray.h diff --git a/NativeScript/jsi/shared/PreparedSignatureDispatch.h b/NativeScript/jsi/shared/PreparedSignatureDispatch.h new file mode 100644 index 000000000..17cfc40a4 --- /dev/null +++ b/NativeScript/jsi/shared/PreparedSignatureDispatch.h @@ -0,0 +1,87 @@ +#ifndef NATIVESCRIPT_JSI_SHARED_PREPARED_SIGNATURE_DISPATCH_H +#define NATIVESCRIPT_JSI_SHARED_PREPARED_SIGNATURE_DISPATCH_H + +// Lookup side of generated signature dispatch: given a dispatch id, find the +// ahead-of-time generated trampoline for it. +// +// Platform-neutral. It needs only the hashing/lookup core, not the +// metadata-format-specific half that computes the ids -- which is why this can +// live here while ffi/objc/shared/SignatureDispatchCore.h cannot. + +#include "jsi/shared/SignatureHashing.h" + +#ifndef NS_GSD_BACKEND_PREPARED +#define NS_GSD_BACKEND_PREPARED 0 +#endif + +#ifndef NS_GSD_BACKEND_HERMES +#define NS_GSD_BACKEND_HERMES 0 +#endif + +#ifndef NS_GSD_BACKEND_NAPI +#define NS_GSD_BACKEND_NAPI 0 +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_DISPATCH +#define NS_HAS_GENERATED_SIGNATURE_DISPATCH 0 +#endif + +#define NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH \ + (NS_GSD_BACKEND_HERMES || NS_GSD_BACKEND_NAPI || NS_GSD_BACKEND_PREPARED) + +#if NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH && \ + !NS_HAS_GENERATED_SIGNATURE_DISPATCH +#error GeneratedSignatureDispatch.inc did not enable this generated signature dispatch backend. +#endif + +#if !NS_HAS_GENERATED_SIGNATURE_DISPATCH +namespace nativescript { +inline constexpr ObjCDispatchEntry kGeneratedObjCDispatchEntries[] = { + {0, nullptr}}; +inline constexpr CFunctionDispatchEntry kGeneratedCFunctionDispatchEntries[] = { + {0, nullptr}}; +inline constexpr BlockDispatchEntry kGeneratedBlockDispatchEntries[] = { + {0, nullptr}}; +} // namespace nativescript +#endif + +namespace nativescript { + +inline ObjCPreparedInvoker lookupObjCPreparedInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCDispatchEntries, dispatchId); +} + +inline CFunctionPreparedInvoker lookupCFunctionPreparedInvoker( + uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedCFunctionDispatchEntries, dispatchId); +} + +inline BlockPreparedInvoker lookupBlockPreparedInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedBlockDispatchEntries, dispatchId); +} + +inline bool isPreparedGeneratedDispatchRequired() { +#if NS_HAS_GENERATED_SIGNATURE_DISPATCH && \ + (NS_GSD_BACKEND_PREPARED || NS_GSD_BACKEND_HERMES) + return isGeneratedDispatchEnabled(); +#else + return false; +#endif +} + +} // namespace nativescript + +#endif // NATIVESCRIPT_JSI_SHARED_PREPARED_SIGNATURE_DISPATCH_H diff --git a/NativeScript/jsi/shared/SignatureHashing.h b/NativeScript/jsi/shared/SignatureHashing.h new file mode 100644 index 000000000..0589c0fe3 --- /dev/null +++ b/NativeScript/jsi/shared/SignatureHashing.h @@ -0,0 +1,110 @@ +#ifndef NS_JSI_SHARED_SIGNATURE_HASHING_H +#define NS_JSI_SHARED_SIGNATURE_HASHING_H + +// Platform-neutral half of generated signature dispatch (GSD). +// +// This header must stay free of Objective-C and of any platform metadata +// format: it is compiled by the Apple runtime and, in future, by the Android +// runtime. The metadata-driven signature hashing (which walks the Objective-C +// metadata via metagen::MDMetadataReader) deliberately stays behind in +// ffi/objc/shared/SignatureDispatchCore.h, because the Android metadata is a +// different binary format entirely. +// +// What is genuinely common is the mechanism: hash a signature with FNV-1a, +// compose a dispatch id from (hash, call kind, flags), and binary-search a +// sorted, ahead-of-time generated table for a typed trampoline. + +#include +#include +#include + +namespace nativescript { + +enum class SignatureCallKind : uint8_t { + ObjCMethod = 1, + CFunction = 2, + BlockInvoke = 3, +}; + +using ObjCPreparedInvoker = void (*)(void* fnptr, void** avalues, + void* rvalue); +using CFunctionPreparedInvoker = void (*)(void* fnptr, void** avalues, + void* rvalue); +using BlockPreparedInvoker = void (*)(void* fnptr, void** avalues, + void* rvalue); + +struct ObjCDispatchEntry { + uint64_t dispatchId; + ObjCPreparedInvoker invoker; +}; + +struct CFunctionDispatchEntry { + uint64_t dispatchId; + CFunctionPreparedInvoker invoker; +}; + +struct BlockDispatchEntry { + uint64_t dispatchId; + BlockPreparedInvoker invoker; +}; + +inline constexpr uint64_t kSignatureHashOffsetBasis = 14695981039346656037ull; +inline constexpr uint64_t kSignatureHashPrime = 1099511628211ull; + +inline uint64_t hashBytesFnv1a(const void* data, size_t size, + uint64_t seed = kSignatureHashOffsetBasis) { + const auto* bytes = static_cast(data); + uint64_t hash = seed; + for (size_t i = 0; i < size; i++) { + hash ^= static_cast(bytes[i]); + hash *= kSignatureHashPrime; + } + return hash; +} + +inline uint64_t composeSignatureDispatchId(uint64_t signatureHash, + SignatureCallKind kind, + uint8_t flags) { + const uint8_t kindByte = static_cast(kind); + uint64_t hash = hashBytesFnv1a(&kindByte, sizeof(kindByte)); + hash = hashBytesFnv1a(&flags, sizeof(flags), hash); + return hashBytesFnv1a(&signatureHash, sizeof(signatureHash), hash); +} + +template +inline Invoker lookupDispatchInvoker(const Entry (&entries)[N], + uint64_t dispatchId) { + if (dispatchId == 0 || N <= 1) { + return nullptr; + } + + size_t low = 1; + size_t high = N; + while (low < high) { + const size_t mid = low + ((high - low) >> 1); + const uint64_t midId = entries[mid].dispatchId; + if (midId < dispatchId) { + low = mid + 1; + } else { + high = mid; + } + } + + if (low < N && entries[low].dispatchId == dispatchId) { + return entries[low].invoker; + } + return nullptr; +} + +inline bool isGeneratedDispatchEnabled() { + static const bool enabled = []() { + const char* disableFlag = std::getenv("NS_DISABLE_GSD"); + return disableFlag == nullptr || disableFlag[0] == '\0' || + (disableFlag[0] == '0' && disableFlag[1] == '\0'); + }(); + return enabled; +} + +} // namespace nativescript + +#endif // NS_JSI_SHARED_SIGNATURE_HASHING_H diff --git a/NativeScript/ffi/objc/shared/Tasks.cpp b/NativeScript/jsi/shared/Tasks.cpp similarity index 100% rename from NativeScript/ffi/objc/shared/Tasks.cpp rename to NativeScript/jsi/shared/Tasks.cpp diff --git a/NativeScript/jsi/shared/Tasks.h b/NativeScript/jsi/shared/Tasks.h new file mode 100644 index 000000000..cd3b4a68c --- /dev/null +++ b/NativeScript/jsi/shared/Tasks.h @@ -0,0 +1,20 @@ +#ifndef Tasks_h +#define Tasks_h + +#include +#include + +namespace nativescript { + +class Tasks { +public: + static void Register(std::function task); + static void Drain(); + static void ClearTasks(); +private: + static std::vector> tasks_; +}; + +} + +#endif /* Tasks_h */ diff --git a/NativeScript/ffi/objc/v8/NativeApiV8HostObjects.mm b/NativeScript/jsi/v8/V8HostObjects.cpp similarity index 99% rename from NativeScript/ffi/objc/v8/NativeApiV8HostObjects.mm rename to NativeScript/jsi/v8/V8HostObjects.cpp index 97ebf2fe7..372e214d7 100644 --- a/NativeScript/ffi/objc/v8/NativeApiV8HostObjects.mm +++ b/NativeScript/jsi/v8/V8HostObjects.cpp @@ -1,5 +1,5 @@ -#include "NativeApiV8Runtime.h" -#include "../shared/NativeApiStackValueArray.h" +#include "jsi/v8/V8Runtime.h" +#include "jsi/shared/NativeApiStackValueArray.h" #ifdef TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Runtime.mm b/NativeScript/jsi/v8/V8Runtime.cpp similarity index 97% rename from NativeScript/ffi/objc/v8/NativeApiV8Runtime.mm rename to NativeScript/jsi/v8/V8Runtime.cpp index 10f8e3d86..0be822dfd 100644 --- a/NativeScript/ffi/objc/v8/NativeApiV8Runtime.mm +++ b/NativeScript/jsi/v8/V8Runtime.cpp @@ -1,4 +1,4 @@ -#include "NativeApiV8Runtime.h" +#include "jsi/v8/V8Runtime.h" #ifdef TARGET_ENGINE_V8 diff --git a/NativeScript/jsi/v8/V8Runtime.h b/NativeScript/jsi/v8/V8Runtime.h new file mode 100644 index 000000000..c9a015edc --- /dev/null +++ b/NativeScript/jsi/v8/V8Runtime.h @@ -0,0 +1,819 @@ +#ifndef NATIVESCRIPT_JSI_V8_V8_RUNTIME_H +#define NATIVESCRIPT_JSI_V8_V8_RUNTIME_H + +// V8 behind the JSI-shaped `nativescript::engine` API. +// +// This header is platform-neutral: it depends on V8 and the C++ standard +// library, and on nothing from Foundation, the Objective-C runtime, or the +// Apple metadata format. Android compiles it as-is. +// +// The Apple bridge's extra baggage lives in ffi/objc/v8/NativeApiV8Runtime.h, +// which includes this file. Apple sources keep including that path unchanged. + +#ifdef TARGET_ENGINE_V8 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "v8.h" + +namespace nativescript { +namespace engine { + +class Runtime; +class Value; +class Object; +class Function; +class Array; +class String; +class BigInt; +class ArrayBuffer; + +class JSError : public std::runtime_error { + public: + JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} + explicit JSError(const std::string& message) : std::runtime_error(message) {} +}; + +class StringBuffer { + public: + explicit StringBuffer(std::string value) : value_(std::move(value)) {} + const char* data() const { return value_.data(); } + size_t size() const { return value_.size(); } + + private: + std::string value_; +}; + +class MutableBuffer { + public: + virtual ~MutableBuffer() = default; + virtual size_t size() const = 0; + virtual uint8_t* data() = 0; +}; + +class PropNameID { + public: + PropNameID() = default; + explicit PropNameID(std::string value) : value_(std::move(value)) {} + + static PropNameID forAscii(Runtime&, const char* value) { + return PropNameID(value != nullptr ? value : ""); + } + + static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } + + std::string utf8(Runtime&) const { return value_; } + + private: + std::string value_; +}; + +class HostObject { + public: + virtual ~HostObject() = default; + virtual Value get(Runtime& runtime, const PropNameID& name); + virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); +}; + +using HostFunctionType = std::function; + +namespace v8engine { + +struct RuntimeState { + explicit RuntimeState(v8::Isolate* isolate, v8::Local context) : isolate(isolate) { + this->context.Reset(isolate, context); + } + + ~RuntimeState() { + nativeClassArgumentLast.value.Reset(); + nativeClassArgumentLast.nativeClass = nullptr; + for (auto& entry : nativeClassArgumentCache) { + entry.value.Reset(); + entry.nativeClass = nullptr; + } + nativeSelectorArgumentLast.value.Reset(); + nativeSelectorArgumentLast.selector = nullptr; + for (auto& entry : nativeSelectorArgumentCache) { + entry.value.Reset(); + entry.selector = nullptr; + } + context.Reset(); + } + + v8::Local localContext() const { + v8::Local ctx = context.Get(isolate); + return ctx.IsEmpty() ? isolate->GetCurrentContext() : ctx; + } + + v8::Isolate* isolate = nullptr; + v8::Global context; + v8::Global hostObjectTemplate; + v8::Global nativeObjectTemplate; // kNonMasking for instances + std::vector> retainedNativeData; + // Argument-marshalling caches: they memoise "this JS value denotes that + // native type / method" so a repeated call does not re-resolve it. + // + // Both native handles are held opaquely so this struct stays platform + // neutral. They are Class and SEL on Apple, and the JNI equivalents + // (jclass, jmethodID) on Android -- all four are pointers. The bridge that + // populates and reads these casts back to its own types; see + // ffi/objc/v8/NativeApiV8Marshalling.mm for the Apple side. + struct NativeClassArgumentCacheEntry { + v8::Global value; + const void* nativeClass = nullptr; + }; + NativeClassArgumentCacheEntry nativeClassArgumentLast; + NativeClassArgumentCacheEntry nativeClassArgumentCache[4]; + size_t nativeClassArgumentCacheNext = 0; + struct NativeSelectorArgumentCacheEntry { + v8::Global value; + const void* selector = nullptr; + }; + NativeSelectorArgumentCacheEntry nativeSelectorArgumentLast; + NativeSelectorArgumentCacheEntry nativeSelectorArgumentCache[4]; + size_t nativeSelectorArgumentCacheNext = 0; +}; + +struct ValueStorage { + enum class Kind : uint8_t { + Undefined, + Null, + Bool, + Number, + V8, + V8Borrowed, + }; + + explicit ValueStorage(Kind kind) : kind(kind) {} + + ~ValueStorage() { value.Reset(); } + + Kind kind = Kind::Undefined; + bool boolValue = false; + double numberValue = 0; + v8::Global value; + v8::Local borrowedValue; +}; + +template +const void* hostObjectTypeToken() { + static int token = 0; + return &token; +} + +struct HostObjectHolder { + HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, + const void* typeToken) + : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} + + ~HostObjectHolder() { object.Reset(); } + + std::shared_ptr state; + std::shared_ptr hostObject; + const void* typeToken = nullptr; + v8::Global object; +}; + +struct FunctionHolder { + FunctionHolder(std::shared_ptr state, HostFunctionType callback) + : state(std::move(state)), callback(std::move(callback)) {} + + ~FunctionHolder() { function.Reset(); } + + std::shared_ptr state; + HostFunctionType callback; + v8::Global function; +}; + +struct ArrayBufferHolder { + explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} + + std::shared_ptr buffer; + v8::Global object; +}; + +inline v8::Local makeV8String(v8::Isolate* isolate, const std::string& value) { + return v8::String::NewFromUtf8(isolate, value.c_str(), v8::NewStringType::kNormal, + static_cast(value.size())) + .ToLocalChecked(); +} + +inline std::string toUtf8(v8::Isolate* isolate, v8::Local value) { + if (value.IsEmpty()) { + return {}; + } + v8::String::Utf8Value utf8(isolate, value); + return *utf8 != nullptr ? std::string(*utf8, utf8.length()) : std::string(); +} + +inline std::string propertyNameToUtf8(v8::Isolate* isolate, v8::Local property) { + if (property->IsSymbol() && + property.As()->StrictEquals(v8::Symbol::GetIterator(isolate))) { + return "Symbol.iterator"; + } + return toUtf8(isolate, property); +} + +inline std::string currentExceptionMessage(v8::Isolate* isolate, v8::TryCatch& tryCatch) { + if (tryCatch.HasCaught()) { + return toUtf8(isolate, tryCatch.Exception()); + } + return "NativeScript V8 engine operation failed."; +} + +inline void throwV8Exception(v8::Isolate* isolate, const std::exception& exception) { + isolate->ThrowException(v8::Exception::Error(makeV8String(isolate, exception.what()))); +} + +} // namespace v8engine + +class Runtime { + public: + Runtime(v8::Isolate* isolate, v8::Local context) + : state_(std::make_shared(isolate, context)) {} + + explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} + + v8::Isolate* isolate() const { return state_->isolate; } + v8::Local context() const { return state_->localContext(); } + v8engine::RuntimeState* rawState() const { return state_.get(); } + std::shared_ptr state() const { return state_; } + + Object global(); + + Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); + + void drainMicrotasks() { isolate()->PerformMicrotaskCheckpoint(); } + + private: + std::shared_ptr state_; +}; + +class String { + public: + String() = default; + String(Runtime& runtime, v8::Local value); + + static String createFromUtf8(Runtime& runtime, const char* value) { + return String(runtime, + v8engine::makeV8String(runtime.isolate(), value != nullptr ? value : "")); + } + + static String createFromUtf8(Runtime& runtime, const std::string& value) { + return String(runtime, v8engine::makeV8String(runtime.isolate(), value)); + } + + static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { + return String(runtime, v8::String::NewFromUtf8( + runtime.isolate(), + reinterpret_cast( + value != nullptr ? value : reinterpret_cast("")), + v8::NewStringType::kNormal, static_cast(length)) + .ToLocalChecked()); + } + + std::string utf8(Runtime& runtime) const { + return v8engine::toUtf8(runtime.isolate(), local(runtime)); + } + + v8::Local local(Runtime& runtime) const { + return storage_->value.Get(runtime.isolate()).As(); + } + + operator Value() const; + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class Value { + public: + Value() : kind_(v8engine::ValueStorage::Kind::Undefined) {} + + Value(bool value) : kind_(v8engine::ValueStorage::Kind::Bool), boolValue_(value) {} + + Value(double value) : kind_(v8engine::ValueStorage::Kind::Number), numberValue_(value) {} + + Value(int value) : Value(static_cast(value)) {} + Value(uint32_t value) : Value(static_cast(value)) {} + + Value(Runtime& runtime, const Value& value) { + if (value.kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + // Promote borrowed to owned + storage_ = + std::make_shared(v8engine::ValueStorage::Kind::V8); + storage_->value.Reset(runtime.isolate(), value.borrowedValue_); + kind_ = v8engine::ValueStorage::Kind::V8; + return; + } + kind_ = value.kind_; + boolValue_ = value.boolValue_; + numberValue_ = value.numberValue_; + borrowedValue_ = value.borrowedValue_; + storage_ = value.storage_; + } + Value(Runtime& runtime, Value&& value) + : kind_(value.kind_), + boolValue_(value.boolValue_), + numberValue_(value.numberValue_), + borrowedValue_(value.borrowedValue_), + storage_(std::move(value.storage_)) {} + Value(Runtime& runtime, const String& value); + Value(Runtime& runtime, const Object& object); + Value(Runtime& runtime, const Function& function); + Value(Runtime& runtime, const Array& array); + Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); + Value(Runtime& runtime, const BigInt& bigint); + + static Value undefined() { return Value(); } + + static Value null() { + Value value; + value.kind_ = v8engine::ValueStorage::Kind::Null; + return value; + } + + static bool strictEquals(Runtime& runtime, const Value& lhs, + const Value& rhs) { + return lhs.local(runtime)->StrictEquals(rhs.local(runtime)); + } + + bool isUndefined() const; + bool isNull() const; + bool isBool() const; + bool getBool() const; + bool isNumber() const; + double getNumber() const; + + bool isObject() const; + bool isString() const; + bool isBigInt() const; + bool isSymbol() const; + + Object asObject(Runtime& runtime) const; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + v8::Local local(Runtime& runtime) const { + v8::Isolate* isolate = runtime.isolate(); + switch (kind_) { + case v8engine::ValueStorage::Kind::Undefined: + return v8::Undefined(isolate); + case v8engine::ValueStorage::Kind::Null: + return v8::Null(isolate); + case v8engine::ValueStorage::Kind::Bool: + return v8::Boolean::New(isolate, boolValue_); + case v8engine::ValueStorage::Kind::Number: + return v8::Number::New(isolate, numberValue_); + case v8engine::ValueStorage::Kind::V8: + return storage_->value.Get(isolate); + case v8engine::ValueStorage::Kind::V8Borrowed: + return borrowedValue_; + } + } + + Value(Runtime& runtime, v8::Local value) + : kind_(v8engine::ValueStorage::Kind::V8), + storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->value.Reset(runtime.isolate(), value); + } + + static Value borrowed(Runtime&, v8::Local value) { + Value result; + result.kind_ = v8engine::ValueStorage::Kind::V8Borrowed; + result.borrowedValue_ = value; + return result; + } + + // Access the shared storage (for Object/Function/Array interop) + std::shared_ptr storage() const { return storage_; } + + static Value fromStorage(std::shared_ptr s) { + Value v; + v.kind_ = s->kind; + v.boolValue_ = s->boolValue; + v.numberValue_ = s->numberValue; + v.borrowedValue_ = s->borrowedValue; + v.storage_ = std::move(s); + return v; + } + + private: + friend class Runtime; + friend class Object; + friend class String; + friend class BigInt; + friend class ArrayBuffer; + friend class Function; + friend class Array; + + v8engine::ValueStorage::Kind kind_ = v8engine::ValueStorage::Kind::Undefined; + bool boolValue_ = false; + double numberValue_ = 0; + v8::Local borrowedValue_; + std::shared_ptr storage_; +}; + +class Object { + public: + Object() = default; + explicit Object(Runtime& runtime) + : storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->value.Reset(runtime.isolate(), v8::Object::New(runtime.isolate())); + } + + static Object fromValueStorage(std::shared_ptr storage) { + Object object; + object.storage_ = std::move(storage); + return object; + } + + template + static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken(runtime, std::move(baseHost), + v8engine::hostObjectTypeToken()); + } + + // Create a native object instance using kNonMasking template for fast + // prototype-based property access. + template + static Object createNativeInstanceHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createNativeInstanceWithToken(runtime, std::move(baseHost), + v8engine::hostObjectTypeToken()); + } + + static Object createNativeInstanceWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken); + + Value getProperty(Runtime& runtime, const char* name) const { + return getProperty(runtime, + v8engine::makeV8String(runtime.isolate(), name != nullptr ? name : "")); + } + + Value getProperty(Runtime& runtime, const std::string& name) const { + return getProperty(runtime, name.c_str()); + } + + Value getProperty(Runtime& runtime, const Value& key) const { + return getProperty(runtime, key.local(runtime)); + } + + Value getProperty(Runtime& runtime, v8::Local key) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + if (!local(runtime)->Get(runtime.context(), key).ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return Value(runtime, result); + } + + Object getPropertyAsObject(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime); + } + + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + void setProperty(Runtime& runtime, const char* name, const Value& value) { + setProperty(runtime, v8engine::makeV8String(runtime.isolate(), name != nullptr ? name : ""), + value); + } + + void setProperty(Runtime& runtime, const char* name, const String& value) { + setProperty(runtime, name, Value(runtime, value)); + } + + void setProperty(Runtime& runtime, const char* name, const Object& value) { + setProperty(runtime, name, Value(runtime, value)); + } + + void setProperty(Runtime& runtime, const char* name, const Function& value); + void setProperty(Runtime& runtime, const char* name, const Array& value); + void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); + void setProperty(Runtime& runtime, const char* name, bool value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const char* name, double value) { + setProperty(runtime, name, Value(value)); + } + + void setProperty(Runtime& runtime, const std::string& name, const Value& value) { + setProperty(runtime, name.c_str(), value); + } + + void setProperty(Runtime& runtime, const Value& key, const Value& value) { + setProperty(runtime, key.local(runtime), value); + } + + void setProperty(Runtime& runtime, v8::Local key, const Value& value) { + v8::TryCatch tryCatch(runtime.isolate()); + if (!local(runtime)->Set(runtime.context(), key, value.local(runtime)).FromMaybe(false)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + } + + bool hasProperty(Runtime& runtime, const char* name) const { + v8::TryCatch tryCatch(runtime.isolate()); + return local(runtime) + ->Has(runtime.context(), + v8engine::makeV8String(runtime.isolate(), name != nullptr ? name : "")) + .FromMaybe(false); + } + + bool isFunction(Runtime& runtime) const { return local(runtime)->IsFunction(); } + bool isArray(Runtime& runtime) const { return local(runtime)->IsArray(); } + bool isArrayBuffer(Runtime& runtime) const { return local(runtime)->IsArrayBuffer(); } + + Function asFunction(Runtime& runtime) const; + Array getArray(Runtime& runtime) const; + ArrayBuffer getArrayBuffer(Runtime& runtime) const; + Array getPropertyNames(Runtime& runtime) const; + + template + bool isHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + return holder != nullptr && holder->typeToken == v8engine::hostObjectTypeToken(); + } + + template + std::shared_ptr getHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + if (holder == nullptr || holder->typeToken != v8engine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); + } + + v8::Local local(Runtime& runtime) const { + if (storage_->kind == v8engine::ValueStorage::Kind::V8Borrowed) { + return storage_->borrowedValue.As(); + } + return storage_->value.Get(runtime.isolate()).As(); + } + + operator Value() const { + return Value::fromStorage(storage_); + } + + protected: + friend class Value; + friend class Runtime; + friend class Function; + friend class Array; + friend class ArrayBuffer; + + explicit Object(std::shared_ptr storage) : storage_(std::move(storage)) {} + + static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken); + + v8engine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { + v8::Local object = local(runtime); + if (object->InternalFieldCount() < 1) { + return nullptr; + } + return static_cast(object->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + } + + std::shared_ptr storage_; +}; + +class Function : public Object { + public: + Function() = default; + explicit Function(Object object) : Object(std::move(object.storage_)) {} + + static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback); + + Value call(Runtime& runtime, const Value* args, size_t count) const { + v8::TryCatch tryCatch(runtime.isolate()); + std::vector> argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + v8::Local result; + if (!local(runtime) + .As() + ->Call(runtime.context(), runtime.context()->Global(), static_cast(argv.size()), + argv.data()) + .ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return Value(runtime, result); + } + + Value call(Runtime& runtime) const { + return call(runtime, static_cast(nullptr), 0); + } + + Value call(Runtime& runtime, std::nullptr_t, size_t) const { + return call(runtime, static_cast(nullptr), 0); + } + + template + Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { + return call(runtime, static_cast(args), count); + } + + template + Value call(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return call(runtime, static_cast(argv), sizeof...(Args)); + } + + Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, + size_t count = 0) const { + v8::TryCatch tryCatch(runtime.isolate()); + std::vector> argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + v8::Local result; + if (!local(runtime) + .As() + ->Call(runtime.context(), thisObject.local(runtime), static_cast(argv.size()), + argv.data()) + .ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return Value(runtime, result); + } + + Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { + v8::TryCatch tryCatch(runtime.isolate()); + std::vector> argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + v8::Local result; + if (!local(runtime) + .As() + ->NewInstance(runtime.context(), static_cast(argv.size()), argv.data()) + .ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return Value(runtime, result); + } + + Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { + return callAsConstructor(runtime, static_cast(nullptr), 0); + } + + template + Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { + return callAsConstructor(runtime, static_cast(args), count); + } + + template + Value callAsConstructor(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); + } + + operator Value() const { + return Value::fromStorage(storage_); + } +}; + +class Array : public Object { + public: + explicit Array(Runtime& runtime, size_t size) + : Object(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->value.Reset(runtime.isolate(), + v8::Array::New(runtime.isolate(), static_cast(size))); + } + + explicit Array(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { return local(runtime).As()->Length(); } + + Value getValueAtIndex(Runtime& runtime, size_t index) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + if (!local(runtime)->Get(runtime.context(), static_cast(index)).ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return Value(runtime, result); + } + + void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { + v8::TryCatch tryCatch(runtime.isolate()); + if (!local(runtime) + ->Set(runtime.context(), static_cast(index), value.local(runtime)) + .FromMaybe(false)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + } + + void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { + setValueAtIndex(runtime, index, Value(runtime, value)); + } + + operator Value() const { + return Value::fromStorage(storage_); + } +}; + +class BigInt { + public: + BigInt() = default; + BigInt(Runtime& runtime, v8::Local value) + : storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->value.Reset(runtime.isolate(), value); + } + + static BigInt fromInt64(Runtime& runtime, int64_t value) { + return BigInt(runtime, v8::BigInt::New(runtime.isolate(), value)); + } + + static BigInt fromUint64(Runtime& runtime, uint64_t value) { + return BigInt(runtime, v8::BigInt::NewFromUnsigned(runtime.isolate(), value)); + } + + String toString(Runtime& runtime, int radix) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + (void)radix; + if (!local(runtime)->ToString(runtime.context()).ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return String(runtime, result); + } + + v8::Local local(Runtime& runtime) const { + return storage_->value.Get(runtime.isolate()).As(); + } + + operator Value() const { + return Value::fromStorage(storage_); + } + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class ArrayBuffer : public Object { + public: + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + : Object(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + auto holder = new v8engine::ArrayBufferHolder(std::move(buffer)); + auto backingStore = v8::ArrayBuffer::NewBackingStore( + holder->buffer->data(), holder->buffer->size(), + [](void*, size_t, void* deleterData) { + auto* holder = static_cast(deleterData); + holder->object.Reset(); + delete holder; + }, + holder); + v8::Local arrayBuffer = + v8::ArrayBuffer::New(runtime.isolate(), std::move(backingStore)); + storage_->value.Reset(runtime.isolate(), arrayBuffer); + holder->object.Reset(runtime.isolate(), arrayBuffer); + } + + explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { return local(runtime).As()->ByteLength(); } + + uint8_t* data(Runtime& runtime) const { + auto backingStore = local(runtime).As()->GetBackingStore(); + return static_cast(backingStore->Data()); + } + + operator Value() const { + return Value::fromStorage(storage_); + } +}; +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_V8 + +#endif // NATIVESCRIPT_JSI_V8_V8_RUNTIME_H diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Value.mm b/NativeScript/jsi/v8/V8Value.cpp similarity index 99% rename from NativeScript/ffi/objc/v8/NativeApiV8Value.mm rename to NativeScript/jsi/v8/V8Value.cpp index dab1e0271..5ac94b6af 100644 --- a/NativeScript/ffi/objc/v8/NativeApiV8Value.mm +++ b/NativeScript/jsi/v8/V8Value.cpp @@ -1,4 +1,4 @@ -#include "NativeApiV8Runtime.h" +#include "jsi/v8/V8Runtime.h" #ifdef TARGET_ENGINE_V8 diff --git a/package.json b/package.json index e77e44af8..1fcfb4ce1 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "build-node-api": "./scripts/build_all_react_native.sh", "build-rn-turbomodule": "./scripts/build_react_native_turbomodule.sh", "check:ffi-boundaries": "./scripts/check_ffi_boundaries.sh", + "check:jsi-neutral": "./scripts/check_jsi_layer_neutral.sh", "test-rn-turbomodule": "./scripts/test_react_native_turbomodule.sh", "test-rn-ffi": "./scripts/test_react_native_ffi_compat.sh", "demo-rn-turbomodule": "./scripts/create_react_native_demo.sh", diff --git a/scripts/check_ffi_boundaries.sh b/scripts/check_ffi_boundaries.sh index b57225008..d9f67ab5a 100755 --- a/scripts/check_ffi_boundaries.sh +++ b/scripts/check_ffi_boundaries.sh @@ -146,8 +146,13 @@ for dir in "$SHARED_DIR" "$NAPI_DIR" "$V8_DIR" "$JSC_DIR" "$QUICKJS_DIR"; do fi done +# "JSI" here means facebook::jsi, the Hermes-only API -- not NativeScript/jsi, +# which is our own platform-neutral engine layer that every backend shares. +# Hence #include "jsi/hermes/..." rather than a blanket #include "jsi/": the +# quoted form now names our tree, and only its hermes subdirectory is off +# limits to the other backends. if [ "${#NON_HERMES_JSI_DIRS[@]}" -gt 0 ] && - search_sources '(NativeApiJsi|facebook::jsi|&2 exit 1 @@ -162,6 +167,9 @@ fi if [ -d "$JNI_NAPI_DIR" ] && search_sources '(^|[^[:alnum:]_])(facebook::jsi|v8::|JSContextRef|JSValueRef|JSContext|JSValue|JSRuntime|quickjs)($|[^[:alnum:]_])|(&2 exit 1 fi diff --git a/scripts/check_jsi_layer_neutral.sh b/scripts/check_jsi_layer_neutral.sh new file mode 100755 index 000000000..ed5c6d9cb --- /dev/null +++ b/scripts/check_jsi_layer_neutral.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# The shared JSI layer (NativeScript/jsi) is compiled by the Apple runtime today +# and by the Android runtime in future. Android has no Objective-C compiler and +# no Foundation framework, so a single stray #import there does not merely warn: +# it makes the file unbuildable on Android. +# +# This is easy to regress and hard to notice, because `#import +# ` compiles perfectly well under -x c++ on macOS. A +# green Apple build proves nothing. Hence this check. +set -e + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +LAYER="$ROOT/NativeScript/jsi" + +if [ ! -d "$LAYER" ]; then + echo "check_jsi_layer_neutral: $LAYER does not exist yet; nothing to check." + exit 0 +fi + +# Objective-C constructs, and platform headers that only exist on Apple. +PATTERN='#import|@interface|@implementation|@protocol|@selector|objc_msgSend|objc_get|/dev/null); then + echo "ERROR: Objective-C found in the shared JSI layer (NativeScript/jsi)." + echo "This code must compile on Android, which has no Objective-C compiler." + echo "Move the Apple-specific part back under NativeScript/ffi/objc/." + echo + echo "$MATCHES" + exit 1 +fi + +# .mm is Objective-C++ by definition and cannot build on Android. +if MM=$(find "$LAYER" -name '*.mm' -print 2>/dev/null | grep .); then + echo "ERROR: .mm sources in the shared JSI layer; these cannot build on Android:" + echo "$MM" + exit 1 +fi + +echo "check_jsi_layer_neutral: OK ($(find "$LAYER" -type f | wc -l | tr -d ' ') files, no Objective-C)"