From 52965d4e7014195b0922b9048964498125ca0b90 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 23 Oct 2026 10:38:58 -0700 Subject: [PATCH 01/10] feat(jsi): sync JSI to React Native 0.88.0 Brings in the IRuntime split along with runtime data (jsi::UUID), Array::push, ArrayBuffer::tryGetMutableBuffer, Value-keyed property access and the create*Error helpers. Runtime supplies JS-backed defaults for every new method, so V8Runtime only needs invalidate() to become noexcept to match the new PointerValue contract. Nitro Modules v1 (nitro-core) requires these APIs. --- .../platforms/android/cpp/jsi/decorator.h | 662 ++++++--- .../android/cpp/jsi/instrumentation.h | 29 +- .../platforms/android/cpp/jsi/jsi-inl.h | 294 ++-- .../platforms/android/cpp/jsi/jsi.cpp | 629 ++++++-- .../platforms/android/cpp/jsi/jsi.h | 1307 +++++++++++++---- .../android/cpp/jsi/jsilib-posix.cpp | 43 +- .../platforms/android/cpp/jsi/jsilib.h | 31 +- .../platforms/android/cpp/jsi/threadsafe.h | 31 +- .../android/cpp/v8runtime/V8PointerValue.cpp | 2 +- .../android/cpp/v8runtime/V8PointerValue.h | 2 +- .../NativeScriptJSI/include/jsi/decorator.h | 662 ++++++--- .../include/jsi/instrumentation.h | 29 +- .../NativeScriptJSI/include/jsi/jsi-inl.h | 294 ++-- .../Sources/NativeScriptJSI/include/jsi/jsi.h | 1307 +++++++++++++---- .../NativeScriptJSI/include/jsi/jsilib.h | 31 +- .../NativeScriptJSI/include/jsi/threadsafe.h | 31 +- .../Sources/NativeScriptJSI/jsi/jsi.cpp | 629 ++++++-- .../NativeScriptJSI/jsi/jsilib-posix.cpp | 43 +- .../v8runtime/V8PointerValue.cpp | 2 +- .../v8runtime/V8PointerValue.h | 2 +- 20 files changed, 4618 insertions(+), 1442 deletions(-) diff --git a/packages/android-jsi/platforms/android/cpp/jsi/decorator.h b/packages/android-jsi/platforms/android/cpp/jsi/decorator.h index deaa86b73..f101c1832 100644 --- a/packages/android-jsi/platforms/android/cpp/jsi/decorator.h +++ b/packages/android-jsi/platforms/android/cpp/jsi/decorator.h @@ -9,8 +9,8 @@ #include -#include "jsi/instrumentation.h" -#include "jsi/jsi.h" +#include +#include // This file contains objects to help API users create their own // runtime adapters, i.e. if you want to compose runtimes to add your @@ -27,10 +27,12 @@ class DecoratedHostFunction { DecoratedHostFunction(Runtime& drt, HostFunctionType plainHF) : drt_(drt), plainHF_(std::move(plainHF)) {} - Runtime& decoratedRuntime() { return drt_; } + Runtime& decoratedRuntime() { + return drt_; + } - Value operator()(Runtime&, const Value& thisVal, const Value* args, - size_t count) { + Value + operator()(Runtime&, const Value& thisVal, const Value* args, size_t count) { return plainHF_(decoratedRuntime(), thisVal, args, count); } @@ -60,7 +62,9 @@ class DecoratedHostObject : public HostObject { // The derived class methods can call this to get a reference to the // decorated runtime, since the rt passed to the callback will be // the plain runtime. - Runtime& decoratedRuntime() { return drt_; } + Runtime& decoratedRuntime() { + return drt_; + } Value get(Runtime&, const PropNameID& name) override { return plainHO_->get(decoratedRuntime(), name); @@ -99,14 +103,22 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { static_assert( std::is_base_of::value, "RuntimeDecorator's Plain type must derive from jsi::Runtime"); - static_assert(std::is_base_of::value, - "RuntimeDecorator's Base type must derive from jsi::Runtime"); + static_assert( + std::is_base_of::value, + "RuntimeDecorator's Base type must derive from jsi::Runtime"); + return plain_; + } + const Plain& plain() const { return plain_; } - const Plain& plain() const { return plain_; } - Value evaluateJavaScript(const std::shared_ptr& buffer, - const std::string& sourceURL) override { + ICast* castInterface(const UUID& interfaceUUID) override { + return plain().castInterface(interfaceUUID); + } + + Value evaluateJavaScript( + const std::shared_ptr& buffer, + const std::string& sourceURL) override { return plain().evaluateJavaScript(buffer, sourceURL); } std::shared_ptr prepareJavaScript( @@ -124,10 +136,18 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { bool drainMicrotasks(int maxMicrotasksHint) override { return plain().drainMicrotasks(maxMicrotasksHint); } - Object global() override { return plain().global(); } - std::string description() override { return plain().description(); }; - bool isInspectable() override { return plain().isInspectable(); }; - Instrumentation& instrumentation() override { return *this; } + Object global() override { + return plain().global(); + } + std::string description() override { + return plain().description(); + } + bool isInspectable() override { + return plain().isInspectable(); + } + Instrumentation& instrumentation() override { + return *this; + } protected: // plain is generally going to be a reference to an object managed @@ -140,39 +160,45 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { return plain_.cloneSymbol(pv); - }; + } Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { return plain_.cloneBigInt(pv); - }; + } Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { return plain_.cloneString(pv); - }; + } Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { return plain_.cloneObject(pv); - }; + } Runtime::PointerValue* clonePropNameID( const Runtime::PointerValue* pv) override { return plain_.clonePropNameID(pv); - }; + } - PropNameID createPropNameIDFromAscii(const char* str, - size_t length) override { + PropNameID createPropNameIDFromAscii(const char* str, size_t length) + override { return plain_.createPropNameIDFromAscii(str, length); - }; - PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, - size_t length) override { + } + PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) + override { return plain_.createPropNameIDFromUtf8(utf8, length); - }; + } PropNameID createPropNameIDFromString(const String& str) override { return plain_.createPropNameIDFromString(str); - }; + } + PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) + override { + return plain_.createPropNameIDFromUtf16(utf16, length); + } PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { return plain_.createPropNameIDFromSymbol(sym); - }; - std::string utf8(const PropNameID& id) override { return plain_.utf8(id); }; + } + std::string utf8(const PropNameID& id) override { + return plain_.utf8(id); + } bool compare(const PropNameID& a, const PropNameID& b) override { return plain_.compare(a, b); - }; + } std::string symbolToString(const Symbol& sym) override { return plain_.symbolToString(sym); @@ -190,33 +216,46 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { bool bigintIsUint64(const BigInt& b) override { return plain_.bigintIsUint64(b); } - uint64_t truncate(const BigInt& b) override { return plain_.truncate(b); } + uint64_t truncate(const BigInt& b) override { + return plain_.truncate(b); + } String bigintToString(const BigInt& bigint, int radix) override { return plain_.bigintToString(bigint, radix); } String createStringFromAscii(const char* str, size_t length) override { return plain_.createStringFromAscii(str, length); - }; + } String createStringFromUtf8(const uint8_t* utf8, size_t length) override { return plain_.createStringFromUtf8(utf8, length); - }; - std::string utf8(const String& s) override { return plain_.utf8(s); } + } + String createStringFromUtf16(const char16_t* utf16, size_t length) override { + return plain_.createStringFromUtf16(utf16, length); + } + std::string utf8(const String& s) override { + return plain_.utf8(s); + } - std::u16string utf16(const String& str) override { return plain_.utf16(str); } + std::u16string utf16(const String& str) override { + return plain_.utf16(str); + } std::u16string utf16(const PropNameID& sym) override { return plain_.utf16(sym); } - void getStringData(const jsi::String& str, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)) override { + void getStringData( + const jsi::String& str, + void* ctx, + void ( + *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { plain_.getStringData(str, ctx, cb); } - void getPropNameIdData(const jsi::PropNameID& sym, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)) override { + void getPropNameIdData( + const jsi::PropNameID& sym, + void* ctx, + void ( + *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { plain_.getPropNameIdData(sym, ctx, cb); } @@ -224,22 +263,24 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { return plain_.createObjectWithPrototype(prototype); } - Object createObject() override { return plain_.createObject(); }; + Object createObject() override { + return plain_.createObject(); + } Object createObject(std::shared_ptr ho) override { return plain_.createObject( std::make_shared(*this, std::move(ho))); - }; + } std::shared_ptr getHostObject(const jsi::Object& o) override { std::shared_ptr dho = plain_.getHostObject(o); return static_cast(*dho).plainHO_; - }; + } HostFunctionType& getHostFunction(const jsi::Function& f) override { HostFunctionType& dhf = plain_.getHostFunction(f); // This will fail if a cpp file including this header is not compiled // with RTTI. return dhf.target()->plainHF_; - }; + } bool hasNativeState(const Object& o) override { return plain_.hasNativeState(o); @@ -247,8 +288,8 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { std::shared_ptr getNativeState(const Object& o) override { return plain_.getNativeState(o); } - void setNativeState(const Object& o, - std::shared_ptr state) override { + void setNativeState(const Object& o, std::shared_ptr state) + override { plain_.setNativeState(o, state); } @@ -266,102 +307,195 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { Value getProperty(const Object& o, const PropNameID& name) override { return plain_.getProperty(o, name); - }; + } Value getProperty(const Object& o, const String& name) override { return plain_.getProperty(o, name); - }; + } + Value getProperty(const Object& o, const Value& name) override { + return plain_.getProperty(o, name); + } bool hasProperty(const Object& o, const PropNameID& name) override { return plain_.hasProperty(o, name); - }; + } bool hasProperty(const Object& o, const String& name) override { return plain_.hasProperty(o, name); - }; - void setPropertyValue(const Object& o, const PropNameID& name, - const Value& value) override { + } + bool hasProperty(const Object& o, const Value& name) override { + return plain_.hasProperty(o, name); + } + void setPropertyValue( + const Object& o, + const PropNameID& name, + const Value& value) override { plain_.setPropertyValue(o, name, value); - }; - void setPropertyValue(const Object& o, const String& name, - const Value& value) override { + } + void setPropertyValue(const Object& o, const String& name, const Value& value) + override { plain_.setPropertyValue(o, name, value); - }; + } + void setPropertyValue(const Object& o, const Value& name, const Value& value) + override { + plain_.setPropertyValue(o, name, value); + } - bool isArray(const Object& o) const override { return plain_.isArray(o); }; + void deleteProperty(const Object& object, const PropNameID& name) override { + plain_.deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const String& name) override { + plain_.deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const Value& name) override { + plain_.deleteProperty(object, name); + } + + bool isArray(const Object& o) const override { + return plain_.isArray(o); + } bool isArrayBuffer(const Object& o) const override { return plain_.isArrayBuffer(o); - }; + } + bool isTypedArray(const Object& o) const override { + return plain_.isTypedArray(o); + } + bool isUint8Array(const Object& o) const override { + return plain_.isUint8Array(o); + } bool isFunction(const Object& o) const override { return plain_.isFunction(o); - }; + } bool isHostObject(const jsi::Object& o) const override { return plain_.isHostObject(o); - }; + } bool isHostFunction(const jsi::Function& f) const override { return plain_.isHostFunction(f); - }; + } Array getPropertyNames(const Object& o) override { return plain_.getPropertyNames(o); - }; + } WeakObject createWeakObject(const Object& o) override { return plain_.createWeakObject(o); - }; + } Value lockWeakObject(const WeakObject& wo) override { return plain_.lockWeakObject(wo); - }; + } Array createArray(size_t length) override { return plain_.createArray(length); - }; + } ArrayBuffer createArrayBuffer( std::shared_ptr buffer) override { return plain_.createArrayBuffer(std::move(buffer)); - }; - size_t size(const Array& a) override { return plain_.size(a); }; - size_t size(const ArrayBuffer& ab) override { return plain_.size(ab); }; - uint8_t* data(const ArrayBuffer& ab) override { return plain_.data(ab); }; + } + size_t size(const Array& a) override { + return plain_.size(a); + } + size_t size(const ArrayBuffer& ab) override { + return plain_.size(ab); + } + uint8_t* data(const ArrayBuffer& ab) override { + return plain_.data(ab); + } + bool detached(const ArrayBuffer& ab) override { + return plain_.detached(ab); + } Value getValueAtIndex(const Array& a, size_t i) override { return plain_.getValueAtIndex(a, i); - }; - void setValueAtIndexImpl(const Array& a, size_t i, - const Value& value) override { + } + void setValueAtIndexImpl(const Array& a, size_t i, const Value& value) + override { plain_.setValueAtIndexImpl(a, i, value); - }; + } - Function createFunctionFromHostFunction(const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) override { + size_t push(const Array& a, const Value* elements, size_t count) override { + return plain_.push(a, elements, count); + } + + Function createFunctionFromHostFunction( + const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) override { return plain_.createFunctionFromHostFunction( name, paramCount, DecoratedHostFunction(*this, std::move(func))); - }; - Value call(const Function& f, const Value& jsThis, const Value* args, - size_t count) override { + } + Value call( + const Function& f, + const Value& jsThis, + const Value* args, + size_t count) override { return plain_.call(f, jsThis, args, count); - }; - Value callAsConstructor(const Function& f, const Value* args, - size_t count) override { + } + Value callAsConstructor(const Function& f, const Value* args, size_t count) + override { return plain_.callAsConstructor(f, args, count); - }; + } + + void setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)) override { + return plain_.setRuntimeDataImpl(uuid, data, deleter); + } + + const void* getRuntimeDataImpl(const UUID& uuid) override { + return plain_.getRuntimeDataImpl(uuid); + } + + std::shared_ptr tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) override { + return plain_.tryGetMutableBuffer(arrayBuffer); + } + + ArrayBuffer buffer(const TypedArray& typedArray) override { + return plain_.buffer(typedArray); + } + size_t byteOffset(const TypedArray& typedArray) override { + return plain_.byteOffset(typedArray); + } + size_t byteLength(const TypedArray& typedArray) override { + return plain_.byteLength(typedArray); + } + size_t length(const TypedArray& typedArray) override { + return plain_.length(typedArray); + } + + Uint8Array createUint8Array(size_t length) override { + return plain_.createUint8Array(length); + } + + Uint8Array createUint8Array( + const ArrayBuffer& buffer, + size_t offset, + size_t length) override { + return plain_.createUint8Array(buffer, offset, length); + } // Private data for managing scopes. - Runtime::ScopeState* pushScope() override { return plain_.pushScope(); } - void popScope(Runtime::ScopeState* ss) override { plain_.popScope(ss); } + Runtime::ScopeState* pushScope() override { + return plain_.pushScope(); + } + void popScope(Runtime::ScopeState* ss) override { + plain_.popScope(ss); + } bool strictEquals(const Symbol& a, const Symbol& b) const override { return plain_.strictEquals(a, b); - }; + } bool strictEquals(const BigInt& a, const BigInt& b) const override { return plain_.strictEquals(a, b); - }; + } bool strictEquals(const String& a, const String& b) const override { return plain_.strictEquals(a, b); - }; + } bool strictEquals(const Object& a, const Object& b) const override { return plain_.strictEquals(a, b); - }; + } bool instanceOf(const Object& o, const Function& f) override { return plain_.instanceOf(o, f); - }; + } // jsi::Instrumentation methods @@ -379,9 +513,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { } void startTrackingHeapObjectStackTraces( - std::function)> - callback) override { + std::function)> callback) override { plain().instrumentation().startTrackingHeapObjectStackTraces( std::move(callback)); } @@ -398,13 +533,15 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { plain().instrumentation().stopHeapSampling(os); } - void createSnapshotToFile(const std::string& path, - const HeapSnapshotOptions& options) override { + void createSnapshotToFile( + const std::string& path, + const HeapSnapshotOptions& options) override { plain().instrumentation().createSnapshotToFile(path, options); } - void createSnapshotToStream(std::ostream& os, - const HeapSnapshotOptions& options) override { + void createSnapshotToStream( + std::ostream& os, + const HeapSnapshotOptions& options) override { plain().instrumentation().createSnapshotToStream(os, options); } @@ -421,6 +558,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { .writeBasicBlockProfileTraceToFile(fileName); } + void dumpOpcodeStats(std::ostream& os) const override { + const_cast(plain()).instrumentation().dumpOpcodeStats(os); + } + /// Dump external profiler symbols to the given file name. void dumpProfilerSymbolsToFile(const std::string& fileName) const override { const_cast(plain()).instrumentation().dumpProfilerSymbolsToFile( @@ -452,12 +593,16 @@ struct AfterCaller { // default above exists, and that is used instead. template struct BeforeCaller { - static void before(T& t) { t.before(); } + static void before(T& t) { + t.before(); + } }; template struct AfterCaller { - static void after(T& t) { t.after(); } + static void after(T& t) { + t.after(); + } }; // It's possible to use multiple decorators by nesting @@ -466,7 +611,9 @@ struct AfterCaller { // example. template struct BeforeCaller> { - static void before(std::tuple& tuple) { all_before<0, T...>(tuple); } + static void before(std::tuple& tuple) { + all_before<0, T...>(tuple); + } private: template @@ -481,7 +628,9 @@ struct BeforeCaller> { template struct AfterCaller> { - static void after(std::tuple& tuple) { all_after<0, T...>(tuple); } + static void after(std::tuple& tuple) { + all_after<0, T...>(tuple); + } private: template @@ -494,7 +643,7 @@ struct AfterCaller> { static void all_after(std::tuple&) {} }; -} // namespace detail +} // namespace detail // A decorator which implements an around idiom. A With instance is // RAII constructed before each call to the undecorated class; the @@ -510,8 +659,14 @@ class WithRuntimeDecorator : public RuntimeDecorator { // the derived class. WithRuntimeDecorator(Plain& plain, With& with) : RD(plain), with_(with) {} - Value evaluateJavaScript(const std::shared_ptr& buffer, - const std::string& sourceURL) override { + ICast* castInterface(const UUID& interfaceUUID) override { + Around around{with_}; + return RD::castInterface(interfaceUUID); + } + + Value evaluateJavaScript( + const std::shared_ptr& buffer, + const std::string& sourceURL) override { Around around{with_}; return RD::evaluateJavaScript(buffer, sourceURL); } @@ -541,11 +696,11 @@ class WithRuntimeDecorator : public RuntimeDecorator { std::string description() override { Around around{with_}; return RD::description(); - }; + } bool isInspectable() override { Around around{with_}; return RD::isInspectable(); - }; + } // The jsi:: prefix is necessary because MSVC compiler complains C2247: // Instrumentation is not accessible because RuntimeDecorator uses private @@ -560,90 +715,99 @@ class WithRuntimeDecorator : public RuntimeDecorator { Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { Around around{with_}; return RD::cloneSymbol(pv); - }; + } Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { Around around{with_}; return RD::cloneBigInt(pv); - }; + } Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { Around around{with_}; return RD::cloneString(pv); - }; + } Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { Around around{with_}; return RD::cloneObject(pv); - }; + } Runtime::PointerValue* clonePropNameID( const Runtime::PointerValue* pv) override { Around around{with_}; return RD::clonePropNameID(pv); - }; + } - PropNameID createPropNameIDFromAscii(const char* str, - size_t length) override { + PropNameID createPropNameIDFromAscii(const char* str, size_t length) + override { Around around{with_}; return RD::createPropNameIDFromAscii(str, length); - }; - PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, - size_t length) override { + } + PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) + override { Around around{with_}; return RD::createPropNameIDFromUtf8(utf8, length); - }; + } + PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) + override { + Around around{with_}; + return RD::createPropNameIDFromUtf16(utf16, length); + } PropNameID createPropNameIDFromString(const String& str) override { Around around{with_}; return RD::createPropNameIDFromString(str); - }; + } PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { Around around{with_}; return RD::createPropNameIDFromSymbol(sym); - }; + } std::string utf8(const PropNameID& id) override { Around around{with_}; return RD::utf8(id); - }; + } bool compare(const PropNameID& a, const PropNameID& b) override { Around around{with_}; return RD::compare(a, b); - }; + } std::string symbolToString(const Symbol& sym) override { Around around{with_}; return RD::symbolToString(sym); - }; + } BigInt createBigIntFromInt64(int64_t i) override { Around around{with_}; return RD::createBigIntFromInt64(i); - }; + } BigInt createBigIntFromUint64(uint64_t i) override { Around around{with_}; return RD::createBigIntFromUint64(i); - }; + } bool bigintIsInt64(const BigInt& bi) override { Around around{with_}; return RD::bigintIsInt64(bi); - }; + } bool bigintIsUint64(const BigInt& bi) override { Around around{with_}; return RD::bigintIsUint64(bi); - }; + } uint64_t truncate(const BigInt& bi) override { Around around{with_}; return RD::truncate(bi); - }; + } String bigintToString(const BigInt& bi, int i) override { Around around{with_}; return RD::bigintToString(bi, i); - }; + } String createStringFromAscii(const char* str, size_t length) override { Around around{with_}; return RD::createStringFromAscii(str, length); - }; + } String createStringFromUtf8(const uint8_t* utf8, size_t length) override { Around around{with_}; return RD::createStringFromUtf8(utf8, length); - }; + } + String createStringFromUtf16(const char16_t* utf16, size_t length) override { + Around around{with_}; + return RD::createStringFromUtf16(utf16, length); + } std::string utf8(const String& s) override { Around around{with_}; return RD::utf8(s); @@ -658,16 +822,20 @@ class WithRuntimeDecorator : public RuntimeDecorator { return RD::utf16(sym); } - void getStringData(const jsi::String& str, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)) override { + void getStringData( + const jsi::String& str, + void* ctx, + void ( + *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { Around around{with_}; RD::getStringData(str, ctx, cb); } - void getPropNameIdData(const jsi::PropNameID& sym, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)) override { + void getPropNameIdData( + const jsi::PropNameID& sym, + void* ctx, + void ( + *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { Around around{with_}; RD::getPropNameIdData(sym, ctx, cb); } @@ -675,7 +843,7 @@ class WithRuntimeDecorator : public RuntimeDecorator { Value createValueFromJsonUtf8(const uint8_t* json, size_t length) override { Around around{with_}; return RD::createValueFromJsonUtf8(json, length); - }; + } Object createObjectWithPrototype(const Value& prototype) override { Around around{with_}; @@ -685,33 +853,33 @@ class WithRuntimeDecorator : public RuntimeDecorator { Object createObject() override { Around around{with_}; return RD::createObject(); - }; + } Object createObject(std::shared_ptr ho) override { Around around{with_}; return RD::createObject(std::move(ho)); - }; + } std::shared_ptr getHostObject(const jsi::Object& o) override { Around around{with_}; return RD::getHostObject(o); - }; + } HostFunctionType& getHostFunction(const jsi::Function& f) override { Around around{with_}; return RD::getHostFunction(f); - }; + } bool hasNativeState(const Object& o) override { Around around{with_}; return RD::hasNativeState(o); - }; + } std::shared_ptr getNativeState(const Object& o) override { Around around{with_}; return RD::getNativeState(o); - }; - void setNativeState(const Object& o, - std::shared_ptr state) override { + } + void setNativeState(const Object& o, std::shared_ptr state) + override { Around around{with_}; RD::setNativeState(o, state); - }; + } void setPrototypeOf(const Object& object, const Value& prototype) override { Around around{with_}; @@ -726,111 +894,196 @@ class WithRuntimeDecorator : public RuntimeDecorator { Value getProperty(const Object& o, const PropNameID& name) override { Around around{with_}; return RD::getProperty(o, name); - }; + } Value getProperty(const Object& o, const String& name) override { Around around{with_}; return RD::getProperty(o, name); - }; + } + Value getProperty(const Object& o, const Value& name) override { + Around around{with_}; + return RD::getProperty(o, name); + } bool hasProperty(const Object& o, const PropNameID& name) override { Around around{with_}; return RD::hasProperty(o, name); - }; + } bool hasProperty(const Object& o, const String& name) override { Around around{with_}; return RD::hasProperty(o, name); - }; - void setPropertyValue(const Object& o, const PropNameID& name, - const Value& value) override { + } + bool hasProperty(const Object& o, const Value& name) override { + Around around{with_}; + return RD::hasProperty(o, name); + } + void setPropertyValue( + const Object& o, + const PropNameID& name, + const Value& value) override { Around around{with_}; RD::setPropertyValue(o, name, value); - }; - void setPropertyValue(const Object& o, const String& name, - const Value& value) override { + } + void setPropertyValue(const Object& o, const String& name, const Value& value) + override { Around around{with_}; RD::setPropertyValue(o, name, value); - }; + } + void setPropertyValue(const Object& o, const Value& name, const Value& value) + override { + Around around{with_}; + RD::setPropertyValue(o, name, value); + } + + void deleteProperty(const Object& object, const PropNameID& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const String& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const Value& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } bool isArray(const Object& o) const override { Around around{with_}; return RD::isArray(o); - }; + } bool isArrayBuffer(const Object& o) const override { Around around{with_}; return RD::isArrayBuffer(o); - }; + } + bool isTypedArray(const Object& o) const override { + Around around{with_}; + return RD::isTypedArray(o); + } + bool isUint8Array(const Object& o) const override { + Around around{with_}; + return RD::isUint8Array(o); + } bool isFunction(const Object& o) const override { Around around{with_}; return RD::isFunction(o); - }; + } bool isHostObject(const jsi::Object& o) const override { Around around{with_}; return RD::isHostObject(o); - }; + } bool isHostFunction(const jsi::Function& f) const override { Around around{with_}; return RD::isHostFunction(f); - }; + } Array getPropertyNames(const Object& o) override { Around around{with_}; return RD::getPropertyNames(o); - }; + } WeakObject createWeakObject(const Object& o) override { Around around{with_}; return RD::createWeakObject(o); - }; + } Value lockWeakObject(const WeakObject& wo) override { Around around{with_}; return RD::lockWeakObject(wo); - }; + } Array createArray(size_t length) override { Around around{with_}; return RD::createArray(length); - }; + } ArrayBuffer createArrayBuffer( std::shared_ptr buffer) override { return RD::createArrayBuffer(std::move(buffer)); - }; + } size_t size(const Array& a) override { Around around{with_}; return RD::size(a); - }; + } size_t size(const ArrayBuffer& ab) override { Around around{with_}; return RD::size(ab); - }; + } uint8_t* data(const ArrayBuffer& ab) override { Around around{with_}; return RD::data(ab); - }; + } + bool detached(const ArrayBuffer& ab) override { + Around around{with_}; + return RD::detached(ab); + } Value getValueAtIndex(const Array& a, size_t i) override { Around around{with_}; return RD::getValueAtIndex(a, i); - }; - void setValueAtIndexImpl(const Array& a, size_t i, - const Value& value) override { + } + void setValueAtIndexImpl(const Array& a, size_t i, const Value& value) + override { Around around{with_}; RD::setValueAtIndexImpl(a, i, value); - }; + } + size_t push(const Array& a, const Value* elements, size_t count) override { + Around around{with_}; + return RD::push(a, elements, count); + } - Function createFunctionFromHostFunction(const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) override { + Function createFunctionFromHostFunction( + const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) override { Around around{with_}; - return RD::createFunctionFromHostFunction(name, paramCount, - std::move(func)); - }; - Value call(const Function& f, const Value& jsThis, const Value* args, - size_t count) override { + return RD::createFunctionFromHostFunction( + name, paramCount, std::move(func)); + } + Value call( + const Function& f, + const Value& jsThis, + const Value* args, + size_t count) override { Around around{with_}; return RD::call(f, jsThis, args, count); - }; - Value callAsConstructor(const Function& f, const Value* args, - size_t count) override { + } + Value callAsConstructor(const Function& f, const Value* args, size_t count) + override { Around around{with_}; return RD::callAsConstructor(f, args, count); - }; + } + + std::shared_ptr tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) override { + Around around{with_}; + return RD::tryGetMutableBuffer(arrayBuffer); + } + + ArrayBuffer buffer(const TypedArray& typedArray) override { + Around around{with_}; + return RD::buffer(typedArray); + } + size_t byteOffset(const TypedArray& typedArray) override { + Around around{with_}; + return RD::byteOffset(typedArray); + } + size_t byteLength(const TypedArray& typedArray) override { + Around around{with_}; + return RD::byteLength(typedArray); + } + size_t length(const TypedArray& typedArray) override { + Around around{with_}; + return RD::length(typedArray); + } + + Uint8Array createUint8Array(size_t length) override { + Around around{with_}; + return RD::createUint8Array(length); + } + Uint8Array createUint8Array( + const ArrayBuffer& buffer, + size_t offset, + size_t length) override { + Around around{with_}; + return RD::createUint8Array(buffer, offset, length); + } // Private data for managing scopes. Runtime::ScopeState* pushScope() override { @@ -845,31 +1098,44 @@ class WithRuntimeDecorator : public RuntimeDecorator { bool strictEquals(const Symbol& a, const Symbol& b) const override { Around around{with_}; return RD::strictEquals(a, b); - }; + } bool strictEquals(const BigInt& a, const BigInt& b) const override { Around around{with_}; return RD::strictEquals(a, b); - }; + } bool strictEquals(const String& a, const String& b) const override { Around around{with_}; return RD::strictEquals(a, b); - }; + } bool strictEquals(const Object& a, const Object& b) const override { Around around{with_}; return RD::strictEquals(a, b); - }; + } bool instanceOf(const Object& o, const Function& f) override { Around around{with_}; return RD::instanceOf(o, f); - }; + } - void setExternalMemoryPressure(const jsi::Object& obj, - size_t amount) override { + void setExternalMemoryPressure(const jsi::Object& obj, size_t amount) + override { Around around{with_}; RD::setExternalMemoryPressure(obj, amount); - }; + } + + void setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)) override { + Around around{with_}; + RD::setRuntimeDataImpl(uuid, data, deleter); + } + + const void* getRuntimeDataImpl(const UUID& uuid) override { + Around around{with_}; + return RD::getRuntimeDataImpl(uuid); + } private: // Wrap an RAII type around With& to guarantee after always happens. @@ -877,7 +1143,9 @@ class WithRuntimeDecorator : public RuntimeDecorator { Around(With& with) : with_(with) { detail::BeforeCaller::before(with_); } - ~Around() { detail::AfterCaller::after(with_); } + ~Around() { + detail::AfterCaller::after(with_); + } With& with_; }; @@ -885,5 +1153,5 @@ class WithRuntimeDecorator : public RuntimeDecorator { With& with_; }; -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/android-jsi/platforms/android/cpp/jsi/instrumentation.h b/packages/android-jsi/platforms/android/cpp/jsi/instrumentation.h index db22f4a41..4a88951f6 100644 --- a/packages/android-jsi/platforms/android/cpp/jsi/instrumentation.h +++ b/packages/android-jsi/platforms/android/cpp/jsi/instrumentation.h @@ -13,7 +13,7 @@ #include #include -#include "jsi/jsi.h" +#include namespace facebook { namespace jsi { @@ -74,10 +74,10 @@ class JSI_EXPORT Instrumentation { /// been updated. This callback will be invoked on the same thread that the /// runtime is using. virtual void startTrackingHeapObjectStackTraces( - std::function stats)> - fragmentCallback) = 0; + std::function stats)> fragmentCallback) = 0; /// Stop capture JS stack-traces for JS heap allocated objects. virtual void stopTrackingHeapObjectStackTraces() = 0; @@ -99,17 +99,17 @@ class JSI_EXPORT Instrumentation { /// /// \param path to save the heap capture. /// \param options additional options for what to capture. - virtual void createSnapshotToFile(const std::string& path, - const HeapSnapshotOptions& options = { - false}) = 0; + virtual void createSnapshotToFile( + const std::string& path, + const HeapSnapshotOptions& options = {false}) = 0; /// Captures the heap to an output stream /// /// \param os output stream to write to. /// \param options additional options for what to capture. - virtual void createSnapshotToStream(std::ostream& os, - const HeapSnapshotOptions& options = { - false}) = 0; + virtual void createSnapshotToStream( + std::ostream& os, + const HeapSnapshotOptions& options = {false}) = 0; /// If the runtime has been created to trace to a temp file, flush /// any unwritten parts of the trace of bridge traffic to the file, @@ -121,9 +121,12 @@ class JSI_EXPORT Instrumentation { virtual void writeBasicBlockProfileTraceToFile( const std::string& fileName) const = 0; + /// Write the opcode stats to the given stream. + virtual void dumpOpcodeStats(std::ostream& os) const = 0; + /// Dump external profiler symbols to the given file name. virtual void dumpProfilerSymbolsToFile(const std::string& fileName) const = 0; }; -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/android-jsi/platforms/android/cpp/jsi/jsi-inl.h b/packages/android-jsi/platforms/android/cpp/jsi/jsi-inl.h index 8f30c0566..6a29f6479 100644 --- a/packages/android-jsi/platforms/android/cpp/jsi/jsi-inl.h +++ b/packages/android-jsi/platforms/android/cpp/jsi/jsi-inl.h @@ -11,37 +11,48 @@ namespace facebook { namespace jsi { namespace detail { -inline Value toValue(Runtime&, std::nullptr_t) { return Value::null(); } -inline Value toValue(Runtime&, bool b) { return Value(b); } -inline Value toValue(Runtime&, double d) { return Value(d); } -inline Value toValue(Runtime&, float f) { +inline Value toValue(IRuntime&, std::nullptr_t) { + return Value::null(); +} +inline Value toValue(IRuntime&, bool b) { + return Value(b); +} +inline Value toValue(IRuntime&, double d) { + return Value(d); +} +inline Value toValue(IRuntime&, float f) { return Value(static_cast(f)); } -inline Value toValue(Runtime&, int i) { return Value(i); } -inline Value toValue(Runtime& runtime, const char* str) { +inline Value toValue(IRuntime&, int i) { + return Value(i); +} +inline Value toValue(IRuntime& runtime, const char* str) { return String::createFromAscii(runtime, str); } -inline Value toValue(Runtime& runtime, const std::string& str) { +inline Value toValue(IRuntime& runtime, const std::string& str) { return String::createFromUtf8(runtime, str); } template -inline Value toValue(Runtime& runtime, const T& other) { - static_assert(std::is_base_of::value, - "This type cannot be converted to Value"); +inline Value toValue(IRuntime& runtime, const T& other) { + static_assert( + std::is_base_of::value, + "This type cannot be converted to Value"); return Value(runtime, other); } -inline Value toValue(Runtime& runtime, const Value& value) { +inline Value toValue(IRuntime& runtime, const Value& value) { return Value(runtime, value); } -inline Value&& toValue(Runtime&, Value&& value) { return std::move(value); } +inline Value&& toValue(IRuntime&, Value&& value) { + return std::move(value); +} -inline PropNameID toPropNameID(Runtime& runtime, const char* name) { +inline PropNameID toPropNameID(IRuntime& runtime, const char* name) { return PropNameID::forAscii(runtime, name); } -inline PropNameID toPropNameID(Runtime& runtime, const std::string& name) { +inline PropNameID toPropNameID(IRuntime& runtime, const std::string& name) { return PropNameID::forUtf8(runtime, name); } -inline PropNameID&& toPropNameID(Runtime&, PropNameID&& name) { +inline PropNameID&& toPropNameID(IRuntime&, PropNameID&& name) { return std::move(name); } @@ -52,7 +63,7 @@ template std::make_exception_ptr(E{std::forward(args)...})); } -} // namespace detail +} // namespace detail template inline T Runtime::make(Runtime::PointerValue* pv) { @@ -73,116 +84,173 @@ inline const Runtime::PointerValue* Runtime::getPointerValue( return value.data_.pointer.ptr_; } -Value Object::getPrototype(Runtime& runtime) const { +inline void Runtime::setRuntimeData( + const UUID& dataUUID, + const std::shared_ptr& data) { + auto* dataPtr = new std::shared_ptr(data); + setRuntimeDataImpl(dataUUID, dataPtr, [](const void* data) { + delete (const std::shared_ptr*)data; + }); +} + +inline std::shared_ptr Runtime::getRuntimeData(const UUID& dataUUID) { + auto* data = (const std::shared_ptr*)getRuntimeDataImpl(dataUUID); + return data ? *data : nullptr; +} + +Value Object::getPrototype(IRuntime& runtime) const { return runtime.getPrototypeOf(*this); } -inline Value Object::getProperty(Runtime& runtime, const char* name) const { +inline Value Object::getProperty(IRuntime& runtime, const char* name) const { return getProperty(runtime, String::createFromAscii(runtime, name)); } -inline Value Object::getProperty(Runtime& runtime, const String& name) const { +inline Value Object::getProperty(IRuntime& runtime, const String& name) const { + return runtime.getProperty(*this, name); +} + +inline Value Object::getProperty(IRuntime& runtime, const PropNameID& name) + const { return runtime.getProperty(*this, name); } -inline Value Object::getProperty(Runtime& runtime, - const PropNameID& name) const { +inline Value Object::getProperty(IRuntime& runtime, const Value& name) const { return runtime.getProperty(*this, name); } -inline bool Object::hasProperty(Runtime& runtime, const char* name) const { +inline bool Object::hasProperty(IRuntime& runtime, const char* name) const { return hasProperty(runtime, String::createFromAscii(runtime, name)); } -inline bool Object::hasProperty(Runtime& runtime, const String& name) const { +inline bool Object::hasProperty(IRuntime& runtime, const String& name) const { + return runtime.hasProperty(*this, name); +} + +inline bool Object::hasProperty(IRuntime& runtime, const PropNameID& name) + const { return runtime.hasProperty(*this, name); } -inline bool Object::hasProperty(Runtime& runtime, - const PropNameID& name) const { +inline bool Object::hasProperty(IRuntime& runtime, const Value& name) const { return runtime.hasProperty(*this, name); } template -void Object::setProperty(Runtime& runtime, const char* name, T&& value) const { - setProperty(runtime, String::createFromAscii(runtime, name), - std::forward(value)); +void Object::setProperty(IRuntime& runtime, const char* name, T&& value) const { + setProperty( + runtime, String::createFromAscii(runtime, name), std::forward(value)); } template -void Object::setProperty(Runtime& runtime, const String& name, - T&& value) const { - setPropertyValue(runtime, name, - detail::toValue(runtime, std::forward(value))); +void Object::setProperty(IRuntime& runtime, const String& name, T&& value) + const { + setPropertyValue( + runtime, name, detail::toValue(runtime, std::forward(value))); } template -void Object::setProperty(Runtime& runtime, const PropNameID& name, - T&& value) const { - setPropertyValue(runtime, name, - detail::toValue(runtime, std::forward(value))); +void Object::setProperty(IRuntime& runtime, const PropNameID& name, T&& value) + const { + setPropertyValue( + runtime, name, detail::toValue(runtime, std::forward(value))); +} + +template +void Object::setProperty(IRuntime& runtime, const Value& name, T&& value) + const { + setPropertyValue( + runtime, name, detail::toValue(runtime, std::forward(value))); +} + +inline void Object::deleteProperty(IRuntime& runtime, const char* name) const { + deleteProperty(runtime, String::createFromAscii(runtime, name)); } -inline Array Object::getArray(Runtime& runtime) const& { +inline void Object::deleteProperty(IRuntime& runtime, const String& name) + const { + runtime.deleteProperty(*this, name); +} + +inline void Object::deleteProperty(IRuntime& runtime, const PropNameID& name) + const { + runtime.deleteProperty(*this, name); +} + +inline void Object::deleteProperty(IRuntime& runtime, const Value& name) const { + runtime.deleteProperty(*this, name); +} + +inline Array Object::getArray(IRuntime& runtime) const& { assert(runtime.isArray(*this)); - (void)runtime; // when assert is disabled we need to mark this as used + (void)runtime; // when assert is disabled we need to mark this as used return Array(runtime.cloneObject(ptr_)); } -inline Array Object::getArray(Runtime& runtime) && { +inline Array Object::getArray(IRuntime& runtime) && { assert(runtime.isArray(*this)); - (void)runtime; // when assert is disabled we need to mark this as used + (void)runtime; // when assert is disabled we need to mark this as used Runtime::PointerValue* value = ptr_; ptr_ = nullptr; return Array(value); } -inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const& { +inline ArrayBuffer Object::getArrayBuffer(IRuntime& runtime) const& { assert(runtime.isArrayBuffer(*this)); - (void)runtime; // when assert is disabled we need to mark this as used + (void)runtime; // when assert is disabled we need to mark this as used return ArrayBuffer(runtime.cloneObject(ptr_)); } -inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) && { +inline ArrayBuffer Object::getArrayBuffer(IRuntime& runtime) && { assert(runtime.isArrayBuffer(*this)); - (void)runtime; // when assert is disabled we need to mark this as used + (void)runtime; // when assert is disabled we need to mark this as used Runtime::PointerValue* value = ptr_; ptr_ = nullptr; return ArrayBuffer(value); } -inline Function Object::getFunction(Runtime& runtime) const& { +inline TypedArray Object::getTypedArray(IRuntime& runtime) const& { + assert(runtime.isTypedArray(*this)); + return TypedArray(runtime.cloneObject(ptr_)); +} + +inline Uint8Array Object::getUint8Array(IRuntime& runtime) const& { + assert(runtime.isUint8Array(*this)); + return Uint8Array(runtime.cloneObject(ptr_)); +} + +inline Function Object::getFunction(IRuntime& runtime) const& { assert(runtime.isFunction(*this)); return Function(runtime.cloneObject(ptr_)); } -inline Function Object::getFunction(Runtime& runtime) && { +inline Function Object::getFunction(IRuntime& runtime) && { assert(runtime.isFunction(*this)); - (void)runtime; // when assert is disabled we need to mark this as used + (void)runtime; // when assert is disabled we need to mark this as used Runtime::PointerValue* value = ptr_; ptr_ = nullptr; return Function(value); } template -inline bool Object::isHostObject(Runtime& runtime) const { +inline bool Object::isHostObject(IRuntime& runtime) const { return runtime.isHostObject(*this) && - std::dynamic_pointer_cast(runtime.getHostObject(*this)); + std::dynamic_pointer_cast(runtime.getHostObject(*this)); } template <> -inline bool Object::isHostObject(Runtime& runtime) const { +inline bool Object::isHostObject(IRuntime& runtime) const { return runtime.isHostObject(*this); } template -inline std::shared_ptr Object::getHostObject(Runtime& runtime) const { +inline std::shared_ptr Object::getHostObject(IRuntime& runtime) const { assert(isHostObject(runtime)); return std::static_pointer_cast(runtime.getHostObject(*this)); } template -inline std::shared_ptr Object::asHostObject(Runtime& runtime) const { +inline std::shared_ptr Object::asHostObject(IRuntime& runtime) const { if (!isHostObject(runtime)) { detail::throwOrDie( "Object is not a HostObject of desired type"); @@ -192,117 +260,144 @@ inline std::shared_ptr Object::asHostObject(Runtime& runtime) const { template <> inline std::shared_ptr Object::getHostObject( - Runtime& runtime) const { + IRuntime& runtime) const { assert(runtime.isHostObject(*this)); return runtime.getHostObject(*this); } template -inline bool Object::hasNativeState(Runtime& runtime) const { +inline bool Object::hasNativeState(IRuntime& runtime) const { return runtime.hasNativeState(*this) && - std::dynamic_pointer_cast(runtime.getNativeState(*this)); + std::dynamic_pointer_cast(runtime.getNativeState(*this)); } template <> -inline bool Object::hasNativeState(Runtime& runtime) const { +inline bool Object::hasNativeState(IRuntime& runtime) const { return runtime.hasNativeState(*this); } template -inline std::shared_ptr Object::getNativeState(Runtime& runtime) const { +inline std::shared_ptr Object::getNativeState(IRuntime& runtime) const { assert(hasNativeState(runtime)); return std::static_pointer_cast(runtime.getNativeState(*this)); } -inline void Object::setNativeState(Runtime& runtime, - std::shared_ptr state) const { +inline void Object::setNativeState( + IRuntime& runtime, + std::shared_ptr state) const { runtime.setNativeState(*this, state); } -inline void Object::setExternalMemoryPressure(Runtime& runtime, - size_t amt) const { +inline void Object::setExternalMemoryPressure(IRuntime& runtime, size_t amt) + const { runtime.setExternalMemoryPressure(*this, amt); } -inline Array Object::getPropertyNames(Runtime& runtime) const { +inline Array Object::getPropertyNames(IRuntime& runtime) const { return runtime.getPropertyNames(*this); } -inline Value WeakObject::lock(Runtime& runtime) const { +inline Value WeakObject::lock(IRuntime& runtime) const { return runtime.lockWeakObject(*this); } template -void Array::setValueAtIndex(Runtime& runtime, size_t i, T&& value) const { - setValueAtIndexImpl(runtime, i, - detail::toValue(runtime, std::forward(value))); +void Array::setValueAtIndex(IRuntime& runtime, size_t i, T&& value) const { + setValueAtIndexImpl( + runtime, i, detail::toValue(runtime, std::forward(value))); } -inline Value Array::getValueAtIndex(Runtime& runtime, size_t i) const { +inline Value Array::getValueAtIndex(IRuntime& runtime, size_t i) const { return runtime.getValueAtIndex(*this, i); } -inline Function Function::createFromHostFunction(Runtime& runtime, - const jsi::PropNameID& name, - unsigned int paramCount, - jsi::HostFunctionType func) { - return runtime.createFunctionFromHostFunction(name, paramCount, - std::move(func)); +inline Function Function::createFromHostFunction( + IRuntime& runtime, + const jsi::PropNameID& name, + unsigned int paramCount, + jsi::HostFunctionType func) { + return runtime.createFunctionFromHostFunction( + name, paramCount, std::move(func)); } -inline Value Function::call(Runtime& runtime, const Value* args, - size_t count) const { +inline Value Function::call(IRuntime& runtime, const Value* args, size_t count) + const { return runtime.call(*this, Value::undefined(), args, count); } -inline Value Function::call(Runtime& runtime, - std::initializer_list args) const { +inline Value Function::call( + IRuntime& runtime, + std::initializer_list args) const { return call(runtime, args.begin(), args.size()); } template -inline Value Function::call(Runtime& runtime, Args&&... args) const { +inline Value Function::call(IRuntime& runtime, Args&&... args) const { // A more awesome version of this would be able to create raw values // which can be used directly without wrapping and unwrapping, but // this will do for now. return call(runtime, {detail::toValue(runtime, std::forward(args))...}); } -inline Value Function::callWithThis(Runtime& runtime, const Object& jsThis, - const Value* args, size_t count) const { +inline Value Function::callWithThis( + IRuntime& runtime, + const Object& jsThis, + const Value* args, + size_t count) const { return runtime.call(*this, Value(runtime, jsThis), args, count); } -inline Value Function::callWithThis(Runtime& runtime, const Object& jsThis, - std::initializer_list args) const { +inline Value Function::callWithThis( + IRuntime& runtime, + const Object& jsThis, + std::initializer_list args) const { return callWithThis(runtime, jsThis, args.begin(), args.size()); } template -inline Value Function::callWithThis(Runtime& runtime, const Object& jsThis, - Args&&... args) const { +inline Value Function::callWithThis( + IRuntime& runtime, + const Object& jsThis, + Args&&... args) const { // A more awesome version of this would be able to create raw values // which can be used directly without wrapping and unwrapping, but // this will do for now. - return callWithThis(runtime, jsThis, - {detail::toValue(runtime, std::forward(args))...}); + return callWithThis( + runtime, jsThis, {detail::toValue(runtime, std::forward(args))...}); } template -inline Array Array::createWithElements(Runtime& runtime, Args&&... args) { +inline Array Array::createWithElements(IRuntime& runtime, Args&&... args) { return createWithElements( runtime, {detail::toValue(runtime, std::forward(args))...}); } template -inline std::vector PropNameID::names(Runtime& runtime, - Args&&... args) { +inline size_t Array::push(IRuntime& runtime, Args&&... args) { + return push(runtime, {detail::toValue(runtime, std::forward(args))...}); +} + +inline size_t Array::push( + IRuntime& runtime, + std::initializer_list elements) { + return push(runtime, elements.begin(), elements.size()); +} + +inline size_t +Array::push(IRuntime& runtime, const Value* elements, size_t count) { + return runtime.push(*this, elements, count); +} + +template +inline std::vector PropNameID::names( + IRuntime& runtime, + Args&&... args) { return names({detail::toPropNameID(runtime, std::forward(args))...}); } template inline std::vector PropNameID::names( - PropNameID(&&propertyNames)[N]) { + PropNameID (&&propertyNames)[N]) { std::vector result; result.reserve(N); for (auto& name : propertyNames) { @@ -311,26 +406,29 @@ inline std::vector PropNameID::names( return result; } -inline Value Function::callAsConstructor(Runtime& runtime, const Value* args, - size_t count) const { +inline Value Function::callAsConstructor( + IRuntime& runtime, + const Value* args, + size_t count) const { return runtime.callAsConstructor(*this, args, count); } inline Value Function::callAsConstructor( - Runtime& runtime, std::initializer_list args) const { + IRuntime& runtime, + std::initializer_list args) const { return callAsConstructor(runtime, args.begin(), args.size()); } template -inline Value Function::callAsConstructor(Runtime& runtime, - Args&&... args) const { +inline Value Function::callAsConstructor(IRuntime& runtime, Args&&... args) + const { return callAsConstructor( runtime, {detail::toValue(runtime, std::forward(args))...}); } -String BigInt::toString(Runtime& runtime, int radix) const { +String BigInt::toString(IRuntime& runtime, int radix) const { return runtime.bigintToString(*this, radix); } -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/android-jsi/platforms/android/cpp/jsi/jsi.cpp b/packages/android-jsi/platforms/android/cpp/jsi/jsi.cpp index 5e3aac990..792b5bf5a 100644 --- a/packages/android-jsi/platforms/android/cpp/jsi/jsi.cpp +++ b/packages/android-jsi/platforms/android/cpp/jsi/jsi.cpp @@ -5,22 +5,80 @@ * LICENSE file in the root directory of this source tree. */ -#include "jsi/jsi.h" - #include #include #include +#include +#include #include -#include "jsi/instrumentation.h" +#include +#include namespace facebook { namespace jsi { namespace { +/// A global map used to store custom runtime data for VMs that do not provide +/// their own default implementation of setRuntimeData and getRuntimeData. +struct RuntimeDataGlobal { + /// Mutex protecting the Runtime data map + std::mutex mutex_{}; + /// Maps a runtime pointer to a map of its custom data. At destruction of the + /// runtime, its entry will be removed from the global map. + std::unordered_map< + Runtime*, + std::unordered_map< + UUID, + std::pair, + UUID::Hash>> + dataMap_; +}; + +RuntimeDataGlobal& getRuntimeDataGlobal() { + static RuntimeDataGlobal runtimeData{}; + return runtimeData; +} + +/// A host object that, when destructed, will remove the runtime's custom data +/// entry from the global map of custom data. +class RemoveRuntimeDataHostObject : public jsi::HostObject { + public: + explicit RemoveRuntimeDataHostObject(Runtime* runtime) : runtime_(runtime) {} + + RemoveRuntimeDataHostObject(const RemoveRuntimeDataHostObject&) = default; + RemoveRuntimeDataHostObject(RemoveRuntimeDataHostObject&&) = default; + RemoveRuntimeDataHostObject& operator=(const RemoveRuntimeDataHostObject&) = + default; + RemoveRuntimeDataHostObject& operator=(RemoveRuntimeDataHostObject&&) = + default; + + ~RemoveRuntimeDataHostObject() override { + auto& runtimeDataGlobal = getRuntimeDataGlobal(); + std::lock_guard lock(runtimeDataGlobal.mutex_); + auto runtimeMapIt = runtimeDataGlobal.dataMap_.find(runtime_); + // We install the RemoveRuntimeDataHostObject only when the first custom + // data for the runtime is added, and only this object is responsible for + // clearing runtime data. Thus, we should always be able to find the data + // entry. + assert( + runtimeMapIt != runtimeDataGlobal.dataMap_.end() && + "Custom runtime data not found for this runtime"); + + for (auto [_, entry] : runtimeMapIt->second) { + auto* deleter = entry.second; + deleter(entry.first); + } + runtimeDataGlobal.dataMap_.erase(runtime_); + } + + private: + Runtime* runtime_; +}; + // This is used for generating short exception strings. -std::string kindToString(const Value& v, Runtime* rt = nullptr) { +std::string kindToString(const Value& v, IRuntime* rt = nullptr) { if (v.isUndefined()) { return "undefined"; } else if (v.isNull()) { @@ -46,7 +104,10 @@ std::string kindToString(const Value& v, Runtime* rt = nullptr) { // failure is in building a JSError, this will lead to infinite // recursion. This function is used in place of getPropertyAsFunction // when building JSError, to avoid that infinite recursion. -Value callGlobalFunction(Runtime& runtime, const char* name, const Value& arg) { +Value callGlobalFunction( + IRuntime& runtime, + const char* name, + const Value& arg) { Value v = runtime.global().getProperty(runtime, name); if (!v.isObject()) { throw JSINativeException( @@ -122,7 +183,7 @@ uint32_t decodeUTF8(const char*& input) { return replacementCharacter; } ret = ((ch & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) | - (ch3 & 0x3F); + (ch3 & 0x3F); input += 4; if (ret <= 0xFFFF) { return replacementCharacter; @@ -164,7 +225,47 @@ std::u16string convertUTF8ToUTF16(const std::string& utf8) { return ret; } -} // namespace +// Given a unsigned number, which is less than 16, return the hex character. +inline char hexDigit(unsigned x) { + return x < 10 ? '0' + x : 'A' + (x - 10); +} + +// Given a sequence of UTF 16 code units, return true if all code units are +// ASCII characters +bool isAllASCII(const char16_t* utf16, size_t length) { + for (const char16_t* e = utf16 + length; utf16 != e; ++utf16) { + if (*utf16 > 0x7F) { + return false; + } + } + return true; +} + +// Given a sequences of UTF 16 code units, return a string that explicitly +// expresses the code units +std::string getUtf16CodeUnitString(const char16_t* utf16, size_t length) { + // Every character will need 4 hex digits + the character escape "\u". + // Plus 2 character for the opening and closing single quote. + std::string s = std::string(6 * length + 2, 0); + s.front() = '\''; + + for (size_t i = 0; i != length; ++i) { + char16_t ch = utf16[i]; + size_t start = (6 * i) + 1; + + s[start] = '\\'; + s[start + 1] = 'u'; + + s[start + 2] = hexDigit((ch >> 12) & 0x000f); + s[start + 3] = hexDigit((ch >> 8) & 0x000f); + s[start + 4] = hexDigit((ch >> 4) & 0x000f); + s[start + 5] = hexDigit(ch & 0x000f); + } + s.back() = '\''; + return s; +} + +} // namespace Buffer::~Buffer() = default; @@ -172,7 +273,9 @@ MutableBuffer::~MutableBuffer() = default; PreparedJavaScript::~PreparedJavaScript() = default; -Value HostObject::get(Runtime&, const PropNameID&) { return Value(); } +Value HostObject::get(Runtime&, const PropNameID&) { + return Value(); +} void HostObject::set(Runtime& rt, const PropNameID& name, const Value&) { std::string msg("TypeError: Cannot assign to property '"); @@ -185,11 +288,21 @@ HostObject::~HostObject() {} NativeState::~NativeState() {} +#ifdef JSI_UNSTABLE +Serialized::~Serialized() {} +#endif + Runtime::~Runtime() {} +ICast* Runtime::castInterface(const UUID& /*interfaceUUID*/) { + return nullptr; +} + Instrumentation& Runtime::instrumentation() { class NoInstrumentation : public Instrumentation { - std::string getRecordedGCStats() override { return ""; } + std::string getRecordedGCStats() override { + return ""; + } std::unordered_map getHeapInfo(bool) override { return std::unordered_map{}; @@ -198,31 +311,41 @@ Instrumentation& Runtime::instrumentation() { void collectGarbage(std::string) override {} void startTrackingHeapObjectStackTraces( - std::function)>) override {} + std::function)>) override {} void stopTrackingHeapObjectStackTraces() override {} void startHeapSampling(size_t) override {} void stopHeapSampling(std::ostream&) override {} - void createSnapshotToFile(const std::string& /*path*/, - const HeapSnapshotOptions& /*options*/) override { + void createSnapshotToFile( + const std::string& /*path*/, + const HeapSnapshotOptions& /*options*/) override { throw JSINativeException( "Default instrumentation cannot create a heap snapshot"); } void createSnapshotToStream( - std::ostream& /*os*/, const HeapSnapshotOptions& /*options*/) override { + std::ostream& /*os*/, + const HeapSnapshotOptions& /*options*/) override { throw JSINativeException( "Default instrumentation cannot create a heap snapshot"); } - std::string flushAndDisableBridgeTrafficTrace() override { std::abort(); } + std::string flushAndDisableBridgeTrafficTrace() override { + std::abort(); + } void writeBasicBlockProfileTraceToFile(const std::string&) const override { std::abort(); } + void dumpOpcodeStats(std::ostream&) const override { + std::abort(); + } + void dumpProfilerSymbolsToFile(const std::string&) const override { std::abort(); } @@ -239,6 +362,25 @@ Value Runtime::createValueFromJsonUtf8(const uint8_t* json, size_t length) { return parseJson.call(*this, String::createFromUtf8(*this, json, length)); } +String Runtime::createStringFromUtf16(const char16_t* utf16, size_t length) { + if (isAllASCII(utf16, length)) { + std::string buffer(utf16, utf16 + length); + return createStringFromAscii(buffer.data(), length); + } + auto s = getUtf16CodeUnitString(utf16, length); + return global() + .getPropertyAsFunction(*this, "eval") + .call(*this, s) + .getString(*this); +} + +PropNameID Runtime::createPropNameIDFromUtf16( + const char16_t* utf16, + size_t length) { + auto jsString = createStringFromUtf16(utf16, length); + return createPropNameIDFromString(jsString); +} + std::u16string Runtime::utf16(const PropNameID& sym) { auto utf8Str = utf8(sym); return convertUTF8ToUTF16(utf8Str); @@ -249,16 +391,18 @@ std::u16string Runtime::utf16(const String& str) { return convertUTF8ToUTF16(utf8Str); } -void Runtime::getStringData(const jsi::String& str, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)) { +void Runtime::getStringData( + const jsi::String& str, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) { auto utf16Str = utf16(str); cb(ctx, false, utf16Str.data(), utf16Str.size()); } -void Runtime::getPropNameIdData(const jsi::PropNameID& sym, void* ctx, - void (*cb)(void* ctx, bool ascii, - const void* data, size_t num)) { +void Runtime::getPropNameIdData( + const jsi::PropNameID& sym, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) { auto utf16Str = utf16(sym); cb(ctx, false, utf16Str.data(), utf16Str.size()); } @@ -284,6 +428,244 @@ Object Runtime::createObjectWithPrototype(const Value& prototype) { return createFn.call(*this, prototype).asObject(*this); } +void Runtime::deleteProperty(const Object& object, const PropNameID& name) { + auto nameStr = String::createFromUtf16(*this, name.utf16(*this)); + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, nameStr).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} + +void Runtime::deleteProperty(const Object& object, const String& name) { + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, name).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} + +void Runtime::deleteProperty(const Object& object, const Value& name) { + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, name).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} + +size_t Runtime::push(const Array& arr, const Value* elements, size_t count) { + size_t newSize = size(arr); + for (size_t i = 0; i < count; i++) { + arr.setProperty(*this, Value((int)newSize), elements[i]); + ++newSize; + } + arr.setProperty(*this, "length", Value((int)newSize)); + return newSize; +} + +void Runtime::setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)) { + auto& runtimeDataGlobal = getRuntimeDataGlobal(); + std::lock_guard lock(runtimeDataGlobal.mutex_); + if (auto it = runtimeDataGlobal.dataMap_.find(this); + it != runtimeDataGlobal.dataMap_.end()) { + auto& map = it->second; + if (auto entryIt = map.find(uuid); entryIt != map.end()) { + // Free the old data + auto oldData = entryIt->second.first; + auto oldDataDeleter = entryIt->second.second; + oldDataDeleter(oldData); + } + map[uuid] = {data, deleter}; + return; + } + // No custom data entry exist for this runtime in the global map, so create + // one. + runtimeDataGlobal.dataMap_[this][uuid] = {data, deleter}; + + // The first time data is added for this runtime is added to the map, install + // a host object on the global object of the runtime. This host object is used + // to release the runtime's entry from the global custom data map when the + // runtime is destroyed. + // Also, try to protect the host object by making it non-configurable, + // non-enumerable, and non-writable. These JSI operations are purposely + // performed after runtime-specific data map is added and the host object is + // created to prevent data leaks if any operations fail. + Object ho = Object::createFromHostObject( + *this, std::make_shared(this)); + global().setProperty(*this, "_jsiRuntimeDataCleanUp", ho); + auto definePropertyFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "defineProperty"); + auto desc = Object(*this); + desc.setProperty(*this, "configurable", Value(false)); + desc.setProperty(*this, "enumerable", Value(false)); + desc.setProperty(*this, "writable", Value(false)); + definePropertyFn.call(*this, global(), "_jsiRuntimeDataCleanUp", desc); +} + +const void* Runtime::getRuntimeDataImpl(const UUID& uuid) { + auto& runtimeDataGlobal = getRuntimeDataGlobal(); + std::lock_guard lock(runtimeDataGlobal.mutex_); + if (auto runtimeMapIt = runtimeDataGlobal.dataMap_.find(this); + runtimeMapIt != runtimeDataGlobal.dataMap_.end()) { + if (auto customDataIt = runtimeMapIt->second.find(uuid); + customDataIt != runtimeMapIt->second.end()) { + return customDataIt->second.first; + } + } + return nullptr; +} + +Value Runtime::getProperty(const Object& object, const Value& name) { + auto getFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "get"); + return getFn.call(*this, object, name); +} + +bool Runtime::hasProperty(const Object& object, const Value& name) { + auto hasFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "has"); + return hasFn.call(*this, object, name).getBool(); +} + +void Runtime::setPropertyValue( + const Object& object, + const Value& name, + const Value& value) { + auto setFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "set"); + auto setResult = setFn.call(*this, object, name, value).getBool(); + if (!setResult) { + throw JSError(*this, "Failed to set the property"); + } +} + +std::shared_ptr Runtime::tryGetMutableBuffer( + const jsi::ArrayBuffer&) { + return nullptr; +} + +Uint8Array Runtime::createUint8Array(size_t length) { + auto uint8ArrayCtor = global().getPropertyAsFunction(*this, "Uint8Array"); + auto result = + uint8ArrayCtor.callAsConstructor(*this, static_cast(length)); + return Uint8Array(cloneObject(getPointerValue(result.getObject(*this)))); +} + +Uint8Array Runtime::createUint8Array( + const ArrayBuffer& buffer, + size_t offset, + size_t length) { + auto uint8ArrayCtor = global().getPropertyAsFunction(*this, "Uint8Array"); + auto result = uint8ArrayCtor.callAsConstructor( + *this, buffer, static_cast(offset), static_cast(length)); + return Uint8Array(cloneObject(getPointerValue(result.getObject(*this)))); +} + +Value Runtime::createError(const String& msg) { + return callGlobalFunction(*this, "Error", Value(*this, msg)); +} + +Value Runtime::createEvalError(const String& msg) { + return callGlobalFunction(*this, "EvalError", Value(*this, msg)); +} + +Value Runtime::createRangeError(const String& msg) { + return callGlobalFunction(*this, "RangeError", Value(*this, msg)); +} + +Value Runtime::createReferenceError(const String& msg) { + return callGlobalFunction(*this, "ReferenceError", Value(*this, msg)); +} + +Value Runtime::createSyntaxError(const String& msg) { + return callGlobalFunction(*this, "SyntaxError", Value(*this, msg)); +} + +Value Runtime::createTypeError(const String& msg) { + return callGlobalFunction(*this, "TypeError", Value(*this, msg)); +} + +Value Runtime::createURIError(const String& msg) { + return callGlobalFunction(*this, "URIError", Value(*this, msg)); +} + +size_t Runtime::length(const String& str) { + return utf16(str).size(); +} + +bool Runtime::detached(const ArrayBuffer& buffer) { + Value prop = buffer.getProperty(*this, "detached"); + if (!prop.isBool()) { + throw JSINativeException( + "ArrayBuffer.detached is not supported by this runtime"); + } + return prop.getBool(); +} + +ArrayBuffer Runtime::buffer(const TypedArray& typedArray) { + Value buffer = typedArray.getProperty(*this, "buffer"); + if (!buffer.isObject()) { + throw JSINativeException("TypedArray.buffer is not an object"); + } + Object bufferObj = buffer.getObject(*this); + if (!bufferObj.isArrayBuffer(*this)) { + throw JSINativeException("TypedArray.buffer is not an ArrayBuffer"); + } + return bufferObj.getArrayBuffer(*this); +} + +size_t Runtime::byteOffset(const TypedArray& typedArray) { + Value byteOffset = typedArray.getProperty(*this, "byteOffset"); + if (!byteOffset.isNumber()) { + throw JSINativeException("TypedArray.byteOffset is not a number"); + } + return static_cast(byteOffset.getNumber()); +} + +size_t Runtime::byteLength(const TypedArray& typedArray) { + Value byteLength = typedArray.getProperty(*this, "byteLength"); + if (!byteLength.isNumber()) { + throw JSINativeException("TypedArray.byteLength is not a number"); + } + return static_cast(byteLength.getNumber()); +} + +size_t Runtime::length(const TypedArray& typedArray) { + Value length = typedArray.getProperty(*this, "length"); + if (!length.isNumber()) { + throw JSINativeException("TypedArray.length is not a number"); + } + return static_cast(length.getNumber()); +} + +bool Runtime::isTypedArray(const Object& obj) const { + Runtime& self = const_cast(*this); + // Uint8Array.__proto__ is the %TypedArray% intrinsic constructor. + auto uint8ArrayCtor = self.global().getPropertyAsFunction(self, "Uint8Array"); + auto typedArrayCtor = + uint8ArrayCtor.getProperty(self, "__proto__").getObject(self); + return self.instanceOf(obj, typedArrayCtor.getFunction(self)); +} + +bool Runtime::isUint8Array(const Object& obj) const { + Runtime& self = const_cast(*this); + auto uint8ArrayCtor = self.global().getPropertyAsFunction(self, "Uint8Array"); + return self.instanceOf(obj, uint8ArrayCtor); +} + Pointer& Pointer::operator=(Pointer&& other) noexcept { if (ptr_) { ptr_->invalidate(); @@ -293,67 +675,92 @@ Pointer& Pointer::operator=(Pointer&& other) noexcept { return *this; } -Object Object::getPropertyAsObject(Runtime& runtime, const char* name) const { +Object Object::getPropertyAsObject(IRuntime& runtime, const char* name) const { Value v = getProperty(runtime, name); if (!v.isObject()) { - throw JSError(runtime, std::string("getPropertyAsObject: property '") + - name + "' is " + kindToString(v, &runtime) + - ", expected an Object"); + throw JSError( + runtime, + std::string("getPropertyAsObject: property '") + name + "' is " + + kindToString(v, &runtime) + ", expected an Object"); } return v.getObject(runtime); } -Function Object::getPropertyAsFunction(Runtime& runtime, - const char* name) const { +Function Object::getPropertyAsFunction(IRuntime& runtime, const char* name) + const { Object obj = getPropertyAsObject(runtime, name); if (!obj.isFunction(runtime)) { - throw JSError(runtime, std::string("getPropertyAsFunction: property '") + - name + "' is " + - kindToString(std::move(obj), &runtime) + - ", expected a Function"); + throw JSError( + runtime, + std::string("getPropertyAsFunction: property '") + name + "' is " + + kindToString(std::move(obj), &runtime) + ", expected a Function"); }; return std::move(obj).getFunction(runtime); } -Array Object::asArray(Runtime& runtime) const& { +Array Object::asArray(IRuntime& runtime) const& { if (!isArray(runtime)) { - throw JSError(runtime, "Object is " + - kindToString(Value(runtime, *this), &runtime) + - ", expected an array"); + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected an array"); } return getArray(runtime); } -Array Object::asArray(Runtime& runtime) && { +Array Object::asArray(IRuntime& runtime) && { if (!isArray(runtime)) { - throw JSError(runtime, "Object is " + - kindToString(Value(runtime, *this), &runtime) + - ", expected an array"); + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected an array"); } return std::move(*this).getArray(runtime); } -Function Object::asFunction(Runtime& runtime) const& { +Function Object::asFunction(IRuntime& runtime) const& { if (!isFunction(runtime)) { - throw JSError(runtime, "Object is " + - kindToString(Value(runtime, *this), &runtime) + - ", expected a function"); + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected a function"); } return getFunction(runtime); } -Function Object::asFunction(Runtime& runtime) && { +Function Object::asFunction(IRuntime& runtime) && { if (!isFunction(runtime)) { - throw JSError(runtime, "Object is " + - kindToString(Value(runtime, *this), &runtime) + - ", expected a function"); + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected a function"); } return std::move(*this).getFunction(runtime); } +TypedArray Object::asTypedArray(IRuntime& runtime) const& { + if (!isTypedArray(runtime)) { + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected a TypedArray"); + } + return getTypedArray(runtime); +} + +Uint8Array Object::asUint8Array(IRuntime& runtime) const& { + if (!isUint8Array(runtime)) { + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected a Uint8Array"); + } + return getUint8Array(runtime); +} + Value::Value(Value&& other) noexcept : Value(other.kind_) { if (kind_ == BooleanKind) { data_.boolean = other.data_.boolean; @@ -366,7 +773,7 @@ Value::Value(Value&& other) noexcept : Value(other.kind_) { other.kind_ = UndefinedKind; } -Value::Value(Runtime& runtime, const Value& other) : Value(other.kind_) { +Value::Value(IRuntime& runtime, const Value& other) : Value(other.kind_) { // data_ is uninitialized, so use placement new to create non-POD // types in it. Any other kind of initialization will call a dtor // first, which is incorrect. @@ -391,7 +798,7 @@ Value::~Value() { } } -bool Value::strictEquals(Runtime& runtime, const Value& a, const Value& b) { +bool Value::strictEquals(IRuntime& runtime, const Value& a, const Value& b) { if (a.kind_ != b.kind_) { return false; } @@ -404,25 +811,29 @@ bool Value::strictEquals(Runtime& runtime, const Value& a, const Value& b) { case NumberKind: return a.data_.number == b.data_.number; case SymbolKind: - return runtime.strictEquals(static_cast(a.data_.pointer), - static_cast(b.data_.pointer)); + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); case BigIntKind: - return runtime.strictEquals(static_cast(a.data_.pointer), - static_cast(b.data_.pointer)); + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); case StringKind: - return runtime.strictEquals(static_cast(a.data_.pointer), - static_cast(b.data_.pointer)); + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); case ObjectKind: - return runtime.strictEquals(static_cast(a.data_.pointer), - static_cast(b.data_.pointer)); + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); } return false; } bool Value::asBool() const { if (!isBool()) { - throw JSINativeException("Value is " + kindToString(*this) + - ", expected a boolean"); + throw JSINativeException( + "Value is " + kindToString(*this) + ", expected a boolean"); } return getBool(); @@ -430,14 +841,14 @@ bool Value::asBool() const { double Value::asNumber() const { if (!isNumber()) { - throw JSINativeException("Value is " + kindToString(*this) + - ", expected a number"); + throw JSINativeException( + "Value is " + kindToString(*this) + ", expected a number"); } return getNumber(); } -Object Value::asObject(Runtime& rt) const& { +Object Value::asObject(IRuntime& rt) const& { if (!isObject()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected an Object"); @@ -446,7 +857,7 @@ Object Value::asObject(Runtime& rt) const& { return getObject(rt); } -Object Value::asObject(Runtime& rt) && { +Object Value::asObject(IRuntime& rt) && { if (!isObject()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected an Object"); @@ -456,7 +867,7 @@ Object Value::asObject(Runtime& rt) && { return static_cast(ptr); } -Symbol Value::asSymbol(Runtime& rt) const& { +Symbol Value::asSymbol(IRuntime& rt) const& { if (!isSymbol()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol"); @@ -465,7 +876,7 @@ Symbol Value::asSymbol(Runtime& rt) const& { return getSymbol(rt); } -Symbol Value::asSymbol(Runtime& rt) && { +Symbol Value::asSymbol(IRuntime& rt) && { if (!isSymbol()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol"); @@ -474,7 +885,7 @@ Symbol Value::asSymbol(Runtime& rt) && { return std::move(*this).getSymbol(rt); } -BigInt Value::asBigInt(Runtime& rt) const& { +BigInt Value::asBigInt(IRuntime& rt) const& { if (!isBigInt()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt"); @@ -483,7 +894,7 @@ BigInt Value::asBigInt(Runtime& rt) const& { return getBigInt(rt); } -BigInt Value::asBigInt(Runtime& rt) && { +BigInt Value::asBigInt(IRuntime& rt) && { if (!isBigInt()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt"); @@ -492,7 +903,7 @@ BigInt Value::asBigInt(Runtime& rt) && { return std::move(*this).getBigInt(rt); } -String Value::asString(Runtime& rt) const& { +String Value::asString(IRuntime& rt) const& { if (!isString()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a String"); @@ -501,7 +912,7 @@ String Value::asString(Runtime& rt) const& { return getString(rt); } -String Value::asString(Runtime& rt) && { +String Value::asString(IRuntime& rt) && { if (!isString()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a String"); @@ -510,27 +921,28 @@ String Value::asString(Runtime& rt) && { return std::move(*this).getString(rt); } -String Value::toString(Runtime& runtime) const { +String Value::toString(IRuntime& runtime) const { Function toString = runtime.global().getPropertyAsFunction(runtime, "String"); return toString.call(runtime, *this).getString(runtime); } -uint64_t BigInt::asUint64(Runtime& runtime) const { +uint64_t BigInt::asUint64(IRuntime& runtime) const { if (!isUint64(runtime)) { throw JSError(runtime, "Lossy truncation in BigInt64::asUint64"); } return getUint64(runtime); } -int64_t BigInt::asInt64(Runtime& runtime) const { +int64_t BigInt::asInt64(IRuntime& runtime) const { if (!isInt64(runtime)) { throw JSError(runtime, "Lossy truncation in BigInt64::asInt64"); } return getInt64(runtime); } -Array Array::createWithElements(Runtime& rt, - std::initializer_list elements) { +Array Array::createWithElements( + IRuntime& rt, + std::initializer_list elements) { Array result(rt, elements.size()); size_t index = 0; for (const auto& element : elements) { @@ -539,25 +951,32 @@ Array Array::createWithElements(Runtime& rt, return result; } -std::vector HostObject::getPropertyNames(Runtime&) { return {}; } +std::vector HostObject::getPropertyNames(Runtime&) { + return {}; +} -Runtime::ScopeState* Runtime::pushScope() { return nullptr; } +Runtime::ScopeState* Runtime::pushScope() { + return nullptr; +} void Runtime::popScope(ScopeState*) {} -JSError::JSError(Runtime& rt, Value&& value) { setValue(rt, std::move(value)); } +JSError::JSError(IRuntime& rt, Value&& value) { + setValue(rt, std::move(value)); +} -JSError::JSError(Runtime& rt, std::string msg) : message_(std::move(msg)) { +JSError::JSError(IRuntime& rt, std::string msg) : message_(std::move(msg)) { try { - setValue(rt, callGlobalFunction(rt, "Error", - String::createFromUtf8(rt, message_))); + setValue( + rt, + callGlobalFunction(rt, "Error", String::createFromUtf8(rt, message_))); } catch (const JSIException& ex) { message_ = std::string(ex.what()) + " (while raising " + message_ + ")"; setValue(rt, String::createFromUtf8(rt, message_)); } } -JSError::JSError(Runtime& rt, std::string msg, std::string stack) +JSError::JSError(IRuntime& rt, std::string msg, std::string stack) : message_(std::move(msg)), stack_(std::move(stack)) { try { Object e(rt); @@ -569,7 +988,7 @@ JSError::JSError(Runtime& rt, std::string msg, std::string stack) } } -JSError::JSError(std::string what, Runtime& rt, Value&& value) +JSError::JSError(std::string what, IRuntime& rt, Value&& value) : JSIException(std::move(what)) { setValue(rt, std::move(value)); } @@ -580,7 +999,41 @@ JSError::JSError(Value&& value, std::string message, std::string stack) message_(std::move(message)), stack_(std::move(stack)) {} -void JSError::setValue(Runtime& rt, Value&& value) { +JSError JSError::createEvalError(IRuntime& rt, const std::string& message) { + return JSError( + message, rt, rt.createEvalError(String::createFromUtf8(rt, message))); +} + +JSError JSError::createRangeError(IRuntime& rt, const std::string& message) { + return JSError( + message, rt, rt.createRangeError(String::createFromUtf8(rt, message))); +} + +JSError JSError::createReferenceError( + IRuntime& rt, + const std::string& message) { + return JSError( + message, + rt, + rt.createReferenceError(String::createFromUtf8(rt, message))); +} + +JSError JSError::createSyntaxError(IRuntime& rt, const std::string& message) { + return JSError( + message, rt, rt.createSyntaxError(String::createFromUtf8(rt, message))); +} + +JSError JSError::createTypeError(IRuntime& rt, const std::string& message) { + return JSError( + message, rt, rt.createTypeError(String::createFromUtf8(rt, message))); +} + +JSError JSError::createURIError(IRuntime& rt, const std::string& message) { + return JSError( + message, rt, rt.createURIError(String::createFromUtf8(rt, message))); +} + +void JSError::setValue(IRuntime& rt, Value&& value) { value_ = std::make_shared(std::move(value)); if ((message_.empty() || stack_.empty()) && value_->isObject()) { @@ -599,7 +1052,7 @@ void JSError::setValue(Runtime& rt, Value&& value) { } } catch (const JSIException& ex) { message_ = std::string("[Exception while creating message string: ") + - ex.what() + "]"; + ex.what() + "]"; } } @@ -616,7 +1069,7 @@ void JSError::setValue(Runtime& rt, Value&& value) { } } catch (const JSIException& ex) { message_ = std::string("[Exception while creating stack string: ") + - ex.what() + "]"; + ex.what() + "]"; } } } @@ -635,7 +1088,7 @@ void JSError::setValue(Runtime& rt, Value&& value) { } } catch (const JSIException& ex) { message_ = std::string("[Exception while creating message string: ") + - ex.what() + "]"; + ex.what() + "]"; } } @@ -654,5 +1107,5 @@ JSINativeException::~JSINativeException() {} JSError::~JSError() {} -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/android-jsi/platforms/android/cpp/jsi/jsi.h b/packages/android-jsi/platforms/android/cpp/jsi/jsi.h index 7d760ef5e..bf98028fb 100644 --- a/packages/android-jsi/platforms/android/cpp/jsi/jsi.h +++ b/packages/android-jsi/platforms/android/cpp/jsi/jsi.h @@ -8,6 +8,8 @@ #pragma once #include +#include +#include #include #include #include @@ -21,16 +23,117 @@ #define JSI_EXPORT __declspec(dllexport) #else #define JSI_EXPORT -#endif // CREATE_SHARED_LIBRARY -#else // _MSC_VER +#endif // CREATE_SHARED_LIBRARY +#else // _MSC_VER #define JSI_EXPORT __attribute__((visibility("default"))) -#endif // _MSC_VER -#endif // !defined(JSI_EXPORT) +#endif // _MSC_VER +#endif // !defined(JSI_EXPORT) class FBJSRuntime; namespace facebook { namespace jsi { +/// UUID version 1 implementation. This should be constructed with constant +/// arguments to identify fixed UUIDs. +class JSI_EXPORT UUID { + public: + // Construct from raw parts + constexpr UUID( + uint32_t timeLow, + uint16_t timeMid, + uint16_t timeHighAndVersion, + uint16_t variantAndClockSeq, + uint64_t node) + : high( + ((uint64_t)(timeLow) << 32) | ((uint64_t)(timeMid) << 16) | + ((uint64_t)(timeHighAndVersion))), + low(((uint64_t)(variantAndClockSeq) << 48) | node) {} + + // Default constructor (zero UUID) + constexpr UUID() : high(0), low(0) {} + + constexpr UUID(const UUID&) = default; + constexpr UUID& operator=(const UUID&) = default; + + constexpr bool operator==(const UUID& other) const { + return high == other.high && low == other.low; + } + constexpr bool operator!=(const UUID& other) const { + return !(*this == other); + } + + // Ordering (for std::map, sorting, etc.) + constexpr bool operator<(const UUID& other) const { + return (high < other.high) || (high == other.high && low < other.low); + } + + // Hash support for UUID (for unordered_map compatibility) + struct Hash { + std::size_t operator()(const UUID& uuid) const noexcept { + return std::hash{}(uuid.high) ^ + (std::hash{}(uuid.low) << 1); + } + }; + + // UUID format: 8-4-4-4-12 + std::string toString() const { + std::string buffer(36, ' '); + std::snprintf( + buffer.data(), + buffer.size() + 1, + "%08x-%04x-%04x-%04x-%012llx", + getTimeLow(), + getTimeMid(), + getTimeHighAndVersion(), + getVariantAndClockSeq(), + (unsigned long long)getNode()); + return buffer; + } + + constexpr uint32_t getTimeLow() const { + return (uint32_t)(high >> 32); + } + + constexpr uint16_t getTimeMid() const { + return (uint16_t)(high >> 16); + } + + constexpr uint16_t getTimeHighAndVersion() const { + return (uint16_t)high; + } + + constexpr uint16_t getVariantAndClockSeq() const { + return (uint16_t)(low >> 48); + } + + constexpr uint64_t getNode() const { + return low & 0xFFFFFFFFFFFF; + } + + private: + uint64_t high; + uint64_t low; +}; + +/// Base interface that all JSI interfaces inherit from. Users should not try to +/// manipulate this base type directly, and should use castInterface to get the +/// appropriate subtype. +struct JSI_EXPORT ICast { + /// If the current object can be cast into the interface specified by \p + /// interfaceUUID, return a pointer to the object. Otherwise, return a null + /// pointer. + /// The returned interface has the same lifetime as the underlying object. It + /// does not need to be released when not needed. + virtual ICast* castInterface(const UUID& interfaceUUID) = 0; + + protected: + /// Interfaces are not destructible, thus the destructor is intentionally + /// protected to prevent delete calls on the interface. + /// Additionally, the destructor is non-virtual to reduce the vtable + /// complexity from inheritance. + ~ICast() = default; +}; + /// Base class for buffers of data or bytecode that need to be passed to the /// runtime. The buffer is expected to be fully immutable, so the result of /// size(), data(), and the contents of the pointer returned by data() must not @@ -45,7 +148,9 @@ class JSI_EXPORT Buffer { class JSI_EXPORT StringBuffer : public Buffer { public: StringBuffer(std::string s) : s_(std::move(s)) {} - size_t size() const override { return s_.size(); } + size_t size() const override { + return s_.size(); + } const uint8_t* data() const override { return reinterpret_cast(s_.data()); } @@ -78,6 +183,7 @@ class JSI_EXPORT PreparedJavaScript { virtual ~PreparedJavaScript() = 0; }; +class IRuntime; class Runtime; class Pointer; class PropNameID; @@ -94,6 +200,8 @@ class Instrumentation; class Scope; class JSIException; class JSError; +class TypedArray; +class Uint8Array; /// A function which has this type can be registered as a function /// callable from JavaScript using Function::createFromHostFunction(). @@ -106,8 +214,8 @@ class JSError; /// HostFunctions may or may not be called in strict mode; that is `thisVal` /// can be any value - it will not necessarily be coerced to an object or /// or set to the global object. -using HostFunctionType = std::function; +using HostFunctionType = std::function< + Value(Runtime& rt, const Value& thisVal, const Value* args, size_t count)>; /// An object which implements this interface can be registered as an /// Object with the JS runtime. @@ -117,7 +225,7 @@ class JSI_EXPORT HostObject { // object. (This may be as late as when the Runtime is shut down.) // You have no control over which thread it is called on. This will // be called from inside the GC, so it is unsafe to do any VM - // operations which require a Runtime&. Derived classes' dtors + // operations which require a IRuntime&. Derived classes' dtors // should also avoid doing anything expensive. Calling the dtor on // a jsi object is explicitly ok. If you want to do JS operations, // or any nontrivial work, you should add it to a work queue, and @@ -149,27 +257,92 @@ class JSI_EXPORT NativeState { virtual ~NativeState(); }; -/// Represents a JS runtime. Movable, but not copyable. Note that -/// this object may not be thread-aware, but cannot be used safely from -/// multiple threads at once. The application is responsible for -/// ensuring that it is used safely. This could mean using the -/// Runtime from a single thread, using a mutex, doing all work on a -/// serial queue, etc. This restriction applies to the methods of -/// this class, and any method in the API which take a Runtime& as an -/// argument. Destructors (all but ~Scope), operators, or other methods -/// which do not take Runtime& as an argument are safe to call from any -/// thread, but it is still forbidden to make write operations on a single -/// instance of any class from more than one thread. In addition, to -/// make shutdown safe, destruction of objects associated with the Runtime -/// must be destroyed before the Runtime is destroyed, or from the -/// destructor of a managed HostObject or HostFunction. Informally, this -/// means that the main source of unsafe behavior is to hold a jsi object -/// in a non-Runtime-managed object, and not clean it up before the Runtime -/// is shut down. If your lifecycle is such that avoiding this is hard, -/// you will probably need to do use your own locks. -class JSI_EXPORT Runtime { +// JSI_UNSTABLE gates features that will be released with a Hermes version in +// the future. Until released, these features may be subject to change. After +// release, these features will be moved out of JSI_UNSTABLE and become frozen. +#ifdef JSI_UNSTABLE +/// Opaque class that is used to store serialized object from a runtime. The +/// lifetime of this object is orthogonal to the original runtime object, and +/// may outlive the original object. +class JSI_EXPORT Serialized { + public: + /// Uses \p secretAddr to validate if the Serialized data is supported. If so, + /// return the pointer to the underlying serialized data. Otherwise, return a + /// nullptr. This should be used by the runtime to deserialize the data. + virtual void* getPrivate(const void* secretAddr) = 0; + virtual ~Serialized(); +}; + +/// Provides a set of APIs that allows copying objects between different +/// runtime instances. The runtimes instances must be of the same type. As an +/// example, a serialized object from Hermes runtime may only be deserialized by +/// another Hermes runtime. +class JSI_EXPORT ISerialization : public ICast { public: - virtual ~Runtime(); + static constexpr jsi::UUID uuid{ + 0xd40fe0ec, + 0xa47c, + 0x42c9, + 0x8c09, + 0x661aeab832d8}; + + /// Serializes the given Value \p value using the structured clone algorithm. + /// It returns a shared pointer of an opaque Serialized object that can be + /// deserialized multiple times. The lifetime of the Serialized object is not + /// tied to the lifetime of the original object. + virtual std::shared_ptr serialize(const Value& value) = 0; + + /// Given a Serialized object provided by \p serialized, deserialize it using + /// the structured clone algorithm into a JS value in the current runtime. + /// Returns the deserialized JS value. + virtual Value deserialize(const std::shared_ptr& serialized) = 0; + + /// Serializes the given jsi::Value \p value using the structured clone + /// algorithm. \p transferList must be a JS Array. Given the length property + /// of \p transferList, this API will transfer everything at index [0, length + /// - 1] to the serialized object. The transferred values will no longer be + /// usable in the original runtime. It returns a unique pointer of an opaque + /// Serialized object that can be deserialized once only by + /// deserializeWithTransfer. The lifetime of the Serialized object is not tied + /// to the lifetime of the original object. + virtual std::unique_ptr serializeWithTransfer( + const Value& value, + const Array& transferList) = 0; + + /// Using the structure clone algorithm, deserialize the object provided by \p + /// serialized into a JS value in the current runtime. \p serialized must be + /// created by serializeWithTransfer. If the current runtime does not support + /// the serialization scheme in \p serialized, then this method will throw and + /// \p serialized will remain unmodified. Otherwise, this will consume the + /// serialized data entirely and make the serialized objects in the current + /// runtime. Any transferred values in the serialized object will be owned by + /// the current runtime. + /// This method returns an Array containing the deserialized values, where + /// the first element is the value passed into serializeWithTransfer, + /// followed by all transferred values. + virtual Array deserializeWithTransfer( + std::unique_ptr& serialized) = 0; + + protected: + ~ISerialization() = default; +}; + +#endif // JSI_UNSTABLE + +/// An interface that provides various functionalities of the JS runtime. +/// The APIs must not be called from multiple threads concurrently. It is the +/// user's responsibility ensure thread safety when using IRuntime. +/// Users should cast their runtime to IRuntime to access these APIs. However, +/// for backward compatibility, these APIs are also accessible via the Runtime +/// object directly. +class JSI_EXPORT IRuntime : public ICast { + public: + static constexpr jsi::UUID uuid{ + 0xc2e8e22e, + 0xd7a6, + 0x11f0, + 0x8de9, + 0x0242ac120002}; /// Evaluates the given JavaScript \c buffer. \c sourceURL is used /// to annotate the stack trace if there is an exception. The @@ -181,8 +354,9 @@ class JSI_EXPORT Runtime { /// through the JSI API. For example, it will be much slower to use this to /// call a global function than using the JSI APIs to read the function /// property from the global object and then calling it explicitly. - virtual Value evaluateJavaScript(const std::shared_ptr& buffer, - const std::string& sourceURL) = 0; + virtual Value evaluateJavaScript( + const std::shared_ptr& buffer, + const std::string& sourceURL) = 0; /// Prepares to evaluate the given JavaScript \c buffer by processing it into /// a form optimized for execution. This may include pre-parsing, compiling, @@ -196,7 +370,8 @@ class JSI_EXPORT Runtime { /// As with evaluateJavaScript(), using JavaScript code should be avoided /// when the JSI API is sufficient. virtual std::shared_ptr prepareJavaScript( - const std::shared_ptr& buffer, std::string sourceURL) = 0; + const std::shared_ptr& buffer, + std::string sourceURL) = 0; /// Evaluates a PreparedJavaScript. If evaluation causes an error, a /// JSIException will be thrown. @@ -261,22 +436,31 @@ class JSI_EXPORT Runtime { /// \return an interface to extract metrics from this \c Runtime. The default /// implementation of this function returns an \c Instrumentation instance /// which returns no metrics. - virtual Instrumentation& instrumentation(); - - protected: - friend class Pointer; - friend class PropNameID; - friend class Symbol; - friend class BigInt; - friend class String; - friend class Object; - friend class WeakObject; - friend class Array; - friend class ArrayBuffer; - friend class Function; - friend class Value; - friend class Scope; - friend class JSError; + virtual Instrumentation& instrumentation() = 0; + + /// Stores the pointer \p data with the \p dataUUID in the runtime. This can + /// be used to store some custom data within the runtime. When the runtime is + /// destroyed, or if an entry at an existing key is overwritten, the runtime + /// will release its ownership of the held object. + virtual void setRuntimeData( + const UUID& dataUUID, + const std::shared_ptr& data) = 0; + /// Returns the data associated with the \p uuid in the runtime. If there's no + /// data associated with the uuid, return a null pointer. + virtual std::shared_ptr getRuntimeData(const UUID& dataUUID) = 0; + + /// Stores the pointer \p data with the \p uuid in the runtime. This can be + /// used to store some custom data within the runtime. When the runtime is + /// destroyed, or if an entry at an existing key is overwritten, the runtime + /// will release its ownership by calling \p deleter. + virtual void setRuntimeDataImpl( + const UUID& dataUUID, + const void* data, + void (*deleter)(const void* data)) = 0; + + /// Returns the data associated with the \p uuid in the runtime. If there's no + /// data associated with the uuid, return a null pointer. + virtual const void* getRuntimeDataImpl(const UUID& dataUUID) = 0; // Potential optimization: avoid the cloneFoo() virtual dispatch, // and instead just fix the number of fields, and copy them, since @@ -284,22 +468,27 @@ class JSI_EXPORT Runtime { // rvalue arguments/methods would also reduce the number of clones. struct PointerValue { - virtual void invalidate() = 0; + virtual void invalidate() noexcept = 0; protected: virtual ~PointerValue() = default; }; - virtual PointerValue* cloneSymbol(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneBigInt(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneString(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneObject(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* clonePropNameID(const Runtime::PointerValue* pv) = 0; - - virtual PropNameID createPropNameIDFromAscii(const char* str, - size_t length) = 0; - virtual PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, - size_t length) = 0; + virtual PointerValue* cloneSymbol(const IRuntime::PointerValue* pv) = 0; + virtual PointerValue* cloneBigInt(const IRuntime::PointerValue* pv) = 0; + virtual PointerValue* cloneString(const IRuntime::PointerValue* pv) = 0; + virtual PointerValue* cloneObject(const IRuntime::PointerValue* pv) = 0; + virtual PointerValue* clonePropNameID(const IRuntime::PointerValue* pv) = 0; + + virtual PropNameID createPropNameIDFromAscii( + const char* str, + size_t length) = 0; + virtual PropNameID createPropNameIDFromUtf8( + const uint8_t* utf8, + size_t length) = 0; + virtual PropNameID createPropNameIDFromUtf16( + const char16_t* utf16, + size_t length) = 0; virtual PropNameID createPropNameIDFromString(const String& str) = 0; virtual PropNameID createPropNameIDFromSymbol(const Symbol& sym) = 0; virtual std::string utf8(const PropNameID&) = 0; @@ -316,11 +505,14 @@ class JSI_EXPORT Runtime { virtual String createStringFromAscii(const char* str, size_t length) = 0; virtual String createStringFromUtf8(const uint8_t* utf8, size_t length) = 0; + virtual String createStringFromUtf16( + const char16_t* utf16, + size_t length) = 0; virtual std::string utf8(const String&) = 0; // \return a \c Value created from a utf8-encoded JSON string. The default // implementation creates a \c String and invokes JSON.parse. - virtual Value createValueFromJsonUtf8(const uint8_t* json, size_t length); + virtual Value createValueFromJsonUtf8(const uint8_t* json, size_t length) = 0; virtual Object createObject() = 0; virtual Object createObject(std::shared_ptr ho) = 0; @@ -328,27 +520,40 @@ class JSI_EXPORT Runtime { virtual HostFunctionType& getHostFunction(const jsi::Function&) = 0; // Creates a new Object with the custom prototype - virtual Object createObjectWithPrototype(const Value& prototype); + virtual Object createObjectWithPrototype(const Value& prototype) = 0; virtual bool hasNativeState(const jsi::Object&) = 0; virtual std::shared_ptr getNativeState(const jsi::Object&) = 0; - virtual void setNativeState(const jsi::Object&, - std::shared_ptr state) = 0; + virtual void setNativeState( + const jsi::Object&, + std::shared_ptr state) = 0; - virtual void setPrototypeOf(const Object& object, const Value& prototype); - virtual Value getPrototypeOf(const Object& object); + virtual void setPrototypeOf(const Object& object, const Value& prototype) = 0; + virtual Value getPrototypeOf(const Object& object) = 0; virtual Value getProperty(const Object&, const PropNameID& name) = 0; virtual Value getProperty(const Object&, const String& name) = 0; + virtual Value getProperty(const Object&, const Value& name) = 0; virtual bool hasProperty(const Object&, const PropNameID& name) = 0; virtual bool hasProperty(const Object&, const String& name) = 0; - virtual void setPropertyValue(const Object&, const PropNameID& name, - const Value& value) = 0; - virtual void setPropertyValue(const Object&, const String& name, - const Value& value) = 0; + virtual bool hasProperty(const Object&, const Value& name) = 0; + virtual void setPropertyValue( + const Object&, + const PropNameID& name, + const Value& value) = 0; + virtual void + setPropertyValue(const Object&, const String& name, const Value& value) = 0; + virtual void + setPropertyValue(const Object&, const Value& name, const Value& value) = 0; + + virtual void deleteProperty(const Object&, const PropNameID& name) = 0; + virtual void deleteProperty(const Object&, const String& name) = 0; + virtual void deleteProperty(const Object&, const Value& name) = 0; virtual bool isArray(const Object&) const = 0; virtual bool isArrayBuffer(const Object&) const = 0; + virtual bool isTypedArray(const Object&) const = 0; + virtual bool isUint8Array(const Object&) const = 0; virtual bool isFunction(const Object&) const = 0; virtual bool isHostObject(const jsi::Object&) const = 0; virtual bool isHostFunction(const jsi::Function&) const = 0; @@ -363,22 +568,28 @@ class JSI_EXPORT Runtime { virtual size_t size(const Array&) = 0; virtual size_t size(const ArrayBuffer&) = 0; virtual uint8_t* data(const ArrayBuffer&) = 0; + virtual bool detached(const ArrayBuffer&) = 0; virtual Value getValueAtIndex(const Array&, size_t i) = 0; - virtual void setValueAtIndexImpl(const Array&, size_t i, - const Value& value) = 0; - - virtual Function createFunctionFromHostFunction(const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) = 0; - virtual Value call(const Function&, const Value& jsThis, const Value* args, - size_t count) = 0; - virtual Value callAsConstructor(const Function&, const Value* args, - size_t count) = 0; + virtual void + setValueAtIndexImpl(const Array&, size_t i, const Value& value) = 0; + virtual size_t push(const Array&, const Value*, size_t) = 0; + + virtual Function createFunctionFromHostFunction( + const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) = 0; + virtual Value call( + const Function&, + const Value& jsThis, + const Value* args, + size_t count) = 0; + virtual Value + callAsConstructor(const Function&, const Value* args, size_t count) = 0; // Private data for managing scopes. struct ScopeState; - virtual ScopeState* pushScope(); - virtual void popScope(ScopeState*); + virtual ScopeState* pushScope() = 0; + virtual void popScope(ScopeState*) = 0; virtual bool strictEquals(const Symbol& a, const Symbol& b) const = 0; virtual bool strictEquals(const BigInt& a, const BigInt& b) const = 0; @@ -388,11 +599,12 @@ class JSI_EXPORT Runtime { virtual bool instanceOf(const Object& o, const Function& f) = 0; /// See Object::setExternalMemoryPressure. - virtual void setExternalMemoryPressure(const jsi::Object& obj, - size_t amount) = 0; + virtual void setExternalMemoryPressure( + const jsi::Object& obj, + size_t amount) = 0; - virtual std::u16string utf16(const String& str); - virtual std::u16string utf16(const PropNameID& sym); + virtual std::u16string utf16(const String& str) = 0; + virtual std::u16string utf16(const PropNameID& sym) = 0; /// Invokes the provided callback \p cb with the String content in \p str. /// The callback must take in three arguments: bool ascii, const void* data, @@ -403,9 +615,10 @@ class JSI_EXPORT Runtime { /// multiple times, with a different format on each invocation. The callback /// must not access runtime functionality, as any operation on the runtime may /// invalidate the data pointers. - virtual void getStringData(const jsi::String& str, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)); + virtual void getStringData( + const jsi::String& str, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) = 0; /// Invokes the provided callback \p cb with the PropNameID content in \p sym. /// The callback must take in three arguments: bool ascii, const void* data, @@ -416,9 +629,184 @@ class JSI_EXPORT Runtime { /// multiple times, with a different format on each invocation. The callback /// must not access runtime functionality, as any operation on the runtime may /// invalidate the data pointers. - virtual void getPropNameIdData(const jsi::PropNameID& sym, void* ctx, - void (*cb)(void* ctx, bool ascii, - const void* data, size_t num)); + virtual void getPropNameIdData( + const jsi::PropNameID& sym, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) = 0; + + /// If possible, returns the MutableBuffer representing \p arrayBuffer's + /// underlying data, else return a nullptr. Importantly, the returned + /// MutableBuffer directly points to \p arrayBuffer's data instead of copying + /// the data over. The data's lifetime is valid for the lifetime of + /// MutableBuffer, which is orthogonal from \p arrayBuffer. + virtual std::shared_ptr tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) = 0; + + /// \return the underlying buffer of the \p typedArray. + virtual ArrayBuffer buffer(const TypedArray& typedArray) = 0; + /// \return the 'byteOffset' property of the \p typedArray. + virtual size_t byteOffset(const TypedArray& typedArray) = 0; + /// \return the 'byteLength' property of the \p typedArray. + virtual size_t byteLength(const TypedArray& typedArray) = 0; + /// \return the 'length; property of the \p typedArray. + virtual size_t length(const TypedArray& typedArray) = 0; + + /// Create a JS UInt8Array with length \p length. + virtual Uint8Array createUint8Array(size_t length) = 0; + /// Create a JS UInt8Array using the ArrayBuffer \p buffer starting at byte + /// offset \p offset and length \p length. + virtual Uint8Array + createUint8Array(const ArrayBuffer& buffer, size_t offset, size_t length) = 0; + + /// Create a new Error object with the message property set to \p message. + virtual Value createError(const String& msg) = 0; + /// Create a new EvalError object with the message property set to \p message. + virtual Value createEvalError(const String& msg) = 0; + /// Create a new RangeError object with the message property set to \p + /// message. + virtual Value createRangeError(const String& msg) = 0; + /// Create a new ReferenceError object with the message property set to \p + /// message. + virtual Value createReferenceError(const String& msg) = 0; + /// Create a new SyntaxError object with the message property set to \p + /// message. + virtual Value createSyntaxError(const String& msg) = 0; + /// Create a new TypeError object with the message property set to \p message. + virtual Value createTypeError(const String& msg) = 0; + /// Create a new URIError object with the message property set to \p message. + virtual Value createURIError(const String& msg) = 0; + + /// Returns the number of code units in the string, equivalent to 'length' + /// property of a JS string. + virtual size_t length(const String& str) = 0; + + protected: + virtual ~IRuntime() = default; +}; + +/// Represents a JS runtime. Movable, but not copyable. Note that +/// this object may not be thread-aware, but cannot be used safely from +/// multiple threads at once. The application is responsible for +/// ensuring that it is used safely. This could mean using the +/// Runtime from a single thread, using a mutex, doing all work on a +/// serial queue, etc. This restriction applies to the methods of +/// this class, and any method in the API which take a Runtime& as an +/// argument. Destructors (all but ~Scope), operators, or other methods +/// which do not take Runtime& as an argument are safe to call from any +/// thread, but it is still forbidden to make write operations on a single +/// instance of any class from more than one thread. In addition, to +/// make shutdown safe, destruction of objects associated with the Runtime +/// must be destroyed before the Runtime is destroyed, or from the +/// destructor of a managed HostObject or HostFunction. Informally, this +/// means that the main source of unsafe behavior is to hold a jsi object +/// in a non-Runtime-managed object, and not clean it up before the Runtime +/// is shut down. If your lifecycle is such that avoiding this is hard, +/// you will probably need to do use your own locks. +class JSI_EXPORT Runtime : public IRuntime { + public: + virtual ~Runtime() override; + + using IRuntime::getProperty; + using IRuntime::hasProperty; + using IRuntime::setPropertyValue; + ICast* castInterface(const UUID& uuid) override; + + Instrumentation& instrumentation() override; + + /// Stores the pointer \p data with the \p uuid in the runtime. This can be + /// used to store some custom data within the runtime. When the runtime is + /// destroyed, or if an entry at an existing key is overwritten, the runtime + /// will release its ownership of the held object. + void setRuntimeData(const UUID& uuid, const std::shared_ptr& data) + override; + + /// Returns the data associated with the \p uuid in the runtime. If there's no + /// data associated with the uuid, return a null pointer. + std::shared_ptr getRuntimeData(const UUID& uuid) override; + + Value getProperty(const Object&, const Value& name) override; + bool hasProperty(const Object&, const Value& name) override; + void setPropertyValue(const Object&, const Value& name, const Value& value) + override; + void deleteProperty(const Object&, const PropNameID& name) override; + void deleteProperty(const Object&, const String& name) override; + void deleteProperty(const Object&, const Value& name) override; + + void setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)) override; + const void* getRuntimeDataImpl(const UUID& uuid) override; + + PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) + override; + String createStringFromUtf16(const char16_t* utf16, size_t length) override; + + Value createValueFromJsonUtf8(const uint8_t* json, size_t length) override; + + Object createObjectWithPrototype(const Value& prototype) override; + void setPrototypeOf(const Object& object, const Value& prototype) override; + Value getPrototypeOf(const Object& object) override; + + ScopeState* pushScope() override; + void popScope(ScopeState*) override; + + std::u16string utf16(const String& str) override; + std::u16string utf16(const PropNameID& sym) override; + + void getStringData( + const jsi::String& str, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) override; + void getPropNameIdData( + const jsi::PropNameID& sym, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) override; + + size_t push(const Array&, const Value*, size_t) override; + + std::shared_ptr tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) override; + + bool detached(const ArrayBuffer&) override; + + ArrayBuffer buffer(const TypedArray& typedArray) override; + size_t byteOffset(const TypedArray& typedArray) override; + size_t byteLength(const TypedArray& typedArray) override; + size_t length(const TypedArray& typedArray) override; + + bool isTypedArray(const Object&) const override; + bool isUint8Array(const Object&) const override; + Uint8Array createUint8Array(size_t length) override; + Uint8Array createUint8Array( + const ArrayBuffer& buffer, + size_t offset, + size_t length) override; + + Value createError(const String& msg) override; + Value createEvalError(const String& msg) override; + Value createRangeError(const String& msg) override; + Value createReferenceError(const String& msg) override; + Value createSyntaxError(const String& msg) override; + Value createTypeError(const String& msg) override; + Value createURIError(const String& msg) override; + + size_t length(const String& str) override; + + protected: + friend class Pointer; + friend class PropNameID; + friend class Symbol; + friend class BigInt; + friend class String; + friend class Object; + friend class WeakObject; + friend class Array; + friend class ArrayBuffer; + friend class Function; + friend class Value; + friend class Scope; + friend class JSError; // These exist so derived classes can access the private parts of // Value, Symbol, String, and Object, which are all friends of Runtime. @@ -461,7 +849,7 @@ class JSI_EXPORT PropNameID : public Pointer { public: using Pointer::Pointer; - PropNameID(Runtime& runtime, const PropNameID& other) + PropNameID(IRuntime& runtime, const PropNameID& other) : Pointer(runtime.clonePropNameID(other.ptr_)) {} PropNameID(PropNameID&& other) = default; @@ -469,59 +857,79 @@ class JSI_EXPORT PropNameID : public Pointer { /// Create a JS property name id from ascii values. The data is /// copied. - static PropNameID forAscii(Runtime& runtime, const char* str, size_t length) { + static PropNameID + forAscii(IRuntime& runtime, const char* str, size_t length) { return runtime.createPropNameIDFromAscii(str, length); } /// Create a property name id from a nul-terminated C ascii name. The data is /// copied. - static PropNameID forAscii(Runtime& runtime, const char* str) { + static PropNameID forAscii(IRuntime& runtime, const char* str) { return forAscii(runtime, str, strlen(str)); } /// Create a PropNameID from a C++ string. The string is copied. - static PropNameID forAscii(Runtime& runtime, const std::string& str) { + static PropNameID forAscii(IRuntime& runtime, const std::string& str) { return forAscii(runtime, str.c_str(), str.size()); } /// Create a PropNameID from utf8 values. The data is copied. /// Results are undefined if \p utf8 contains invalid code points. - static PropNameID forUtf8(Runtime& runtime, const uint8_t* utf8, - size_t length) { + static PropNameID + forUtf8(IRuntime& runtime, const uint8_t* utf8, size_t length) { return runtime.createPropNameIDFromUtf8(utf8, length); } /// Create a PropNameID from utf8-encoded octets stored in a /// std::string. The string data is transformed and copied. /// Results are undefined if \p utf8 contains invalid code points. - static PropNameID forUtf8(Runtime& runtime, const std::string& utf8) { + static PropNameID forUtf8(IRuntime& runtime, const std::string& utf8) { return runtime.createPropNameIDFromUtf8( reinterpret_cast(utf8.data()), utf8.size()); } + /// Given a series of UTF-16 encoded code units, create a PropNameId. The + /// input may contain unpaired surrogates, which will be interpreted as a code + /// point of the same value. + static PropNameID + forUtf16(IRuntime& runtime, const char16_t* utf16, size_t length) { + return runtime.createPropNameIDFromUtf16(utf16, length); + } + + /// Given a series of UTF-16 encoded code units stored inside std::u16string, + /// create a PropNameId. The input may contain unpaired surrogates, which + /// will be interpreted as a code point of the same value. + static PropNameID forUtf16(IRuntime& runtime, const std::u16string& str) { + return runtime.createPropNameIDFromUtf16(str.data(), str.size()); + } + /// Create a PropNameID from a JS string. - static PropNameID forString(Runtime& runtime, const jsi::String& str) { + static PropNameID forString(IRuntime& runtime, const jsi::String& str) { return runtime.createPropNameIDFromString(str); } /// Create a PropNameID from a JS symbol. - static PropNameID forSymbol(Runtime& runtime, const jsi::Symbol& sym) { + static PropNameID forSymbol(IRuntime& runtime, const jsi::Symbol& sym) { return runtime.createPropNameIDFromSymbol(sym); } // Creates a vector of PropNameIDs constructed from given arguments. template - static std::vector names(Runtime& runtime, Args&&... args); + static std::vector names(IRuntime& runtime, Args&&... args); // Creates a vector of given PropNameIDs. template - static std::vector names(PropNameID(&&propertyNames)[N]); + static std::vector names(PropNameID (&&propertyNames)[N]); /// Copies the data in a PropNameID as utf8 into a C++ string. - std::string utf8(Runtime& runtime) const { return runtime.utf8(*this); } + std::string utf8(IRuntime& runtime) const { + return runtime.utf8(*this); + } /// Copies the data in a PropNameID as utf16 into a C++ string. - std::u16string utf16(Runtime& runtime) const { return runtime.utf16(*this); } + std::u16string utf16(IRuntime& runtime) const { + return runtime.utf16(*this); + } /// Invokes the user provided callback to process the content in PropNameId. /// The callback must take in three arguments: bool ascii, const void* data, @@ -532,15 +940,17 @@ class JSI_EXPORT PropNameID : public Pointer { /// callback must not access runtime functionality, as any operation on the /// runtime may invalidate the data pointers. template - void getPropNameIdData(Runtime& runtime, CB& cb) const { + void getPropNameIdData(IRuntime& runtime, CB& cb) const { runtime.getPropNameIdData( *this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) { (*((CB*)ctx))(ascii, data, num); }); } - static bool compare(Runtime& runtime, const jsi::PropNameID& a, - const jsi::PropNameID& b) { + static bool compare( + IRuntime& runtime, + const jsi::PropNameID& a, + const jsi::PropNameID& b) { return runtime.compare(a, b); } @@ -561,13 +971,14 @@ class JSI_EXPORT Symbol : public Pointer { Symbol& operator=(Symbol&& other) = default; /// \return whether a and b refer to the same symbol. - static bool strictEquals(Runtime& runtime, const Symbol& a, const Symbol& b) { + static bool + strictEquals(IRuntime& runtime, const Symbol& a, const Symbol& b) { return runtime.strictEquals(a, b); } /// Converts a Symbol into a C++ string as JS .toString would. The output /// will look like \c Symbol(description) . - std::string toString(Runtime& runtime) const { + std::string toString(IRuntime& runtime) const { return runtime.symbolToString(*this); } @@ -584,45 +995,52 @@ class JSI_EXPORT BigInt : public Pointer { BigInt& operator=(BigInt&& other) = default; /// Create a BigInt representing the signed 64-bit \p value. - static BigInt fromInt64(Runtime& runtime, int64_t value) { + static BigInt fromInt64(IRuntime& runtime, int64_t value) { return runtime.createBigIntFromInt64(value); } /// Create a BigInt representing the unsigned 64-bit \p value. - static BigInt fromUint64(Runtime& runtime, uint64_t value) { + static BigInt fromUint64(IRuntime& runtime, uint64_t value) { return runtime.createBigIntFromUint64(value); } /// \return whether a === b. - static bool strictEquals(Runtime& runtime, const BigInt& a, const BigInt& b) { + static bool + strictEquals(IRuntime& runtime, const BigInt& a, const BigInt& b) { return runtime.strictEquals(a, b); } /// \returns This bigint truncated to a signed 64-bit integer. - int64_t getInt64(Runtime& runtime) const { return runtime.truncate(*this); } + int64_t getInt64(IRuntime& runtime) const { + return runtime.truncate(*this); + } /// \returns Whether this bigint can be losslessly converted to int64_t. - bool isInt64(Runtime& runtime) const { return runtime.bigintIsInt64(*this); } + bool isInt64(IRuntime& runtime) const { + return runtime.bigintIsInt64(*this); + } /// \returns This bigint truncated to a signed 64-bit integer. Throws a /// JSIException if the truncation is lossy. - int64_t asInt64(Runtime& runtime) const; + int64_t asInt64(IRuntime& runtime) const; /// \returns This bigint truncated to an unsigned 64-bit integer. - uint64_t getUint64(Runtime& runtime) const { return runtime.truncate(*this); } + uint64_t getUint64(IRuntime& runtime) const { + return runtime.truncate(*this); + } /// \returns Whether this bigint can be losslessly converted to uint64_t. - bool isUint64(Runtime& runtime) const { + bool isUint64(IRuntime& runtime) const { return runtime.bigintIsUint64(*this); } /// \returns This bigint truncated to an unsigned 64-bit integer. Throws a /// JSIException if the truncation is lossy. - uint64_t asUint64(Runtime& runtime) const; + uint64_t asUint64(IRuntime& runtime) const; /// \returns this BigInt converted to a String in base \p radix. Throws a /// JSIException if radix is not in the [2, 36] range. - inline String toString(Runtime& runtime, int radix = 10) const; + inline String toString(IRuntime& runtime, int radix = 10) const; friend class Runtime; friend class Value; @@ -638,49 +1056,76 @@ class JSI_EXPORT String : public Pointer { /// Create a JS string from ascii values. The string data is /// copied. - static String createFromAscii(Runtime& runtime, const char* str, - size_t length) { + static String + createFromAscii(IRuntime& runtime, const char* str, size_t length) { return runtime.createStringFromAscii(str, length); } /// Create a JS string from a nul-terminated C ascii string. The /// string data is copied. - static String createFromAscii(Runtime& runtime, const char* str) { + static String createFromAscii(IRuntime& runtime, const char* str) { return createFromAscii(runtime, str, strlen(str)); } /// Create a JS string from a C++ string. The string data is /// copied. - static String createFromAscii(Runtime& runtime, const std::string& str) { + static String createFromAscii(IRuntime& runtime, const std::string& str) { return createFromAscii(runtime, str.c_str(), str.size()); } /// Create a JS string from utf8-encoded octets. The string data is /// transformed and copied. Results are undefined if \p utf8 contains invalid /// code points. - static String createFromUtf8(Runtime& runtime, const uint8_t* utf8, - size_t length) { + static String + createFromUtf8(IRuntime& runtime, const uint8_t* utf8, size_t length) { return runtime.createStringFromUtf8(utf8, length); } /// Create a JS string from utf8-encoded octets stored in a /// std::string. The string data is transformed and copied. Results are /// undefined if \p utf8 contains invalid code points. - static String createFromUtf8(Runtime& runtime, const std::string& utf8) { + static String createFromUtf8(IRuntime& runtime, const std::string& utf8) { return runtime.createStringFromUtf8( reinterpret_cast(utf8.data()), utf8.length()); } + /// Given a series of UTF-16 encoded code units, create a JS String. The input + /// may contain unpaired surrogates, which will be interpreted as a code point + /// of the same value. + static String + createFromUtf16(IRuntime& runtime, const char16_t* utf16, size_t length) { + return runtime.createStringFromUtf16(utf16, length); + } + + /// Given a series of UTF-16 encoded code units stored inside std::u16string, + /// create a JS String. The input may contain unpaired surrogates, which will + /// be interpreted as a code point of the same value. + static String createFromUtf16( + IRuntime& runtime, + const std::u16string& utf16) { + return runtime.createStringFromUtf16(utf16.data(), utf16.length()); + } + /// \return whether a and b contain the same characters. - static bool strictEquals(Runtime& runtime, const String& a, const String& b) { + static bool + strictEquals(IRuntime& runtime, const String& a, const String& b) { return runtime.strictEquals(a, b); } + /// \return the 'length' property of this JS string. + size_t length(IRuntime& runtime) const { + return runtime.length(*this); + } + /// Copies the data in a JS string as utf8 into a C++ string. - std::string utf8(Runtime& runtime) const { return runtime.utf8(*this); } + std::string utf8(IRuntime& runtime) const { + return runtime.utf8(*this); + } /// Copies the data in a JS string as utf16 into a C++ string. - std::u16string utf16(Runtime& runtime) const { return runtime.utf16(*this); } + std::u16string utf16(IRuntime& runtime) const { + return runtime.utf16(*this); + } /// Invokes the user provided callback to process content in String. The /// callback must take in three arguments: bool ascii, const void* data, and @@ -691,7 +1136,7 @@ class JSI_EXPORT String : public Pointer { /// must not access runtime functionality, as any operation on the runtime may /// invalidate the data pointers. template - void getStringData(Runtime& runtime, CB& cb) const { + void getStringData(IRuntime& runtime, CB& cb) const { runtime.getStringData( *this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) { (*((CB*)ctx))(ascii, data, num); @@ -714,194 +1159,265 @@ class JSI_EXPORT Object : public Pointer { Object& operator=(Object&& other) = default; /// Creates a new Object instance, like '{}' in JS. - Object(Runtime& runtime) : Object(runtime.createObject()) {} + explicit Object(IRuntime& runtime) : Object(runtime.createObject()) {} - static Object createFromHostObject(Runtime& runtime, - std::shared_ptr ho) { + static Object createFromHostObject( + IRuntime& runtime, + std::shared_ptr ho) { return runtime.createObject(ho); } /// Creates a new Object with the custom prototype - static Object create(Runtime& runtime, const Value& prototype) { + static Object create(IRuntime& runtime, const Value& prototype) { return runtime.createObjectWithPrototype(prototype); } /// \return whether this and \c obj are the same JSObject or not. - static bool strictEquals(Runtime& runtime, const Object& a, const Object& b) { + static bool + strictEquals(IRuntime& runtime, const Object& a, const Object& b) { return runtime.strictEquals(a, b); } /// \return the result of `this instanceOf ctor` in JS. - bool instanceOf(Runtime& rt, const Function& ctor) const { + bool instanceOf(IRuntime& rt, const Function& ctor) const { return rt.instanceOf(*this, ctor); } /// Sets \p prototype as the prototype of the object. The prototype must be /// either an Object or null. If the prototype was not set successfully, this /// method will throw. - void setPrototype(Runtime& runtime, const Value& prototype) const { + void setPrototype(IRuntime& runtime, const Value& prototype) const { return runtime.setPrototypeOf(*this, prototype); } /// \return the prototype of the object - inline Value getPrototype(Runtime& runtime) const; + inline Value getPrototype(IRuntime& runtime) const; /// \return the property of the object with the given ascii name. /// If the name isn't a property on the object, returns the /// undefined value. - Value getProperty(Runtime& runtime, const char* name) const; + Value getProperty(IRuntime& runtime, const char* name) const; /// \return the property of the object with the String name. /// If the name isn't a property on the object, returns the /// undefined value. - Value getProperty(Runtime& runtime, const String& name) const; + Value getProperty(IRuntime& runtime, const String& name) const; /// \return the property of the object with the given JS PropNameID /// name. If the name isn't a property on the object, returns the /// undefined value. - Value getProperty(Runtime& runtime, const PropNameID& name) const; + Value getProperty(IRuntime& runtime, const PropNameID& name) const; + + /// \return the Property of the object with the given JS Value name. If the + /// name isn't a property on the object, returns the undefined value.This + /// attempts to convert the JS Value to convert to a property key. If the + /// conversion fails, this method may throw. + Value getProperty(IRuntime& runtime, const Value& name) const; /// \return true if and only if the object has a property with the /// given ascii name. - bool hasProperty(Runtime& runtime, const char* name) const; + bool hasProperty(IRuntime& runtime, const char* name) const; /// \return true if and only if the object has a property with the /// given String name. - bool hasProperty(Runtime& runtime, const String& name) const; + bool hasProperty(IRuntime& runtime, const String& name) const; /// \return true if and only if the object has a property with the /// given PropNameID name. - bool hasProperty(Runtime& runtime, const PropNameID& name) const; + bool hasProperty(IRuntime& runtime, const PropNameID& name) const; + + /// \return true if and only if the object has a property with the given + /// JS Value name. This attempts to convert the JS Value to convert to a + /// property key. If the conversion fails, this method may throw. + bool hasProperty(IRuntime& runtime, const Value& name) const; /// Sets the property value from a Value or anything which can be /// used to make one: nullptr_t, bool, double, int, const char*, /// String, or Object. template - void setProperty(Runtime& runtime, const char* name, T&& value) const; + void setProperty(IRuntime& runtime, const char* name, T&& value) const; /// Sets the property value from a Value or anything which can be /// used to make one: nullptr_t, bool, double, int, const char*, /// String, or Object. template - void setProperty(Runtime& runtime, const String& name, T&& value) const; + void setProperty(IRuntime& runtime, const String& name, T&& value) const; /// Sets the property value from a Value or anything which can be /// used to make one: nullptr_t, bool, double, int, const char*, /// String, or Object. template - void setProperty(Runtime& runtime, const PropNameID& name, T&& value) const; + void setProperty(IRuntime& runtime, const PropNameID& name, T&& value) const; + + /// Sets the property value from a Value or anything which can be + /// used to make one: nullptr_t, bool, double, int, const char*, + /// String, or Object. This takes a JS Value as the property name, and + /// attempts to convert to a property key. If the conversion fails, this + /// method may throw. + template + void setProperty(IRuntime& runtime, const Value& name, T&& value) const; + + /// Delete the property with the given ascii name. Throws if the deletion + /// failed. + void deleteProperty(IRuntime& runtime, const char* name) const; + + /// Delete the property with the given String name. Throws if the deletion + /// failed. + void deleteProperty(IRuntime& runtime, const String& name) const; + + /// Delete the property with the given PropNameID name. Throws if the deletion + /// failed. + void deleteProperty(IRuntime& runtime, const PropNameID& name) const; + + /// Delete the property with the given Value name. Throws if the deletion + /// failed. + void deleteProperty(IRuntime& runtime, const Value& name) const; /// \return true iff JS \c Array.isArray() would return \c true. If /// so, then \c getArray() will succeed. - bool isArray(Runtime& runtime) const { return runtime.isArray(*this); } + bool isArray(IRuntime& runtime) const { + return runtime.isArray(*this); + } /// \return true iff the Object is an ArrayBuffer. If so, then \c /// getArrayBuffer() will succeed. - bool isArrayBuffer(Runtime& runtime) const { + bool isArrayBuffer(IRuntime& runtime) const { return runtime.isArrayBuffer(*this); } + /// \return true iff the Object is a TypedArray (Uint8Array, Int32Array, + /// Float64Array, etc.). If so, then \c getTypedArray() will succeed. + bool isTypedArray(IRuntime& runtime) const { + return runtime.isTypedArray(*this); + } + + /// \return true iff the Object is an Uint8Array. If so, then \c + /// getUint8Array() will succeed + bool isUint8Array(IRuntime& runtime) const { + return runtime.isUint8Array(*this); + } + /// \return true iff the Object is callable. If so, then \c /// getFunction will succeed. - bool isFunction(Runtime& runtime) const { return runtime.isFunction(*this); } + bool isFunction(IRuntime& runtime) const { + return runtime.isFunction(*this); + } /// \return true iff the Object was initialized with \c createFromHostObject /// and the HostObject passed is of type \c T. If returns \c true then /// \c getHostObject will succeed. template - bool isHostObject(Runtime& runtime) const; + bool isHostObject(IRuntime& runtime) const; /// \return an Array instance which refers to the same underlying /// object. If \c isArray() would return false, this will assert. - Array getArray(Runtime& runtime) const&; + Array getArray(IRuntime& runtime) const&; /// \return an Array instance which refers to the same underlying /// object. If \c isArray() would return false, this will assert. - Array getArray(Runtime& runtime) &&; + Array getArray(IRuntime& runtime) &&; /// \return an Array instance which refers to the same underlying /// object. If \c isArray() would return false, this will throw /// JSIException. - Array asArray(Runtime& runtime) const&; + Array asArray(IRuntime& runtime) const&; /// \return an Array instance which refers to the same underlying /// object. If \c isArray() would return false, this will throw /// JSIException. - Array asArray(Runtime& runtime) &&; + Array asArray(IRuntime& runtime) &&; + + /// \return a TypedArray instance which refers to the same underlying + /// object. If \c isTypedArray() would return false, this will throw + /// JSIException. + TypedArray asTypedArray(IRuntime& runtime) const&; + + /// \return an Uint8Array instance which refers to the same underlying + /// object. If \c isUint8Array() would return false, this will throw + /// JSIException. + Uint8Array asUint8Array(IRuntime& runtime) const&; /// \return an ArrayBuffer instance which refers to the same underlying /// object. If \c isArrayBuffer() would return false, this will assert. - ArrayBuffer getArrayBuffer(Runtime& runtime) const&; + ArrayBuffer getArrayBuffer(IRuntime& runtime) const&; /// \return an ArrayBuffer instance which refers to the same underlying /// object. If \c isArrayBuffer() would return false, this will assert. - ArrayBuffer getArrayBuffer(Runtime& runtime) &&; + ArrayBuffer getArrayBuffer(IRuntime& runtime) &&; + + /// \return a TypedArray instance which refers to the same underlying + /// object. If \c isTypedArray() would return false, this will assert. + TypedArray getTypedArray(IRuntime& runtime) const&; + + /// \return an Uint8Array instance which refers to the same underlying + /// object. If \c isUint8Array() would return false, this will assert. + Uint8Array getUint8Array(IRuntime& runtime) const&; /// \return a Function instance which refers to the same underlying /// object. If \c isFunction() would return false, this will assert. - Function getFunction(Runtime& runtime) const&; + Function getFunction(IRuntime& runtime) const&; /// \return a Function instance which refers to the same underlying /// object. If \c isFunction() would return false, this will assert. - Function getFunction(Runtime& runtime) &&; + Function getFunction(IRuntime& runtime) &&; /// \return a Function instance which refers to the same underlying /// object. If \c isFunction() would return false, this will throw /// JSIException. - Function asFunction(Runtime& runtime) const&; + Function asFunction(IRuntime& runtime) const&; /// \return a Function instance which refers to the same underlying /// object. If \c isFunction() would return false, this will throw /// JSIException. - Function asFunction(Runtime& runtime) &&; + Function asFunction(IRuntime& runtime) &&; /// \return a shared_ptr which refers to the same underlying /// \c HostObject that was used to create this object. If \c isHostObject /// is false, this will assert. Note that this does a type check and will /// assert if the underlying HostObject isn't of type \c T template - std::shared_ptr getHostObject(Runtime& runtime) const; + std::shared_ptr getHostObject(IRuntime& runtime) const; /// \return a shared_ptr which refers to the same underlying /// \c HostObject that was used to create this object. If \c isHostObject /// is false, this will throw. template - std::shared_ptr asHostObject(Runtime& runtime) const; + std::shared_ptr asHostObject(IRuntime& runtime) const; /// \return whether this object has native state of type T previously set by /// \c setNativeState. template - bool hasNativeState(Runtime& runtime) const; + bool hasNativeState(IRuntime& runtime) const; /// \return a shared_ptr to the state previously set by \c setNativeState. /// If \c hasNativeState is false, this will assert. Note that this does a /// type check and will assert if the native state isn't of type \c T template - std::shared_ptr getNativeState(Runtime& runtime) const; + std::shared_ptr getNativeState(IRuntime& runtime) const; /// Set the internal native state property of this object, overwriting any old /// value. Creates a new shared_ptr to the object managed by \p state, which /// will live until the value at this property becomes unreachable. /// /// Throws a type error if this object is a proxy or host object. - void setNativeState(Runtime& runtime, - std::shared_ptr state) const; + void setNativeState(IRuntime& runtime, std::shared_ptr state) + const; /// \return same as \c getProperty(name).asObject(), except with /// a better exception message. - Object getPropertyAsObject(Runtime& runtime, const char* name) const; + Object getPropertyAsObject(IRuntime& runtime, const char* name) const; /// \return similar to \c /// getProperty(name).getObject().getFunction(), except it will /// throw JSIException instead of asserting if the property is /// not an object, or the object is not callable. - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + Function getPropertyAsFunction(IRuntime& runtime, const char* name) const; /// \return an Array consisting of all enumerable property names in /// the object and its prototype chain. All values in the return /// will be isString(). (This is probably not optimal, but it /// works. I only need it in one place.) - Array getPropertyNames(Runtime& runtime) const; + Array getPropertyNames(IRuntime& runtime) const; /// Inform the runtime that there is additional memory associated with a given /// JavaScript object that is not visible to the GC. This can be used if an @@ -911,16 +1427,27 @@ class JSI_EXPORT Object : public Pointer { /// calls will overwrite any previously set value. Once the object is garbage /// collected, the associated external memory will be considered freed and may /// no longer factor into GC decisions. - void setExternalMemoryPressure(Runtime& runtime, size_t amt) const; + void setExternalMemoryPressure(IRuntime& runtime, size_t amt) const; protected: - void setPropertyValue(Runtime& runtime, const String& name, - const Value& value) const { + void setPropertyValue( + IRuntime& runtime, + const String& name, + const Value& value) const { return runtime.setPropertyValue(*this, name, value); } - void setPropertyValue(Runtime& runtime, const PropNameID& name, - const Value& value) const { + void setPropertyValue( + IRuntime& runtime, + const PropNameID& name, + const Value& value) const { + return runtime.setPropertyValue(*this, name, value); + } + + void setPropertyValue( + IRuntime& runtime, + const Value& name, + const Value& value) const { return runtime.setPropertyValue(*this, name, value); } @@ -939,14 +1466,14 @@ class JSI_EXPORT WeakObject : public Pointer { WeakObject& operator=(WeakObject&& other) = default; /// Create a WeakObject from an Object. - WeakObject(Runtime& runtime, const Object& o) + WeakObject(IRuntime& runtime, const Object& o) : WeakObject(runtime.createWeakObject(o)) {} /// \return a Value representing the underlying Object if it is still valid; /// otherwise returns \c undefined. Note that this method has nothing to do /// with threads or concurrency. The name is based on std::weak_ptr::lock() /// which serves a similar purpose. - Value lock(Runtime& runtime) const; + Value lock(IRuntime& runtime) const; friend class Runtime; }; @@ -957,47 +1484,63 @@ class JSI_EXPORT Array : public Object { public: Array(Array&&) = default; /// Creates a new Array instance, with \c length undefined elements. - Array(Runtime& runtime, size_t length) : Array(runtime.createArray(length)) {} + Array(IRuntime& runtime, size_t length) + : Array(runtime.createArray(length)) {} Array& operator=(Array&&) = default; /// \return the size of the Array, according to its length property. /// (C++ naming convention) - size_t size(Runtime& runtime) const { return runtime.size(*this); } + size_t size(IRuntime& runtime) const { + return runtime.size(*this); + } /// \return the size of the Array, according to its length property. /// (JS naming convention) - size_t length(Runtime& runtime) const { return size(runtime); } + size_t length(IRuntime& runtime) const { + return size(runtime); + } /// \return the property of the array at index \c i. If there is no /// such property, returns the undefined value. If \c i is out of /// range [ 0..\c length ] throws a JSIException. - Value getValueAtIndex(Runtime& runtime, size_t i) const; + Value getValueAtIndex(IRuntime& runtime, size_t i) const; /// Sets the property of the array at index \c i. The argument /// value behaves as with Object::setProperty(). If \c i is out of /// range [ 0..\c length ] throws a JSIException. template - void setValueAtIndex(Runtime& runtime, size_t i, T&& value) const; + void setValueAtIndex(IRuntime& runtime, size_t i, T&& value) const; + + /// Appends provides values to the end of the Array in the order they appear. + /// Returns the new length of the array. + template + size_t push(IRuntime& runtime, Args&&... args); - /// There is no current API for changing the size of an array once - /// created. We'll probably need that eventually. + /// Appends everything in \p elements to the end of the Array in the order + /// they appear. Returns the new length of the array. + size_t push(IRuntime& runtime, std::initializer_list elements); + + /// Appends \p count elements at \p elements to the end of the Array in the + /// order they appear. + size_t push(IRuntime& runtime, const Value* elements, size_t count); /// Creates a new Array instance from provided values template - static Array createWithElements(Runtime&, Args&&... args); + static Array createWithElements(IRuntime&, Args&&... args); /// Creates a new Array instance from initializer list. - static Array createWithElements(Runtime& runtime, - std::initializer_list elements); + static Array createWithElements( + IRuntime& runtime, + std::initializer_list elements); private: friend class Object; friend class Value; friend class Runtime; - void setValueAtIndexImpl(Runtime& runtime, size_t i, - const Value& value) const { + void setValueAtIndexImpl(IRuntime& runtime, size_t i, const Value& value) + const { return runtime.setValueAtIndexImpl(*this, i, value); } @@ -1010,17 +1553,32 @@ class JSI_EXPORT ArrayBuffer : public Object { ArrayBuffer(ArrayBuffer&&) = default; ArrayBuffer& operator=(ArrayBuffer&&) = default; - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + ArrayBuffer(IRuntime& runtime, std::shared_ptr buffer) : ArrayBuffer(runtime.createArrayBuffer(std::move(buffer))) {} /// \return the size of the ArrayBuffer storage. This is not affected by /// overriding the byteLength property. /// (C++ naming convention) - size_t size(Runtime& runtime) const { return runtime.size(*this); } + size_t size(IRuntime& runtime) const { + return runtime.size(*this); + } + + size_t length(IRuntime& runtime) const { + return runtime.size(*this); + } - size_t length(Runtime& runtime) const { return runtime.size(*this); } + uint8_t* data(IRuntime& runtime) const { + return runtime.data(*this); + } + + std::shared_ptr tryGetMutableBuffer(IRuntime& runtime) const { + return runtime.tryGetMutableBuffer(*this); + } - uint8_t* data(Runtime& runtime) const { return runtime.data(*this); } + /// \return true if the ArrayBuffer is detached, false otherwise. + bool detached(IRuntime& runtime) const { + return runtime.detached(*this); + } private: friend class Object; @@ -1047,10 +1605,11 @@ class JSI_EXPORT Function : public Object { /// shut down, and may occur on an arbitrary thread. If the function contains /// any captured values, you are responsible for ensuring that their /// destructors are safe to call on any thread. - static Function createFromHostFunction(Runtime& runtime, - const jsi::PropNameID& name, - unsigned int paramCount, - jsi::HostFunctionType func); + static Function createFromHostFunction( + IRuntime& runtime, + const jsi::PropNameID& name, + unsigned int paramCount, + jsi::HostFunctionType func); /// Calls the function with \c count \c args. The \c this value of the JS /// function will not be set by the C++ caller, similar to calling @@ -1058,7 +1617,7 @@ class JSI_EXPORT Function : public Object { /// \b Note: as with Function.prototype.apply, \c this may not always be /// \c undefined in the function itself. If the function is non-strict, /// \c this will be set to the global object. - Value call(Runtime& runtime, const Value* args, size_t count) const; + Value call(IRuntime& runtime, const Value* args, size_t count) const; /// Calls the function with a \c std::initializer_list of Value /// arguments. The \c this value of the JS function will not be set by the @@ -1067,7 +1626,7 @@ class JSI_EXPORT Function : public Object { /// \b Note: as with Function.prototype.apply, \c this may not always be /// \c undefined in the function itself. If the function is non-strict, /// \c this will be set to the global object. - Value call(Runtime& runtime, std::initializer_list args) const; + Value call(IRuntime& runtime, std::initializer_list args) const; /// Calls the function with any number of arguments similarly to /// Object::setProperty(). The \c this value of the JS function will not be @@ -1077,44 +1636,49 @@ class JSI_EXPORT Function : public Object { /// \c undefined in the function itself. If the function is non-strict, /// \c this will be set to the global object. template - Value call(Runtime& runtime, Args&&... args) const; + Value call(IRuntime& runtime, Args&&... args) const; /// Calls the function with \c count \c args and \c jsThis value passed /// as the \c this value. - Value callWithThis(Runtime& Runtime, const Object& jsThis, const Value* args, - size_t count) const; + Value callWithThis( + IRuntime& Runtime, + const Object& jsThis, + const Value* args, + size_t count) const; /// Calls the function with a \c std::initializer_list of Value /// arguments and \c jsThis passed as the \c this value. - Value callWithThis(Runtime& runtime, const Object& jsThis, - std::initializer_list args) const; + Value callWithThis( + IRuntime& runtime, + const Object& jsThis, + std::initializer_list args) const; /// Calls the function with any number of arguments similarly to /// Object::setProperty(), and with \c jsThis passed as the \c this value. template - Value callWithThis(Runtime& runtime, const Object& jsThis, - Args&&... args) const; + Value callWithThis(IRuntime& runtime, const Object& jsThis, Args&&... args) + const; /// Calls the function as a constructor with \c count \c args. Equivalent /// to calling `new Func` where `Func` is the js function reqresented by /// this. - Value callAsConstructor(Runtime& runtime, const Value* args, - size_t count) const; + Value callAsConstructor(IRuntime& runtime, const Value* args, size_t count) + const; /// Same as above `callAsConstructor`, except use an initializer_list to /// supply the arguments. - Value callAsConstructor(Runtime& runtime, - std::initializer_list args) const; + Value callAsConstructor(IRuntime& runtime, std::initializer_list args) + const; /// Same as above `callAsConstructor`, but automatically converts/wraps /// any argument with a jsi Value. template - Value callAsConstructor(Runtime& runtime, Args&&... args) const; + Value callAsConstructor(IRuntime& runtime, Args&&... args) const; /// Returns whether this was created with Function::createFromHostFunction. /// If true then you can use getHostFunction to get the underlying /// HostFunctionType. - bool isHostFunction(Runtime& runtime) const { + bool isHostFunction(IRuntime& runtime) const { return runtime.isHostFunction(*this); } @@ -1125,7 +1689,7 @@ class JSI_EXPORT Function : public Object { /// Note: The reference returned is borrowed from the JS object underlying /// \c this, and thus only lasts as long as the object underlying /// \c this does. - HostFunctionType& getHostFunction(Runtime& runtime) const { + HostFunctionType& getHostFunction(IRuntime& runtime) const { assert(isHostFunction(runtime)); return runtime.getHostFunction(*this); } @@ -1138,6 +1702,64 @@ class JSI_EXPORT Function : public Object { Function(Runtime::PointerValue* value) : Object(value) {} }; +/// Represents a JS TypedArray +class JSI_EXPORT TypedArray : public Object { + public: + TypedArray(TypedArray&&) = default; + TypedArray& operator=(TypedArray&&) = default; + + // Gets the buffer of this TypedArray + ArrayBuffer buffer(IRuntime& runtime) { + return runtime.buffer(*this); + } + + // Gets the byte offset of this TypedArray + size_t byteOffset(IRuntime& runtime) { + return runtime.byteOffset(*this); + } + + // Gets the byte length of this TypedArray + size_t byteLength(IRuntime& runtime) { + return runtime.byteLength(*this); + } + + // Gets the element length of this TypedArray + size_t length(IRuntime& runtime) { + return runtime.length(*this); + } + + private: + friend class Object; + friend class Value; + friend class Runtime; + friend class Uint8Array; + + explicit TypedArray(Runtime::PointerValue* value) : Object(value) {} +}; + +// Represents a JS Uint8Array +class JSI_EXPORT Uint8Array : public TypedArray { + public: + Uint8Array(Uint8Array&&) = default; + Uint8Array& operator=(Uint8Array&&) = default; + + Uint8Array(IRuntime& runtime, size_t length) + : Uint8Array(runtime.createUint8Array(length)) {} + Uint8Array( + IRuntime& runtime, + const ArrayBuffer& buffer, + size_t offset, + size_t length) + : Uint8Array(runtime.createUint8Array(buffer, offset, length)) {} + + private: + friend class Object; + friend class Value; + friend class Runtime; + + explicit Uint8Array(Runtime::PointerValue* value) : TypedArray(value) {} +}; + /// Represents any JS Value (undefined, null, boolean, number, symbol, /// string, or object). Movable, or explicitly copyable (has no copy /// ctor). @@ -1150,20 +1772,28 @@ class JSI_EXPORT Value { /* implicit */ Value(std::nullptr_t) : kind_(NullKind) {} /// Creates a boolean JS value. - /* implicit */ Value(bool b) : Value(BooleanKind) { data_.boolean = b; } + /* implicit */ Value(bool b) : Value(BooleanKind) { + data_.boolean = b; + } /// Creates a number JS value. - /* implicit */ Value(double d) : Value(NumberKind) { data_.number = d; } + /* implicit */ Value(double d) : Value(NumberKind) { + data_.number = d; + } /// Creates a number JS value. - /* implicit */ Value(int i) : Value(NumberKind) { data_.number = i; } + /* implicit */ Value(int i) : Value(NumberKind) { + data_.number = i; + } /// Moves a Symbol, String, or Object rvalue into a new JS value. - template ::value || - std::is_base_of::value || - std::is_base_of::value || - std::is_base_of::value>> + template < + typename T, + typename = std::enable_if_t< + std::is_base_of::value || + std::is_base_of::value || + std::is_base_of::value || + std::is_base_of::value>> /* implicit */ Value(T&& other) : Value(kindOf(other)) { new (&data_.pointer) T(std::move(other)); } @@ -1172,59 +1802,65 @@ class JSI_EXPORT Value { /// compile error. template Value(const char*) { - static_assert(!std::is_same::value, - "Value cannot be constructed directly from const char*"); + static_assert( + !std::is_same::value, + "Value cannot be constructed directly from const char*"); } Value(Value&& other) noexcept; /// Copies a Symbol lvalue into a new JS value. - Value(Runtime& runtime, const Symbol& sym) : Value(SymbolKind) { + Value(IRuntime& runtime, const Symbol& sym) : Value(SymbolKind) { new (&data_.pointer) Symbol(runtime.cloneSymbol(sym.ptr_)); } /// Copies a BigInt lvalue into a new JS value. - Value(Runtime& runtime, const BigInt& bigint) : Value(BigIntKind) { + Value(IRuntime& runtime, const BigInt& bigint) : Value(BigIntKind) { new (&data_.pointer) BigInt(runtime.cloneBigInt(bigint.ptr_)); } /// Copies a String lvalue into a new JS value. - Value(Runtime& runtime, const String& str) : Value(StringKind) { + Value(IRuntime& runtime, const String& str) : Value(StringKind) { new (&data_.pointer) String(runtime.cloneString(str.ptr_)); } /// Copies a Object lvalue into a new JS value. - Value(Runtime& runtime, const Object& obj) : Value(ObjectKind) { + Value(IRuntime& runtime, const Object& obj) : Value(ObjectKind) { new (&data_.pointer) Object(runtime.cloneObject(obj.ptr_)); } /// Creates a JS value from another Value lvalue. - Value(Runtime& runtime, const Value& value); + Value(IRuntime& runtime, const Value& value); /// Value(rt, "foo") will treat foo as a bool. This makes doing /// that a compile error. template - Value(Runtime&, const char*) { - static_assert(!std::is_same::value, - "Value cannot be constructed directly from const char*"); + Value(IRuntime&, const char*) { + static_assert( + !std::is_same::value, + "Value cannot be constructed directly from const char*"); } ~Value(); // \return the undefined \c Value. - static Value undefined() { return Value(); } + static Value undefined() { + return Value(); + } // \return the null \c Value. - static Value null() { return Value(nullptr); } + static Value null() { + return Value(nullptr); + } // \return a \c Value created from a utf8-encoded JSON string. - static Value createFromJsonUtf8(Runtime& runtime, const uint8_t* json, - size_t length) { + static Value + createFromJsonUtf8(IRuntime& runtime, const uint8_t* json, size_t length) { return runtime.createValueFromJsonUtf8(json, length); } /// \return according to the Strict Equality Comparison algorithm, see: /// https://262.ecma-international.org/11.0/#sec-strict-equality-comparison - static bool strictEquals(Runtime& runtime, const Value& a, const Value& b); + static bool strictEquals(IRuntime& runtime, const Value& a, const Value& b); Value& operator=(Value&& other) noexcept { this->~Value(); @@ -1232,21 +1868,59 @@ class JSI_EXPORT Value { return *this; } - bool isUndefined() const { return kind_ == UndefinedKind; } + bool isUndefined() const { + return kind_ == UndefinedKind; + } + + bool isNull() const { + return kind_ == NullKind; + } + + bool isBool() const { + return kind_ == BooleanKind; + } - bool isNull() const { return kind_ == NullKind; } + bool isNumber() const { + return kind_ == NumberKind; + } - bool isBool() const { return kind_ == BooleanKind; } + bool isString() const { + return kind_ == StringKind; + } - bool isNumber() const { return kind_ == NumberKind; } + bool isBigInt() const { + return kind_ == BigIntKind; + } + + bool isSymbol() const { + return kind_ == SymbolKind; + } - bool isString() const { return kind_ == StringKind; } + bool isObject() const { + return kind_ == ObjectKind; + } - bool isBigInt() const { return kind_ == BigIntKind; } + /// \returns true if `Number.isInteger(value)` returns true, false otherwise. + bool isInteger() const { + // 1. If number is an integral Number, return true. + // 2. Return false. - bool isSymbol() const { return kind_ == SymbolKind; } + // The spec's definition of integral number: + // When the term integer is used in this specification, it refers to a + // mathematical value which is in the set of integers, unless otherwise + // stated. When the term integral Number is used in this specification, it + // refers to a finite Number value whose mathematical value is in the set of + // integers. - bool isObject() const { return kind_ == ObjectKind; } + if (!isNumber()) { + return false; + } + auto number = data_.number; + if (!std::isfinite(number)) { + return false; + } + return number == std::trunc(number); + } /// \return the boolean value, or asserts if not a boolean. bool getBool() const { @@ -1269,14 +1943,14 @@ class JSI_EXPORT Value { double asNumber() const; /// \return the Symbol value, or asserts if not a symbol. - Symbol getSymbol(Runtime& runtime) const& { + Symbol getSymbol(IRuntime& runtime) const& { assert(isSymbol()); return Symbol(runtime.cloneSymbol(data_.pointer.ptr_)); } /// \return the Symbol value, or asserts if not a symbol. /// Can be used on rvalue references to avoid cloning more symbols. - Symbol getSymbol(Runtime&) && { + Symbol getSymbol(IRuntime&) && { assert(isSymbol()); auto ptr = data_.pointer.ptr_; data_.pointer.ptr_ = nullptr; @@ -1285,18 +1959,18 @@ class JSI_EXPORT Value { /// \return the Symbol value, or throws JSIException if not a /// symbol - Symbol asSymbol(Runtime& runtime) const&; - Symbol asSymbol(Runtime& runtime) &&; + Symbol asSymbol(IRuntime& runtime) const&; + Symbol asSymbol(IRuntime& runtime) &&; /// \return the BigInt value, or asserts if not a bigint. - BigInt getBigInt(Runtime& runtime) const& { + BigInt getBigInt(IRuntime& runtime) const& { assert(isBigInt()); return BigInt(runtime.cloneBigInt(data_.pointer.ptr_)); } /// \return the BigInt value, or asserts if not a bigint. /// Can be used on rvalue references to avoid cloning more bigints. - BigInt getBigInt(Runtime&) && { + BigInt getBigInt(IRuntime&) && { assert(isBigInt()); auto ptr = data_.pointer.ptr_; data_.pointer.ptr_ = nullptr; @@ -1305,18 +1979,18 @@ class JSI_EXPORT Value { /// \return the BigInt value, or throws JSIException if not a /// bigint - BigInt asBigInt(Runtime& runtime) const&; - BigInt asBigInt(Runtime& runtime) &&; + BigInt asBigInt(IRuntime& runtime) const&; + BigInt asBigInt(IRuntime& runtime) &&; /// \return the String value, or asserts if not a string. - String getString(Runtime& runtime) const& { + String getString(IRuntime& runtime) const& { assert(isString()); return String(runtime.cloneString(data_.pointer.ptr_)); } /// \return the String value, or asserts if not a string. /// Can be used on rvalue references to avoid cloning more strings. - String getString(Runtime&) && { + String getString(IRuntime&) && { assert(isString()); auto ptr = data_.pointer.ptr_; data_.pointer.ptr_ = nullptr; @@ -1325,18 +1999,18 @@ class JSI_EXPORT Value { /// \return the String value, or throws JSIException if not a /// string. - String asString(Runtime& runtime) const&; - String asString(Runtime& runtime) &&; + String asString(IRuntime& runtime) const&; + String asString(IRuntime& runtime) &&; /// \return the Object value, or asserts if not an object. - Object getObject(Runtime& runtime) const& { + Object getObject(IRuntime& runtime) const& { assert(isObject()); return Object(runtime.cloneObject(data_.pointer.ptr_)); } /// \return the Object value, or asserts if not an object. /// Can be used on rvalue references to avoid cloning more objects. - Object getObject(Runtime&) && { + Object getObject(IRuntime&) && { assert(isObject()); auto ptr = data_.pointer.ptr_; data_.pointer.ptr_ = nullptr; @@ -1345,11 +2019,11 @@ class JSI_EXPORT Value { /// \return the Object value, or throws JSIException if not an /// object. - Object asObject(Runtime& runtime) const&; - Object asObject(Runtime& runtime) &&; + Object asObject(IRuntime& runtime) const&; + Object asObject(IRuntime& runtime) &&; // \return a String like JS .toString() would do. - String toString(Runtime& runtime) const; + String toString(IRuntime& runtime) const; private: friend class Runtime; @@ -1369,8 +2043,9 @@ class JSI_EXPORT Value { union Data { // Value's ctor and dtor will manage the lifecycle of the contained Data. Data() { - static_assert(sizeof(Data) == sizeof(uint64_t), - "Value data should fit in a 64-bit register"); + static_assert( + sizeof(Data) == sizeof(uint64_t), + "Value data should fit in a 64-bit register"); } ~Data() {} @@ -1378,15 +2053,23 @@ class JSI_EXPORT Value { bool boolean; double number; // pointers - Pointer pointer; // Symbol, String, Object, Array, Function + Pointer pointer; // Symbol, String, Object, Array, Function }; Value(ValueKind kind) : kind_(kind) {} - constexpr static ValueKind kindOf(const Symbol&) { return SymbolKind; } - constexpr static ValueKind kindOf(const BigInt&) { return BigIntKind; } - constexpr static ValueKind kindOf(const String&) { return StringKind; } - constexpr static ValueKind kindOf(const Object&) { return ObjectKind; } + constexpr static ValueKind kindOf(const Symbol&) { + return SymbolKind; + } + constexpr static ValueKind kindOf(const BigInt&) { + return BigIntKind; + } + constexpr static ValueKind kindOf(const String&) { + return StringKind; + } + constexpr static ValueKind kindOf(const Object&) { + return ObjectKind; + } ValueKind kind_; Data data_; @@ -1414,8 +2097,10 @@ class JSI_EXPORT Value { /// locking, provided that the lock (if any) is managed with RAII helpers. class JSI_EXPORT Scope { public: - explicit Scope(Runtime& rt) : rt_(rt), prv_(rt.pushScope()) {} - ~Scope() { rt_.popScope(prv_); } + explicit Scope(IRuntime& rt) : rt_(rt), prv_(rt.pushScope()) {} + ~Scope() { + rt_.popScope(prv_); + } Scope(const Scope&) = delete; Scope(Scope&&) = delete; @@ -1424,13 +2109,13 @@ class JSI_EXPORT Scope { Scope& operator=(Scope&&) = delete; template - static auto callInNewScope(Runtime& rt, F f) -> decltype(f()) { + static auto callInNewScope(IRuntime& rt, F f) -> decltype(f()) { Scope s(rt); return f(); } private: - Runtime& rt_; + IRuntime& rt_; Runtime::ScopeState* prv_; }; @@ -1443,7 +2128,9 @@ class JSI_EXPORT JSIException : public std::exception { public: JSIException(const JSIException&) = default; - virtual const char* what() const noexcept override { return what_.c_str(); } + virtual const char* what() const noexcept override { + return what_.c_str(); + } virtual ~JSIException() override; @@ -1468,38 +2155,66 @@ class JSI_EXPORT JSINativeException : public JSIException { class JSI_EXPORT JSError : public JSIException { public: /// Creates a JSError referring to provided \c value - JSError(Runtime& r, Value&& value); + JSError(IRuntime& r, Value&& value); /// Creates a JSError referring to new \c Error instance capturing current /// JavaScript stack. The error message property is set to given \c message. - JSError(Runtime& rt, std::string message); + JSError(IRuntime& rt, std::string message); /// Creates a JSError referring to new \c Error instance capturing current /// JavaScript stack. The error message property is set to given \c message. - JSError(Runtime& rt, const char* message) + JSError(IRuntime& rt, const char* message) : JSError(rt, std::string(message)) {} /// Creates a JSError referring to a JavaScript Object having message and /// stack properties set to provided values. - JSError(Runtime& rt, std::string message, std::string stack); + JSError(IRuntime& rt, std::string message, std::string stack); /// Creates a JSError referring to provided value and what string /// set to provided message. This argument order is a bit weird, /// but necessary to avoid ambiguity with the above. - JSError(std::string what, Runtime& rt, Value&& value); + JSError(std::string what, IRuntime& rt, Value&& value); /// Creates a JSError referring to the provided value, message and stack. This /// constructor does not take a Runtime parameter, and therefore cannot result /// in recursively invoking the JSError constructor. JSError(Value&& value, std::string message, std::string stack); + /// Creates a JSError referring to new \c EvalError instance. The error + /// message property is set to given \c message. + static JSError createEvalError(IRuntime& rt, const std::string& message); + + /// Creates a JSError referring to new \c RangeError instance. The error + /// message property is set to given \c message. + static JSError createRangeError(IRuntime& rt, const std::string& message); + + /// Creates a JSError referring to new \c ReferenceError instance. The error + /// message property is set to given \c message. + static JSError createReferenceError(IRuntime& rt, const std::string& message); + + /// Creates a JSError referring to new \c SyntaxError instance. The error + /// message property is set to given \c message. + static JSError createSyntaxError(IRuntime& rt, const std::string& message); + + /// Creates a JSError referring to new \c TypeError instance. The error + /// message property is set to given \c message. + static JSError createTypeError(IRuntime& rt, const std::string& message); + + /// Creates a JSError referring to new \c URIError instance. The error + /// message property is set to given \c message. + static JSError createURIError(IRuntime& rt, const std::string& message); + JSError(const JSError&) = default; virtual ~JSError(); - const std::string& getStack() const { return stack_; } + const std::string& getStack() const { + return stack_; + } - const std::string& getMessage() const { return message_; } + const std::string& getMessage() const { + return message_; + } const jsi::Value& value() const { assert(value_); @@ -1510,7 +2225,7 @@ class JSI_EXPORT JSError : public JSIException { // This initializes the value_ member and does some other // validation, so it must be called by every branch through the // constructors. - void setValue(Runtime& rt, Value&& value); + void setValue(IRuntime& rt, Value&& value); // This needs to be on the heap, because throw requires the object // be copyable, and Value is not. @@ -1519,7 +2234,33 @@ class JSI_EXPORT JSError : public JSIException { std::string stack_; }; -} // namespace jsi -} // namespace facebook +/// Helper function to cast the object pointed to by \p ptr into an interface +/// specified by \c U. If cast is successful, return a pointer to the object +/// as a raw pointer of \c U. Otherwise, return nullptr. +/// The returned interface same lifetime as the object referenced by \p ptr. +template +U* castInterface(T* ptr) { + if (ptr) { + return static_cast(ptr->castInterface(U::uuid)); + } + return nullptr; +} + +/// Helper function to cast the object managed by the shared_ptr \p ptr into an +/// interface specified by \c U. If the cast is successful, return a shared_ptr +/// of type \c U to the object. Otherwise, return an empty pointer. +/// The returned shared_ptr shares ownership of the object with \p ptr. +template +std::shared_ptr dynamicInterfaceCast(T&& ptr) { + auto* p = ptr->castInterface(U::uuid); + U* res = static_cast(p); + if (res) { + return std::shared_ptr(std::forward(ptr), res); + } + return nullptr; +} + +} // namespace jsi +} // namespace facebook -#include "jsi/jsi-inl.h" \ No newline at end of file +#include diff --git a/packages/android-jsi/platforms/android/cpp/jsi/jsilib-posix.cpp b/packages/android-jsi/platforms/android/cpp/jsi/jsilib-posix.cpp index 42fa1a09f..b47d0d4ab 100644 --- a/packages/android-jsi/platforms/android/cpp/jsi/jsilib-posix.cpp +++ b/packages/android-jsi/platforms/android/cpp/jsi/jsilib-posix.cpp @@ -12,12 +12,11 @@ #include #include #include - #include #include #include -#include "jsi/jsilib.h" +#include namespace facebook { namespace jsi { @@ -26,8 +25,9 @@ namespace { constexpr size_t kErrorBufferSize = 512; -__attribute__((format(printf, 1, 2))) void throwFormattedError(const char* fmt, - ...) { +__attribute__((format(printf, 1, 2))) void throwFormattedError( + const char* fmt, + ...) { char logBuffer[kErrorBufferSize]; va_list va_args; @@ -36,8 +36,8 @@ __attribute__((format(printf, 1, 2))) void throwFormattedError(const char* fmt, va_end(va_args); if (result < 0) { - throw JSINativeException(std::string("Failed to format error message: ") + - fmt); + throw JSINativeException( + std::string("Failed to format error message: ") + fmt); } throw JSINativeException(logBuffer); @@ -48,18 +48,20 @@ class ScopedFile { ScopedFile(const std::string& path) : path_(path), fd_(::open(path.c_str(), O_RDONLY)) { if (fd_ == -1) { - throwFormattedError("Could not open %s: %s", path.c_str(), - strerror(errno)); + throwFormattedError( + "Could not open %s: %s", path.c_str(), strerror(errno)); } } - ~ScopedFile() { ::close(fd_); } + ~ScopedFile() { + ::close(fd_); + } size_t size() { struct stat fileInfo; if (::fstat(fd_, &fileInfo) == -1) { - throwFormattedError("Could not stat %s: %s", path_.c_str(), - strerror(errno)); + throwFormattedError( + "Could not stat %s: %s", path_.c_str(), strerror(errno)); } return fileInfo.st_size; } @@ -67,8 +69,8 @@ class ScopedFile { uint8_t* mmap(size_t size) { void* result = ::mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd_, 0); if (result == MAP_FAILED) { - throwFormattedError("Could not mmap %s: %s", path_.c_str(), - strerror(errno)); + throwFormattedError( + "Could not mmap %s: %s", path_.c_str(), strerror(errno)); } return reinterpret_cast(result); } @@ -77,7 +79,7 @@ class ScopedFile { const int fd_; }; -} // namespace +} // namespace FileBuffer::FileBuffer(const std::string& path) { ScopedFile file(path); @@ -89,15 +91,18 @@ FileBuffer::~FileBuffer() { if (::munmap(data_, size_)) { // terminate the program with pending exception try { - throwFormattedError("Could not unmap memory (%p, %zu bytes): %s", data_, - size_, strerror(errno)); + throwFormattedError( + "Could not unmap memory (%p, %zu bytes): %s", + data_, + size_, + strerror(errno)); } catch (...) { std::terminate(); } } } -} // namespace jsi -} // namespace facebook +} // namespace jsi +} // namespace facebook -#endif // !defined(_WINDOWS) \ No newline at end of file +#endif // !defined(_WINDOWS) diff --git a/packages/android-jsi/platforms/android/cpp/jsi/jsilib.h b/packages/android-jsi/platforms/android/cpp/jsi/jsilib.h index 0dad2ee87..c94de89f6 100644 --- a/packages/android-jsi/platforms/android/cpp/jsi/jsilib.h +++ b/packages/android-jsi/platforms/android/cpp/jsi/jsilib.h @@ -7,7 +7,7 @@ #pragma once -#include "jsi/jsi.h" +#include namespace facebook { namespace jsi { @@ -17,9 +17,13 @@ class FileBuffer : public Buffer { FileBuffer(const std::string& path); ~FileBuffer() override; - size_t size() const override { return size_; } + size_t size() const override { + return size_; + } - const uint8_t* data() const override { return data_; } + const uint8_t* data() const override { + return data_; + } private: size_t size_; @@ -34,15 +38,22 @@ class SourceJavaScriptPreparation final : public jsi::PreparedJavaScript, std::string sourceURL_; public: - SourceJavaScriptPreparation(std::shared_ptr buf, - std::string sourceURL) + SourceJavaScriptPreparation( + std::shared_ptr buf, + std::string sourceURL) : buf_(std::move(buf)), sourceURL_(std::move(sourceURL)) {} - const std::string& sourceURL() const { return sourceURL_; } + const std::string& sourceURL() const { + return sourceURL_; + } - size_t size() const override { return buf_->size(); } - const uint8_t* data() const override { return buf_->data(); } + size_t size() const override { + return buf_->size(); + } + const uint8_t* data() const override { + return buf_->data(); + } }; -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/android-jsi/platforms/android/cpp/jsi/threadsafe.h b/packages/android-jsi/platforms/android/cpp/jsi/threadsafe.h index 768edb4ae..cb10a335f 100644 --- a/packages/android-jsi/platforms/android/cpp/jsi/threadsafe.h +++ b/packages/android-jsi/platforms/android/cpp/jsi/threadsafe.h @@ -9,8 +9,8 @@ #include -#include "jsi/decorator.h" -#include "jsi/jsi.h" +#include +#include namespace facebook { namespace jsi { @@ -28,8 +28,12 @@ template struct WithLock { L lock; WithLock(R& r) : lock(r) {} - void before() { lock.lock(); } - void after() { lock.unlock(); } + void before() { + lock.lock(); + } + void after() { + lock.unlock(); + } }; // The actual implementation of a given ThreadSafeRuntime. It's parameterized @@ -46,8 +50,9 @@ class ThreadSafeRuntimeImpl final public: template ThreadSafeRuntimeImpl(Args&&... args) - : WithRuntimeDecorator, R, ThreadSafeRuntime>(unsafe_, - lock_), + : WithRuntimeDecorator, R, ThreadSafeRuntime>( + unsafe_, + lock_), unsafe_(std::forward(args)...), lock_(unsafe_) {} @@ -55,16 +60,20 @@ class ThreadSafeRuntimeImpl final return WithRuntimeDecorator, R, ThreadSafeRuntime>::plain(); } - void lock() const override { lock_.before(); } + void lock() const override { + lock_.before(); + } - void unlock() const override { lock_.after(); } + void unlock() const override { + lock_.after(); + } private: R unsafe_; mutable WithLock lock_; }; -} // namespace detail +} // namespace detail -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp index d8aaa5847..6c5a98e2b 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp @@ -59,7 +59,7 @@ V8PointerValue* V8PointerValue::createFromUtf8(v8::Isolate* isolate, return new V8PointerValue(isolate, v8String); } -void V8PointerValue::invalidate() { +void V8PointerValue::invalidate() noexcept { { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.h b/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.h index 0ed63ff08..47831850e 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.h +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.h @@ -34,7 +34,7 @@ class V8PointerValue final : public V8Runtime::PointerValue { const uint8_t* str, size_t length); private: - void invalidate() override; + void invalidate() noexcept override; private: friend class JSIV8ValueConverter; diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/decorator.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/decorator.h index deaa86b73..f101c1832 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/decorator.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/decorator.h @@ -9,8 +9,8 @@ #include -#include "jsi/instrumentation.h" -#include "jsi/jsi.h" +#include +#include // This file contains objects to help API users create their own // runtime adapters, i.e. if you want to compose runtimes to add your @@ -27,10 +27,12 @@ class DecoratedHostFunction { DecoratedHostFunction(Runtime& drt, HostFunctionType plainHF) : drt_(drt), plainHF_(std::move(plainHF)) {} - Runtime& decoratedRuntime() { return drt_; } + Runtime& decoratedRuntime() { + return drt_; + } - Value operator()(Runtime&, const Value& thisVal, const Value* args, - size_t count) { + Value + operator()(Runtime&, const Value& thisVal, const Value* args, size_t count) { return plainHF_(decoratedRuntime(), thisVal, args, count); } @@ -60,7 +62,9 @@ class DecoratedHostObject : public HostObject { // The derived class methods can call this to get a reference to the // decorated runtime, since the rt passed to the callback will be // the plain runtime. - Runtime& decoratedRuntime() { return drt_; } + Runtime& decoratedRuntime() { + return drt_; + } Value get(Runtime&, const PropNameID& name) override { return plainHO_->get(decoratedRuntime(), name); @@ -99,14 +103,22 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { static_assert( std::is_base_of::value, "RuntimeDecorator's Plain type must derive from jsi::Runtime"); - static_assert(std::is_base_of::value, - "RuntimeDecorator's Base type must derive from jsi::Runtime"); + static_assert( + std::is_base_of::value, + "RuntimeDecorator's Base type must derive from jsi::Runtime"); + return plain_; + } + const Plain& plain() const { return plain_; } - const Plain& plain() const { return plain_; } - Value evaluateJavaScript(const std::shared_ptr& buffer, - const std::string& sourceURL) override { + ICast* castInterface(const UUID& interfaceUUID) override { + return plain().castInterface(interfaceUUID); + } + + Value evaluateJavaScript( + const std::shared_ptr& buffer, + const std::string& sourceURL) override { return plain().evaluateJavaScript(buffer, sourceURL); } std::shared_ptr prepareJavaScript( @@ -124,10 +136,18 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { bool drainMicrotasks(int maxMicrotasksHint) override { return plain().drainMicrotasks(maxMicrotasksHint); } - Object global() override { return plain().global(); } - std::string description() override { return plain().description(); }; - bool isInspectable() override { return plain().isInspectable(); }; - Instrumentation& instrumentation() override { return *this; } + Object global() override { + return plain().global(); + } + std::string description() override { + return plain().description(); + } + bool isInspectable() override { + return plain().isInspectable(); + } + Instrumentation& instrumentation() override { + return *this; + } protected: // plain is generally going to be a reference to an object managed @@ -140,39 +160,45 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { return plain_.cloneSymbol(pv); - }; + } Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { return plain_.cloneBigInt(pv); - }; + } Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { return plain_.cloneString(pv); - }; + } Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { return plain_.cloneObject(pv); - }; + } Runtime::PointerValue* clonePropNameID( const Runtime::PointerValue* pv) override { return plain_.clonePropNameID(pv); - }; + } - PropNameID createPropNameIDFromAscii(const char* str, - size_t length) override { + PropNameID createPropNameIDFromAscii(const char* str, size_t length) + override { return plain_.createPropNameIDFromAscii(str, length); - }; - PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, - size_t length) override { + } + PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) + override { return plain_.createPropNameIDFromUtf8(utf8, length); - }; + } PropNameID createPropNameIDFromString(const String& str) override { return plain_.createPropNameIDFromString(str); - }; + } + PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) + override { + return plain_.createPropNameIDFromUtf16(utf16, length); + } PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { return plain_.createPropNameIDFromSymbol(sym); - }; - std::string utf8(const PropNameID& id) override { return plain_.utf8(id); }; + } + std::string utf8(const PropNameID& id) override { + return plain_.utf8(id); + } bool compare(const PropNameID& a, const PropNameID& b) override { return plain_.compare(a, b); - }; + } std::string symbolToString(const Symbol& sym) override { return plain_.symbolToString(sym); @@ -190,33 +216,46 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { bool bigintIsUint64(const BigInt& b) override { return plain_.bigintIsUint64(b); } - uint64_t truncate(const BigInt& b) override { return plain_.truncate(b); } + uint64_t truncate(const BigInt& b) override { + return plain_.truncate(b); + } String bigintToString(const BigInt& bigint, int radix) override { return plain_.bigintToString(bigint, radix); } String createStringFromAscii(const char* str, size_t length) override { return plain_.createStringFromAscii(str, length); - }; + } String createStringFromUtf8(const uint8_t* utf8, size_t length) override { return plain_.createStringFromUtf8(utf8, length); - }; - std::string utf8(const String& s) override { return plain_.utf8(s); } + } + String createStringFromUtf16(const char16_t* utf16, size_t length) override { + return plain_.createStringFromUtf16(utf16, length); + } + std::string utf8(const String& s) override { + return plain_.utf8(s); + } - std::u16string utf16(const String& str) override { return plain_.utf16(str); } + std::u16string utf16(const String& str) override { + return plain_.utf16(str); + } std::u16string utf16(const PropNameID& sym) override { return plain_.utf16(sym); } - void getStringData(const jsi::String& str, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)) override { + void getStringData( + const jsi::String& str, + void* ctx, + void ( + *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { plain_.getStringData(str, ctx, cb); } - void getPropNameIdData(const jsi::PropNameID& sym, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)) override { + void getPropNameIdData( + const jsi::PropNameID& sym, + void* ctx, + void ( + *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { plain_.getPropNameIdData(sym, ctx, cb); } @@ -224,22 +263,24 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { return plain_.createObjectWithPrototype(prototype); } - Object createObject() override { return plain_.createObject(); }; + Object createObject() override { + return plain_.createObject(); + } Object createObject(std::shared_ptr ho) override { return plain_.createObject( std::make_shared(*this, std::move(ho))); - }; + } std::shared_ptr getHostObject(const jsi::Object& o) override { std::shared_ptr dho = plain_.getHostObject(o); return static_cast(*dho).plainHO_; - }; + } HostFunctionType& getHostFunction(const jsi::Function& f) override { HostFunctionType& dhf = plain_.getHostFunction(f); // This will fail if a cpp file including this header is not compiled // with RTTI. return dhf.target()->plainHF_; - }; + } bool hasNativeState(const Object& o) override { return plain_.hasNativeState(o); @@ -247,8 +288,8 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { std::shared_ptr getNativeState(const Object& o) override { return plain_.getNativeState(o); } - void setNativeState(const Object& o, - std::shared_ptr state) override { + void setNativeState(const Object& o, std::shared_ptr state) + override { plain_.setNativeState(o, state); } @@ -266,102 +307,195 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { Value getProperty(const Object& o, const PropNameID& name) override { return plain_.getProperty(o, name); - }; + } Value getProperty(const Object& o, const String& name) override { return plain_.getProperty(o, name); - }; + } + Value getProperty(const Object& o, const Value& name) override { + return plain_.getProperty(o, name); + } bool hasProperty(const Object& o, const PropNameID& name) override { return plain_.hasProperty(o, name); - }; + } bool hasProperty(const Object& o, const String& name) override { return plain_.hasProperty(o, name); - }; - void setPropertyValue(const Object& o, const PropNameID& name, - const Value& value) override { + } + bool hasProperty(const Object& o, const Value& name) override { + return plain_.hasProperty(o, name); + } + void setPropertyValue( + const Object& o, + const PropNameID& name, + const Value& value) override { plain_.setPropertyValue(o, name, value); - }; - void setPropertyValue(const Object& o, const String& name, - const Value& value) override { + } + void setPropertyValue(const Object& o, const String& name, const Value& value) + override { plain_.setPropertyValue(o, name, value); - }; + } + void setPropertyValue(const Object& o, const Value& name, const Value& value) + override { + plain_.setPropertyValue(o, name, value); + } - bool isArray(const Object& o) const override { return plain_.isArray(o); }; + void deleteProperty(const Object& object, const PropNameID& name) override { + plain_.deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const String& name) override { + plain_.deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const Value& name) override { + plain_.deleteProperty(object, name); + } + + bool isArray(const Object& o) const override { + return plain_.isArray(o); + } bool isArrayBuffer(const Object& o) const override { return plain_.isArrayBuffer(o); - }; + } + bool isTypedArray(const Object& o) const override { + return plain_.isTypedArray(o); + } + bool isUint8Array(const Object& o) const override { + return plain_.isUint8Array(o); + } bool isFunction(const Object& o) const override { return plain_.isFunction(o); - }; + } bool isHostObject(const jsi::Object& o) const override { return plain_.isHostObject(o); - }; + } bool isHostFunction(const jsi::Function& f) const override { return plain_.isHostFunction(f); - }; + } Array getPropertyNames(const Object& o) override { return plain_.getPropertyNames(o); - }; + } WeakObject createWeakObject(const Object& o) override { return plain_.createWeakObject(o); - }; + } Value lockWeakObject(const WeakObject& wo) override { return plain_.lockWeakObject(wo); - }; + } Array createArray(size_t length) override { return plain_.createArray(length); - }; + } ArrayBuffer createArrayBuffer( std::shared_ptr buffer) override { return plain_.createArrayBuffer(std::move(buffer)); - }; - size_t size(const Array& a) override { return plain_.size(a); }; - size_t size(const ArrayBuffer& ab) override { return plain_.size(ab); }; - uint8_t* data(const ArrayBuffer& ab) override { return plain_.data(ab); }; + } + size_t size(const Array& a) override { + return plain_.size(a); + } + size_t size(const ArrayBuffer& ab) override { + return plain_.size(ab); + } + uint8_t* data(const ArrayBuffer& ab) override { + return plain_.data(ab); + } + bool detached(const ArrayBuffer& ab) override { + return plain_.detached(ab); + } Value getValueAtIndex(const Array& a, size_t i) override { return plain_.getValueAtIndex(a, i); - }; - void setValueAtIndexImpl(const Array& a, size_t i, - const Value& value) override { + } + void setValueAtIndexImpl(const Array& a, size_t i, const Value& value) + override { plain_.setValueAtIndexImpl(a, i, value); - }; + } - Function createFunctionFromHostFunction(const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) override { + size_t push(const Array& a, const Value* elements, size_t count) override { + return plain_.push(a, elements, count); + } + + Function createFunctionFromHostFunction( + const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) override { return plain_.createFunctionFromHostFunction( name, paramCount, DecoratedHostFunction(*this, std::move(func))); - }; - Value call(const Function& f, const Value& jsThis, const Value* args, - size_t count) override { + } + Value call( + const Function& f, + const Value& jsThis, + const Value* args, + size_t count) override { return plain_.call(f, jsThis, args, count); - }; - Value callAsConstructor(const Function& f, const Value* args, - size_t count) override { + } + Value callAsConstructor(const Function& f, const Value* args, size_t count) + override { return plain_.callAsConstructor(f, args, count); - }; + } + + void setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)) override { + return plain_.setRuntimeDataImpl(uuid, data, deleter); + } + + const void* getRuntimeDataImpl(const UUID& uuid) override { + return plain_.getRuntimeDataImpl(uuid); + } + + std::shared_ptr tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) override { + return plain_.tryGetMutableBuffer(arrayBuffer); + } + + ArrayBuffer buffer(const TypedArray& typedArray) override { + return plain_.buffer(typedArray); + } + size_t byteOffset(const TypedArray& typedArray) override { + return plain_.byteOffset(typedArray); + } + size_t byteLength(const TypedArray& typedArray) override { + return plain_.byteLength(typedArray); + } + size_t length(const TypedArray& typedArray) override { + return plain_.length(typedArray); + } + + Uint8Array createUint8Array(size_t length) override { + return plain_.createUint8Array(length); + } + + Uint8Array createUint8Array( + const ArrayBuffer& buffer, + size_t offset, + size_t length) override { + return plain_.createUint8Array(buffer, offset, length); + } // Private data for managing scopes. - Runtime::ScopeState* pushScope() override { return plain_.pushScope(); } - void popScope(Runtime::ScopeState* ss) override { plain_.popScope(ss); } + Runtime::ScopeState* pushScope() override { + return plain_.pushScope(); + } + void popScope(Runtime::ScopeState* ss) override { + plain_.popScope(ss); + } bool strictEquals(const Symbol& a, const Symbol& b) const override { return plain_.strictEquals(a, b); - }; + } bool strictEquals(const BigInt& a, const BigInt& b) const override { return plain_.strictEquals(a, b); - }; + } bool strictEquals(const String& a, const String& b) const override { return plain_.strictEquals(a, b); - }; + } bool strictEquals(const Object& a, const Object& b) const override { return plain_.strictEquals(a, b); - }; + } bool instanceOf(const Object& o, const Function& f) override { return plain_.instanceOf(o, f); - }; + } // jsi::Instrumentation methods @@ -379,9 +513,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { } void startTrackingHeapObjectStackTraces( - std::function)> - callback) override { + std::function)> callback) override { plain().instrumentation().startTrackingHeapObjectStackTraces( std::move(callback)); } @@ -398,13 +533,15 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { plain().instrumentation().stopHeapSampling(os); } - void createSnapshotToFile(const std::string& path, - const HeapSnapshotOptions& options) override { + void createSnapshotToFile( + const std::string& path, + const HeapSnapshotOptions& options) override { plain().instrumentation().createSnapshotToFile(path, options); } - void createSnapshotToStream(std::ostream& os, - const HeapSnapshotOptions& options) override { + void createSnapshotToStream( + std::ostream& os, + const HeapSnapshotOptions& options) override { plain().instrumentation().createSnapshotToStream(os, options); } @@ -421,6 +558,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { .writeBasicBlockProfileTraceToFile(fileName); } + void dumpOpcodeStats(std::ostream& os) const override { + const_cast(plain()).instrumentation().dumpOpcodeStats(os); + } + /// Dump external profiler symbols to the given file name. void dumpProfilerSymbolsToFile(const std::string& fileName) const override { const_cast(plain()).instrumentation().dumpProfilerSymbolsToFile( @@ -452,12 +593,16 @@ struct AfterCaller { // default above exists, and that is used instead. template struct BeforeCaller { - static void before(T& t) { t.before(); } + static void before(T& t) { + t.before(); + } }; template struct AfterCaller { - static void after(T& t) { t.after(); } + static void after(T& t) { + t.after(); + } }; // It's possible to use multiple decorators by nesting @@ -466,7 +611,9 @@ struct AfterCaller { // example. template struct BeforeCaller> { - static void before(std::tuple& tuple) { all_before<0, T...>(tuple); } + static void before(std::tuple& tuple) { + all_before<0, T...>(tuple); + } private: template @@ -481,7 +628,9 @@ struct BeforeCaller> { template struct AfterCaller> { - static void after(std::tuple& tuple) { all_after<0, T...>(tuple); } + static void after(std::tuple& tuple) { + all_after<0, T...>(tuple); + } private: template @@ -494,7 +643,7 @@ struct AfterCaller> { static void all_after(std::tuple&) {} }; -} // namespace detail +} // namespace detail // A decorator which implements an around idiom. A With instance is // RAII constructed before each call to the undecorated class; the @@ -510,8 +659,14 @@ class WithRuntimeDecorator : public RuntimeDecorator { // the derived class. WithRuntimeDecorator(Plain& plain, With& with) : RD(plain), with_(with) {} - Value evaluateJavaScript(const std::shared_ptr& buffer, - const std::string& sourceURL) override { + ICast* castInterface(const UUID& interfaceUUID) override { + Around around{with_}; + return RD::castInterface(interfaceUUID); + } + + Value evaluateJavaScript( + const std::shared_ptr& buffer, + const std::string& sourceURL) override { Around around{with_}; return RD::evaluateJavaScript(buffer, sourceURL); } @@ -541,11 +696,11 @@ class WithRuntimeDecorator : public RuntimeDecorator { std::string description() override { Around around{with_}; return RD::description(); - }; + } bool isInspectable() override { Around around{with_}; return RD::isInspectable(); - }; + } // The jsi:: prefix is necessary because MSVC compiler complains C2247: // Instrumentation is not accessible because RuntimeDecorator uses private @@ -560,90 +715,99 @@ class WithRuntimeDecorator : public RuntimeDecorator { Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { Around around{with_}; return RD::cloneSymbol(pv); - }; + } Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { Around around{with_}; return RD::cloneBigInt(pv); - }; + } Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { Around around{with_}; return RD::cloneString(pv); - }; + } Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { Around around{with_}; return RD::cloneObject(pv); - }; + } Runtime::PointerValue* clonePropNameID( const Runtime::PointerValue* pv) override { Around around{with_}; return RD::clonePropNameID(pv); - }; + } - PropNameID createPropNameIDFromAscii(const char* str, - size_t length) override { + PropNameID createPropNameIDFromAscii(const char* str, size_t length) + override { Around around{with_}; return RD::createPropNameIDFromAscii(str, length); - }; - PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, - size_t length) override { + } + PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) + override { Around around{with_}; return RD::createPropNameIDFromUtf8(utf8, length); - }; + } + PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) + override { + Around around{with_}; + return RD::createPropNameIDFromUtf16(utf16, length); + } PropNameID createPropNameIDFromString(const String& str) override { Around around{with_}; return RD::createPropNameIDFromString(str); - }; + } PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { Around around{with_}; return RD::createPropNameIDFromSymbol(sym); - }; + } std::string utf8(const PropNameID& id) override { Around around{with_}; return RD::utf8(id); - }; + } bool compare(const PropNameID& a, const PropNameID& b) override { Around around{with_}; return RD::compare(a, b); - }; + } std::string symbolToString(const Symbol& sym) override { Around around{with_}; return RD::symbolToString(sym); - }; + } BigInt createBigIntFromInt64(int64_t i) override { Around around{with_}; return RD::createBigIntFromInt64(i); - }; + } BigInt createBigIntFromUint64(uint64_t i) override { Around around{with_}; return RD::createBigIntFromUint64(i); - }; + } bool bigintIsInt64(const BigInt& bi) override { Around around{with_}; return RD::bigintIsInt64(bi); - }; + } bool bigintIsUint64(const BigInt& bi) override { Around around{with_}; return RD::bigintIsUint64(bi); - }; + } uint64_t truncate(const BigInt& bi) override { Around around{with_}; return RD::truncate(bi); - }; + } String bigintToString(const BigInt& bi, int i) override { Around around{with_}; return RD::bigintToString(bi, i); - }; + } String createStringFromAscii(const char* str, size_t length) override { Around around{with_}; return RD::createStringFromAscii(str, length); - }; + } String createStringFromUtf8(const uint8_t* utf8, size_t length) override { Around around{with_}; return RD::createStringFromUtf8(utf8, length); - }; + } + String createStringFromUtf16(const char16_t* utf16, size_t length) override { + Around around{with_}; + return RD::createStringFromUtf16(utf16, length); + } std::string utf8(const String& s) override { Around around{with_}; return RD::utf8(s); @@ -658,16 +822,20 @@ class WithRuntimeDecorator : public RuntimeDecorator { return RD::utf16(sym); } - void getStringData(const jsi::String& str, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)) override { + void getStringData( + const jsi::String& str, + void* ctx, + void ( + *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { Around around{with_}; RD::getStringData(str, ctx, cb); } - void getPropNameIdData(const jsi::PropNameID& sym, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)) override { + void getPropNameIdData( + const jsi::PropNameID& sym, + void* ctx, + void ( + *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { Around around{with_}; RD::getPropNameIdData(sym, ctx, cb); } @@ -675,7 +843,7 @@ class WithRuntimeDecorator : public RuntimeDecorator { Value createValueFromJsonUtf8(const uint8_t* json, size_t length) override { Around around{with_}; return RD::createValueFromJsonUtf8(json, length); - }; + } Object createObjectWithPrototype(const Value& prototype) override { Around around{with_}; @@ -685,33 +853,33 @@ class WithRuntimeDecorator : public RuntimeDecorator { Object createObject() override { Around around{with_}; return RD::createObject(); - }; + } Object createObject(std::shared_ptr ho) override { Around around{with_}; return RD::createObject(std::move(ho)); - }; + } std::shared_ptr getHostObject(const jsi::Object& o) override { Around around{with_}; return RD::getHostObject(o); - }; + } HostFunctionType& getHostFunction(const jsi::Function& f) override { Around around{with_}; return RD::getHostFunction(f); - }; + } bool hasNativeState(const Object& o) override { Around around{with_}; return RD::hasNativeState(o); - }; + } std::shared_ptr getNativeState(const Object& o) override { Around around{with_}; return RD::getNativeState(o); - }; - void setNativeState(const Object& o, - std::shared_ptr state) override { + } + void setNativeState(const Object& o, std::shared_ptr state) + override { Around around{with_}; RD::setNativeState(o, state); - }; + } void setPrototypeOf(const Object& object, const Value& prototype) override { Around around{with_}; @@ -726,111 +894,196 @@ class WithRuntimeDecorator : public RuntimeDecorator { Value getProperty(const Object& o, const PropNameID& name) override { Around around{with_}; return RD::getProperty(o, name); - }; + } Value getProperty(const Object& o, const String& name) override { Around around{with_}; return RD::getProperty(o, name); - }; + } + Value getProperty(const Object& o, const Value& name) override { + Around around{with_}; + return RD::getProperty(o, name); + } bool hasProperty(const Object& o, const PropNameID& name) override { Around around{with_}; return RD::hasProperty(o, name); - }; + } bool hasProperty(const Object& o, const String& name) override { Around around{with_}; return RD::hasProperty(o, name); - }; - void setPropertyValue(const Object& o, const PropNameID& name, - const Value& value) override { + } + bool hasProperty(const Object& o, const Value& name) override { + Around around{with_}; + return RD::hasProperty(o, name); + } + void setPropertyValue( + const Object& o, + const PropNameID& name, + const Value& value) override { Around around{with_}; RD::setPropertyValue(o, name, value); - }; - void setPropertyValue(const Object& o, const String& name, - const Value& value) override { + } + void setPropertyValue(const Object& o, const String& name, const Value& value) + override { Around around{with_}; RD::setPropertyValue(o, name, value); - }; + } + void setPropertyValue(const Object& o, const Value& name, const Value& value) + override { + Around around{with_}; + RD::setPropertyValue(o, name, value); + } + + void deleteProperty(const Object& object, const PropNameID& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const String& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const Value& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } bool isArray(const Object& o) const override { Around around{with_}; return RD::isArray(o); - }; + } bool isArrayBuffer(const Object& o) const override { Around around{with_}; return RD::isArrayBuffer(o); - }; + } + bool isTypedArray(const Object& o) const override { + Around around{with_}; + return RD::isTypedArray(o); + } + bool isUint8Array(const Object& o) const override { + Around around{with_}; + return RD::isUint8Array(o); + } bool isFunction(const Object& o) const override { Around around{with_}; return RD::isFunction(o); - }; + } bool isHostObject(const jsi::Object& o) const override { Around around{with_}; return RD::isHostObject(o); - }; + } bool isHostFunction(const jsi::Function& f) const override { Around around{with_}; return RD::isHostFunction(f); - }; + } Array getPropertyNames(const Object& o) override { Around around{with_}; return RD::getPropertyNames(o); - }; + } WeakObject createWeakObject(const Object& o) override { Around around{with_}; return RD::createWeakObject(o); - }; + } Value lockWeakObject(const WeakObject& wo) override { Around around{with_}; return RD::lockWeakObject(wo); - }; + } Array createArray(size_t length) override { Around around{with_}; return RD::createArray(length); - }; + } ArrayBuffer createArrayBuffer( std::shared_ptr buffer) override { return RD::createArrayBuffer(std::move(buffer)); - }; + } size_t size(const Array& a) override { Around around{with_}; return RD::size(a); - }; + } size_t size(const ArrayBuffer& ab) override { Around around{with_}; return RD::size(ab); - }; + } uint8_t* data(const ArrayBuffer& ab) override { Around around{with_}; return RD::data(ab); - }; + } + bool detached(const ArrayBuffer& ab) override { + Around around{with_}; + return RD::detached(ab); + } Value getValueAtIndex(const Array& a, size_t i) override { Around around{with_}; return RD::getValueAtIndex(a, i); - }; - void setValueAtIndexImpl(const Array& a, size_t i, - const Value& value) override { + } + void setValueAtIndexImpl(const Array& a, size_t i, const Value& value) + override { Around around{with_}; RD::setValueAtIndexImpl(a, i, value); - }; + } + size_t push(const Array& a, const Value* elements, size_t count) override { + Around around{with_}; + return RD::push(a, elements, count); + } - Function createFunctionFromHostFunction(const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) override { + Function createFunctionFromHostFunction( + const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) override { Around around{with_}; - return RD::createFunctionFromHostFunction(name, paramCount, - std::move(func)); - }; - Value call(const Function& f, const Value& jsThis, const Value* args, - size_t count) override { + return RD::createFunctionFromHostFunction( + name, paramCount, std::move(func)); + } + Value call( + const Function& f, + const Value& jsThis, + const Value* args, + size_t count) override { Around around{with_}; return RD::call(f, jsThis, args, count); - }; - Value callAsConstructor(const Function& f, const Value* args, - size_t count) override { + } + Value callAsConstructor(const Function& f, const Value* args, size_t count) + override { Around around{with_}; return RD::callAsConstructor(f, args, count); - }; + } + + std::shared_ptr tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) override { + Around around{with_}; + return RD::tryGetMutableBuffer(arrayBuffer); + } + + ArrayBuffer buffer(const TypedArray& typedArray) override { + Around around{with_}; + return RD::buffer(typedArray); + } + size_t byteOffset(const TypedArray& typedArray) override { + Around around{with_}; + return RD::byteOffset(typedArray); + } + size_t byteLength(const TypedArray& typedArray) override { + Around around{with_}; + return RD::byteLength(typedArray); + } + size_t length(const TypedArray& typedArray) override { + Around around{with_}; + return RD::length(typedArray); + } + + Uint8Array createUint8Array(size_t length) override { + Around around{with_}; + return RD::createUint8Array(length); + } + Uint8Array createUint8Array( + const ArrayBuffer& buffer, + size_t offset, + size_t length) override { + Around around{with_}; + return RD::createUint8Array(buffer, offset, length); + } // Private data for managing scopes. Runtime::ScopeState* pushScope() override { @@ -845,31 +1098,44 @@ class WithRuntimeDecorator : public RuntimeDecorator { bool strictEquals(const Symbol& a, const Symbol& b) const override { Around around{with_}; return RD::strictEquals(a, b); - }; + } bool strictEquals(const BigInt& a, const BigInt& b) const override { Around around{with_}; return RD::strictEquals(a, b); - }; + } bool strictEquals(const String& a, const String& b) const override { Around around{with_}; return RD::strictEquals(a, b); - }; + } bool strictEquals(const Object& a, const Object& b) const override { Around around{with_}; return RD::strictEquals(a, b); - }; + } bool instanceOf(const Object& o, const Function& f) override { Around around{with_}; return RD::instanceOf(o, f); - }; + } - void setExternalMemoryPressure(const jsi::Object& obj, - size_t amount) override { + void setExternalMemoryPressure(const jsi::Object& obj, size_t amount) + override { Around around{with_}; RD::setExternalMemoryPressure(obj, amount); - }; + } + + void setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)) override { + Around around{with_}; + RD::setRuntimeDataImpl(uuid, data, deleter); + } + + const void* getRuntimeDataImpl(const UUID& uuid) override { + Around around{with_}; + return RD::getRuntimeDataImpl(uuid); + } private: // Wrap an RAII type around With& to guarantee after always happens. @@ -877,7 +1143,9 @@ class WithRuntimeDecorator : public RuntimeDecorator { Around(With& with) : with_(with) { detail::BeforeCaller::before(with_); } - ~Around() { detail::AfterCaller::after(with_); } + ~Around() { + detail::AfterCaller::after(with_); + } With& with_; }; @@ -885,5 +1153,5 @@ class WithRuntimeDecorator : public RuntimeDecorator { With& with_; }; -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/instrumentation.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/instrumentation.h index db22f4a41..4a88951f6 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/instrumentation.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/instrumentation.h @@ -13,7 +13,7 @@ #include #include -#include "jsi/jsi.h" +#include namespace facebook { namespace jsi { @@ -74,10 +74,10 @@ class JSI_EXPORT Instrumentation { /// been updated. This callback will be invoked on the same thread that the /// runtime is using. virtual void startTrackingHeapObjectStackTraces( - std::function stats)> - fragmentCallback) = 0; + std::function stats)> fragmentCallback) = 0; /// Stop capture JS stack-traces for JS heap allocated objects. virtual void stopTrackingHeapObjectStackTraces() = 0; @@ -99,17 +99,17 @@ class JSI_EXPORT Instrumentation { /// /// \param path to save the heap capture. /// \param options additional options for what to capture. - virtual void createSnapshotToFile(const std::string& path, - const HeapSnapshotOptions& options = { - false}) = 0; + virtual void createSnapshotToFile( + const std::string& path, + const HeapSnapshotOptions& options = {false}) = 0; /// Captures the heap to an output stream /// /// \param os output stream to write to. /// \param options additional options for what to capture. - virtual void createSnapshotToStream(std::ostream& os, - const HeapSnapshotOptions& options = { - false}) = 0; + virtual void createSnapshotToStream( + std::ostream& os, + const HeapSnapshotOptions& options = {false}) = 0; /// If the runtime has been created to trace to a temp file, flush /// any unwritten parts of the trace of bridge traffic to the file, @@ -121,9 +121,12 @@ class JSI_EXPORT Instrumentation { virtual void writeBasicBlockProfileTraceToFile( const std::string& fileName) const = 0; + /// Write the opcode stats to the given stream. + virtual void dumpOpcodeStats(std::ostream& os) const = 0; + /// Dump external profiler symbols to the given file name. virtual void dumpProfilerSymbolsToFile(const std::string& fileName) const = 0; }; -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsi-inl.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsi-inl.h index 8f30c0566..6a29f6479 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsi-inl.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsi-inl.h @@ -11,37 +11,48 @@ namespace facebook { namespace jsi { namespace detail { -inline Value toValue(Runtime&, std::nullptr_t) { return Value::null(); } -inline Value toValue(Runtime&, bool b) { return Value(b); } -inline Value toValue(Runtime&, double d) { return Value(d); } -inline Value toValue(Runtime&, float f) { +inline Value toValue(IRuntime&, std::nullptr_t) { + return Value::null(); +} +inline Value toValue(IRuntime&, bool b) { + return Value(b); +} +inline Value toValue(IRuntime&, double d) { + return Value(d); +} +inline Value toValue(IRuntime&, float f) { return Value(static_cast(f)); } -inline Value toValue(Runtime&, int i) { return Value(i); } -inline Value toValue(Runtime& runtime, const char* str) { +inline Value toValue(IRuntime&, int i) { + return Value(i); +} +inline Value toValue(IRuntime& runtime, const char* str) { return String::createFromAscii(runtime, str); } -inline Value toValue(Runtime& runtime, const std::string& str) { +inline Value toValue(IRuntime& runtime, const std::string& str) { return String::createFromUtf8(runtime, str); } template -inline Value toValue(Runtime& runtime, const T& other) { - static_assert(std::is_base_of::value, - "This type cannot be converted to Value"); +inline Value toValue(IRuntime& runtime, const T& other) { + static_assert( + std::is_base_of::value, + "This type cannot be converted to Value"); return Value(runtime, other); } -inline Value toValue(Runtime& runtime, const Value& value) { +inline Value toValue(IRuntime& runtime, const Value& value) { return Value(runtime, value); } -inline Value&& toValue(Runtime&, Value&& value) { return std::move(value); } +inline Value&& toValue(IRuntime&, Value&& value) { + return std::move(value); +} -inline PropNameID toPropNameID(Runtime& runtime, const char* name) { +inline PropNameID toPropNameID(IRuntime& runtime, const char* name) { return PropNameID::forAscii(runtime, name); } -inline PropNameID toPropNameID(Runtime& runtime, const std::string& name) { +inline PropNameID toPropNameID(IRuntime& runtime, const std::string& name) { return PropNameID::forUtf8(runtime, name); } -inline PropNameID&& toPropNameID(Runtime&, PropNameID&& name) { +inline PropNameID&& toPropNameID(IRuntime&, PropNameID&& name) { return std::move(name); } @@ -52,7 +63,7 @@ template std::make_exception_ptr(E{std::forward(args)...})); } -} // namespace detail +} // namespace detail template inline T Runtime::make(Runtime::PointerValue* pv) { @@ -73,116 +84,173 @@ inline const Runtime::PointerValue* Runtime::getPointerValue( return value.data_.pointer.ptr_; } -Value Object::getPrototype(Runtime& runtime) const { +inline void Runtime::setRuntimeData( + const UUID& dataUUID, + const std::shared_ptr& data) { + auto* dataPtr = new std::shared_ptr(data); + setRuntimeDataImpl(dataUUID, dataPtr, [](const void* data) { + delete (const std::shared_ptr*)data; + }); +} + +inline std::shared_ptr Runtime::getRuntimeData(const UUID& dataUUID) { + auto* data = (const std::shared_ptr*)getRuntimeDataImpl(dataUUID); + return data ? *data : nullptr; +} + +Value Object::getPrototype(IRuntime& runtime) const { return runtime.getPrototypeOf(*this); } -inline Value Object::getProperty(Runtime& runtime, const char* name) const { +inline Value Object::getProperty(IRuntime& runtime, const char* name) const { return getProperty(runtime, String::createFromAscii(runtime, name)); } -inline Value Object::getProperty(Runtime& runtime, const String& name) const { +inline Value Object::getProperty(IRuntime& runtime, const String& name) const { + return runtime.getProperty(*this, name); +} + +inline Value Object::getProperty(IRuntime& runtime, const PropNameID& name) + const { return runtime.getProperty(*this, name); } -inline Value Object::getProperty(Runtime& runtime, - const PropNameID& name) const { +inline Value Object::getProperty(IRuntime& runtime, const Value& name) const { return runtime.getProperty(*this, name); } -inline bool Object::hasProperty(Runtime& runtime, const char* name) const { +inline bool Object::hasProperty(IRuntime& runtime, const char* name) const { return hasProperty(runtime, String::createFromAscii(runtime, name)); } -inline bool Object::hasProperty(Runtime& runtime, const String& name) const { +inline bool Object::hasProperty(IRuntime& runtime, const String& name) const { + return runtime.hasProperty(*this, name); +} + +inline bool Object::hasProperty(IRuntime& runtime, const PropNameID& name) + const { return runtime.hasProperty(*this, name); } -inline bool Object::hasProperty(Runtime& runtime, - const PropNameID& name) const { +inline bool Object::hasProperty(IRuntime& runtime, const Value& name) const { return runtime.hasProperty(*this, name); } template -void Object::setProperty(Runtime& runtime, const char* name, T&& value) const { - setProperty(runtime, String::createFromAscii(runtime, name), - std::forward(value)); +void Object::setProperty(IRuntime& runtime, const char* name, T&& value) const { + setProperty( + runtime, String::createFromAscii(runtime, name), std::forward(value)); } template -void Object::setProperty(Runtime& runtime, const String& name, - T&& value) const { - setPropertyValue(runtime, name, - detail::toValue(runtime, std::forward(value))); +void Object::setProperty(IRuntime& runtime, const String& name, T&& value) + const { + setPropertyValue( + runtime, name, detail::toValue(runtime, std::forward(value))); } template -void Object::setProperty(Runtime& runtime, const PropNameID& name, - T&& value) const { - setPropertyValue(runtime, name, - detail::toValue(runtime, std::forward(value))); +void Object::setProperty(IRuntime& runtime, const PropNameID& name, T&& value) + const { + setPropertyValue( + runtime, name, detail::toValue(runtime, std::forward(value))); +} + +template +void Object::setProperty(IRuntime& runtime, const Value& name, T&& value) + const { + setPropertyValue( + runtime, name, detail::toValue(runtime, std::forward(value))); +} + +inline void Object::deleteProperty(IRuntime& runtime, const char* name) const { + deleteProperty(runtime, String::createFromAscii(runtime, name)); } -inline Array Object::getArray(Runtime& runtime) const& { +inline void Object::deleteProperty(IRuntime& runtime, const String& name) + const { + runtime.deleteProperty(*this, name); +} + +inline void Object::deleteProperty(IRuntime& runtime, const PropNameID& name) + const { + runtime.deleteProperty(*this, name); +} + +inline void Object::deleteProperty(IRuntime& runtime, const Value& name) const { + runtime.deleteProperty(*this, name); +} + +inline Array Object::getArray(IRuntime& runtime) const& { assert(runtime.isArray(*this)); - (void)runtime; // when assert is disabled we need to mark this as used + (void)runtime; // when assert is disabled we need to mark this as used return Array(runtime.cloneObject(ptr_)); } -inline Array Object::getArray(Runtime& runtime) && { +inline Array Object::getArray(IRuntime& runtime) && { assert(runtime.isArray(*this)); - (void)runtime; // when assert is disabled we need to mark this as used + (void)runtime; // when assert is disabled we need to mark this as used Runtime::PointerValue* value = ptr_; ptr_ = nullptr; return Array(value); } -inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const& { +inline ArrayBuffer Object::getArrayBuffer(IRuntime& runtime) const& { assert(runtime.isArrayBuffer(*this)); - (void)runtime; // when assert is disabled we need to mark this as used + (void)runtime; // when assert is disabled we need to mark this as used return ArrayBuffer(runtime.cloneObject(ptr_)); } -inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) && { +inline ArrayBuffer Object::getArrayBuffer(IRuntime& runtime) && { assert(runtime.isArrayBuffer(*this)); - (void)runtime; // when assert is disabled we need to mark this as used + (void)runtime; // when assert is disabled we need to mark this as used Runtime::PointerValue* value = ptr_; ptr_ = nullptr; return ArrayBuffer(value); } -inline Function Object::getFunction(Runtime& runtime) const& { +inline TypedArray Object::getTypedArray(IRuntime& runtime) const& { + assert(runtime.isTypedArray(*this)); + return TypedArray(runtime.cloneObject(ptr_)); +} + +inline Uint8Array Object::getUint8Array(IRuntime& runtime) const& { + assert(runtime.isUint8Array(*this)); + return Uint8Array(runtime.cloneObject(ptr_)); +} + +inline Function Object::getFunction(IRuntime& runtime) const& { assert(runtime.isFunction(*this)); return Function(runtime.cloneObject(ptr_)); } -inline Function Object::getFunction(Runtime& runtime) && { +inline Function Object::getFunction(IRuntime& runtime) && { assert(runtime.isFunction(*this)); - (void)runtime; // when assert is disabled we need to mark this as used + (void)runtime; // when assert is disabled we need to mark this as used Runtime::PointerValue* value = ptr_; ptr_ = nullptr; return Function(value); } template -inline bool Object::isHostObject(Runtime& runtime) const { +inline bool Object::isHostObject(IRuntime& runtime) const { return runtime.isHostObject(*this) && - std::dynamic_pointer_cast(runtime.getHostObject(*this)); + std::dynamic_pointer_cast(runtime.getHostObject(*this)); } template <> -inline bool Object::isHostObject(Runtime& runtime) const { +inline bool Object::isHostObject(IRuntime& runtime) const { return runtime.isHostObject(*this); } template -inline std::shared_ptr Object::getHostObject(Runtime& runtime) const { +inline std::shared_ptr Object::getHostObject(IRuntime& runtime) const { assert(isHostObject(runtime)); return std::static_pointer_cast(runtime.getHostObject(*this)); } template -inline std::shared_ptr Object::asHostObject(Runtime& runtime) const { +inline std::shared_ptr Object::asHostObject(IRuntime& runtime) const { if (!isHostObject(runtime)) { detail::throwOrDie( "Object is not a HostObject of desired type"); @@ -192,117 +260,144 @@ inline std::shared_ptr Object::asHostObject(Runtime& runtime) const { template <> inline std::shared_ptr Object::getHostObject( - Runtime& runtime) const { + IRuntime& runtime) const { assert(runtime.isHostObject(*this)); return runtime.getHostObject(*this); } template -inline bool Object::hasNativeState(Runtime& runtime) const { +inline bool Object::hasNativeState(IRuntime& runtime) const { return runtime.hasNativeState(*this) && - std::dynamic_pointer_cast(runtime.getNativeState(*this)); + std::dynamic_pointer_cast(runtime.getNativeState(*this)); } template <> -inline bool Object::hasNativeState(Runtime& runtime) const { +inline bool Object::hasNativeState(IRuntime& runtime) const { return runtime.hasNativeState(*this); } template -inline std::shared_ptr Object::getNativeState(Runtime& runtime) const { +inline std::shared_ptr Object::getNativeState(IRuntime& runtime) const { assert(hasNativeState(runtime)); return std::static_pointer_cast(runtime.getNativeState(*this)); } -inline void Object::setNativeState(Runtime& runtime, - std::shared_ptr state) const { +inline void Object::setNativeState( + IRuntime& runtime, + std::shared_ptr state) const { runtime.setNativeState(*this, state); } -inline void Object::setExternalMemoryPressure(Runtime& runtime, - size_t amt) const { +inline void Object::setExternalMemoryPressure(IRuntime& runtime, size_t amt) + const { runtime.setExternalMemoryPressure(*this, amt); } -inline Array Object::getPropertyNames(Runtime& runtime) const { +inline Array Object::getPropertyNames(IRuntime& runtime) const { return runtime.getPropertyNames(*this); } -inline Value WeakObject::lock(Runtime& runtime) const { +inline Value WeakObject::lock(IRuntime& runtime) const { return runtime.lockWeakObject(*this); } template -void Array::setValueAtIndex(Runtime& runtime, size_t i, T&& value) const { - setValueAtIndexImpl(runtime, i, - detail::toValue(runtime, std::forward(value))); +void Array::setValueAtIndex(IRuntime& runtime, size_t i, T&& value) const { + setValueAtIndexImpl( + runtime, i, detail::toValue(runtime, std::forward(value))); } -inline Value Array::getValueAtIndex(Runtime& runtime, size_t i) const { +inline Value Array::getValueAtIndex(IRuntime& runtime, size_t i) const { return runtime.getValueAtIndex(*this, i); } -inline Function Function::createFromHostFunction(Runtime& runtime, - const jsi::PropNameID& name, - unsigned int paramCount, - jsi::HostFunctionType func) { - return runtime.createFunctionFromHostFunction(name, paramCount, - std::move(func)); +inline Function Function::createFromHostFunction( + IRuntime& runtime, + const jsi::PropNameID& name, + unsigned int paramCount, + jsi::HostFunctionType func) { + return runtime.createFunctionFromHostFunction( + name, paramCount, std::move(func)); } -inline Value Function::call(Runtime& runtime, const Value* args, - size_t count) const { +inline Value Function::call(IRuntime& runtime, const Value* args, size_t count) + const { return runtime.call(*this, Value::undefined(), args, count); } -inline Value Function::call(Runtime& runtime, - std::initializer_list args) const { +inline Value Function::call( + IRuntime& runtime, + std::initializer_list args) const { return call(runtime, args.begin(), args.size()); } template -inline Value Function::call(Runtime& runtime, Args&&... args) const { +inline Value Function::call(IRuntime& runtime, Args&&... args) const { // A more awesome version of this would be able to create raw values // which can be used directly without wrapping and unwrapping, but // this will do for now. return call(runtime, {detail::toValue(runtime, std::forward(args))...}); } -inline Value Function::callWithThis(Runtime& runtime, const Object& jsThis, - const Value* args, size_t count) const { +inline Value Function::callWithThis( + IRuntime& runtime, + const Object& jsThis, + const Value* args, + size_t count) const { return runtime.call(*this, Value(runtime, jsThis), args, count); } -inline Value Function::callWithThis(Runtime& runtime, const Object& jsThis, - std::initializer_list args) const { +inline Value Function::callWithThis( + IRuntime& runtime, + const Object& jsThis, + std::initializer_list args) const { return callWithThis(runtime, jsThis, args.begin(), args.size()); } template -inline Value Function::callWithThis(Runtime& runtime, const Object& jsThis, - Args&&... args) const { +inline Value Function::callWithThis( + IRuntime& runtime, + const Object& jsThis, + Args&&... args) const { // A more awesome version of this would be able to create raw values // which can be used directly without wrapping and unwrapping, but // this will do for now. - return callWithThis(runtime, jsThis, - {detail::toValue(runtime, std::forward(args))...}); + return callWithThis( + runtime, jsThis, {detail::toValue(runtime, std::forward(args))...}); } template -inline Array Array::createWithElements(Runtime& runtime, Args&&... args) { +inline Array Array::createWithElements(IRuntime& runtime, Args&&... args) { return createWithElements( runtime, {detail::toValue(runtime, std::forward(args))...}); } template -inline std::vector PropNameID::names(Runtime& runtime, - Args&&... args) { +inline size_t Array::push(IRuntime& runtime, Args&&... args) { + return push(runtime, {detail::toValue(runtime, std::forward(args))...}); +} + +inline size_t Array::push( + IRuntime& runtime, + std::initializer_list elements) { + return push(runtime, elements.begin(), elements.size()); +} + +inline size_t +Array::push(IRuntime& runtime, const Value* elements, size_t count) { + return runtime.push(*this, elements, count); +} + +template +inline std::vector PropNameID::names( + IRuntime& runtime, + Args&&... args) { return names({detail::toPropNameID(runtime, std::forward(args))...}); } template inline std::vector PropNameID::names( - PropNameID(&&propertyNames)[N]) { + PropNameID (&&propertyNames)[N]) { std::vector result; result.reserve(N); for (auto& name : propertyNames) { @@ -311,26 +406,29 @@ inline std::vector PropNameID::names( return result; } -inline Value Function::callAsConstructor(Runtime& runtime, const Value* args, - size_t count) const { +inline Value Function::callAsConstructor( + IRuntime& runtime, + const Value* args, + size_t count) const { return runtime.callAsConstructor(*this, args, count); } inline Value Function::callAsConstructor( - Runtime& runtime, std::initializer_list args) const { + IRuntime& runtime, + std::initializer_list args) const { return callAsConstructor(runtime, args.begin(), args.size()); } template -inline Value Function::callAsConstructor(Runtime& runtime, - Args&&... args) const { +inline Value Function::callAsConstructor(IRuntime& runtime, Args&&... args) + const { return callAsConstructor( runtime, {detail::toValue(runtime, std::forward(args))...}); } -String BigInt::toString(Runtime& runtime, int radix) const { +String BigInt::toString(IRuntime& runtime, int radix) const { return runtime.bigintToString(*this, radix); } -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsi.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsi.h index 7d760ef5e..bf98028fb 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsi.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsi.h @@ -8,6 +8,8 @@ #pragma once #include +#include +#include #include #include #include @@ -21,16 +23,117 @@ #define JSI_EXPORT __declspec(dllexport) #else #define JSI_EXPORT -#endif // CREATE_SHARED_LIBRARY -#else // _MSC_VER +#endif // CREATE_SHARED_LIBRARY +#else // _MSC_VER #define JSI_EXPORT __attribute__((visibility("default"))) -#endif // _MSC_VER -#endif // !defined(JSI_EXPORT) +#endif // _MSC_VER +#endif // !defined(JSI_EXPORT) class FBJSRuntime; namespace facebook { namespace jsi { +/// UUID version 1 implementation. This should be constructed with constant +/// arguments to identify fixed UUIDs. +class JSI_EXPORT UUID { + public: + // Construct from raw parts + constexpr UUID( + uint32_t timeLow, + uint16_t timeMid, + uint16_t timeHighAndVersion, + uint16_t variantAndClockSeq, + uint64_t node) + : high( + ((uint64_t)(timeLow) << 32) | ((uint64_t)(timeMid) << 16) | + ((uint64_t)(timeHighAndVersion))), + low(((uint64_t)(variantAndClockSeq) << 48) | node) {} + + // Default constructor (zero UUID) + constexpr UUID() : high(0), low(0) {} + + constexpr UUID(const UUID&) = default; + constexpr UUID& operator=(const UUID&) = default; + + constexpr bool operator==(const UUID& other) const { + return high == other.high && low == other.low; + } + constexpr bool operator!=(const UUID& other) const { + return !(*this == other); + } + + // Ordering (for std::map, sorting, etc.) + constexpr bool operator<(const UUID& other) const { + return (high < other.high) || (high == other.high && low < other.low); + } + + // Hash support for UUID (for unordered_map compatibility) + struct Hash { + std::size_t operator()(const UUID& uuid) const noexcept { + return std::hash{}(uuid.high) ^ + (std::hash{}(uuid.low) << 1); + } + }; + + // UUID format: 8-4-4-4-12 + std::string toString() const { + std::string buffer(36, ' '); + std::snprintf( + buffer.data(), + buffer.size() + 1, + "%08x-%04x-%04x-%04x-%012llx", + getTimeLow(), + getTimeMid(), + getTimeHighAndVersion(), + getVariantAndClockSeq(), + (unsigned long long)getNode()); + return buffer; + } + + constexpr uint32_t getTimeLow() const { + return (uint32_t)(high >> 32); + } + + constexpr uint16_t getTimeMid() const { + return (uint16_t)(high >> 16); + } + + constexpr uint16_t getTimeHighAndVersion() const { + return (uint16_t)high; + } + + constexpr uint16_t getVariantAndClockSeq() const { + return (uint16_t)(low >> 48); + } + + constexpr uint64_t getNode() const { + return low & 0xFFFFFFFFFFFF; + } + + private: + uint64_t high; + uint64_t low; +}; + +/// Base interface that all JSI interfaces inherit from. Users should not try to +/// manipulate this base type directly, and should use castInterface to get the +/// appropriate subtype. +struct JSI_EXPORT ICast { + /// If the current object can be cast into the interface specified by \p + /// interfaceUUID, return a pointer to the object. Otherwise, return a null + /// pointer. + /// The returned interface has the same lifetime as the underlying object. It + /// does not need to be released when not needed. + virtual ICast* castInterface(const UUID& interfaceUUID) = 0; + + protected: + /// Interfaces are not destructible, thus the destructor is intentionally + /// protected to prevent delete calls on the interface. + /// Additionally, the destructor is non-virtual to reduce the vtable + /// complexity from inheritance. + ~ICast() = default; +}; + /// Base class for buffers of data or bytecode that need to be passed to the /// runtime. The buffer is expected to be fully immutable, so the result of /// size(), data(), and the contents of the pointer returned by data() must not @@ -45,7 +148,9 @@ class JSI_EXPORT Buffer { class JSI_EXPORT StringBuffer : public Buffer { public: StringBuffer(std::string s) : s_(std::move(s)) {} - size_t size() const override { return s_.size(); } + size_t size() const override { + return s_.size(); + } const uint8_t* data() const override { return reinterpret_cast(s_.data()); } @@ -78,6 +183,7 @@ class JSI_EXPORT PreparedJavaScript { virtual ~PreparedJavaScript() = 0; }; +class IRuntime; class Runtime; class Pointer; class PropNameID; @@ -94,6 +200,8 @@ class Instrumentation; class Scope; class JSIException; class JSError; +class TypedArray; +class Uint8Array; /// A function which has this type can be registered as a function /// callable from JavaScript using Function::createFromHostFunction(). @@ -106,8 +214,8 @@ class JSError; /// HostFunctions may or may not be called in strict mode; that is `thisVal` /// can be any value - it will not necessarily be coerced to an object or /// or set to the global object. -using HostFunctionType = std::function; +using HostFunctionType = std::function< + Value(Runtime& rt, const Value& thisVal, const Value* args, size_t count)>; /// An object which implements this interface can be registered as an /// Object with the JS runtime. @@ -117,7 +225,7 @@ class JSI_EXPORT HostObject { // object. (This may be as late as when the Runtime is shut down.) // You have no control over which thread it is called on. This will // be called from inside the GC, so it is unsafe to do any VM - // operations which require a Runtime&. Derived classes' dtors + // operations which require a IRuntime&. Derived classes' dtors // should also avoid doing anything expensive. Calling the dtor on // a jsi object is explicitly ok. If you want to do JS operations, // or any nontrivial work, you should add it to a work queue, and @@ -149,27 +257,92 @@ class JSI_EXPORT NativeState { virtual ~NativeState(); }; -/// Represents a JS runtime. Movable, but not copyable. Note that -/// this object may not be thread-aware, but cannot be used safely from -/// multiple threads at once. The application is responsible for -/// ensuring that it is used safely. This could mean using the -/// Runtime from a single thread, using a mutex, doing all work on a -/// serial queue, etc. This restriction applies to the methods of -/// this class, and any method in the API which take a Runtime& as an -/// argument. Destructors (all but ~Scope), operators, or other methods -/// which do not take Runtime& as an argument are safe to call from any -/// thread, but it is still forbidden to make write operations on a single -/// instance of any class from more than one thread. In addition, to -/// make shutdown safe, destruction of objects associated with the Runtime -/// must be destroyed before the Runtime is destroyed, or from the -/// destructor of a managed HostObject or HostFunction. Informally, this -/// means that the main source of unsafe behavior is to hold a jsi object -/// in a non-Runtime-managed object, and not clean it up before the Runtime -/// is shut down. If your lifecycle is such that avoiding this is hard, -/// you will probably need to do use your own locks. -class JSI_EXPORT Runtime { +// JSI_UNSTABLE gates features that will be released with a Hermes version in +// the future. Until released, these features may be subject to change. After +// release, these features will be moved out of JSI_UNSTABLE and become frozen. +#ifdef JSI_UNSTABLE +/// Opaque class that is used to store serialized object from a runtime. The +/// lifetime of this object is orthogonal to the original runtime object, and +/// may outlive the original object. +class JSI_EXPORT Serialized { + public: + /// Uses \p secretAddr to validate if the Serialized data is supported. If so, + /// return the pointer to the underlying serialized data. Otherwise, return a + /// nullptr. This should be used by the runtime to deserialize the data. + virtual void* getPrivate(const void* secretAddr) = 0; + virtual ~Serialized(); +}; + +/// Provides a set of APIs that allows copying objects between different +/// runtime instances. The runtimes instances must be of the same type. As an +/// example, a serialized object from Hermes runtime may only be deserialized by +/// another Hermes runtime. +class JSI_EXPORT ISerialization : public ICast { public: - virtual ~Runtime(); + static constexpr jsi::UUID uuid{ + 0xd40fe0ec, + 0xa47c, + 0x42c9, + 0x8c09, + 0x661aeab832d8}; + + /// Serializes the given Value \p value using the structured clone algorithm. + /// It returns a shared pointer of an opaque Serialized object that can be + /// deserialized multiple times. The lifetime of the Serialized object is not + /// tied to the lifetime of the original object. + virtual std::shared_ptr serialize(const Value& value) = 0; + + /// Given a Serialized object provided by \p serialized, deserialize it using + /// the structured clone algorithm into a JS value in the current runtime. + /// Returns the deserialized JS value. + virtual Value deserialize(const std::shared_ptr& serialized) = 0; + + /// Serializes the given jsi::Value \p value using the structured clone + /// algorithm. \p transferList must be a JS Array. Given the length property + /// of \p transferList, this API will transfer everything at index [0, length + /// - 1] to the serialized object. The transferred values will no longer be + /// usable in the original runtime. It returns a unique pointer of an opaque + /// Serialized object that can be deserialized once only by + /// deserializeWithTransfer. The lifetime of the Serialized object is not tied + /// to the lifetime of the original object. + virtual std::unique_ptr serializeWithTransfer( + const Value& value, + const Array& transferList) = 0; + + /// Using the structure clone algorithm, deserialize the object provided by \p + /// serialized into a JS value in the current runtime. \p serialized must be + /// created by serializeWithTransfer. If the current runtime does not support + /// the serialization scheme in \p serialized, then this method will throw and + /// \p serialized will remain unmodified. Otherwise, this will consume the + /// serialized data entirely and make the serialized objects in the current + /// runtime. Any transferred values in the serialized object will be owned by + /// the current runtime. + /// This method returns an Array containing the deserialized values, where + /// the first element is the value passed into serializeWithTransfer, + /// followed by all transferred values. + virtual Array deserializeWithTransfer( + std::unique_ptr& serialized) = 0; + + protected: + ~ISerialization() = default; +}; + +#endif // JSI_UNSTABLE + +/// An interface that provides various functionalities of the JS runtime. +/// The APIs must not be called from multiple threads concurrently. It is the +/// user's responsibility ensure thread safety when using IRuntime. +/// Users should cast their runtime to IRuntime to access these APIs. However, +/// for backward compatibility, these APIs are also accessible via the Runtime +/// object directly. +class JSI_EXPORT IRuntime : public ICast { + public: + static constexpr jsi::UUID uuid{ + 0xc2e8e22e, + 0xd7a6, + 0x11f0, + 0x8de9, + 0x0242ac120002}; /// Evaluates the given JavaScript \c buffer. \c sourceURL is used /// to annotate the stack trace if there is an exception. The @@ -181,8 +354,9 @@ class JSI_EXPORT Runtime { /// through the JSI API. For example, it will be much slower to use this to /// call a global function than using the JSI APIs to read the function /// property from the global object and then calling it explicitly. - virtual Value evaluateJavaScript(const std::shared_ptr& buffer, - const std::string& sourceURL) = 0; + virtual Value evaluateJavaScript( + const std::shared_ptr& buffer, + const std::string& sourceURL) = 0; /// Prepares to evaluate the given JavaScript \c buffer by processing it into /// a form optimized for execution. This may include pre-parsing, compiling, @@ -196,7 +370,8 @@ class JSI_EXPORT Runtime { /// As with evaluateJavaScript(), using JavaScript code should be avoided /// when the JSI API is sufficient. virtual std::shared_ptr prepareJavaScript( - const std::shared_ptr& buffer, std::string sourceURL) = 0; + const std::shared_ptr& buffer, + std::string sourceURL) = 0; /// Evaluates a PreparedJavaScript. If evaluation causes an error, a /// JSIException will be thrown. @@ -261,22 +436,31 @@ class JSI_EXPORT Runtime { /// \return an interface to extract metrics from this \c Runtime. The default /// implementation of this function returns an \c Instrumentation instance /// which returns no metrics. - virtual Instrumentation& instrumentation(); - - protected: - friend class Pointer; - friend class PropNameID; - friend class Symbol; - friend class BigInt; - friend class String; - friend class Object; - friend class WeakObject; - friend class Array; - friend class ArrayBuffer; - friend class Function; - friend class Value; - friend class Scope; - friend class JSError; + virtual Instrumentation& instrumentation() = 0; + + /// Stores the pointer \p data with the \p dataUUID in the runtime. This can + /// be used to store some custom data within the runtime. When the runtime is + /// destroyed, or if an entry at an existing key is overwritten, the runtime + /// will release its ownership of the held object. + virtual void setRuntimeData( + const UUID& dataUUID, + const std::shared_ptr& data) = 0; + /// Returns the data associated with the \p uuid in the runtime. If there's no + /// data associated with the uuid, return a null pointer. + virtual std::shared_ptr getRuntimeData(const UUID& dataUUID) = 0; + + /// Stores the pointer \p data with the \p uuid in the runtime. This can be + /// used to store some custom data within the runtime. When the runtime is + /// destroyed, or if an entry at an existing key is overwritten, the runtime + /// will release its ownership by calling \p deleter. + virtual void setRuntimeDataImpl( + const UUID& dataUUID, + const void* data, + void (*deleter)(const void* data)) = 0; + + /// Returns the data associated with the \p uuid in the runtime. If there's no + /// data associated with the uuid, return a null pointer. + virtual const void* getRuntimeDataImpl(const UUID& dataUUID) = 0; // Potential optimization: avoid the cloneFoo() virtual dispatch, // and instead just fix the number of fields, and copy them, since @@ -284,22 +468,27 @@ class JSI_EXPORT Runtime { // rvalue arguments/methods would also reduce the number of clones. struct PointerValue { - virtual void invalidate() = 0; + virtual void invalidate() noexcept = 0; protected: virtual ~PointerValue() = default; }; - virtual PointerValue* cloneSymbol(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneBigInt(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneString(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneObject(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* clonePropNameID(const Runtime::PointerValue* pv) = 0; - - virtual PropNameID createPropNameIDFromAscii(const char* str, - size_t length) = 0; - virtual PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, - size_t length) = 0; + virtual PointerValue* cloneSymbol(const IRuntime::PointerValue* pv) = 0; + virtual PointerValue* cloneBigInt(const IRuntime::PointerValue* pv) = 0; + virtual PointerValue* cloneString(const IRuntime::PointerValue* pv) = 0; + virtual PointerValue* cloneObject(const IRuntime::PointerValue* pv) = 0; + virtual PointerValue* clonePropNameID(const IRuntime::PointerValue* pv) = 0; + + virtual PropNameID createPropNameIDFromAscii( + const char* str, + size_t length) = 0; + virtual PropNameID createPropNameIDFromUtf8( + const uint8_t* utf8, + size_t length) = 0; + virtual PropNameID createPropNameIDFromUtf16( + const char16_t* utf16, + size_t length) = 0; virtual PropNameID createPropNameIDFromString(const String& str) = 0; virtual PropNameID createPropNameIDFromSymbol(const Symbol& sym) = 0; virtual std::string utf8(const PropNameID&) = 0; @@ -316,11 +505,14 @@ class JSI_EXPORT Runtime { virtual String createStringFromAscii(const char* str, size_t length) = 0; virtual String createStringFromUtf8(const uint8_t* utf8, size_t length) = 0; + virtual String createStringFromUtf16( + const char16_t* utf16, + size_t length) = 0; virtual std::string utf8(const String&) = 0; // \return a \c Value created from a utf8-encoded JSON string. The default // implementation creates a \c String and invokes JSON.parse. - virtual Value createValueFromJsonUtf8(const uint8_t* json, size_t length); + virtual Value createValueFromJsonUtf8(const uint8_t* json, size_t length) = 0; virtual Object createObject() = 0; virtual Object createObject(std::shared_ptr ho) = 0; @@ -328,27 +520,40 @@ class JSI_EXPORT Runtime { virtual HostFunctionType& getHostFunction(const jsi::Function&) = 0; // Creates a new Object with the custom prototype - virtual Object createObjectWithPrototype(const Value& prototype); + virtual Object createObjectWithPrototype(const Value& prototype) = 0; virtual bool hasNativeState(const jsi::Object&) = 0; virtual std::shared_ptr getNativeState(const jsi::Object&) = 0; - virtual void setNativeState(const jsi::Object&, - std::shared_ptr state) = 0; + virtual void setNativeState( + const jsi::Object&, + std::shared_ptr state) = 0; - virtual void setPrototypeOf(const Object& object, const Value& prototype); - virtual Value getPrototypeOf(const Object& object); + virtual void setPrototypeOf(const Object& object, const Value& prototype) = 0; + virtual Value getPrototypeOf(const Object& object) = 0; virtual Value getProperty(const Object&, const PropNameID& name) = 0; virtual Value getProperty(const Object&, const String& name) = 0; + virtual Value getProperty(const Object&, const Value& name) = 0; virtual bool hasProperty(const Object&, const PropNameID& name) = 0; virtual bool hasProperty(const Object&, const String& name) = 0; - virtual void setPropertyValue(const Object&, const PropNameID& name, - const Value& value) = 0; - virtual void setPropertyValue(const Object&, const String& name, - const Value& value) = 0; + virtual bool hasProperty(const Object&, const Value& name) = 0; + virtual void setPropertyValue( + const Object&, + const PropNameID& name, + const Value& value) = 0; + virtual void + setPropertyValue(const Object&, const String& name, const Value& value) = 0; + virtual void + setPropertyValue(const Object&, const Value& name, const Value& value) = 0; + + virtual void deleteProperty(const Object&, const PropNameID& name) = 0; + virtual void deleteProperty(const Object&, const String& name) = 0; + virtual void deleteProperty(const Object&, const Value& name) = 0; virtual bool isArray(const Object&) const = 0; virtual bool isArrayBuffer(const Object&) const = 0; + virtual bool isTypedArray(const Object&) const = 0; + virtual bool isUint8Array(const Object&) const = 0; virtual bool isFunction(const Object&) const = 0; virtual bool isHostObject(const jsi::Object&) const = 0; virtual bool isHostFunction(const jsi::Function&) const = 0; @@ -363,22 +568,28 @@ class JSI_EXPORT Runtime { virtual size_t size(const Array&) = 0; virtual size_t size(const ArrayBuffer&) = 0; virtual uint8_t* data(const ArrayBuffer&) = 0; + virtual bool detached(const ArrayBuffer&) = 0; virtual Value getValueAtIndex(const Array&, size_t i) = 0; - virtual void setValueAtIndexImpl(const Array&, size_t i, - const Value& value) = 0; - - virtual Function createFunctionFromHostFunction(const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) = 0; - virtual Value call(const Function&, const Value& jsThis, const Value* args, - size_t count) = 0; - virtual Value callAsConstructor(const Function&, const Value* args, - size_t count) = 0; + virtual void + setValueAtIndexImpl(const Array&, size_t i, const Value& value) = 0; + virtual size_t push(const Array&, const Value*, size_t) = 0; + + virtual Function createFunctionFromHostFunction( + const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) = 0; + virtual Value call( + const Function&, + const Value& jsThis, + const Value* args, + size_t count) = 0; + virtual Value + callAsConstructor(const Function&, const Value* args, size_t count) = 0; // Private data for managing scopes. struct ScopeState; - virtual ScopeState* pushScope(); - virtual void popScope(ScopeState*); + virtual ScopeState* pushScope() = 0; + virtual void popScope(ScopeState*) = 0; virtual bool strictEquals(const Symbol& a, const Symbol& b) const = 0; virtual bool strictEquals(const BigInt& a, const BigInt& b) const = 0; @@ -388,11 +599,12 @@ class JSI_EXPORT Runtime { virtual bool instanceOf(const Object& o, const Function& f) = 0; /// See Object::setExternalMemoryPressure. - virtual void setExternalMemoryPressure(const jsi::Object& obj, - size_t amount) = 0; + virtual void setExternalMemoryPressure( + const jsi::Object& obj, + size_t amount) = 0; - virtual std::u16string utf16(const String& str); - virtual std::u16string utf16(const PropNameID& sym); + virtual std::u16string utf16(const String& str) = 0; + virtual std::u16string utf16(const PropNameID& sym) = 0; /// Invokes the provided callback \p cb with the String content in \p str. /// The callback must take in three arguments: bool ascii, const void* data, @@ -403,9 +615,10 @@ class JSI_EXPORT Runtime { /// multiple times, with a different format on each invocation. The callback /// must not access runtime functionality, as any operation on the runtime may /// invalidate the data pointers. - virtual void getStringData(const jsi::String& str, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)); + virtual void getStringData( + const jsi::String& str, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) = 0; /// Invokes the provided callback \p cb with the PropNameID content in \p sym. /// The callback must take in three arguments: bool ascii, const void* data, @@ -416,9 +629,184 @@ class JSI_EXPORT Runtime { /// multiple times, with a different format on each invocation. The callback /// must not access runtime functionality, as any operation on the runtime may /// invalidate the data pointers. - virtual void getPropNameIdData(const jsi::PropNameID& sym, void* ctx, - void (*cb)(void* ctx, bool ascii, - const void* data, size_t num)); + virtual void getPropNameIdData( + const jsi::PropNameID& sym, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) = 0; + + /// If possible, returns the MutableBuffer representing \p arrayBuffer's + /// underlying data, else return a nullptr. Importantly, the returned + /// MutableBuffer directly points to \p arrayBuffer's data instead of copying + /// the data over. The data's lifetime is valid for the lifetime of + /// MutableBuffer, which is orthogonal from \p arrayBuffer. + virtual std::shared_ptr tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) = 0; + + /// \return the underlying buffer of the \p typedArray. + virtual ArrayBuffer buffer(const TypedArray& typedArray) = 0; + /// \return the 'byteOffset' property of the \p typedArray. + virtual size_t byteOffset(const TypedArray& typedArray) = 0; + /// \return the 'byteLength' property of the \p typedArray. + virtual size_t byteLength(const TypedArray& typedArray) = 0; + /// \return the 'length; property of the \p typedArray. + virtual size_t length(const TypedArray& typedArray) = 0; + + /// Create a JS UInt8Array with length \p length. + virtual Uint8Array createUint8Array(size_t length) = 0; + /// Create a JS UInt8Array using the ArrayBuffer \p buffer starting at byte + /// offset \p offset and length \p length. + virtual Uint8Array + createUint8Array(const ArrayBuffer& buffer, size_t offset, size_t length) = 0; + + /// Create a new Error object with the message property set to \p message. + virtual Value createError(const String& msg) = 0; + /// Create a new EvalError object with the message property set to \p message. + virtual Value createEvalError(const String& msg) = 0; + /// Create a new RangeError object with the message property set to \p + /// message. + virtual Value createRangeError(const String& msg) = 0; + /// Create a new ReferenceError object with the message property set to \p + /// message. + virtual Value createReferenceError(const String& msg) = 0; + /// Create a new SyntaxError object with the message property set to \p + /// message. + virtual Value createSyntaxError(const String& msg) = 0; + /// Create a new TypeError object with the message property set to \p message. + virtual Value createTypeError(const String& msg) = 0; + /// Create a new URIError object with the message property set to \p message. + virtual Value createURIError(const String& msg) = 0; + + /// Returns the number of code units in the string, equivalent to 'length' + /// property of a JS string. + virtual size_t length(const String& str) = 0; + + protected: + virtual ~IRuntime() = default; +}; + +/// Represents a JS runtime. Movable, but not copyable. Note that +/// this object may not be thread-aware, but cannot be used safely from +/// multiple threads at once. The application is responsible for +/// ensuring that it is used safely. This could mean using the +/// Runtime from a single thread, using a mutex, doing all work on a +/// serial queue, etc. This restriction applies to the methods of +/// this class, and any method in the API which take a Runtime& as an +/// argument. Destructors (all but ~Scope), operators, or other methods +/// which do not take Runtime& as an argument are safe to call from any +/// thread, but it is still forbidden to make write operations on a single +/// instance of any class from more than one thread. In addition, to +/// make shutdown safe, destruction of objects associated with the Runtime +/// must be destroyed before the Runtime is destroyed, or from the +/// destructor of a managed HostObject or HostFunction. Informally, this +/// means that the main source of unsafe behavior is to hold a jsi object +/// in a non-Runtime-managed object, and not clean it up before the Runtime +/// is shut down. If your lifecycle is such that avoiding this is hard, +/// you will probably need to do use your own locks. +class JSI_EXPORT Runtime : public IRuntime { + public: + virtual ~Runtime() override; + + using IRuntime::getProperty; + using IRuntime::hasProperty; + using IRuntime::setPropertyValue; + ICast* castInterface(const UUID& uuid) override; + + Instrumentation& instrumentation() override; + + /// Stores the pointer \p data with the \p uuid in the runtime. This can be + /// used to store some custom data within the runtime. When the runtime is + /// destroyed, or if an entry at an existing key is overwritten, the runtime + /// will release its ownership of the held object. + void setRuntimeData(const UUID& uuid, const std::shared_ptr& data) + override; + + /// Returns the data associated with the \p uuid in the runtime. If there's no + /// data associated with the uuid, return a null pointer. + std::shared_ptr getRuntimeData(const UUID& uuid) override; + + Value getProperty(const Object&, const Value& name) override; + bool hasProperty(const Object&, const Value& name) override; + void setPropertyValue(const Object&, const Value& name, const Value& value) + override; + void deleteProperty(const Object&, const PropNameID& name) override; + void deleteProperty(const Object&, const String& name) override; + void deleteProperty(const Object&, const Value& name) override; + + void setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)) override; + const void* getRuntimeDataImpl(const UUID& uuid) override; + + PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) + override; + String createStringFromUtf16(const char16_t* utf16, size_t length) override; + + Value createValueFromJsonUtf8(const uint8_t* json, size_t length) override; + + Object createObjectWithPrototype(const Value& prototype) override; + void setPrototypeOf(const Object& object, const Value& prototype) override; + Value getPrototypeOf(const Object& object) override; + + ScopeState* pushScope() override; + void popScope(ScopeState*) override; + + std::u16string utf16(const String& str) override; + std::u16string utf16(const PropNameID& sym) override; + + void getStringData( + const jsi::String& str, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) override; + void getPropNameIdData( + const jsi::PropNameID& sym, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) override; + + size_t push(const Array&, const Value*, size_t) override; + + std::shared_ptr tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) override; + + bool detached(const ArrayBuffer&) override; + + ArrayBuffer buffer(const TypedArray& typedArray) override; + size_t byteOffset(const TypedArray& typedArray) override; + size_t byteLength(const TypedArray& typedArray) override; + size_t length(const TypedArray& typedArray) override; + + bool isTypedArray(const Object&) const override; + bool isUint8Array(const Object&) const override; + Uint8Array createUint8Array(size_t length) override; + Uint8Array createUint8Array( + const ArrayBuffer& buffer, + size_t offset, + size_t length) override; + + Value createError(const String& msg) override; + Value createEvalError(const String& msg) override; + Value createRangeError(const String& msg) override; + Value createReferenceError(const String& msg) override; + Value createSyntaxError(const String& msg) override; + Value createTypeError(const String& msg) override; + Value createURIError(const String& msg) override; + + size_t length(const String& str) override; + + protected: + friend class Pointer; + friend class PropNameID; + friend class Symbol; + friend class BigInt; + friend class String; + friend class Object; + friend class WeakObject; + friend class Array; + friend class ArrayBuffer; + friend class Function; + friend class Value; + friend class Scope; + friend class JSError; // These exist so derived classes can access the private parts of // Value, Symbol, String, and Object, which are all friends of Runtime. @@ -461,7 +849,7 @@ class JSI_EXPORT PropNameID : public Pointer { public: using Pointer::Pointer; - PropNameID(Runtime& runtime, const PropNameID& other) + PropNameID(IRuntime& runtime, const PropNameID& other) : Pointer(runtime.clonePropNameID(other.ptr_)) {} PropNameID(PropNameID&& other) = default; @@ -469,59 +857,79 @@ class JSI_EXPORT PropNameID : public Pointer { /// Create a JS property name id from ascii values. The data is /// copied. - static PropNameID forAscii(Runtime& runtime, const char* str, size_t length) { + static PropNameID + forAscii(IRuntime& runtime, const char* str, size_t length) { return runtime.createPropNameIDFromAscii(str, length); } /// Create a property name id from a nul-terminated C ascii name. The data is /// copied. - static PropNameID forAscii(Runtime& runtime, const char* str) { + static PropNameID forAscii(IRuntime& runtime, const char* str) { return forAscii(runtime, str, strlen(str)); } /// Create a PropNameID from a C++ string. The string is copied. - static PropNameID forAscii(Runtime& runtime, const std::string& str) { + static PropNameID forAscii(IRuntime& runtime, const std::string& str) { return forAscii(runtime, str.c_str(), str.size()); } /// Create a PropNameID from utf8 values. The data is copied. /// Results are undefined if \p utf8 contains invalid code points. - static PropNameID forUtf8(Runtime& runtime, const uint8_t* utf8, - size_t length) { + static PropNameID + forUtf8(IRuntime& runtime, const uint8_t* utf8, size_t length) { return runtime.createPropNameIDFromUtf8(utf8, length); } /// Create a PropNameID from utf8-encoded octets stored in a /// std::string. The string data is transformed and copied. /// Results are undefined if \p utf8 contains invalid code points. - static PropNameID forUtf8(Runtime& runtime, const std::string& utf8) { + static PropNameID forUtf8(IRuntime& runtime, const std::string& utf8) { return runtime.createPropNameIDFromUtf8( reinterpret_cast(utf8.data()), utf8.size()); } + /// Given a series of UTF-16 encoded code units, create a PropNameId. The + /// input may contain unpaired surrogates, which will be interpreted as a code + /// point of the same value. + static PropNameID + forUtf16(IRuntime& runtime, const char16_t* utf16, size_t length) { + return runtime.createPropNameIDFromUtf16(utf16, length); + } + + /// Given a series of UTF-16 encoded code units stored inside std::u16string, + /// create a PropNameId. The input may contain unpaired surrogates, which + /// will be interpreted as a code point of the same value. + static PropNameID forUtf16(IRuntime& runtime, const std::u16string& str) { + return runtime.createPropNameIDFromUtf16(str.data(), str.size()); + } + /// Create a PropNameID from a JS string. - static PropNameID forString(Runtime& runtime, const jsi::String& str) { + static PropNameID forString(IRuntime& runtime, const jsi::String& str) { return runtime.createPropNameIDFromString(str); } /// Create a PropNameID from a JS symbol. - static PropNameID forSymbol(Runtime& runtime, const jsi::Symbol& sym) { + static PropNameID forSymbol(IRuntime& runtime, const jsi::Symbol& sym) { return runtime.createPropNameIDFromSymbol(sym); } // Creates a vector of PropNameIDs constructed from given arguments. template - static std::vector names(Runtime& runtime, Args&&... args); + static std::vector names(IRuntime& runtime, Args&&... args); // Creates a vector of given PropNameIDs. template - static std::vector names(PropNameID(&&propertyNames)[N]); + static std::vector names(PropNameID (&&propertyNames)[N]); /// Copies the data in a PropNameID as utf8 into a C++ string. - std::string utf8(Runtime& runtime) const { return runtime.utf8(*this); } + std::string utf8(IRuntime& runtime) const { + return runtime.utf8(*this); + } /// Copies the data in a PropNameID as utf16 into a C++ string. - std::u16string utf16(Runtime& runtime) const { return runtime.utf16(*this); } + std::u16string utf16(IRuntime& runtime) const { + return runtime.utf16(*this); + } /// Invokes the user provided callback to process the content in PropNameId. /// The callback must take in three arguments: bool ascii, const void* data, @@ -532,15 +940,17 @@ class JSI_EXPORT PropNameID : public Pointer { /// callback must not access runtime functionality, as any operation on the /// runtime may invalidate the data pointers. template - void getPropNameIdData(Runtime& runtime, CB& cb) const { + void getPropNameIdData(IRuntime& runtime, CB& cb) const { runtime.getPropNameIdData( *this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) { (*((CB*)ctx))(ascii, data, num); }); } - static bool compare(Runtime& runtime, const jsi::PropNameID& a, - const jsi::PropNameID& b) { + static bool compare( + IRuntime& runtime, + const jsi::PropNameID& a, + const jsi::PropNameID& b) { return runtime.compare(a, b); } @@ -561,13 +971,14 @@ class JSI_EXPORT Symbol : public Pointer { Symbol& operator=(Symbol&& other) = default; /// \return whether a and b refer to the same symbol. - static bool strictEquals(Runtime& runtime, const Symbol& a, const Symbol& b) { + static bool + strictEquals(IRuntime& runtime, const Symbol& a, const Symbol& b) { return runtime.strictEquals(a, b); } /// Converts a Symbol into a C++ string as JS .toString would. The output /// will look like \c Symbol(description) . - std::string toString(Runtime& runtime) const { + std::string toString(IRuntime& runtime) const { return runtime.symbolToString(*this); } @@ -584,45 +995,52 @@ class JSI_EXPORT BigInt : public Pointer { BigInt& operator=(BigInt&& other) = default; /// Create a BigInt representing the signed 64-bit \p value. - static BigInt fromInt64(Runtime& runtime, int64_t value) { + static BigInt fromInt64(IRuntime& runtime, int64_t value) { return runtime.createBigIntFromInt64(value); } /// Create a BigInt representing the unsigned 64-bit \p value. - static BigInt fromUint64(Runtime& runtime, uint64_t value) { + static BigInt fromUint64(IRuntime& runtime, uint64_t value) { return runtime.createBigIntFromUint64(value); } /// \return whether a === b. - static bool strictEquals(Runtime& runtime, const BigInt& a, const BigInt& b) { + static bool + strictEquals(IRuntime& runtime, const BigInt& a, const BigInt& b) { return runtime.strictEquals(a, b); } /// \returns This bigint truncated to a signed 64-bit integer. - int64_t getInt64(Runtime& runtime) const { return runtime.truncate(*this); } + int64_t getInt64(IRuntime& runtime) const { + return runtime.truncate(*this); + } /// \returns Whether this bigint can be losslessly converted to int64_t. - bool isInt64(Runtime& runtime) const { return runtime.bigintIsInt64(*this); } + bool isInt64(IRuntime& runtime) const { + return runtime.bigintIsInt64(*this); + } /// \returns This bigint truncated to a signed 64-bit integer. Throws a /// JSIException if the truncation is lossy. - int64_t asInt64(Runtime& runtime) const; + int64_t asInt64(IRuntime& runtime) const; /// \returns This bigint truncated to an unsigned 64-bit integer. - uint64_t getUint64(Runtime& runtime) const { return runtime.truncate(*this); } + uint64_t getUint64(IRuntime& runtime) const { + return runtime.truncate(*this); + } /// \returns Whether this bigint can be losslessly converted to uint64_t. - bool isUint64(Runtime& runtime) const { + bool isUint64(IRuntime& runtime) const { return runtime.bigintIsUint64(*this); } /// \returns This bigint truncated to an unsigned 64-bit integer. Throws a /// JSIException if the truncation is lossy. - uint64_t asUint64(Runtime& runtime) const; + uint64_t asUint64(IRuntime& runtime) const; /// \returns this BigInt converted to a String in base \p radix. Throws a /// JSIException if radix is not in the [2, 36] range. - inline String toString(Runtime& runtime, int radix = 10) const; + inline String toString(IRuntime& runtime, int radix = 10) const; friend class Runtime; friend class Value; @@ -638,49 +1056,76 @@ class JSI_EXPORT String : public Pointer { /// Create a JS string from ascii values. The string data is /// copied. - static String createFromAscii(Runtime& runtime, const char* str, - size_t length) { + static String + createFromAscii(IRuntime& runtime, const char* str, size_t length) { return runtime.createStringFromAscii(str, length); } /// Create a JS string from a nul-terminated C ascii string. The /// string data is copied. - static String createFromAscii(Runtime& runtime, const char* str) { + static String createFromAscii(IRuntime& runtime, const char* str) { return createFromAscii(runtime, str, strlen(str)); } /// Create a JS string from a C++ string. The string data is /// copied. - static String createFromAscii(Runtime& runtime, const std::string& str) { + static String createFromAscii(IRuntime& runtime, const std::string& str) { return createFromAscii(runtime, str.c_str(), str.size()); } /// Create a JS string from utf8-encoded octets. The string data is /// transformed and copied. Results are undefined if \p utf8 contains invalid /// code points. - static String createFromUtf8(Runtime& runtime, const uint8_t* utf8, - size_t length) { + static String + createFromUtf8(IRuntime& runtime, const uint8_t* utf8, size_t length) { return runtime.createStringFromUtf8(utf8, length); } /// Create a JS string from utf8-encoded octets stored in a /// std::string. The string data is transformed and copied. Results are /// undefined if \p utf8 contains invalid code points. - static String createFromUtf8(Runtime& runtime, const std::string& utf8) { + static String createFromUtf8(IRuntime& runtime, const std::string& utf8) { return runtime.createStringFromUtf8( reinterpret_cast(utf8.data()), utf8.length()); } + /// Given a series of UTF-16 encoded code units, create a JS String. The input + /// may contain unpaired surrogates, which will be interpreted as a code point + /// of the same value. + static String + createFromUtf16(IRuntime& runtime, const char16_t* utf16, size_t length) { + return runtime.createStringFromUtf16(utf16, length); + } + + /// Given a series of UTF-16 encoded code units stored inside std::u16string, + /// create a JS String. The input may contain unpaired surrogates, which will + /// be interpreted as a code point of the same value. + static String createFromUtf16( + IRuntime& runtime, + const std::u16string& utf16) { + return runtime.createStringFromUtf16(utf16.data(), utf16.length()); + } + /// \return whether a and b contain the same characters. - static bool strictEquals(Runtime& runtime, const String& a, const String& b) { + static bool + strictEquals(IRuntime& runtime, const String& a, const String& b) { return runtime.strictEquals(a, b); } + /// \return the 'length' property of this JS string. + size_t length(IRuntime& runtime) const { + return runtime.length(*this); + } + /// Copies the data in a JS string as utf8 into a C++ string. - std::string utf8(Runtime& runtime) const { return runtime.utf8(*this); } + std::string utf8(IRuntime& runtime) const { + return runtime.utf8(*this); + } /// Copies the data in a JS string as utf16 into a C++ string. - std::u16string utf16(Runtime& runtime) const { return runtime.utf16(*this); } + std::u16string utf16(IRuntime& runtime) const { + return runtime.utf16(*this); + } /// Invokes the user provided callback to process content in String. The /// callback must take in three arguments: bool ascii, const void* data, and @@ -691,7 +1136,7 @@ class JSI_EXPORT String : public Pointer { /// must not access runtime functionality, as any operation on the runtime may /// invalidate the data pointers. template - void getStringData(Runtime& runtime, CB& cb) const { + void getStringData(IRuntime& runtime, CB& cb) const { runtime.getStringData( *this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) { (*((CB*)ctx))(ascii, data, num); @@ -714,194 +1159,265 @@ class JSI_EXPORT Object : public Pointer { Object& operator=(Object&& other) = default; /// Creates a new Object instance, like '{}' in JS. - Object(Runtime& runtime) : Object(runtime.createObject()) {} + explicit Object(IRuntime& runtime) : Object(runtime.createObject()) {} - static Object createFromHostObject(Runtime& runtime, - std::shared_ptr ho) { + static Object createFromHostObject( + IRuntime& runtime, + std::shared_ptr ho) { return runtime.createObject(ho); } /// Creates a new Object with the custom prototype - static Object create(Runtime& runtime, const Value& prototype) { + static Object create(IRuntime& runtime, const Value& prototype) { return runtime.createObjectWithPrototype(prototype); } /// \return whether this and \c obj are the same JSObject or not. - static bool strictEquals(Runtime& runtime, const Object& a, const Object& b) { + static bool + strictEquals(IRuntime& runtime, const Object& a, const Object& b) { return runtime.strictEquals(a, b); } /// \return the result of `this instanceOf ctor` in JS. - bool instanceOf(Runtime& rt, const Function& ctor) const { + bool instanceOf(IRuntime& rt, const Function& ctor) const { return rt.instanceOf(*this, ctor); } /// Sets \p prototype as the prototype of the object. The prototype must be /// either an Object or null. If the prototype was not set successfully, this /// method will throw. - void setPrototype(Runtime& runtime, const Value& prototype) const { + void setPrototype(IRuntime& runtime, const Value& prototype) const { return runtime.setPrototypeOf(*this, prototype); } /// \return the prototype of the object - inline Value getPrototype(Runtime& runtime) const; + inline Value getPrototype(IRuntime& runtime) const; /// \return the property of the object with the given ascii name. /// If the name isn't a property on the object, returns the /// undefined value. - Value getProperty(Runtime& runtime, const char* name) const; + Value getProperty(IRuntime& runtime, const char* name) const; /// \return the property of the object with the String name. /// If the name isn't a property on the object, returns the /// undefined value. - Value getProperty(Runtime& runtime, const String& name) const; + Value getProperty(IRuntime& runtime, const String& name) const; /// \return the property of the object with the given JS PropNameID /// name. If the name isn't a property on the object, returns the /// undefined value. - Value getProperty(Runtime& runtime, const PropNameID& name) const; + Value getProperty(IRuntime& runtime, const PropNameID& name) const; + + /// \return the Property of the object with the given JS Value name. If the + /// name isn't a property on the object, returns the undefined value.This + /// attempts to convert the JS Value to convert to a property key. If the + /// conversion fails, this method may throw. + Value getProperty(IRuntime& runtime, const Value& name) const; /// \return true if and only if the object has a property with the /// given ascii name. - bool hasProperty(Runtime& runtime, const char* name) const; + bool hasProperty(IRuntime& runtime, const char* name) const; /// \return true if and only if the object has a property with the /// given String name. - bool hasProperty(Runtime& runtime, const String& name) const; + bool hasProperty(IRuntime& runtime, const String& name) const; /// \return true if and only if the object has a property with the /// given PropNameID name. - bool hasProperty(Runtime& runtime, const PropNameID& name) const; + bool hasProperty(IRuntime& runtime, const PropNameID& name) const; + + /// \return true if and only if the object has a property with the given + /// JS Value name. This attempts to convert the JS Value to convert to a + /// property key. If the conversion fails, this method may throw. + bool hasProperty(IRuntime& runtime, const Value& name) const; /// Sets the property value from a Value or anything which can be /// used to make one: nullptr_t, bool, double, int, const char*, /// String, or Object. template - void setProperty(Runtime& runtime, const char* name, T&& value) const; + void setProperty(IRuntime& runtime, const char* name, T&& value) const; /// Sets the property value from a Value or anything which can be /// used to make one: nullptr_t, bool, double, int, const char*, /// String, or Object. template - void setProperty(Runtime& runtime, const String& name, T&& value) const; + void setProperty(IRuntime& runtime, const String& name, T&& value) const; /// Sets the property value from a Value or anything which can be /// used to make one: nullptr_t, bool, double, int, const char*, /// String, or Object. template - void setProperty(Runtime& runtime, const PropNameID& name, T&& value) const; + void setProperty(IRuntime& runtime, const PropNameID& name, T&& value) const; + + /// Sets the property value from a Value or anything which can be + /// used to make one: nullptr_t, bool, double, int, const char*, + /// String, or Object. This takes a JS Value as the property name, and + /// attempts to convert to a property key. If the conversion fails, this + /// method may throw. + template + void setProperty(IRuntime& runtime, const Value& name, T&& value) const; + + /// Delete the property with the given ascii name. Throws if the deletion + /// failed. + void deleteProperty(IRuntime& runtime, const char* name) const; + + /// Delete the property with the given String name. Throws if the deletion + /// failed. + void deleteProperty(IRuntime& runtime, const String& name) const; + + /// Delete the property with the given PropNameID name. Throws if the deletion + /// failed. + void deleteProperty(IRuntime& runtime, const PropNameID& name) const; + + /// Delete the property with the given Value name. Throws if the deletion + /// failed. + void deleteProperty(IRuntime& runtime, const Value& name) const; /// \return true iff JS \c Array.isArray() would return \c true. If /// so, then \c getArray() will succeed. - bool isArray(Runtime& runtime) const { return runtime.isArray(*this); } + bool isArray(IRuntime& runtime) const { + return runtime.isArray(*this); + } /// \return true iff the Object is an ArrayBuffer. If so, then \c /// getArrayBuffer() will succeed. - bool isArrayBuffer(Runtime& runtime) const { + bool isArrayBuffer(IRuntime& runtime) const { return runtime.isArrayBuffer(*this); } + /// \return true iff the Object is a TypedArray (Uint8Array, Int32Array, + /// Float64Array, etc.). If so, then \c getTypedArray() will succeed. + bool isTypedArray(IRuntime& runtime) const { + return runtime.isTypedArray(*this); + } + + /// \return true iff the Object is an Uint8Array. If so, then \c + /// getUint8Array() will succeed + bool isUint8Array(IRuntime& runtime) const { + return runtime.isUint8Array(*this); + } + /// \return true iff the Object is callable. If so, then \c /// getFunction will succeed. - bool isFunction(Runtime& runtime) const { return runtime.isFunction(*this); } + bool isFunction(IRuntime& runtime) const { + return runtime.isFunction(*this); + } /// \return true iff the Object was initialized with \c createFromHostObject /// and the HostObject passed is of type \c T. If returns \c true then /// \c getHostObject will succeed. template - bool isHostObject(Runtime& runtime) const; + bool isHostObject(IRuntime& runtime) const; /// \return an Array instance which refers to the same underlying /// object. If \c isArray() would return false, this will assert. - Array getArray(Runtime& runtime) const&; + Array getArray(IRuntime& runtime) const&; /// \return an Array instance which refers to the same underlying /// object. If \c isArray() would return false, this will assert. - Array getArray(Runtime& runtime) &&; + Array getArray(IRuntime& runtime) &&; /// \return an Array instance which refers to the same underlying /// object. If \c isArray() would return false, this will throw /// JSIException. - Array asArray(Runtime& runtime) const&; + Array asArray(IRuntime& runtime) const&; /// \return an Array instance which refers to the same underlying /// object. If \c isArray() would return false, this will throw /// JSIException. - Array asArray(Runtime& runtime) &&; + Array asArray(IRuntime& runtime) &&; + + /// \return a TypedArray instance which refers to the same underlying + /// object. If \c isTypedArray() would return false, this will throw + /// JSIException. + TypedArray asTypedArray(IRuntime& runtime) const&; + + /// \return an Uint8Array instance which refers to the same underlying + /// object. If \c isUint8Array() would return false, this will throw + /// JSIException. + Uint8Array asUint8Array(IRuntime& runtime) const&; /// \return an ArrayBuffer instance which refers to the same underlying /// object. If \c isArrayBuffer() would return false, this will assert. - ArrayBuffer getArrayBuffer(Runtime& runtime) const&; + ArrayBuffer getArrayBuffer(IRuntime& runtime) const&; /// \return an ArrayBuffer instance which refers to the same underlying /// object. If \c isArrayBuffer() would return false, this will assert. - ArrayBuffer getArrayBuffer(Runtime& runtime) &&; + ArrayBuffer getArrayBuffer(IRuntime& runtime) &&; + + /// \return a TypedArray instance which refers to the same underlying + /// object. If \c isTypedArray() would return false, this will assert. + TypedArray getTypedArray(IRuntime& runtime) const&; + + /// \return an Uint8Array instance which refers to the same underlying + /// object. If \c isUint8Array() would return false, this will assert. + Uint8Array getUint8Array(IRuntime& runtime) const&; /// \return a Function instance which refers to the same underlying /// object. If \c isFunction() would return false, this will assert. - Function getFunction(Runtime& runtime) const&; + Function getFunction(IRuntime& runtime) const&; /// \return a Function instance which refers to the same underlying /// object. If \c isFunction() would return false, this will assert. - Function getFunction(Runtime& runtime) &&; + Function getFunction(IRuntime& runtime) &&; /// \return a Function instance which refers to the same underlying /// object. If \c isFunction() would return false, this will throw /// JSIException. - Function asFunction(Runtime& runtime) const&; + Function asFunction(IRuntime& runtime) const&; /// \return a Function instance which refers to the same underlying /// object. If \c isFunction() would return false, this will throw /// JSIException. - Function asFunction(Runtime& runtime) &&; + Function asFunction(IRuntime& runtime) &&; /// \return a shared_ptr which refers to the same underlying /// \c HostObject that was used to create this object. If \c isHostObject /// is false, this will assert. Note that this does a type check and will /// assert if the underlying HostObject isn't of type \c T template - std::shared_ptr getHostObject(Runtime& runtime) const; + std::shared_ptr getHostObject(IRuntime& runtime) const; /// \return a shared_ptr which refers to the same underlying /// \c HostObject that was used to create this object. If \c isHostObject /// is false, this will throw. template - std::shared_ptr asHostObject(Runtime& runtime) const; + std::shared_ptr asHostObject(IRuntime& runtime) const; /// \return whether this object has native state of type T previously set by /// \c setNativeState. template - bool hasNativeState(Runtime& runtime) const; + bool hasNativeState(IRuntime& runtime) const; /// \return a shared_ptr to the state previously set by \c setNativeState. /// If \c hasNativeState is false, this will assert. Note that this does a /// type check and will assert if the native state isn't of type \c T template - std::shared_ptr getNativeState(Runtime& runtime) const; + std::shared_ptr getNativeState(IRuntime& runtime) const; /// Set the internal native state property of this object, overwriting any old /// value. Creates a new shared_ptr to the object managed by \p state, which /// will live until the value at this property becomes unreachable. /// /// Throws a type error if this object is a proxy or host object. - void setNativeState(Runtime& runtime, - std::shared_ptr state) const; + void setNativeState(IRuntime& runtime, std::shared_ptr state) + const; /// \return same as \c getProperty(name).asObject(), except with /// a better exception message. - Object getPropertyAsObject(Runtime& runtime, const char* name) const; + Object getPropertyAsObject(IRuntime& runtime, const char* name) const; /// \return similar to \c /// getProperty(name).getObject().getFunction(), except it will /// throw JSIException instead of asserting if the property is /// not an object, or the object is not callable. - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + Function getPropertyAsFunction(IRuntime& runtime, const char* name) const; /// \return an Array consisting of all enumerable property names in /// the object and its prototype chain. All values in the return /// will be isString(). (This is probably not optimal, but it /// works. I only need it in one place.) - Array getPropertyNames(Runtime& runtime) const; + Array getPropertyNames(IRuntime& runtime) const; /// Inform the runtime that there is additional memory associated with a given /// JavaScript object that is not visible to the GC. This can be used if an @@ -911,16 +1427,27 @@ class JSI_EXPORT Object : public Pointer { /// calls will overwrite any previously set value. Once the object is garbage /// collected, the associated external memory will be considered freed and may /// no longer factor into GC decisions. - void setExternalMemoryPressure(Runtime& runtime, size_t amt) const; + void setExternalMemoryPressure(IRuntime& runtime, size_t amt) const; protected: - void setPropertyValue(Runtime& runtime, const String& name, - const Value& value) const { + void setPropertyValue( + IRuntime& runtime, + const String& name, + const Value& value) const { return runtime.setPropertyValue(*this, name, value); } - void setPropertyValue(Runtime& runtime, const PropNameID& name, - const Value& value) const { + void setPropertyValue( + IRuntime& runtime, + const PropNameID& name, + const Value& value) const { + return runtime.setPropertyValue(*this, name, value); + } + + void setPropertyValue( + IRuntime& runtime, + const Value& name, + const Value& value) const { return runtime.setPropertyValue(*this, name, value); } @@ -939,14 +1466,14 @@ class JSI_EXPORT WeakObject : public Pointer { WeakObject& operator=(WeakObject&& other) = default; /// Create a WeakObject from an Object. - WeakObject(Runtime& runtime, const Object& o) + WeakObject(IRuntime& runtime, const Object& o) : WeakObject(runtime.createWeakObject(o)) {} /// \return a Value representing the underlying Object if it is still valid; /// otherwise returns \c undefined. Note that this method has nothing to do /// with threads or concurrency. The name is based on std::weak_ptr::lock() /// which serves a similar purpose. - Value lock(Runtime& runtime) const; + Value lock(IRuntime& runtime) const; friend class Runtime; }; @@ -957,47 +1484,63 @@ class JSI_EXPORT Array : public Object { public: Array(Array&&) = default; /// Creates a new Array instance, with \c length undefined elements. - Array(Runtime& runtime, size_t length) : Array(runtime.createArray(length)) {} + Array(IRuntime& runtime, size_t length) + : Array(runtime.createArray(length)) {} Array& operator=(Array&&) = default; /// \return the size of the Array, according to its length property. /// (C++ naming convention) - size_t size(Runtime& runtime) const { return runtime.size(*this); } + size_t size(IRuntime& runtime) const { + return runtime.size(*this); + } /// \return the size of the Array, according to its length property. /// (JS naming convention) - size_t length(Runtime& runtime) const { return size(runtime); } + size_t length(IRuntime& runtime) const { + return size(runtime); + } /// \return the property of the array at index \c i. If there is no /// such property, returns the undefined value. If \c i is out of /// range [ 0..\c length ] throws a JSIException. - Value getValueAtIndex(Runtime& runtime, size_t i) const; + Value getValueAtIndex(IRuntime& runtime, size_t i) const; /// Sets the property of the array at index \c i. The argument /// value behaves as with Object::setProperty(). If \c i is out of /// range [ 0..\c length ] throws a JSIException. template - void setValueAtIndex(Runtime& runtime, size_t i, T&& value) const; + void setValueAtIndex(IRuntime& runtime, size_t i, T&& value) const; + + /// Appends provides values to the end of the Array in the order they appear. + /// Returns the new length of the array. + template + size_t push(IRuntime& runtime, Args&&... args); - /// There is no current API for changing the size of an array once - /// created. We'll probably need that eventually. + /// Appends everything in \p elements to the end of the Array in the order + /// they appear. Returns the new length of the array. + size_t push(IRuntime& runtime, std::initializer_list elements); + + /// Appends \p count elements at \p elements to the end of the Array in the + /// order they appear. + size_t push(IRuntime& runtime, const Value* elements, size_t count); /// Creates a new Array instance from provided values template - static Array createWithElements(Runtime&, Args&&... args); + static Array createWithElements(IRuntime&, Args&&... args); /// Creates a new Array instance from initializer list. - static Array createWithElements(Runtime& runtime, - std::initializer_list elements); + static Array createWithElements( + IRuntime& runtime, + std::initializer_list elements); private: friend class Object; friend class Value; friend class Runtime; - void setValueAtIndexImpl(Runtime& runtime, size_t i, - const Value& value) const { + void setValueAtIndexImpl(IRuntime& runtime, size_t i, const Value& value) + const { return runtime.setValueAtIndexImpl(*this, i, value); } @@ -1010,17 +1553,32 @@ class JSI_EXPORT ArrayBuffer : public Object { ArrayBuffer(ArrayBuffer&&) = default; ArrayBuffer& operator=(ArrayBuffer&&) = default; - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + ArrayBuffer(IRuntime& runtime, std::shared_ptr buffer) : ArrayBuffer(runtime.createArrayBuffer(std::move(buffer))) {} /// \return the size of the ArrayBuffer storage. This is not affected by /// overriding the byteLength property. /// (C++ naming convention) - size_t size(Runtime& runtime) const { return runtime.size(*this); } + size_t size(IRuntime& runtime) const { + return runtime.size(*this); + } + + size_t length(IRuntime& runtime) const { + return runtime.size(*this); + } - size_t length(Runtime& runtime) const { return runtime.size(*this); } + uint8_t* data(IRuntime& runtime) const { + return runtime.data(*this); + } + + std::shared_ptr tryGetMutableBuffer(IRuntime& runtime) const { + return runtime.tryGetMutableBuffer(*this); + } - uint8_t* data(Runtime& runtime) const { return runtime.data(*this); } + /// \return true if the ArrayBuffer is detached, false otherwise. + bool detached(IRuntime& runtime) const { + return runtime.detached(*this); + } private: friend class Object; @@ -1047,10 +1605,11 @@ class JSI_EXPORT Function : public Object { /// shut down, and may occur on an arbitrary thread. If the function contains /// any captured values, you are responsible for ensuring that their /// destructors are safe to call on any thread. - static Function createFromHostFunction(Runtime& runtime, - const jsi::PropNameID& name, - unsigned int paramCount, - jsi::HostFunctionType func); + static Function createFromHostFunction( + IRuntime& runtime, + const jsi::PropNameID& name, + unsigned int paramCount, + jsi::HostFunctionType func); /// Calls the function with \c count \c args. The \c this value of the JS /// function will not be set by the C++ caller, similar to calling @@ -1058,7 +1617,7 @@ class JSI_EXPORT Function : public Object { /// \b Note: as with Function.prototype.apply, \c this may not always be /// \c undefined in the function itself. If the function is non-strict, /// \c this will be set to the global object. - Value call(Runtime& runtime, const Value* args, size_t count) const; + Value call(IRuntime& runtime, const Value* args, size_t count) const; /// Calls the function with a \c std::initializer_list of Value /// arguments. The \c this value of the JS function will not be set by the @@ -1067,7 +1626,7 @@ class JSI_EXPORT Function : public Object { /// \b Note: as with Function.prototype.apply, \c this may not always be /// \c undefined in the function itself. If the function is non-strict, /// \c this will be set to the global object. - Value call(Runtime& runtime, std::initializer_list args) const; + Value call(IRuntime& runtime, std::initializer_list args) const; /// Calls the function with any number of arguments similarly to /// Object::setProperty(). The \c this value of the JS function will not be @@ -1077,44 +1636,49 @@ class JSI_EXPORT Function : public Object { /// \c undefined in the function itself. If the function is non-strict, /// \c this will be set to the global object. template - Value call(Runtime& runtime, Args&&... args) const; + Value call(IRuntime& runtime, Args&&... args) const; /// Calls the function with \c count \c args and \c jsThis value passed /// as the \c this value. - Value callWithThis(Runtime& Runtime, const Object& jsThis, const Value* args, - size_t count) const; + Value callWithThis( + IRuntime& Runtime, + const Object& jsThis, + const Value* args, + size_t count) const; /// Calls the function with a \c std::initializer_list of Value /// arguments and \c jsThis passed as the \c this value. - Value callWithThis(Runtime& runtime, const Object& jsThis, - std::initializer_list args) const; + Value callWithThis( + IRuntime& runtime, + const Object& jsThis, + std::initializer_list args) const; /// Calls the function with any number of arguments similarly to /// Object::setProperty(), and with \c jsThis passed as the \c this value. template - Value callWithThis(Runtime& runtime, const Object& jsThis, - Args&&... args) const; + Value callWithThis(IRuntime& runtime, const Object& jsThis, Args&&... args) + const; /// Calls the function as a constructor with \c count \c args. Equivalent /// to calling `new Func` where `Func` is the js function reqresented by /// this. - Value callAsConstructor(Runtime& runtime, const Value* args, - size_t count) const; + Value callAsConstructor(IRuntime& runtime, const Value* args, size_t count) + const; /// Same as above `callAsConstructor`, except use an initializer_list to /// supply the arguments. - Value callAsConstructor(Runtime& runtime, - std::initializer_list args) const; + Value callAsConstructor(IRuntime& runtime, std::initializer_list args) + const; /// Same as above `callAsConstructor`, but automatically converts/wraps /// any argument with a jsi Value. template - Value callAsConstructor(Runtime& runtime, Args&&... args) const; + Value callAsConstructor(IRuntime& runtime, Args&&... args) const; /// Returns whether this was created with Function::createFromHostFunction. /// If true then you can use getHostFunction to get the underlying /// HostFunctionType. - bool isHostFunction(Runtime& runtime) const { + bool isHostFunction(IRuntime& runtime) const { return runtime.isHostFunction(*this); } @@ -1125,7 +1689,7 @@ class JSI_EXPORT Function : public Object { /// Note: The reference returned is borrowed from the JS object underlying /// \c this, and thus only lasts as long as the object underlying /// \c this does. - HostFunctionType& getHostFunction(Runtime& runtime) const { + HostFunctionType& getHostFunction(IRuntime& runtime) const { assert(isHostFunction(runtime)); return runtime.getHostFunction(*this); } @@ -1138,6 +1702,64 @@ class JSI_EXPORT Function : public Object { Function(Runtime::PointerValue* value) : Object(value) {} }; +/// Represents a JS TypedArray +class JSI_EXPORT TypedArray : public Object { + public: + TypedArray(TypedArray&&) = default; + TypedArray& operator=(TypedArray&&) = default; + + // Gets the buffer of this TypedArray + ArrayBuffer buffer(IRuntime& runtime) { + return runtime.buffer(*this); + } + + // Gets the byte offset of this TypedArray + size_t byteOffset(IRuntime& runtime) { + return runtime.byteOffset(*this); + } + + // Gets the byte length of this TypedArray + size_t byteLength(IRuntime& runtime) { + return runtime.byteLength(*this); + } + + // Gets the element length of this TypedArray + size_t length(IRuntime& runtime) { + return runtime.length(*this); + } + + private: + friend class Object; + friend class Value; + friend class Runtime; + friend class Uint8Array; + + explicit TypedArray(Runtime::PointerValue* value) : Object(value) {} +}; + +// Represents a JS Uint8Array +class JSI_EXPORT Uint8Array : public TypedArray { + public: + Uint8Array(Uint8Array&&) = default; + Uint8Array& operator=(Uint8Array&&) = default; + + Uint8Array(IRuntime& runtime, size_t length) + : Uint8Array(runtime.createUint8Array(length)) {} + Uint8Array( + IRuntime& runtime, + const ArrayBuffer& buffer, + size_t offset, + size_t length) + : Uint8Array(runtime.createUint8Array(buffer, offset, length)) {} + + private: + friend class Object; + friend class Value; + friend class Runtime; + + explicit Uint8Array(Runtime::PointerValue* value) : TypedArray(value) {} +}; + /// Represents any JS Value (undefined, null, boolean, number, symbol, /// string, or object). Movable, or explicitly copyable (has no copy /// ctor). @@ -1150,20 +1772,28 @@ class JSI_EXPORT Value { /* implicit */ Value(std::nullptr_t) : kind_(NullKind) {} /// Creates a boolean JS value. - /* implicit */ Value(bool b) : Value(BooleanKind) { data_.boolean = b; } + /* implicit */ Value(bool b) : Value(BooleanKind) { + data_.boolean = b; + } /// Creates a number JS value. - /* implicit */ Value(double d) : Value(NumberKind) { data_.number = d; } + /* implicit */ Value(double d) : Value(NumberKind) { + data_.number = d; + } /// Creates a number JS value. - /* implicit */ Value(int i) : Value(NumberKind) { data_.number = i; } + /* implicit */ Value(int i) : Value(NumberKind) { + data_.number = i; + } /// Moves a Symbol, String, or Object rvalue into a new JS value. - template ::value || - std::is_base_of::value || - std::is_base_of::value || - std::is_base_of::value>> + template < + typename T, + typename = std::enable_if_t< + std::is_base_of::value || + std::is_base_of::value || + std::is_base_of::value || + std::is_base_of::value>> /* implicit */ Value(T&& other) : Value(kindOf(other)) { new (&data_.pointer) T(std::move(other)); } @@ -1172,59 +1802,65 @@ class JSI_EXPORT Value { /// compile error. template Value(const char*) { - static_assert(!std::is_same::value, - "Value cannot be constructed directly from const char*"); + static_assert( + !std::is_same::value, + "Value cannot be constructed directly from const char*"); } Value(Value&& other) noexcept; /// Copies a Symbol lvalue into a new JS value. - Value(Runtime& runtime, const Symbol& sym) : Value(SymbolKind) { + Value(IRuntime& runtime, const Symbol& sym) : Value(SymbolKind) { new (&data_.pointer) Symbol(runtime.cloneSymbol(sym.ptr_)); } /// Copies a BigInt lvalue into a new JS value. - Value(Runtime& runtime, const BigInt& bigint) : Value(BigIntKind) { + Value(IRuntime& runtime, const BigInt& bigint) : Value(BigIntKind) { new (&data_.pointer) BigInt(runtime.cloneBigInt(bigint.ptr_)); } /// Copies a String lvalue into a new JS value. - Value(Runtime& runtime, const String& str) : Value(StringKind) { + Value(IRuntime& runtime, const String& str) : Value(StringKind) { new (&data_.pointer) String(runtime.cloneString(str.ptr_)); } /// Copies a Object lvalue into a new JS value. - Value(Runtime& runtime, const Object& obj) : Value(ObjectKind) { + Value(IRuntime& runtime, const Object& obj) : Value(ObjectKind) { new (&data_.pointer) Object(runtime.cloneObject(obj.ptr_)); } /// Creates a JS value from another Value lvalue. - Value(Runtime& runtime, const Value& value); + Value(IRuntime& runtime, const Value& value); /// Value(rt, "foo") will treat foo as a bool. This makes doing /// that a compile error. template - Value(Runtime&, const char*) { - static_assert(!std::is_same::value, - "Value cannot be constructed directly from const char*"); + Value(IRuntime&, const char*) { + static_assert( + !std::is_same::value, + "Value cannot be constructed directly from const char*"); } ~Value(); // \return the undefined \c Value. - static Value undefined() { return Value(); } + static Value undefined() { + return Value(); + } // \return the null \c Value. - static Value null() { return Value(nullptr); } + static Value null() { + return Value(nullptr); + } // \return a \c Value created from a utf8-encoded JSON string. - static Value createFromJsonUtf8(Runtime& runtime, const uint8_t* json, - size_t length) { + static Value + createFromJsonUtf8(IRuntime& runtime, const uint8_t* json, size_t length) { return runtime.createValueFromJsonUtf8(json, length); } /// \return according to the Strict Equality Comparison algorithm, see: /// https://262.ecma-international.org/11.0/#sec-strict-equality-comparison - static bool strictEquals(Runtime& runtime, const Value& a, const Value& b); + static bool strictEquals(IRuntime& runtime, const Value& a, const Value& b); Value& operator=(Value&& other) noexcept { this->~Value(); @@ -1232,21 +1868,59 @@ class JSI_EXPORT Value { return *this; } - bool isUndefined() const { return kind_ == UndefinedKind; } + bool isUndefined() const { + return kind_ == UndefinedKind; + } + + bool isNull() const { + return kind_ == NullKind; + } + + bool isBool() const { + return kind_ == BooleanKind; + } - bool isNull() const { return kind_ == NullKind; } + bool isNumber() const { + return kind_ == NumberKind; + } - bool isBool() const { return kind_ == BooleanKind; } + bool isString() const { + return kind_ == StringKind; + } - bool isNumber() const { return kind_ == NumberKind; } + bool isBigInt() const { + return kind_ == BigIntKind; + } + + bool isSymbol() const { + return kind_ == SymbolKind; + } - bool isString() const { return kind_ == StringKind; } + bool isObject() const { + return kind_ == ObjectKind; + } - bool isBigInt() const { return kind_ == BigIntKind; } + /// \returns true if `Number.isInteger(value)` returns true, false otherwise. + bool isInteger() const { + // 1. If number is an integral Number, return true. + // 2. Return false. - bool isSymbol() const { return kind_ == SymbolKind; } + // The spec's definition of integral number: + // When the term integer is used in this specification, it refers to a + // mathematical value which is in the set of integers, unless otherwise + // stated. When the term integral Number is used in this specification, it + // refers to a finite Number value whose mathematical value is in the set of + // integers. - bool isObject() const { return kind_ == ObjectKind; } + if (!isNumber()) { + return false; + } + auto number = data_.number; + if (!std::isfinite(number)) { + return false; + } + return number == std::trunc(number); + } /// \return the boolean value, or asserts if not a boolean. bool getBool() const { @@ -1269,14 +1943,14 @@ class JSI_EXPORT Value { double asNumber() const; /// \return the Symbol value, or asserts if not a symbol. - Symbol getSymbol(Runtime& runtime) const& { + Symbol getSymbol(IRuntime& runtime) const& { assert(isSymbol()); return Symbol(runtime.cloneSymbol(data_.pointer.ptr_)); } /// \return the Symbol value, or asserts if not a symbol. /// Can be used on rvalue references to avoid cloning more symbols. - Symbol getSymbol(Runtime&) && { + Symbol getSymbol(IRuntime&) && { assert(isSymbol()); auto ptr = data_.pointer.ptr_; data_.pointer.ptr_ = nullptr; @@ -1285,18 +1959,18 @@ class JSI_EXPORT Value { /// \return the Symbol value, or throws JSIException if not a /// symbol - Symbol asSymbol(Runtime& runtime) const&; - Symbol asSymbol(Runtime& runtime) &&; + Symbol asSymbol(IRuntime& runtime) const&; + Symbol asSymbol(IRuntime& runtime) &&; /// \return the BigInt value, or asserts if not a bigint. - BigInt getBigInt(Runtime& runtime) const& { + BigInt getBigInt(IRuntime& runtime) const& { assert(isBigInt()); return BigInt(runtime.cloneBigInt(data_.pointer.ptr_)); } /// \return the BigInt value, or asserts if not a bigint. /// Can be used on rvalue references to avoid cloning more bigints. - BigInt getBigInt(Runtime&) && { + BigInt getBigInt(IRuntime&) && { assert(isBigInt()); auto ptr = data_.pointer.ptr_; data_.pointer.ptr_ = nullptr; @@ -1305,18 +1979,18 @@ class JSI_EXPORT Value { /// \return the BigInt value, or throws JSIException if not a /// bigint - BigInt asBigInt(Runtime& runtime) const&; - BigInt asBigInt(Runtime& runtime) &&; + BigInt asBigInt(IRuntime& runtime) const&; + BigInt asBigInt(IRuntime& runtime) &&; /// \return the String value, or asserts if not a string. - String getString(Runtime& runtime) const& { + String getString(IRuntime& runtime) const& { assert(isString()); return String(runtime.cloneString(data_.pointer.ptr_)); } /// \return the String value, or asserts if not a string. /// Can be used on rvalue references to avoid cloning more strings. - String getString(Runtime&) && { + String getString(IRuntime&) && { assert(isString()); auto ptr = data_.pointer.ptr_; data_.pointer.ptr_ = nullptr; @@ -1325,18 +1999,18 @@ class JSI_EXPORT Value { /// \return the String value, or throws JSIException if not a /// string. - String asString(Runtime& runtime) const&; - String asString(Runtime& runtime) &&; + String asString(IRuntime& runtime) const&; + String asString(IRuntime& runtime) &&; /// \return the Object value, or asserts if not an object. - Object getObject(Runtime& runtime) const& { + Object getObject(IRuntime& runtime) const& { assert(isObject()); return Object(runtime.cloneObject(data_.pointer.ptr_)); } /// \return the Object value, or asserts if not an object. /// Can be used on rvalue references to avoid cloning more objects. - Object getObject(Runtime&) && { + Object getObject(IRuntime&) && { assert(isObject()); auto ptr = data_.pointer.ptr_; data_.pointer.ptr_ = nullptr; @@ -1345,11 +2019,11 @@ class JSI_EXPORT Value { /// \return the Object value, or throws JSIException if not an /// object. - Object asObject(Runtime& runtime) const&; - Object asObject(Runtime& runtime) &&; + Object asObject(IRuntime& runtime) const&; + Object asObject(IRuntime& runtime) &&; // \return a String like JS .toString() would do. - String toString(Runtime& runtime) const; + String toString(IRuntime& runtime) const; private: friend class Runtime; @@ -1369,8 +2043,9 @@ class JSI_EXPORT Value { union Data { // Value's ctor and dtor will manage the lifecycle of the contained Data. Data() { - static_assert(sizeof(Data) == sizeof(uint64_t), - "Value data should fit in a 64-bit register"); + static_assert( + sizeof(Data) == sizeof(uint64_t), + "Value data should fit in a 64-bit register"); } ~Data() {} @@ -1378,15 +2053,23 @@ class JSI_EXPORT Value { bool boolean; double number; // pointers - Pointer pointer; // Symbol, String, Object, Array, Function + Pointer pointer; // Symbol, String, Object, Array, Function }; Value(ValueKind kind) : kind_(kind) {} - constexpr static ValueKind kindOf(const Symbol&) { return SymbolKind; } - constexpr static ValueKind kindOf(const BigInt&) { return BigIntKind; } - constexpr static ValueKind kindOf(const String&) { return StringKind; } - constexpr static ValueKind kindOf(const Object&) { return ObjectKind; } + constexpr static ValueKind kindOf(const Symbol&) { + return SymbolKind; + } + constexpr static ValueKind kindOf(const BigInt&) { + return BigIntKind; + } + constexpr static ValueKind kindOf(const String&) { + return StringKind; + } + constexpr static ValueKind kindOf(const Object&) { + return ObjectKind; + } ValueKind kind_; Data data_; @@ -1414,8 +2097,10 @@ class JSI_EXPORT Value { /// locking, provided that the lock (if any) is managed with RAII helpers. class JSI_EXPORT Scope { public: - explicit Scope(Runtime& rt) : rt_(rt), prv_(rt.pushScope()) {} - ~Scope() { rt_.popScope(prv_); } + explicit Scope(IRuntime& rt) : rt_(rt), prv_(rt.pushScope()) {} + ~Scope() { + rt_.popScope(prv_); + } Scope(const Scope&) = delete; Scope(Scope&&) = delete; @@ -1424,13 +2109,13 @@ class JSI_EXPORT Scope { Scope& operator=(Scope&&) = delete; template - static auto callInNewScope(Runtime& rt, F f) -> decltype(f()) { + static auto callInNewScope(IRuntime& rt, F f) -> decltype(f()) { Scope s(rt); return f(); } private: - Runtime& rt_; + IRuntime& rt_; Runtime::ScopeState* prv_; }; @@ -1443,7 +2128,9 @@ class JSI_EXPORT JSIException : public std::exception { public: JSIException(const JSIException&) = default; - virtual const char* what() const noexcept override { return what_.c_str(); } + virtual const char* what() const noexcept override { + return what_.c_str(); + } virtual ~JSIException() override; @@ -1468,38 +2155,66 @@ class JSI_EXPORT JSINativeException : public JSIException { class JSI_EXPORT JSError : public JSIException { public: /// Creates a JSError referring to provided \c value - JSError(Runtime& r, Value&& value); + JSError(IRuntime& r, Value&& value); /// Creates a JSError referring to new \c Error instance capturing current /// JavaScript stack. The error message property is set to given \c message. - JSError(Runtime& rt, std::string message); + JSError(IRuntime& rt, std::string message); /// Creates a JSError referring to new \c Error instance capturing current /// JavaScript stack. The error message property is set to given \c message. - JSError(Runtime& rt, const char* message) + JSError(IRuntime& rt, const char* message) : JSError(rt, std::string(message)) {} /// Creates a JSError referring to a JavaScript Object having message and /// stack properties set to provided values. - JSError(Runtime& rt, std::string message, std::string stack); + JSError(IRuntime& rt, std::string message, std::string stack); /// Creates a JSError referring to provided value and what string /// set to provided message. This argument order is a bit weird, /// but necessary to avoid ambiguity with the above. - JSError(std::string what, Runtime& rt, Value&& value); + JSError(std::string what, IRuntime& rt, Value&& value); /// Creates a JSError referring to the provided value, message and stack. This /// constructor does not take a Runtime parameter, and therefore cannot result /// in recursively invoking the JSError constructor. JSError(Value&& value, std::string message, std::string stack); + /// Creates a JSError referring to new \c EvalError instance. The error + /// message property is set to given \c message. + static JSError createEvalError(IRuntime& rt, const std::string& message); + + /// Creates a JSError referring to new \c RangeError instance. The error + /// message property is set to given \c message. + static JSError createRangeError(IRuntime& rt, const std::string& message); + + /// Creates a JSError referring to new \c ReferenceError instance. The error + /// message property is set to given \c message. + static JSError createReferenceError(IRuntime& rt, const std::string& message); + + /// Creates a JSError referring to new \c SyntaxError instance. The error + /// message property is set to given \c message. + static JSError createSyntaxError(IRuntime& rt, const std::string& message); + + /// Creates a JSError referring to new \c TypeError instance. The error + /// message property is set to given \c message. + static JSError createTypeError(IRuntime& rt, const std::string& message); + + /// Creates a JSError referring to new \c URIError instance. The error + /// message property is set to given \c message. + static JSError createURIError(IRuntime& rt, const std::string& message); + JSError(const JSError&) = default; virtual ~JSError(); - const std::string& getStack() const { return stack_; } + const std::string& getStack() const { + return stack_; + } - const std::string& getMessage() const { return message_; } + const std::string& getMessage() const { + return message_; + } const jsi::Value& value() const { assert(value_); @@ -1510,7 +2225,7 @@ class JSI_EXPORT JSError : public JSIException { // This initializes the value_ member and does some other // validation, so it must be called by every branch through the // constructors. - void setValue(Runtime& rt, Value&& value); + void setValue(IRuntime& rt, Value&& value); // This needs to be on the heap, because throw requires the object // be copyable, and Value is not. @@ -1519,7 +2234,33 @@ class JSI_EXPORT JSError : public JSIException { std::string stack_; }; -} // namespace jsi -} // namespace facebook +/// Helper function to cast the object pointed to by \p ptr into an interface +/// specified by \c U. If cast is successful, return a pointer to the object +/// as a raw pointer of \c U. Otherwise, return nullptr. +/// The returned interface same lifetime as the object referenced by \p ptr. +template +U* castInterface(T* ptr) { + if (ptr) { + return static_cast(ptr->castInterface(U::uuid)); + } + return nullptr; +} + +/// Helper function to cast the object managed by the shared_ptr \p ptr into an +/// interface specified by \c U. If the cast is successful, return a shared_ptr +/// of type \c U to the object. Otherwise, return an empty pointer. +/// The returned shared_ptr shares ownership of the object with \p ptr. +template +std::shared_ptr dynamicInterfaceCast(T&& ptr) { + auto* p = ptr->castInterface(U::uuid); + U* res = static_cast(p); + if (res) { + return std::shared_ptr(std::forward(ptr), res); + } + return nullptr; +} + +} // namespace jsi +} // namespace facebook -#include "jsi/jsi-inl.h" \ No newline at end of file +#include diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsilib.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsilib.h index 0dad2ee87..c94de89f6 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsilib.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/jsilib.h @@ -7,7 +7,7 @@ #pragma once -#include "jsi/jsi.h" +#include namespace facebook { namespace jsi { @@ -17,9 +17,13 @@ class FileBuffer : public Buffer { FileBuffer(const std::string& path); ~FileBuffer() override; - size_t size() const override { return size_; } + size_t size() const override { + return size_; + } - const uint8_t* data() const override { return data_; } + const uint8_t* data() const override { + return data_; + } private: size_t size_; @@ -34,15 +38,22 @@ class SourceJavaScriptPreparation final : public jsi::PreparedJavaScript, std::string sourceURL_; public: - SourceJavaScriptPreparation(std::shared_ptr buf, - std::string sourceURL) + SourceJavaScriptPreparation( + std::shared_ptr buf, + std::string sourceURL) : buf_(std::move(buf)), sourceURL_(std::move(sourceURL)) {} - const std::string& sourceURL() const { return sourceURL_; } + const std::string& sourceURL() const { + return sourceURL_; + } - size_t size() const override { return buf_->size(); } - const uint8_t* data() const override { return buf_->data(); } + size_t size() const override { + return buf_->size(); + } + const uint8_t* data() const override { + return buf_->data(); + } }; -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/threadsafe.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/threadsafe.h index 768edb4ae..cb10a335f 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/threadsafe.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/threadsafe.h @@ -9,8 +9,8 @@ #include -#include "jsi/decorator.h" -#include "jsi/jsi.h" +#include +#include namespace facebook { namespace jsi { @@ -28,8 +28,12 @@ template struct WithLock { L lock; WithLock(R& r) : lock(r) {} - void before() { lock.lock(); } - void after() { lock.unlock(); } + void before() { + lock.lock(); + } + void after() { + lock.unlock(); + } }; // The actual implementation of a given ThreadSafeRuntime. It's parameterized @@ -46,8 +50,9 @@ class ThreadSafeRuntimeImpl final public: template ThreadSafeRuntimeImpl(Args&&... args) - : WithRuntimeDecorator, R, ThreadSafeRuntime>(unsafe_, - lock_), + : WithRuntimeDecorator, R, ThreadSafeRuntime>( + unsafe_, + lock_), unsafe_(std::forward(args)...), lock_(unsafe_) {} @@ -55,16 +60,20 @@ class ThreadSafeRuntimeImpl final return WithRuntimeDecorator, R, ThreadSafeRuntime>::plain(); } - void lock() const override { lock_.before(); } + void lock() const override { + lock_.before(); + } - void unlock() const override { lock_.after(); } + void unlock() const override { + lock_.after(); + } private: R unsafe_; mutable WithLock lock_; }; -} // namespace detail +} // namespace detail -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/jsi/jsi.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/jsi/jsi.cpp index 5e3aac990..792b5bf5a 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/jsi/jsi.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/jsi/jsi.cpp @@ -5,22 +5,80 @@ * LICENSE file in the root directory of this source tree. */ -#include "jsi/jsi.h" - #include #include #include +#include +#include #include -#include "jsi/instrumentation.h" +#include +#include namespace facebook { namespace jsi { namespace { +/// A global map used to store custom runtime data for VMs that do not provide +/// their own default implementation of setRuntimeData and getRuntimeData. +struct RuntimeDataGlobal { + /// Mutex protecting the Runtime data map + std::mutex mutex_{}; + /// Maps a runtime pointer to a map of its custom data. At destruction of the + /// runtime, its entry will be removed from the global map. + std::unordered_map< + Runtime*, + std::unordered_map< + UUID, + std::pair, + UUID::Hash>> + dataMap_; +}; + +RuntimeDataGlobal& getRuntimeDataGlobal() { + static RuntimeDataGlobal runtimeData{}; + return runtimeData; +} + +/// A host object that, when destructed, will remove the runtime's custom data +/// entry from the global map of custom data. +class RemoveRuntimeDataHostObject : public jsi::HostObject { + public: + explicit RemoveRuntimeDataHostObject(Runtime* runtime) : runtime_(runtime) {} + + RemoveRuntimeDataHostObject(const RemoveRuntimeDataHostObject&) = default; + RemoveRuntimeDataHostObject(RemoveRuntimeDataHostObject&&) = default; + RemoveRuntimeDataHostObject& operator=(const RemoveRuntimeDataHostObject&) = + default; + RemoveRuntimeDataHostObject& operator=(RemoveRuntimeDataHostObject&&) = + default; + + ~RemoveRuntimeDataHostObject() override { + auto& runtimeDataGlobal = getRuntimeDataGlobal(); + std::lock_guard lock(runtimeDataGlobal.mutex_); + auto runtimeMapIt = runtimeDataGlobal.dataMap_.find(runtime_); + // We install the RemoveRuntimeDataHostObject only when the first custom + // data for the runtime is added, and only this object is responsible for + // clearing runtime data. Thus, we should always be able to find the data + // entry. + assert( + runtimeMapIt != runtimeDataGlobal.dataMap_.end() && + "Custom runtime data not found for this runtime"); + + for (auto [_, entry] : runtimeMapIt->second) { + auto* deleter = entry.second; + deleter(entry.first); + } + runtimeDataGlobal.dataMap_.erase(runtime_); + } + + private: + Runtime* runtime_; +}; + // This is used for generating short exception strings. -std::string kindToString(const Value& v, Runtime* rt = nullptr) { +std::string kindToString(const Value& v, IRuntime* rt = nullptr) { if (v.isUndefined()) { return "undefined"; } else if (v.isNull()) { @@ -46,7 +104,10 @@ std::string kindToString(const Value& v, Runtime* rt = nullptr) { // failure is in building a JSError, this will lead to infinite // recursion. This function is used in place of getPropertyAsFunction // when building JSError, to avoid that infinite recursion. -Value callGlobalFunction(Runtime& runtime, const char* name, const Value& arg) { +Value callGlobalFunction( + IRuntime& runtime, + const char* name, + const Value& arg) { Value v = runtime.global().getProperty(runtime, name); if (!v.isObject()) { throw JSINativeException( @@ -122,7 +183,7 @@ uint32_t decodeUTF8(const char*& input) { return replacementCharacter; } ret = ((ch & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) | - (ch3 & 0x3F); + (ch3 & 0x3F); input += 4; if (ret <= 0xFFFF) { return replacementCharacter; @@ -164,7 +225,47 @@ std::u16string convertUTF8ToUTF16(const std::string& utf8) { return ret; } -} // namespace +// Given a unsigned number, which is less than 16, return the hex character. +inline char hexDigit(unsigned x) { + return x < 10 ? '0' + x : 'A' + (x - 10); +} + +// Given a sequence of UTF 16 code units, return true if all code units are +// ASCII characters +bool isAllASCII(const char16_t* utf16, size_t length) { + for (const char16_t* e = utf16 + length; utf16 != e; ++utf16) { + if (*utf16 > 0x7F) { + return false; + } + } + return true; +} + +// Given a sequences of UTF 16 code units, return a string that explicitly +// expresses the code units +std::string getUtf16CodeUnitString(const char16_t* utf16, size_t length) { + // Every character will need 4 hex digits + the character escape "\u". + // Plus 2 character for the opening and closing single quote. + std::string s = std::string(6 * length + 2, 0); + s.front() = '\''; + + for (size_t i = 0; i != length; ++i) { + char16_t ch = utf16[i]; + size_t start = (6 * i) + 1; + + s[start] = '\\'; + s[start + 1] = 'u'; + + s[start + 2] = hexDigit((ch >> 12) & 0x000f); + s[start + 3] = hexDigit((ch >> 8) & 0x000f); + s[start + 4] = hexDigit((ch >> 4) & 0x000f); + s[start + 5] = hexDigit(ch & 0x000f); + } + s.back() = '\''; + return s; +} + +} // namespace Buffer::~Buffer() = default; @@ -172,7 +273,9 @@ MutableBuffer::~MutableBuffer() = default; PreparedJavaScript::~PreparedJavaScript() = default; -Value HostObject::get(Runtime&, const PropNameID&) { return Value(); } +Value HostObject::get(Runtime&, const PropNameID&) { + return Value(); +} void HostObject::set(Runtime& rt, const PropNameID& name, const Value&) { std::string msg("TypeError: Cannot assign to property '"); @@ -185,11 +288,21 @@ HostObject::~HostObject() {} NativeState::~NativeState() {} +#ifdef JSI_UNSTABLE +Serialized::~Serialized() {} +#endif + Runtime::~Runtime() {} +ICast* Runtime::castInterface(const UUID& /*interfaceUUID*/) { + return nullptr; +} + Instrumentation& Runtime::instrumentation() { class NoInstrumentation : public Instrumentation { - std::string getRecordedGCStats() override { return ""; } + std::string getRecordedGCStats() override { + return ""; + } std::unordered_map getHeapInfo(bool) override { return std::unordered_map{}; @@ -198,31 +311,41 @@ Instrumentation& Runtime::instrumentation() { void collectGarbage(std::string) override {} void startTrackingHeapObjectStackTraces( - std::function)>) override {} + std::function)>) override {} void stopTrackingHeapObjectStackTraces() override {} void startHeapSampling(size_t) override {} void stopHeapSampling(std::ostream&) override {} - void createSnapshotToFile(const std::string& /*path*/, - const HeapSnapshotOptions& /*options*/) override { + void createSnapshotToFile( + const std::string& /*path*/, + const HeapSnapshotOptions& /*options*/) override { throw JSINativeException( "Default instrumentation cannot create a heap snapshot"); } void createSnapshotToStream( - std::ostream& /*os*/, const HeapSnapshotOptions& /*options*/) override { + std::ostream& /*os*/, + const HeapSnapshotOptions& /*options*/) override { throw JSINativeException( "Default instrumentation cannot create a heap snapshot"); } - std::string flushAndDisableBridgeTrafficTrace() override { std::abort(); } + std::string flushAndDisableBridgeTrafficTrace() override { + std::abort(); + } void writeBasicBlockProfileTraceToFile(const std::string&) const override { std::abort(); } + void dumpOpcodeStats(std::ostream&) const override { + std::abort(); + } + void dumpProfilerSymbolsToFile(const std::string&) const override { std::abort(); } @@ -239,6 +362,25 @@ Value Runtime::createValueFromJsonUtf8(const uint8_t* json, size_t length) { return parseJson.call(*this, String::createFromUtf8(*this, json, length)); } +String Runtime::createStringFromUtf16(const char16_t* utf16, size_t length) { + if (isAllASCII(utf16, length)) { + std::string buffer(utf16, utf16 + length); + return createStringFromAscii(buffer.data(), length); + } + auto s = getUtf16CodeUnitString(utf16, length); + return global() + .getPropertyAsFunction(*this, "eval") + .call(*this, s) + .getString(*this); +} + +PropNameID Runtime::createPropNameIDFromUtf16( + const char16_t* utf16, + size_t length) { + auto jsString = createStringFromUtf16(utf16, length); + return createPropNameIDFromString(jsString); +} + std::u16string Runtime::utf16(const PropNameID& sym) { auto utf8Str = utf8(sym); return convertUTF8ToUTF16(utf8Str); @@ -249,16 +391,18 @@ std::u16string Runtime::utf16(const String& str) { return convertUTF8ToUTF16(utf8Str); } -void Runtime::getStringData(const jsi::String& str, void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, - size_t num)) { +void Runtime::getStringData( + const jsi::String& str, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) { auto utf16Str = utf16(str); cb(ctx, false, utf16Str.data(), utf16Str.size()); } -void Runtime::getPropNameIdData(const jsi::PropNameID& sym, void* ctx, - void (*cb)(void* ctx, bool ascii, - const void* data, size_t num)) { +void Runtime::getPropNameIdData( + const jsi::PropNameID& sym, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) { auto utf16Str = utf16(sym); cb(ctx, false, utf16Str.data(), utf16Str.size()); } @@ -284,6 +428,244 @@ Object Runtime::createObjectWithPrototype(const Value& prototype) { return createFn.call(*this, prototype).asObject(*this); } +void Runtime::deleteProperty(const Object& object, const PropNameID& name) { + auto nameStr = String::createFromUtf16(*this, name.utf16(*this)); + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, nameStr).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} + +void Runtime::deleteProperty(const Object& object, const String& name) { + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, name).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} + +void Runtime::deleteProperty(const Object& object, const Value& name) { + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, name).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} + +size_t Runtime::push(const Array& arr, const Value* elements, size_t count) { + size_t newSize = size(arr); + for (size_t i = 0; i < count; i++) { + arr.setProperty(*this, Value((int)newSize), elements[i]); + ++newSize; + } + arr.setProperty(*this, "length", Value((int)newSize)); + return newSize; +} + +void Runtime::setRuntimeDataImpl( + const UUID& uuid, + const void* data, + void (*deleter)(const void* data)) { + auto& runtimeDataGlobal = getRuntimeDataGlobal(); + std::lock_guard lock(runtimeDataGlobal.mutex_); + if (auto it = runtimeDataGlobal.dataMap_.find(this); + it != runtimeDataGlobal.dataMap_.end()) { + auto& map = it->second; + if (auto entryIt = map.find(uuid); entryIt != map.end()) { + // Free the old data + auto oldData = entryIt->second.first; + auto oldDataDeleter = entryIt->second.second; + oldDataDeleter(oldData); + } + map[uuid] = {data, deleter}; + return; + } + // No custom data entry exist for this runtime in the global map, so create + // one. + runtimeDataGlobal.dataMap_[this][uuid] = {data, deleter}; + + // The first time data is added for this runtime is added to the map, install + // a host object on the global object of the runtime. This host object is used + // to release the runtime's entry from the global custom data map when the + // runtime is destroyed. + // Also, try to protect the host object by making it non-configurable, + // non-enumerable, and non-writable. These JSI operations are purposely + // performed after runtime-specific data map is added and the host object is + // created to prevent data leaks if any operations fail. + Object ho = Object::createFromHostObject( + *this, std::make_shared(this)); + global().setProperty(*this, "_jsiRuntimeDataCleanUp", ho); + auto definePropertyFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "defineProperty"); + auto desc = Object(*this); + desc.setProperty(*this, "configurable", Value(false)); + desc.setProperty(*this, "enumerable", Value(false)); + desc.setProperty(*this, "writable", Value(false)); + definePropertyFn.call(*this, global(), "_jsiRuntimeDataCleanUp", desc); +} + +const void* Runtime::getRuntimeDataImpl(const UUID& uuid) { + auto& runtimeDataGlobal = getRuntimeDataGlobal(); + std::lock_guard lock(runtimeDataGlobal.mutex_); + if (auto runtimeMapIt = runtimeDataGlobal.dataMap_.find(this); + runtimeMapIt != runtimeDataGlobal.dataMap_.end()) { + if (auto customDataIt = runtimeMapIt->second.find(uuid); + customDataIt != runtimeMapIt->second.end()) { + return customDataIt->second.first; + } + } + return nullptr; +} + +Value Runtime::getProperty(const Object& object, const Value& name) { + auto getFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "get"); + return getFn.call(*this, object, name); +} + +bool Runtime::hasProperty(const Object& object, const Value& name) { + auto hasFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "has"); + return hasFn.call(*this, object, name).getBool(); +} + +void Runtime::setPropertyValue( + const Object& object, + const Value& name, + const Value& value) { + auto setFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "set"); + auto setResult = setFn.call(*this, object, name, value).getBool(); + if (!setResult) { + throw JSError(*this, "Failed to set the property"); + } +} + +std::shared_ptr Runtime::tryGetMutableBuffer( + const jsi::ArrayBuffer&) { + return nullptr; +} + +Uint8Array Runtime::createUint8Array(size_t length) { + auto uint8ArrayCtor = global().getPropertyAsFunction(*this, "Uint8Array"); + auto result = + uint8ArrayCtor.callAsConstructor(*this, static_cast(length)); + return Uint8Array(cloneObject(getPointerValue(result.getObject(*this)))); +} + +Uint8Array Runtime::createUint8Array( + const ArrayBuffer& buffer, + size_t offset, + size_t length) { + auto uint8ArrayCtor = global().getPropertyAsFunction(*this, "Uint8Array"); + auto result = uint8ArrayCtor.callAsConstructor( + *this, buffer, static_cast(offset), static_cast(length)); + return Uint8Array(cloneObject(getPointerValue(result.getObject(*this)))); +} + +Value Runtime::createError(const String& msg) { + return callGlobalFunction(*this, "Error", Value(*this, msg)); +} + +Value Runtime::createEvalError(const String& msg) { + return callGlobalFunction(*this, "EvalError", Value(*this, msg)); +} + +Value Runtime::createRangeError(const String& msg) { + return callGlobalFunction(*this, "RangeError", Value(*this, msg)); +} + +Value Runtime::createReferenceError(const String& msg) { + return callGlobalFunction(*this, "ReferenceError", Value(*this, msg)); +} + +Value Runtime::createSyntaxError(const String& msg) { + return callGlobalFunction(*this, "SyntaxError", Value(*this, msg)); +} + +Value Runtime::createTypeError(const String& msg) { + return callGlobalFunction(*this, "TypeError", Value(*this, msg)); +} + +Value Runtime::createURIError(const String& msg) { + return callGlobalFunction(*this, "URIError", Value(*this, msg)); +} + +size_t Runtime::length(const String& str) { + return utf16(str).size(); +} + +bool Runtime::detached(const ArrayBuffer& buffer) { + Value prop = buffer.getProperty(*this, "detached"); + if (!prop.isBool()) { + throw JSINativeException( + "ArrayBuffer.detached is not supported by this runtime"); + } + return prop.getBool(); +} + +ArrayBuffer Runtime::buffer(const TypedArray& typedArray) { + Value buffer = typedArray.getProperty(*this, "buffer"); + if (!buffer.isObject()) { + throw JSINativeException("TypedArray.buffer is not an object"); + } + Object bufferObj = buffer.getObject(*this); + if (!bufferObj.isArrayBuffer(*this)) { + throw JSINativeException("TypedArray.buffer is not an ArrayBuffer"); + } + return bufferObj.getArrayBuffer(*this); +} + +size_t Runtime::byteOffset(const TypedArray& typedArray) { + Value byteOffset = typedArray.getProperty(*this, "byteOffset"); + if (!byteOffset.isNumber()) { + throw JSINativeException("TypedArray.byteOffset is not a number"); + } + return static_cast(byteOffset.getNumber()); +} + +size_t Runtime::byteLength(const TypedArray& typedArray) { + Value byteLength = typedArray.getProperty(*this, "byteLength"); + if (!byteLength.isNumber()) { + throw JSINativeException("TypedArray.byteLength is not a number"); + } + return static_cast(byteLength.getNumber()); +} + +size_t Runtime::length(const TypedArray& typedArray) { + Value length = typedArray.getProperty(*this, "length"); + if (!length.isNumber()) { + throw JSINativeException("TypedArray.length is not a number"); + } + return static_cast(length.getNumber()); +} + +bool Runtime::isTypedArray(const Object& obj) const { + Runtime& self = const_cast(*this); + // Uint8Array.__proto__ is the %TypedArray% intrinsic constructor. + auto uint8ArrayCtor = self.global().getPropertyAsFunction(self, "Uint8Array"); + auto typedArrayCtor = + uint8ArrayCtor.getProperty(self, "__proto__").getObject(self); + return self.instanceOf(obj, typedArrayCtor.getFunction(self)); +} + +bool Runtime::isUint8Array(const Object& obj) const { + Runtime& self = const_cast(*this); + auto uint8ArrayCtor = self.global().getPropertyAsFunction(self, "Uint8Array"); + return self.instanceOf(obj, uint8ArrayCtor); +} + Pointer& Pointer::operator=(Pointer&& other) noexcept { if (ptr_) { ptr_->invalidate(); @@ -293,67 +675,92 @@ Pointer& Pointer::operator=(Pointer&& other) noexcept { return *this; } -Object Object::getPropertyAsObject(Runtime& runtime, const char* name) const { +Object Object::getPropertyAsObject(IRuntime& runtime, const char* name) const { Value v = getProperty(runtime, name); if (!v.isObject()) { - throw JSError(runtime, std::string("getPropertyAsObject: property '") + - name + "' is " + kindToString(v, &runtime) + - ", expected an Object"); + throw JSError( + runtime, + std::string("getPropertyAsObject: property '") + name + "' is " + + kindToString(v, &runtime) + ", expected an Object"); } return v.getObject(runtime); } -Function Object::getPropertyAsFunction(Runtime& runtime, - const char* name) const { +Function Object::getPropertyAsFunction(IRuntime& runtime, const char* name) + const { Object obj = getPropertyAsObject(runtime, name); if (!obj.isFunction(runtime)) { - throw JSError(runtime, std::string("getPropertyAsFunction: property '") + - name + "' is " + - kindToString(std::move(obj), &runtime) + - ", expected a Function"); + throw JSError( + runtime, + std::string("getPropertyAsFunction: property '") + name + "' is " + + kindToString(std::move(obj), &runtime) + ", expected a Function"); }; return std::move(obj).getFunction(runtime); } -Array Object::asArray(Runtime& runtime) const& { +Array Object::asArray(IRuntime& runtime) const& { if (!isArray(runtime)) { - throw JSError(runtime, "Object is " + - kindToString(Value(runtime, *this), &runtime) + - ", expected an array"); + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected an array"); } return getArray(runtime); } -Array Object::asArray(Runtime& runtime) && { +Array Object::asArray(IRuntime& runtime) && { if (!isArray(runtime)) { - throw JSError(runtime, "Object is " + - kindToString(Value(runtime, *this), &runtime) + - ", expected an array"); + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected an array"); } return std::move(*this).getArray(runtime); } -Function Object::asFunction(Runtime& runtime) const& { +Function Object::asFunction(IRuntime& runtime) const& { if (!isFunction(runtime)) { - throw JSError(runtime, "Object is " + - kindToString(Value(runtime, *this), &runtime) + - ", expected a function"); + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected a function"); } return getFunction(runtime); } -Function Object::asFunction(Runtime& runtime) && { +Function Object::asFunction(IRuntime& runtime) && { if (!isFunction(runtime)) { - throw JSError(runtime, "Object is " + - kindToString(Value(runtime, *this), &runtime) + - ", expected a function"); + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected a function"); } return std::move(*this).getFunction(runtime); } +TypedArray Object::asTypedArray(IRuntime& runtime) const& { + if (!isTypedArray(runtime)) { + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected a TypedArray"); + } + return getTypedArray(runtime); +} + +Uint8Array Object::asUint8Array(IRuntime& runtime) const& { + if (!isUint8Array(runtime)) { + throw JSError( + runtime, + "Object is " + kindToString(Value(runtime, *this), &runtime) + + ", expected a Uint8Array"); + } + return getUint8Array(runtime); +} + Value::Value(Value&& other) noexcept : Value(other.kind_) { if (kind_ == BooleanKind) { data_.boolean = other.data_.boolean; @@ -366,7 +773,7 @@ Value::Value(Value&& other) noexcept : Value(other.kind_) { other.kind_ = UndefinedKind; } -Value::Value(Runtime& runtime, const Value& other) : Value(other.kind_) { +Value::Value(IRuntime& runtime, const Value& other) : Value(other.kind_) { // data_ is uninitialized, so use placement new to create non-POD // types in it. Any other kind of initialization will call a dtor // first, which is incorrect. @@ -391,7 +798,7 @@ Value::~Value() { } } -bool Value::strictEquals(Runtime& runtime, const Value& a, const Value& b) { +bool Value::strictEquals(IRuntime& runtime, const Value& a, const Value& b) { if (a.kind_ != b.kind_) { return false; } @@ -404,25 +811,29 @@ bool Value::strictEquals(Runtime& runtime, const Value& a, const Value& b) { case NumberKind: return a.data_.number == b.data_.number; case SymbolKind: - return runtime.strictEquals(static_cast(a.data_.pointer), - static_cast(b.data_.pointer)); + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); case BigIntKind: - return runtime.strictEquals(static_cast(a.data_.pointer), - static_cast(b.data_.pointer)); + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); case StringKind: - return runtime.strictEquals(static_cast(a.data_.pointer), - static_cast(b.data_.pointer)); + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); case ObjectKind: - return runtime.strictEquals(static_cast(a.data_.pointer), - static_cast(b.data_.pointer)); + return runtime.strictEquals( + static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); } return false; } bool Value::asBool() const { if (!isBool()) { - throw JSINativeException("Value is " + kindToString(*this) + - ", expected a boolean"); + throw JSINativeException( + "Value is " + kindToString(*this) + ", expected a boolean"); } return getBool(); @@ -430,14 +841,14 @@ bool Value::asBool() const { double Value::asNumber() const { if (!isNumber()) { - throw JSINativeException("Value is " + kindToString(*this) + - ", expected a number"); + throw JSINativeException( + "Value is " + kindToString(*this) + ", expected a number"); } return getNumber(); } -Object Value::asObject(Runtime& rt) const& { +Object Value::asObject(IRuntime& rt) const& { if (!isObject()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected an Object"); @@ -446,7 +857,7 @@ Object Value::asObject(Runtime& rt) const& { return getObject(rt); } -Object Value::asObject(Runtime& rt) && { +Object Value::asObject(IRuntime& rt) && { if (!isObject()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected an Object"); @@ -456,7 +867,7 @@ Object Value::asObject(Runtime& rt) && { return static_cast(ptr); } -Symbol Value::asSymbol(Runtime& rt) const& { +Symbol Value::asSymbol(IRuntime& rt) const& { if (!isSymbol()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol"); @@ -465,7 +876,7 @@ Symbol Value::asSymbol(Runtime& rt) const& { return getSymbol(rt); } -Symbol Value::asSymbol(Runtime& rt) && { +Symbol Value::asSymbol(IRuntime& rt) && { if (!isSymbol()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol"); @@ -474,7 +885,7 @@ Symbol Value::asSymbol(Runtime& rt) && { return std::move(*this).getSymbol(rt); } -BigInt Value::asBigInt(Runtime& rt) const& { +BigInt Value::asBigInt(IRuntime& rt) const& { if (!isBigInt()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt"); @@ -483,7 +894,7 @@ BigInt Value::asBigInt(Runtime& rt) const& { return getBigInt(rt); } -BigInt Value::asBigInt(Runtime& rt) && { +BigInt Value::asBigInt(IRuntime& rt) && { if (!isBigInt()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt"); @@ -492,7 +903,7 @@ BigInt Value::asBigInt(Runtime& rt) && { return std::move(*this).getBigInt(rt); } -String Value::asString(Runtime& rt) const& { +String Value::asString(IRuntime& rt) const& { if (!isString()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a String"); @@ -501,7 +912,7 @@ String Value::asString(Runtime& rt) const& { return getString(rt); } -String Value::asString(Runtime& rt) && { +String Value::asString(IRuntime& rt) && { if (!isString()) { throw JSError( rt, "Value is " + kindToString(*this, &rt) + ", expected a String"); @@ -510,27 +921,28 @@ String Value::asString(Runtime& rt) && { return std::move(*this).getString(rt); } -String Value::toString(Runtime& runtime) const { +String Value::toString(IRuntime& runtime) const { Function toString = runtime.global().getPropertyAsFunction(runtime, "String"); return toString.call(runtime, *this).getString(runtime); } -uint64_t BigInt::asUint64(Runtime& runtime) const { +uint64_t BigInt::asUint64(IRuntime& runtime) const { if (!isUint64(runtime)) { throw JSError(runtime, "Lossy truncation in BigInt64::asUint64"); } return getUint64(runtime); } -int64_t BigInt::asInt64(Runtime& runtime) const { +int64_t BigInt::asInt64(IRuntime& runtime) const { if (!isInt64(runtime)) { throw JSError(runtime, "Lossy truncation in BigInt64::asInt64"); } return getInt64(runtime); } -Array Array::createWithElements(Runtime& rt, - std::initializer_list elements) { +Array Array::createWithElements( + IRuntime& rt, + std::initializer_list elements) { Array result(rt, elements.size()); size_t index = 0; for (const auto& element : elements) { @@ -539,25 +951,32 @@ Array Array::createWithElements(Runtime& rt, return result; } -std::vector HostObject::getPropertyNames(Runtime&) { return {}; } +std::vector HostObject::getPropertyNames(Runtime&) { + return {}; +} -Runtime::ScopeState* Runtime::pushScope() { return nullptr; } +Runtime::ScopeState* Runtime::pushScope() { + return nullptr; +} void Runtime::popScope(ScopeState*) {} -JSError::JSError(Runtime& rt, Value&& value) { setValue(rt, std::move(value)); } +JSError::JSError(IRuntime& rt, Value&& value) { + setValue(rt, std::move(value)); +} -JSError::JSError(Runtime& rt, std::string msg) : message_(std::move(msg)) { +JSError::JSError(IRuntime& rt, std::string msg) : message_(std::move(msg)) { try { - setValue(rt, callGlobalFunction(rt, "Error", - String::createFromUtf8(rt, message_))); + setValue( + rt, + callGlobalFunction(rt, "Error", String::createFromUtf8(rt, message_))); } catch (const JSIException& ex) { message_ = std::string(ex.what()) + " (while raising " + message_ + ")"; setValue(rt, String::createFromUtf8(rt, message_)); } } -JSError::JSError(Runtime& rt, std::string msg, std::string stack) +JSError::JSError(IRuntime& rt, std::string msg, std::string stack) : message_(std::move(msg)), stack_(std::move(stack)) { try { Object e(rt); @@ -569,7 +988,7 @@ JSError::JSError(Runtime& rt, std::string msg, std::string stack) } } -JSError::JSError(std::string what, Runtime& rt, Value&& value) +JSError::JSError(std::string what, IRuntime& rt, Value&& value) : JSIException(std::move(what)) { setValue(rt, std::move(value)); } @@ -580,7 +999,41 @@ JSError::JSError(Value&& value, std::string message, std::string stack) message_(std::move(message)), stack_(std::move(stack)) {} -void JSError::setValue(Runtime& rt, Value&& value) { +JSError JSError::createEvalError(IRuntime& rt, const std::string& message) { + return JSError( + message, rt, rt.createEvalError(String::createFromUtf8(rt, message))); +} + +JSError JSError::createRangeError(IRuntime& rt, const std::string& message) { + return JSError( + message, rt, rt.createRangeError(String::createFromUtf8(rt, message))); +} + +JSError JSError::createReferenceError( + IRuntime& rt, + const std::string& message) { + return JSError( + message, + rt, + rt.createReferenceError(String::createFromUtf8(rt, message))); +} + +JSError JSError::createSyntaxError(IRuntime& rt, const std::string& message) { + return JSError( + message, rt, rt.createSyntaxError(String::createFromUtf8(rt, message))); +} + +JSError JSError::createTypeError(IRuntime& rt, const std::string& message) { + return JSError( + message, rt, rt.createTypeError(String::createFromUtf8(rt, message))); +} + +JSError JSError::createURIError(IRuntime& rt, const std::string& message) { + return JSError( + message, rt, rt.createURIError(String::createFromUtf8(rt, message))); +} + +void JSError::setValue(IRuntime& rt, Value&& value) { value_ = std::make_shared(std::move(value)); if ((message_.empty() || stack_.empty()) && value_->isObject()) { @@ -599,7 +1052,7 @@ void JSError::setValue(Runtime& rt, Value&& value) { } } catch (const JSIException& ex) { message_ = std::string("[Exception while creating message string: ") + - ex.what() + "]"; + ex.what() + "]"; } } @@ -616,7 +1069,7 @@ void JSError::setValue(Runtime& rt, Value&& value) { } } catch (const JSIException& ex) { message_ = std::string("[Exception while creating stack string: ") + - ex.what() + "]"; + ex.what() + "]"; } } } @@ -635,7 +1088,7 @@ void JSError::setValue(Runtime& rt, Value&& value) { } } catch (const JSIException& ex) { message_ = std::string("[Exception while creating message string: ") + - ex.what() + "]"; + ex.what() + "]"; } } @@ -654,5 +1107,5 @@ JSINativeException::~JSINativeException() {} JSError::~JSError() {} -} // namespace jsi -} // namespace facebook \ No newline at end of file +} // namespace jsi +} // namespace facebook diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/jsi/jsilib-posix.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/jsi/jsilib-posix.cpp index 42fa1a09f..b47d0d4ab 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/jsi/jsilib-posix.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/jsi/jsilib-posix.cpp @@ -12,12 +12,11 @@ #include #include #include - #include #include #include -#include "jsi/jsilib.h" +#include namespace facebook { namespace jsi { @@ -26,8 +25,9 @@ namespace { constexpr size_t kErrorBufferSize = 512; -__attribute__((format(printf, 1, 2))) void throwFormattedError(const char* fmt, - ...) { +__attribute__((format(printf, 1, 2))) void throwFormattedError( + const char* fmt, + ...) { char logBuffer[kErrorBufferSize]; va_list va_args; @@ -36,8 +36,8 @@ __attribute__((format(printf, 1, 2))) void throwFormattedError(const char* fmt, va_end(va_args); if (result < 0) { - throw JSINativeException(std::string("Failed to format error message: ") + - fmt); + throw JSINativeException( + std::string("Failed to format error message: ") + fmt); } throw JSINativeException(logBuffer); @@ -48,18 +48,20 @@ class ScopedFile { ScopedFile(const std::string& path) : path_(path), fd_(::open(path.c_str(), O_RDONLY)) { if (fd_ == -1) { - throwFormattedError("Could not open %s: %s", path.c_str(), - strerror(errno)); + throwFormattedError( + "Could not open %s: %s", path.c_str(), strerror(errno)); } } - ~ScopedFile() { ::close(fd_); } + ~ScopedFile() { + ::close(fd_); + } size_t size() { struct stat fileInfo; if (::fstat(fd_, &fileInfo) == -1) { - throwFormattedError("Could not stat %s: %s", path_.c_str(), - strerror(errno)); + throwFormattedError( + "Could not stat %s: %s", path_.c_str(), strerror(errno)); } return fileInfo.st_size; } @@ -67,8 +69,8 @@ class ScopedFile { uint8_t* mmap(size_t size) { void* result = ::mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd_, 0); if (result == MAP_FAILED) { - throwFormattedError("Could not mmap %s: %s", path_.c_str(), - strerror(errno)); + throwFormattedError( + "Could not mmap %s: %s", path_.c_str(), strerror(errno)); } return reinterpret_cast(result); } @@ -77,7 +79,7 @@ class ScopedFile { const int fd_; }; -} // namespace +} // namespace FileBuffer::FileBuffer(const std::string& path) { ScopedFile file(path); @@ -89,15 +91,18 @@ FileBuffer::~FileBuffer() { if (::munmap(data_, size_)) { // terminate the program with pending exception try { - throwFormattedError("Could not unmap memory (%p, %zu bytes): %s", data_, - size_, strerror(errno)); + throwFormattedError( + "Could not unmap memory (%p, %zu bytes): %s", + data_, + size_, + strerror(errno)); } catch (...) { std::terminate(); } } } -} // namespace jsi -} // namespace facebook +} // namespace jsi +} // namespace facebook -#endif // !defined(_WINDOWS) \ No newline at end of file +#endif // !defined(_WINDOWS) diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.cpp index d8aaa5847..6c5a98e2b 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.cpp @@ -59,7 +59,7 @@ V8PointerValue* V8PointerValue::createFromUtf8(v8::Isolate* isolate, return new V8PointerValue(isolate, v8String); } -void V8PointerValue::invalidate() { +void V8PointerValue::invalidate() noexcept { { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.h b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.h index 0ed63ff08..47831850e 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.h @@ -34,7 +34,7 @@ class V8PointerValue final : public V8Runtime::PointerValue { const uint8_t* str, size_t length); private: - void invalidate() override; + void invalidate() noexcept override; private: friend class JSIV8ValueConverter; From 514e2d8c97c7253991ea51a545a27800c0969664 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 23 Oct 2026 10:40:57 -0700 Subject: [PATCH 02/10] perf(jsi): implement JSI 0.88 APIs natively on V8 Runtime's defaults for the new APIs go through JS (Reflect.get, per-element setProperty) or a process-wide mutex (runtime data), and its tryGetMutableBuffer always copies. Nitro reads runtime data for every struct field and ArrayBuffer it converts, so V8Runtime now implements these directly: - runtime data in a per-runtime map, released before the context - Value-keyed get/has/set/delete and PropNameID/String delete - Array push, ArrayBuffer detached - tryGetMutableBuffer returns the MutableBuffer behind ArrayBuffers created with createArrayBuffer, so native buffers round-trip without a copy - queueMicrotask enqueues on the isolate instead of doing nothing --- .../android/cpp/v8runtime/V8Runtime.cpp | 230 +++++++++++++++++- .../android/cpp/v8runtime/V8Runtime.h | 34 +++ .../NativeScriptJSI/include/V8Runtime.h | 34 +++ .../NativeScriptJSI/v8runtime/V8Runtime.cpp | 230 +++++++++++++++++- 4 files changed, 520 insertions(+), 8 deletions(-) diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp index 03a870401..380ee854c 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp @@ -111,6 +111,21 @@ struct ExternalMemoryTracker { } }; +// Maps a createArrayBuffer() backing store's data pointer to the MutableBuffer +// that owns it, so tryGetMutableBuffer() can hand it back without copying. V8 +// does not expose a backing store's deleter data, and runs deleters on +// arbitrary threads. Leaked so deleters running during exit never see it +// destroyed. +struct RetainedMutableBuffers { + std::mutex mutex; + std::unordered_map*> byData; + + static RetainedMutableBuffers& shared() { + static auto* instance = new RetainedMutableBuffers(); + return *instance; + } +}; + } // namespace // static @@ -247,6 +262,11 @@ V8Runtime::~V8Runtime() { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); v8::HandleScope scopedHandle(isolate_); + // Runtime data can hold jsi::Values, which need the isolate to release. + for (auto& [uuid, entry] : runtimeData_) { + entry.deleter(entry.data); + } + runtimeData_.clear(); if (inspectorClient_) { inspectorClient_.reset(); } @@ -523,7 +543,13 @@ jsi::Value V8Runtime::evaluatePreparedJavaScript( } void V8Runtime::queueMicrotask(const jsi::Function& callback) { - // TODO: add this when we revisit new architecture support + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + isolate_->EnqueueMicrotask( + JSIV8ValueConverter::ToV8Function(*this, callback)); } bool V8Runtime::drainMicrotasks(int maxMicrotasksHint) { @@ -1288,6 +1314,130 @@ void V8Runtime::setPropertyValue(const jsi::Object& object, } } +jsi::Value V8Runtime::getProperty(const jsi::Object& object, + const jsi::Value& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + v8::MaybeLocal result = + v8Object->Get(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8Value(*this, name)); + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + if (result.IsEmpty()) { + return jsi::Value::undefined(); + } + return JSIV8ValueConverter::ToJSIValue(isolate_, result.ToLocalChecked()); +} + +bool V8Runtime::hasProperty(const jsi::Object& object, + const jsi::Value& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + v8::Maybe result = + v8Object->Has(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8Value(*this, name)); + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + return result.FromMaybe(false); +} + +void V8Runtime::setPropertyValue(const jsi::Object& object, + const jsi::Value& name, + const jsi::Value& value) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + if (v8Object + ->Set(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8Value(*this, name), + JSIV8ValueConverter::ToV8Value(*this, value)) + .IsNothing()) { + throw jsi::JSError(*this, "V8Runtime::setPropertyValue failed."); + } +} + +void V8Runtime::DeleteV8Property(v8::Local object, + v8::Local key) { + v8::TryCatch tryCatch(isolate_); + v8::Maybe result = object->Delete(isolate_->GetCurrentContext(), key); + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + if (!result.FromMaybe(false)) { + throw jsi::JSError(*this, "Failed to delete property"); + } +} + +void V8Runtime::deleteProperty(const jsi::Object& object, + const jsi::PropNameID& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), + JSIV8ValueConverter::ToV8String(*this, name)); +} + +void V8Runtime::deleteProperty(const jsi::Object& object, + const jsi::String& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), + JSIV8ValueConverter::ToV8String(*this, name)); +} + +void V8Runtime::deleteProperty(const jsi::Object& object, + const jsi::Value& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), + JSIV8ValueConverter::ToV8Value(*this, name)); +} + +void V8Runtime::setRuntimeDataImpl(const jsi::UUID& uuid, const void* data, + void (*deleter)(const void* data)) { + auto [it, inserted] = + runtimeData_.try_emplace(uuid, RuntimeDataEntry{data, deleter}); + if (!inserted) { + RuntimeDataEntry previous = it->second; + it->second = {data, deleter}; + previous.deleter(previous.data); + } +} + +const void* V8Runtime::getRuntimeDataImpl(const jsi::UUID& uuid) { + auto it = runtimeData_.find(uuid); + return it != runtimeData_.end() ? it->second.data : nullptr; +} + bool V8Runtime::isArray(const jsi::Object& object) const { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); @@ -1429,13 +1579,28 @@ jsi::ArrayBuffer V8Runtime::createArrayBuffer( size_t byteLength = buffer->size(); auto* retainedBuffer = new std::shared_ptr(std::move(buffer)); + if (data != nullptr) { + auto& retained = RetainedMutableBuffers::shared(); + std::lock_guard lock(retained.mutex); + retained.byData[data] = retainedBuffer; + } std::unique_ptr backingStore = v8::ArrayBuffer::NewBackingStore( data, byteLength, - [](void* /*data*/, size_t /*length*/, void* deleterData) { - delete static_cast*>( - deleterData); + [](void* data, size_t /*length*/, void* deleterData) { + auto* retainedBuffer = + static_cast*>(deleterData); + { + auto& retained = RetainedMutableBuffers::shared(); + std::lock_guard lock(retained.mutex); + auto it = retained.byData.find(data); + if (it != retained.byData.end() && + it->second == retainedBuffer) { + retained.byData.erase(it); + } + } + delete retainedBuffer; }, retainedBuffer); @@ -1481,6 +1646,63 @@ uint8_t* V8Runtime::data(const jsi::ArrayBuffer& arrayBuffer) { return reinterpret_cast(v8ArrayBuffer->GetBackingStore()->Data()); } +std::shared_ptr V8Runtime::tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); + v8::ArrayBuffer* v8ArrayBuffer = v8::ArrayBuffer::Cast(*v8Object); + const void* data = v8ArrayBuffer->Data(); + if (data == nullptr) { + return nullptr; + } + + auto& retained = RetainedMutableBuffers::shared(); + std::lock_guard lock(retained.mutex); + auto it = retained.byData.find(data); + if (it == retained.byData.end() || + (*it->second)->size() != v8ArrayBuffer->ByteLength()) { + return nullptr; + } + return *it->second; +} + +bool V8Runtime::detached(const jsi::ArrayBuffer& arrayBuffer) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); + return v8::ArrayBuffer::Cast(*v8Object)->WasDetached(); +} + +size_t V8Runtime::push(const jsi::Array& array, const jsi::Value* elements, + size_t count) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local context = isolate_->GetCurrentContext(); + v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); + uint32_t length = v8Array->Length(); + for (size_t i = 0; i < count; i++) { + if (v8Array + ->Set(context, length + static_cast(i), + JSIV8ValueConverter::ToV8Value(*this, elements[i])) + .IsNothing()) { + throw jsi::JSError(*this, "V8Runtime::push failed."); + } + } + return length + count; +} + jsi::Value V8Runtime::getValueAtIndex(const jsi::Array& array, size_t i) { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h index e2159c0af..1d25b0bb9 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h @@ -7,6 +7,8 @@ #pragma once +#include + // #include #include "V8RuntimeConfig.h" #include "jsi/jsi.h" @@ -39,6 +41,7 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { const v8::Local& script, const std::string& sourceURL); void ReportException(v8::Isolate* isolate, v8::TryCatch* tryCatch) const; + void DeleteV8Property(v8::Local object, v8::Local key); std::unique_ptr LoadCodeCacheIfNeeded( const std::string& sourceURL); @@ -154,6 +157,23 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { void setPropertyValue(const facebook::jsi::Object&, const facebook::jsi::String& name, const facebook::jsi::Value& value) override; + facebook::jsi::Value getProperty(const facebook::jsi::Object&, + const facebook::jsi::Value& name) override; + bool hasProperty(const facebook::jsi::Object&, + const facebook::jsi::Value& name) override; + void setPropertyValue(const facebook::jsi::Object&, + const facebook::jsi::Value& name, + const facebook::jsi::Value& value) override; + void deleteProperty(const facebook::jsi::Object&, + const facebook::jsi::PropNameID& name) override; + void deleteProperty(const facebook::jsi::Object&, + const facebook::jsi::String& name) override; + void deleteProperty(const facebook::jsi::Object&, + const facebook::jsi::Value& name) override; + + void setRuntimeDataImpl(const facebook::jsi::UUID& uuid, const void* data, + void (*deleter)(const void* data)) override; + const void* getRuntimeDataImpl(const facebook::jsi::UUID& uuid) override; bool isArray(const facebook::jsi::Object&) const override; bool isArrayBuffer(const facebook::jsi::Object&) const override; @@ -173,6 +193,11 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { size_t size(const facebook::jsi::Array&) override; size_t size(const facebook::jsi::ArrayBuffer&) override; uint8_t* data(const facebook::jsi::ArrayBuffer&) override; + std::shared_ptr tryGetMutableBuffer( + const facebook::jsi::ArrayBuffer&) override; + bool detached(const facebook::jsi::ArrayBuffer&) override; + size_t push(const facebook::jsi::Array&, const facebook::jsi::Value* elements, + size_t count) override; facebook::jsi::Value getValueAtIndex(const facebook::jsi::Array&, size_t i) override; void setValueAtIndexImpl(const facebook::jsi::Array&, size_t i, @@ -231,6 +256,15 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { v8::Global context_; std::shared_ptr inspectorClient_; bool isSharedRuntime_ = false; + + struct RuntimeDataEntry { + const void* data; + void (*deleter)(const void* data); + }; + // Only touched on the runtime's JS thread, so it needs no lock. + std::unordered_map + runtimeData_; // std::shared_ptr jsQueue_; }; diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h index e2159c0af..1d25b0bb9 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h @@ -7,6 +7,8 @@ #pragma once +#include + // #include #include "V8RuntimeConfig.h" #include "jsi/jsi.h" @@ -39,6 +41,7 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { const v8::Local& script, const std::string& sourceURL); void ReportException(v8::Isolate* isolate, v8::TryCatch* tryCatch) const; + void DeleteV8Property(v8::Local object, v8::Local key); std::unique_ptr LoadCodeCacheIfNeeded( const std::string& sourceURL); @@ -154,6 +157,23 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { void setPropertyValue(const facebook::jsi::Object&, const facebook::jsi::String& name, const facebook::jsi::Value& value) override; + facebook::jsi::Value getProperty(const facebook::jsi::Object&, + const facebook::jsi::Value& name) override; + bool hasProperty(const facebook::jsi::Object&, + const facebook::jsi::Value& name) override; + void setPropertyValue(const facebook::jsi::Object&, + const facebook::jsi::Value& name, + const facebook::jsi::Value& value) override; + void deleteProperty(const facebook::jsi::Object&, + const facebook::jsi::PropNameID& name) override; + void deleteProperty(const facebook::jsi::Object&, + const facebook::jsi::String& name) override; + void deleteProperty(const facebook::jsi::Object&, + const facebook::jsi::Value& name) override; + + void setRuntimeDataImpl(const facebook::jsi::UUID& uuid, const void* data, + void (*deleter)(const void* data)) override; + const void* getRuntimeDataImpl(const facebook::jsi::UUID& uuid) override; bool isArray(const facebook::jsi::Object&) const override; bool isArrayBuffer(const facebook::jsi::Object&) const override; @@ -173,6 +193,11 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { size_t size(const facebook::jsi::Array&) override; size_t size(const facebook::jsi::ArrayBuffer&) override; uint8_t* data(const facebook::jsi::ArrayBuffer&) override; + std::shared_ptr tryGetMutableBuffer( + const facebook::jsi::ArrayBuffer&) override; + bool detached(const facebook::jsi::ArrayBuffer&) override; + size_t push(const facebook::jsi::Array&, const facebook::jsi::Value* elements, + size_t count) override; facebook::jsi::Value getValueAtIndex(const facebook::jsi::Array&, size_t i) override; void setValueAtIndexImpl(const facebook::jsi::Array&, size_t i, @@ -231,6 +256,15 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { v8::Global context_; std::shared_ptr inspectorClient_; bool isSharedRuntime_ = false; + + struct RuntimeDataEntry { + const void* data; + void (*deleter)(const void* data); + }; + // Only touched on the runtime's JS thread, so it needs no lock. + std::unordered_map + runtimeData_; // std::shared_ptr jsQueue_; }; diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp index 03a870401..380ee854c 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp @@ -111,6 +111,21 @@ struct ExternalMemoryTracker { } }; +// Maps a createArrayBuffer() backing store's data pointer to the MutableBuffer +// that owns it, so tryGetMutableBuffer() can hand it back without copying. V8 +// does not expose a backing store's deleter data, and runs deleters on +// arbitrary threads. Leaked so deleters running during exit never see it +// destroyed. +struct RetainedMutableBuffers { + std::mutex mutex; + std::unordered_map*> byData; + + static RetainedMutableBuffers& shared() { + static auto* instance = new RetainedMutableBuffers(); + return *instance; + } +}; + } // namespace // static @@ -247,6 +262,11 @@ V8Runtime::~V8Runtime() { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); v8::HandleScope scopedHandle(isolate_); + // Runtime data can hold jsi::Values, which need the isolate to release. + for (auto& [uuid, entry] : runtimeData_) { + entry.deleter(entry.data); + } + runtimeData_.clear(); if (inspectorClient_) { inspectorClient_.reset(); } @@ -523,7 +543,13 @@ jsi::Value V8Runtime::evaluatePreparedJavaScript( } void V8Runtime::queueMicrotask(const jsi::Function& callback) { - // TODO: add this when we revisit new architecture support + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + isolate_->EnqueueMicrotask( + JSIV8ValueConverter::ToV8Function(*this, callback)); } bool V8Runtime::drainMicrotasks(int maxMicrotasksHint) { @@ -1288,6 +1314,130 @@ void V8Runtime::setPropertyValue(const jsi::Object& object, } } +jsi::Value V8Runtime::getProperty(const jsi::Object& object, + const jsi::Value& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + v8::MaybeLocal result = + v8Object->Get(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8Value(*this, name)); + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + if (result.IsEmpty()) { + return jsi::Value::undefined(); + } + return JSIV8ValueConverter::ToJSIValue(isolate_, result.ToLocalChecked()); +} + +bool V8Runtime::hasProperty(const jsi::Object& object, + const jsi::Value& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + v8::Maybe result = + v8Object->Has(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8Value(*this, name)); + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + return result.FromMaybe(false); +} + +void V8Runtime::setPropertyValue(const jsi::Object& object, + const jsi::Value& name, + const jsi::Value& value) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + if (v8Object + ->Set(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8Value(*this, name), + JSIV8ValueConverter::ToV8Value(*this, value)) + .IsNothing()) { + throw jsi::JSError(*this, "V8Runtime::setPropertyValue failed."); + } +} + +void V8Runtime::DeleteV8Property(v8::Local object, + v8::Local key) { + v8::TryCatch tryCatch(isolate_); + v8::Maybe result = object->Delete(isolate_->GetCurrentContext(), key); + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + if (!result.FromMaybe(false)) { + throw jsi::JSError(*this, "Failed to delete property"); + } +} + +void V8Runtime::deleteProperty(const jsi::Object& object, + const jsi::PropNameID& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), + JSIV8ValueConverter::ToV8String(*this, name)); +} + +void V8Runtime::deleteProperty(const jsi::Object& object, + const jsi::String& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), + JSIV8ValueConverter::ToV8String(*this, name)); +} + +void V8Runtime::deleteProperty(const jsi::Object& object, + const jsi::Value& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), + JSIV8ValueConverter::ToV8Value(*this, name)); +} + +void V8Runtime::setRuntimeDataImpl(const jsi::UUID& uuid, const void* data, + void (*deleter)(const void* data)) { + auto [it, inserted] = + runtimeData_.try_emplace(uuid, RuntimeDataEntry{data, deleter}); + if (!inserted) { + RuntimeDataEntry previous = it->second; + it->second = {data, deleter}; + previous.deleter(previous.data); + } +} + +const void* V8Runtime::getRuntimeDataImpl(const jsi::UUID& uuid) { + auto it = runtimeData_.find(uuid); + return it != runtimeData_.end() ? it->second.data : nullptr; +} + bool V8Runtime::isArray(const jsi::Object& object) const { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); @@ -1429,13 +1579,28 @@ jsi::ArrayBuffer V8Runtime::createArrayBuffer( size_t byteLength = buffer->size(); auto* retainedBuffer = new std::shared_ptr(std::move(buffer)); + if (data != nullptr) { + auto& retained = RetainedMutableBuffers::shared(); + std::lock_guard lock(retained.mutex); + retained.byData[data] = retainedBuffer; + } std::unique_ptr backingStore = v8::ArrayBuffer::NewBackingStore( data, byteLength, - [](void* /*data*/, size_t /*length*/, void* deleterData) { - delete static_cast*>( - deleterData); + [](void* data, size_t /*length*/, void* deleterData) { + auto* retainedBuffer = + static_cast*>(deleterData); + { + auto& retained = RetainedMutableBuffers::shared(); + std::lock_guard lock(retained.mutex); + auto it = retained.byData.find(data); + if (it != retained.byData.end() && + it->second == retainedBuffer) { + retained.byData.erase(it); + } + } + delete retainedBuffer; }, retainedBuffer); @@ -1481,6 +1646,63 @@ uint8_t* V8Runtime::data(const jsi::ArrayBuffer& arrayBuffer) { return reinterpret_cast(v8ArrayBuffer->GetBackingStore()->Data()); } +std::shared_ptr V8Runtime::tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); + v8::ArrayBuffer* v8ArrayBuffer = v8::ArrayBuffer::Cast(*v8Object); + const void* data = v8ArrayBuffer->Data(); + if (data == nullptr) { + return nullptr; + } + + auto& retained = RetainedMutableBuffers::shared(); + std::lock_guard lock(retained.mutex); + auto it = retained.byData.find(data); + if (it == retained.byData.end() || + (*it->second)->size() != v8ArrayBuffer->ByteLength()) { + return nullptr; + } + return *it->second; +} + +bool V8Runtime::detached(const jsi::ArrayBuffer& arrayBuffer) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); + return v8::ArrayBuffer::Cast(*v8Object)->WasDetached(); +} + +size_t V8Runtime::push(const jsi::Array& array, const jsi::Value* elements, + size_t count) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local context = isolate_->GetCurrentContext(); + v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); + uint32_t length = v8Array->Length(); + for (size_t i = 0; i < count; i++) { + if (v8Array + ->Set(context, length + static_cast(i), + JSIV8ValueConverter::ToV8Value(*this, elements[i])) + .IsNothing()) { + throw jsi::JSError(*this, "V8Runtime::push failed."); + } + } + return length + count; +} + jsi::Value V8Runtime::getValueAtIndex(const jsi::Array& array, size_t i) { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); From 11e175834914aa397bd87be2519a467a30cd7eeb Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 23 Oct 2026 10:41:22 -0700 Subject: [PATCH 03/10] fix(jsi): never dispose the NativeScript isolate from V8Runtime V8Runtime wraps the isolate NativeScript already owns, but its destructor disposed that isolate, so DisposeJSIRuntimeForCurrentIsolate() tore down the app's JS engine. --- .../android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp | 4 +--- .../android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h | 1 - packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h | 1 - .../ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp | 4 +--- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp index 380ee854c..9aeec1228 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp @@ -273,9 +273,7 @@ V8Runtime::~V8Runtime() { context_.Reset(); } - if (!isSharedRuntime_) { - isolate_->Dispose(); - } + // The isolate belongs to the NativeScript runtime; never dispose it here. // v8::V8::Dispose(); // v8::V8::DisposePlatform(); } diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h index 1d25b0bb9..8f7f196b6 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h @@ -255,7 +255,6 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { v8::Isolate* isolate_; v8::Global context_; std::shared_ptr inspectorClient_; - bool isSharedRuntime_ = false; struct RuntimeDataEntry { const void* data; diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h index 1d25b0bb9..8f7f196b6 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h @@ -255,7 +255,6 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { v8::Isolate* isolate_; v8::Global context_; std::shared_ptr inspectorClient_; - bool isSharedRuntime_ = false; struct RuntimeDataEntry { const void* data; diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp index 380ee854c..9aeec1228 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp @@ -273,9 +273,7 @@ V8Runtime::~V8Runtime() { context_.Reset(); } - if (!isSharedRuntime_) { - isolate_->Dispose(); - } + // The isolate belongs to the NativeScript runtime; never dispose it here. // v8::V8::Dispose(); // v8::V8::DisposePlatform(); } From 54fb1aa18a2998d2bbc30b5f9204507aa36c19df Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 23 Oct 2026 10:44:39 -0700 Subject: [PATCH 04/10] feat(jsi): add JSICallInvoker for scheduling work on the JS thread Async JSI libraries need a way to get back onto the runtime's thread, the role React Native's CallInvoker plays. tns::GetJSICallInvoker() returns one backed by the JS thread's CFRunLoop on iOS and ALooper on Android. Tasks run through V8Runtime::ExecuteTask: a top-level JS entry under a verbose TryCatch, so errors reach NativeScript's uncaught error handling, followed by a microtask checkpoint so settled promises resume immediately. The per-isolate runtime map is now leaked so exit-time static destructors never release a runtime after its isolate is gone. --- packages/android-jsi/README.md | 22 +++ .../platforms/android/cpp/CMakeLists.txt | 3 + .../android/cpp/v8runtime/JSIRuntime.cpp | 160 ++++++++++++++++-- .../android/cpp/v8runtime/JSIRuntime.h | 25 +++ .../android/cpp/v8runtime/V8Runtime.cpp | 38 +++++ .../android/cpp/v8runtime/V8Runtime.h | 9 + packages/ios-jsi/README.md | 22 +++ .../NativeScriptJSI/include/JSIRuntime.h | 25 +++ .../NativeScriptJSI/include/V8Runtime.h | 9 + .../NativeScriptJSI/v8runtime/JSIRuntime.mm | 86 ++++++++-- .../NativeScriptJSI/v8runtime/V8Runtime.cpp | 38 +++++ 11 files changed, 409 insertions(+), 28 deletions(-) diff --git a/packages/android-jsi/README.md b/packages/android-jsi/README.md index 128ce56dc..942832761 100644 --- a/packages/android-jsi/README.md +++ b/packages/android-jsi/README.md @@ -39,6 +39,28 @@ void installBindings() { Each V8 isolate (the main UI thread and any NativeScript Worker threads) lazily owns its own `std::shared_ptr` keyed by `v8::Isolate::GetCurrent()`, resolving V8 symbols exported by `libNativeScript.so`. +## Scheduling work on the JS thread + +`tns::GetJSICallInvoker()` returns a `tns::JSICallInvoker` (the equivalent of React Native's `CallInvoker`) for the calling thread's runtime, backed by that thread's `ALooper`. Grab it on the JS thread, then call `invokeAsync` from any thread to run work back on it, for example to settle a promise from a background queue: + +```cpp +#include "JSIRuntime.h" + +auto invoker = tns::GetJSICallInvoker(); +std::thread([invoker, resolve] { + double result = expensiveWork(); + invoker->invokeAsync([resolve, result](facebook::jsi::Runtime& rt) { + resolve->call(rt, result); + }); +}).detach(); +``` + +Each task runs as a top-level JS entry followed by a microtask checkpoint, so `await`ing code continues immediately. Errors thrown by a task are reported through NativeScript's uncaught error handling. + +## JSI version + +The bundled `facebook::jsi` headers match React Native 0.88.0, which is what Nitro Modules v1 (`nitro-core`) and other current JSI libraries compile against. Install Nitro with `margelo::nitro::install(*runtime, dispatcher)`, where the dispatcher forwards `runAsync` to `JSICallInvoker::invokeAsync` and `isOnDispatcher` to `isOnJSThread`. + ## JavaScript / TypeScript Usage ```ts diff --git a/packages/android-jsi/platforms/android/cpp/CMakeLists.txt b/packages/android-jsi/platforms/android/cpp/CMakeLists.txt index 252065ef9..290d572b2 100644 --- a/packages/android-jsi/platforms/android/cpp/CMakeLists.txt +++ b/packages/android-jsi/platforms/android/cpp/CMakeLists.txt @@ -49,6 +49,7 @@ target_compile_options( ) find_library(android-log-lib log) +find_library(android-lib android) # V8 symbols are exported by libNativeScript.so which is already loaded in the # Android process by the NativeScript runtime. Allow unresolved shared-library @@ -62,6 +63,7 @@ if(DEFINED NATIVESCRIPT_SO_PATH AND EXISTS "${NATIVESCRIPT_SO_PATH}") target_link_libraries( nativescript-jsi ${android-log-lib} + ${android-lib} NativeScriptRuntime ) else() @@ -75,5 +77,6 @@ else() target_link_libraries( nativescript-jsi ${android-log-lib} + ${android-lib} ) endif() diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/JSIRuntime.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/JSIRuntime.cpp index 961d160e8..ad1b33f51 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/JSIRuntime.cpp +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/JSIRuntime.cpp @@ -1,7 +1,12 @@ #include "JSIRuntime.h" +#include #include +#include +#include +#include +#include #include #include @@ -10,29 +15,156 @@ namespace { -std::mutex g_jsiMutex; -std::unordered_map> - g_jsiRuntimes; +using Task = std::function; -} // namespace +// Tasks waiting for the JS thread's ALooper. The eventfd is only written and +// closed under `mutex`, and only closed on the looper thread, so a wakeup can +// never reach a closed (or reused) fd and a callback never outlives the queue. +struct LooperQueue { + std::weak_ptr runtime; + ALooper* looper = nullptr; + int fd = -1; -namespace tns { + std::mutex mutex; + std::deque tasks; + bool closed = false; -std::shared_ptr GetJSIRuntime() { + // Requires `mutex`. + void Signal() { + uint64_t value = 1; + write(fd, &value, sizeof(value)); + } + + static int Drain(int fd, int /*events*/, void* data) { + auto* holder = static_cast*>(data); + std::shared_ptr queue = *holder; + + std::deque tasks; + { + std::lock_guard lock(queue->mutex); + uint64_t value; + read(fd, &value, sizeof(value)); + if (queue->closed) { + ALooper_removeFd(queue->looper, fd); + close(fd); + ALooper_release(queue->looper); + delete holder; + return 0; + } + tasks.swap(queue->tasks); + } + + if (auto runtime = queue->runtime.lock()) { + for (const Task& task : tasks) { + runtime->ExecuteTask(task); + } + } + return 1; + } +}; + +class LooperCallInvoker final : public tns::JSICallInvoker { + public: + // Must run on the JS thread. Returns nullptr when it has no ALooper. + static std::shared_ptr Create( + std::weak_ptr runtime) { + ALooper* looper = ALooper_forThread(); + if (looper == nullptr) { + return nullptr; + } + int fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (fd == -1) { + return nullptr; + } + + auto queue = std::make_shared(); + queue->runtime = std::move(runtime); + queue->looper = looper; + queue->fd = fd; + + auto* holder = new std::shared_ptr(queue); + if (ALooper_addFd(looper, fd, ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT, + &LooperQueue::Drain, holder) != 1) { + delete holder; + close(fd); + return nullptr; + } + ALooper_acquire(looper); + return std::make_shared(std::move(queue)); + } + + explicit LooperCallInvoker(std::shared_ptr queue) + : queue_(std::move(queue)), thread_(pthread_self()) {} + + ~LooperCallInvoker() override { + std::lock_guard lock(queue_->mutex); + queue_->closed = true; + queue_->tasks.clear(); + queue_->Signal(); + } + + void invokeAsync(Task&& task) override { + std::lock_guard lock(queue_->mutex); + queue_->tasks.push_back(std::move(task)); + queue_->Signal(); + } + + bool isOnJSThread() const override { + return pthread_equal(pthread_self(), thread_) != 0; + } + + private: + std::shared_ptr queue_; + pthread_t thread_; +}; + +struct IsolateEntry { + std::shared_ptr runtime; + std::shared_ptr callInvoker; +}; + +std::mutex g_jsiMutex; + +// Leaked so no runtime is torn down by static destructors during exit, when +// its isolate may already be gone. +std::unordered_map& isolateEntries() { + static auto* entries = new std::unordered_map(); + return *entries; +} + +// Requires g_jsiMutex. +IsolateEntry* entryForCurrentIsolate() { v8::Isolate* isolate = v8::Isolate::GetCurrent(); if (isolate == nullptr) { return nullptr; } + IsolateEntry& entry = isolateEntries()[isolate]; + if (!entry.runtime) { + entry.runtime = std::make_shared(); + } + return &entry; +} + +} // namespace +namespace tns { + +std::shared_ptr GetJSIRuntime() { std::lock_guard lock(g_jsiMutex); - auto it = g_jsiRuntimes.find(isolate); - if (it != g_jsiRuntimes.end()) { - return it->second; - } + IsolateEntry* entry = entryForCurrentIsolate(); + return entry ? entry->runtime : nullptr; +} - auto runtime = std::make_shared(); - g_jsiRuntimes.emplace(isolate, runtime); - return runtime; +std::shared_ptr GetJSICallInvoker() { + std::lock_guard lock(g_jsiMutex); + IsolateEntry* entry = entryForCurrentIsolate(); + if (entry == nullptr) { + return nullptr; + } + if (!entry->callInvoker) { + entry->callInvoker = LooperCallInvoker::Create(entry->runtime); + } + return entry->callInvoker; } void DisposeJSIRuntimeForCurrentIsolate() { @@ -42,7 +174,7 @@ void DisposeJSIRuntimeForCurrentIsolate() { } std::lock_guard lock(g_jsiMutex); - g_jsiRuntimes.erase(isolate); + isolateEntries().erase(isolate); } } // namespace tns diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/JSIRuntime.h b/packages/android-jsi/platforms/android/cpp/v8runtime/JSIRuntime.h index 31976e679..2fcf4de69 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/JSIRuntime.h +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/JSIRuntime.h @@ -1,6 +1,7 @@ #ifndef JSIRUNTIME_H_ #define JSIRUNTIME_H_ +#include #include #include "jsi/jsi.h" @@ -20,9 +21,33 @@ NativeScriptJSIRuntime(void); namespace tns { +// Schedules work on the thread that owns a JSI Runtime, like React Native's +// CallInvoker. Each task is a top-level entry into JS: it runs in the +// isolate's context and is followed by a microtask checkpoint, so promises it +// settles resolve right away. Exceptions escaping a task are reported as +// uncaught errors. +class __attribute__((visibility("default"))) JSICallInvoker { + public: + virtual ~JSICallInvoker() = default; + + // Safe to call from any thread. Tasks run in FIFO order; tasks still queued + // once the runtime is disposed are dropped. + virtual void invokeAsync( + std::function&& task) = 0; + + // Whether the calling thread is the runtime's JS thread. + virtual bool isOnJSThread() const = 0; +}; + __attribute__((visibility("default"))) std::shared_ptr GetJSIRuntime(); +// The call invoker for the calling thread's JSI Runtime, or nullptr when this +// thread has no active V8 isolate or no ALooper. Call it on the JS thread; the +// invoker can then be used from any thread. +__attribute__((visibility("default"))) std::shared_ptr +GetJSICallInvoker(); + __attribute__((visibility("default"))) void DisposeJSIRuntimeForCurrentIsolate(); diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp index 9aeec1228..02e25706c 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp @@ -267,6 +267,7 @@ V8Runtime::~V8Runtime() { entry.deleter(entry.data); } runtimeData_.clear(); + taskTrampoline_.Reset(); if (inspectorClient_) { inspectorClient_.reset(); } @@ -278,6 +279,43 @@ V8Runtime::~V8Runtime() { // v8::V8::DisposePlatform(); } +void V8Runtime::ExecuteTask( + const std::function& task) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Local context = context_.Get(isolate_); + v8::Context::Scope scopedContext(context); + + // Running the task inside a host function turns C++ exceptions into JS + // exceptions, which the verbose TryCatch below then reports. + if (taskTrampoline_.IsEmpty()) { + jsi::Function trampoline = createFunctionFromHostFunction( + jsi::PropNameID::forAscii(*this, "executeTask"), 0, + [this](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value*, + size_t) { + (*currentTask_)(runtime); + return jsi::Value::undefined(); + }); + taskTrampoline_.Reset(isolate_, + JSIV8ValueConverter::ToV8Function(*this, trampoline)); + } + + const auto* outerTask = currentTask_; + currentTask_ = &task; + { + // Verbose hands the exception to the isolate's message listeners, which + // NativeScript uses for uncaught errors. + v8::TryCatch tryCatch(isolate_); + tryCatch.SetVerbose(true); + (void)taskTrampoline_.Get(isolate_)->Call(context, context->Global(), 0, + nullptr); + } + currentTask_ = outerTask; + + isolate_->PerformMicrotaskCheckpoint(); +} + void V8Runtime::OnMainLoopIdle() { // v8::Locker locker(isolate_); // v8::Isolate::Scope scopedIsolate(isolate_); diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h index 8f7f196b6..c5d24c755 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h @@ -7,6 +7,7 @@ #pragma once +#include #include // #include @@ -35,6 +36,11 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { // Calling this function when the platform main runloop is idle void OnMainLoopIdle(); + // Runs `task` as a top-level entry into JS from the runtime's thread, then + // drains microtasks. An exception escaping `task` goes to NativeScript's + // uncaught error handling instead of propagating to the caller. + void ExecuteTask(const std::function& task); + private: v8::Local CreateGlobalContext(v8::Isolate* isolate); facebook::jsi::Value ExecuteScript(v8::Isolate* isolate, @@ -264,6 +270,9 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { std::unordered_map runtimeData_; + + v8::Global taskTrampoline_; + const std::function* currentTask_ = nullptr; // std::shared_ptr jsQueue_; }; diff --git a/packages/ios-jsi/README.md b/packages/ios-jsi/README.md index 34aa1424b..806f21330 100644 --- a/packages/ios-jsi/README.md +++ b/packages/ios-jsi/README.md @@ -54,6 +54,28 @@ let ptr = JSIRuntime.runtimePointer() Each V8 isolate (the main UI thread and any NativeScript Worker threads) lazily owns its own `std::shared_ptr` keyed by `v8::Isolate::GetCurrent()`. +## Scheduling work on the JS thread + +`tns::GetJSICallInvoker()` returns a `tns::JSICallInvoker` (the equivalent of React Native's `CallInvoker`) for the calling thread's runtime. Grab it on the JS thread, then call `invokeAsync` from any thread to run work back on it, for example to settle a promise from a background queue: + +```cpp +#include + +auto invoker = tns::GetJSICallInvoker(); +dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + double result = expensiveWork(); + invoker->invokeAsync([resolve, result](facebook::jsi::Runtime& rt) { + resolve->call(rt, result); + }); +}); +``` + +Each task runs as a top-level JS entry followed by a microtask checkpoint, so `await`ing code continues immediately. Errors thrown by a task are reported through NativeScript's uncaught error handling. + +## JSI version + +The bundled `facebook::jsi` headers match React Native 0.88.0, which is what Nitro Modules v1 (`nitro-core`) and other current JSI libraries compile against. Install Nitro with `margelo::nitro::install(*runtime, dispatcher)`, where the dispatcher forwards `runAsync` to `JSICallInvoker::invokeAsync` and `isOnDispatcher` to `isOnJSThread`. + ## JavaScript / TypeScript Usage ```ts diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/JSIRuntime.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/JSIRuntime.h index f57920772..597337a35 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/JSIRuntime.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/JSIRuntime.h @@ -2,6 +2,7 @@ #define JSIRUNTIME_H_ #ifdef __cplusplus +#include #include #include "jsi/jsi.h" @@ -56,9 +57,33 @@ NativeScriptJSIRuntime(void); namespace tns { +// Schedules work on the thread that owns a JSI Runtime, like React Native's +// CallInvoker. Each task is a top-level entry into JS: it runs in the +// isolate's context and is followed by a microtask checkpoint, so promises it +// settles resolve right away. Exceptions escaping a task are reported as +// uncaught errors. +class __attribute__((visibility("default"))) JSICallInvoker { + public: + virtual ~JSICallInvoker() = default; + + // Safe to call from any thread. Tasks run in FIFO order; tasks still queued + // once the runtime is disposed are dropped. + virtual void invokeAsync( + std::function&& task) = 0; + + // Whether the calling thread is the runtime's JS thread. + virtual bool isOnJSThread() const = 0; +}; + __attribute__((visibility("default"))) std::shared_ptr GetJSIRuntime(); +// The call invoker for the calling thread's JSI Runtime, or nullptr when this +// thread has no active V8 isolate. Call it on the JS thread; the invoker can +// then be used from any thread. +__attribute__((visibility("default"))) std::shared_ptr +GetJSICallInvoker(); + __attribute__((visibility("default"))) void DisposeJSIRuntimeForCurrentIsolate(); diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h index 8f7f196b6..c5d24c755 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h @@ -7,6 +7,7 @@ #pragma once +#include #include // #include @@ -35,6 +36,11 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { // Calling this function when the platform main runloop is idle void OnMainLoopIdle(); + // Runs `task` as a top-level entry into JS from the runtime's thread, then + // drains microtasks. An exception escaping `task` goes to NativeScript's + // uncaught error handling instead of propagating to the caller. + void ExecuteTask(const std::function& task); + private: v8::Local CreateGlobalContext(v8::Isolate* isolate); facebook::jsi::Value ExecuteScript(v8::Isolate* isolate, @@ -264,6 +270,9 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { std::unordered_map runtimeData_; + + v8::Global taskTrampoline_; + const std::function* currentTask_ = nullptr; // std::shared_ptr jsQueue_; }; diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/JSIRuntime.mm b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/JSIRuntime.mm index 2f2939e65..c74902e5c 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/JSIRuntime.mm +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/JSIRuntime.mm @@ -1,5 +1,8 @@ #include "JSIRuntime.h" +#include +#include + #include #include @@ -8,29 +11,84 @@ namespace { -std::mutex g_jsiMutex; -std::unordered_map> - g_jsiRuntimes; +class RunLoopCallInvoker final : public tns::JSICallInvoker { + public: + explicit RunLoopCallInvoker(std::weak_ptr runtime) + : runtime_(std::move(runtime)), + runLoop_((CFRunLoopRef)CFRetain(CFRunLoopGetCurrent())), + thread_(pthread_self()) {} + + ~RunLoopCallInvoker() override { CFRelease(runLoop_); } + + void invokeAsync( + std::function&& task) override { + std::weak_ptr weakRuntime = runtime_; + std::function blockTask = std::move(task); + CFRunLoopPerformBlock(runLoop_, kCFRunLoopCommonModes, ^{ + if (auto runtime = weakRuntime.lock()) { + runtime->ExecuteTask(blockTask); + } + }); + CFRunLoopWakeUp(runLoop_); + } -} // namespace + bool isOnJSThread() const override { + return pthread_equal(pthread_self(), thread_) != 0; + } -namespace tns { + private: + std::weak_ptr runtime_; + CFRunLoopRef runLoop_; + pthread_t thread_; +}; -std::shared_ptr GetJSIRuntime() { +struct IsolateEntry { + std::shared_ptr runtime; + std::shared_ptr callInvoker; +}; + +std::mutex g_jsiMutex; + +// Leaked so no runtime is torn down by static destructors during exit, when +// its isolate may already be gone. +std::unordered_map& isolateEntries() { + static auto* entries = new std::unordered_map(); + return *entries; +} + +// Requires g_jsiMutex. +IsolateEntry* entryForCurrentIsolate() { v8::Isolate* isolate = v8::Isolate::GetCurrent(); if (isolate == nullptr) { return nullptr; } + IsolateEntry& entry = isolateEntries()[isolate]; + if (!entry.runtime) { + entry.runtime = std::make_shared(); + } + return &entry; +} + +} // namespace + +namespace tns { + +std::shared_ptr GetJSIRuntime() { + std::lock_guard lock(g_jsiMutex); + IsolateEntry* entry = entryForCurrentIsolate(); + return entry ? entry->runtime : nullptr; +} +std::shared_ptr GetJSICallInvoker() { std::lock_guard lock(g_jsiMutex); - auto it = g_jsiRuntimes.find(isolate); - if (it != g_jsiRuntimes.end()) { - return it->second; + IsolateEntry* entry = entryForCurrentIsolate(); + if (entry == nullptr) { + return nullptr; } - - auto runtime = std::make_shared(); - g_jsiRuntimes.emplace(isolate, runtime); - return runtime; + if (!entry->callInvoker) { + entry->callInvoker = std::make_shared(entry->runtime); + } + return entry->callInvoker; } void DisposeJSIRuntimeForCurrentIsolate() { @@ -40,7 +98,7 @@ void DisposeJSIRuntimeForCurrentIsolate() { } std::lock_guard lock(g_jsiMutex); - g_jsiRuntimes.erase(isolate); + isolateEntries().erase(isolate); } } // namespace tns diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp index 9aeec1228..02e25706c 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp @@ -267,6 +267,7 @@ V8Runtime::~V8Runtime() { entry.deleter(entry.data); } runtimeData_.clear(); + taskTrampoline_.Reset(); if (inspectorClient_) { inspectorClient_.reset(); } @@ -278,6 +279,43 @@ V8Runtime::~V8Runtime() { // v8::V8::DisposePlatform(); } +void V8Runtime::ExecuteTask( + const std::function& task) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Local context = context_.Get(isolate_); + v8::Context::Scope scopedContext(context); + + // Running the task inside a host function turns C++ exceptions into JS + // exceptions, which the verbose TryCatch below then reports. + if (taskTrampoline_.IsEmpty()) { + jsi::Function trampoline = createFunctionFromHostFunction( + jsi::PropNameID::forAscii(*this, "executeTask"), 0, + [this](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value*, + size_t) { + (*currentTask_)(runtime); + return jsi::Value::undefined(); + }); + taskTrampoline_.Reset(isolate_, + JSIV8ValueConverter::ToV8Function(*this, trampoline)); + } + + const auto* outerTask = currentTask_; + currentTask_ = &task; + { + // Verbose hands the exception to the isolate's message listeners, which + // NativeScript uses for uncaught errors. + v8::TryCatch tryCatch(isolate_); + tryCatch.SetVerbose(true); + (void)taskTrampoline_.Get(isolate_)->Call(context, context->Global(), 0, + nullptr); + } + currentTask_ = outerTask; + + isolate_->PerformMicrotaskCheckpoint(); +} + void V8Runtime::OnMainLoopIdle() { // v8::Locker locker(isolate_); // v8::Isolate::Scope scopedIsolate(isolate_); From adfc15b5074954f5cfd9186cf39423c3d59c5e86 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 23 Oct 2026 10:46:29 -0700 Subject: [PATCH 05/10] perf(jsi): implement UTF-16 string APIs natively on V8 Runtime's defaults transcode through UTF-8 in both directions, and createStringFromUtf16 evaluates an escaped literal for any non-ASCII input. V8Runtime now creates two-byte strings directly, copies out with WriteV2, and getStringData/getPropNameIdData expose V8's flat string contents without copying (Latin-1 one-byte strings are widened, since JSI's ascii flag means 7-bit). Nitro's Swift string bridge relies on these; build it with NITRO_JSI_HAS_FAST_UTF16=1 against this runtime. --- .../android/cpp/v8runtime/V8Runtime.cpp | 118 ++++++++++++++++++ .../android/cpp/v8runtime/V8Runtime.h | 13 ++ .../NativeScriptJSI/include/V8Runtime.h | 13 ++ .../NativeScriptJSI/v8runtime/V8Runtime.cpp | 118 ++++++++++++++++++ 4 files changed, 262 insertions(+) diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp index 02e25706c..b517ae82f 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp @@ -927,6 +927,124 @@ std::string V8Runtime::utf8(const jsi::String& str) { return JSIV8ValueConverter::ToSTLString(utf8); } +namespace { + +v8::Local newStringFromUtf16(v8::Isolate* isolate, + const char16_t* utf16, + size_t length) { + v8::Local string; + if (!v8::String::NewFromTwoByte(isolate, + reinterpret_cast(utf16), + v8::NewStringType::kNormal, + static_cast(length)) + .ToLocal(&string)) { + throw jsi::JSINativeException("Failed to create a UTF-16 string"); + } + return string; +} + +std::u16string stringToUtf16(v8::Isolate* isolate, + v8::Local string) { + std::u16string result(string->Length(), u'\0'); + string->WriteV2(isolate, 0, string->Length(), + reinterpret_cast(result.data())); + return result; +} + +// Hands `string`'s characters to `cb` without copying when possible. JSI's +// `ascii` flag promises 7-bit data, while V8's one-byte strings are Latin-1, +// so non-ASCII one-byte strings are widened to UTF-16 first. +void forStringData(v8::Isolate* isolate, v8::Local string, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)) { + std::u16string widened; + { + v8::String::ValueView view(isolate, string); + size_t length = view.length(); + if (!view.is_one_byte()) { + cb(ctx, false, view.data16(), length); + return; + } + const uint8_t* data = view.data8(); + bool isAscii = true; + for (size_t i = 0; i < length; i++) { + if (data[i] >= 0x80) { + isAscii = false; + break; + } + } + if (isAscii) { + cb(ctx, true, data, length); + return; + } + widened.assign(data, data + length); + } + cb(ctx, false, widened.data(), widened.size()); +} + +} // namespace + +jsi::String V8Runtime::createStringFromUtf16(const char16_t* utf16, + size_t length) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + return make(new V8PointerValue( + isolate_, newStringFromUtf16(isolate_, utf16, length))); +} + +jsi::PropNameID V8Runtime::createPropNameIDFromUtf16(const char16_t* utf16, + size_t length) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + return make(new V8PointerValue( + isolate_, newStringFromUtf16(isolate_, utf16, length))); +} + +std::u16string V8Runtime::utf16(const jsi::String& str) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + + return stringToUtf16(isolate_, JSIV8ValueConverter::ToV8String(*this, str)); +} + +std::u16string V8Runtime::utf16(const jsi::PropNameID& sym) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + + return stringToUtf16(isolate_, JSIV8ValueConverter::ToV8String(*this, sym)); +} + +void V8Runtime::getStringData(const jsi::String& str, void* ctx, + void (*cb)(void* ctx, bool ascii, + const void* data, size_t num)) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + + forStringData(isolate_, JSIV8ValueConverter::ToV8String(*this, str), ctx, + cb); +} + +void V8Runtime::getPropNameIdData(const jsi::PropNameID& sym, void* ctx, + void (*cb)(void* ctx, bool ascii, + const void* data, size_t num)) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + + forStringData(isolate_, JSIV8ValueConverter::ToV8String(*this, sym), ctx, + cb); +} + jsi::Object V8Runtime::createObject() { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h index c5d24c755..481f2e4c1 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h @@ -125,6 +125,19 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { size_t length) override; std::string utf8(const facebook::jsi::String&) override; + facebook::jsi::String createStringFromUtf16(const char16_t* utf16, + size_t length) override; + facebook::jsi::PropNameID createPropNameIDFromUtf16(const char16_t* utf16, + size_t length) override; + std::u16string utf16(const facebook::jsi::String&) override; + std::u16string utf16(const facebook::jsi::PropNameID&) override; + void getStringData(const facebook::jsi::String& str, void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)) override; + void getPropNameIdData(const facebook::jsi::PropNameID& sym, void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)) override; + facebook::jsi::Object createObject() override; facebook::jsi::Object createObject( std::shared_ptr hostObject) override; diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h index c5d24c755..481f2e4c1 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h @@ -125,6 +125,19 @@ class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { size_t length) override; std::string utf8(const facebook::jsi::String&) override; + facebook::jsi::String createStringFromUtf16(const char16_t* utf16, + size_t length) override; + facebook::jsi::PropNameID createPropNameIDFromUtf16(const char16_t* utf16, + size_t length) override; + std::u16string utf16(const facebook::jsi::String&) override; + std::u16string utf16(const facebook::jsi::PropNameID&) override; + void getStringData(const facebook::jsi::String& str, void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)) override; + void getPropNameIdData(const facebook::jsi::PropNameID& sym, void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)) override; + facebook::jsi::Object createObject() override; facebook::jsi::Object createObject( std::shared_ptr hostObject) override; diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp index 02e25706c..b517ae82f 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp @@ -927,6 +927,124 @@ std::string V8Runtime::utf8(const jsi::String& str) { return JSIV8ValueConverter::ToSTLString(utf8); } +namespace { + +v8::Local newStringFromUtf16(v8::Isolate* isolate, + const char16_t* utf16, + size_t length) { + v8::Local string; + if (!v8::String::NewFromTwoByte(isolate, + reinterpret_cast(utf16), + v8::NewStringType::kNormal, + static_cast(length)) + .ToLocal(&string)) { + throw jsi::JSINativeException("Failed to create a UTF-16 string"); + } + return string; +} + +std::u16string stringToUtf16(v8::Isolate* isolate, + v8::Local string) { + std::u16string result(string->Length(), u'\0'); + string->WriteV2(isolate, 0, string->Length(), + reinterpret_cast(result.data())); + return result; +} + +// Hands `string`'s characters to `cb` without copying when possible. JSI's +// `ascii` flag promises 7-bit data, while V8's one-byte strings are Latin-1, +// so non-ASCII one-byte strings are widened to UTF-16 first. +void forStringData(v8::Isolate* isolate, v8::Local string, + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)) { + std::u16string widened; + { + v8::String::ValueView view(isolate, string); + size_t length = view.length(); + if (!view.is_one_byte()) { + cb(ctx, false, view.data16(), length); + return; + } + const uint8_t* data = view.data8(); + bool isAscii = true; + for (size_t i = 0; i < length; i++) { + if (data[i] >= 0x80) { + isAscii = false; + break; + } + } + if (isAscii) { + cb(ctx, true, data, length); + return; + } + widened.assign(data, data + length); + } + cb(ctx, false, widened.data(), widened.size()); +} + +} // namespace + +jsi::String V8Runtime::createStringFromUtf16(const char16_t* utf16, + size_t length) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + return make(new V8PointerValue( + isolate_, newStringFromUtf16(isolate_, utf16, length))); +} + +jsi::PropNameID V8Runtime::createPropNameIDFromUtf16(const char16_t* utf16, + size_t length) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + return make(new V8PointerValue( + isolate_, newStringFromUtf16(isolate_, utf16, length))); +} + +std::u16string V8Runtime::utf16(const jsi::String& str) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + + return stringToUtf16(isolate_, JSIV8ValueConverter::ToV8String(*this, str)); +} + +std::u16string V8Runtime::utf16(const jsi::PropNameID& sym) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + + return stringToUtf16(isolate_, JSIV8ValueConverter::ToV8String(*this, sym)); +} + +void V8Runtime::getStringData(const jsi::String& str, void* ctx, + void (*cb)(void* ctx, bool ascii, + const void* data, size_t num)) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + + forStringData(isolate_, JSIV8ValueConverter::ToV8String(*this, str), ctx, + cb); +} + +void V8Runtime::getPropNameIdData(const jsi::PropNameID& sym, void* ctx, + void (*cb)(void* ctx, bool ascii, + const void* data, size_t num)) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + + forStringData(isolate_, JSIV8ValueConverter::ToV8String(*this, sym), ctx, + cb); +} + jsi::Object V8Runtime::createObject() { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); From 486aa9103ef26ba37338e0dd4d19246fdd77e832 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 23 Oct 2026 11:44:30 -0700 Subject: [PATCH 06/10] fix(android-jsi): load against the NativeScript runtime's V8 libnativescript-jsi.so could not be loaded at all: - It had no DT_NEEDED entry for libNativeScript.so, and Android's linker only binds a library's undefined symbols through its own dependency tree, so every V8 symbol failed to resolve. It now links an empty stub whose soname is libNativeScript.so. - Inline V8 header code (Object::GetInternalField) calls v8::internal::Internals::GetCurrentIsolate(), which libNativeScript.so does not export. Define it as Isolate::GetCurrent(), as V8 does. - The V8 headers were compiled without V8_COMPRESS_POINTERS and V8_31BIT_SMIS_ON_64BIT_ARCH, which the runtime's V8 is built with, so inline handle code misread objects and crashed. They are now PUBLIC definitions so every library including V8 through nativescript-jsi matches. --- .../platforms/android/cpp/CMakeLists.txt | 17 +++++++++++++++++ .../cpp/v8runtime/NativeScriptRuntimeStub.cpp | 3 +++ .../android/cpp/v8runtime/V8ExportShims.cpp | 8 ++++++++ 3 files changed, 28 insertions(+) create mode 100644 packages/android-jsi/platforms/android/cpp/v8runtime/NativeScriptRuntimeStub.cpp create mode 100644 packages/android-jsi/platforms/android/cpp/v8runtime/V8ExportShims.cpp diff --git a/packages/android-jsi/platforms/android/cpp/CMakeLists.txt b/packages/android-jsi/platforms/android/cpp/CMakeLists.txt index 290d572b2..b8eaef234 100644 --- a/packages/android-jsi/platforms/android/cpp/CMakeLists.txt +++ b/packages/android-jsi/platforms/android/cpp/CMakeLists.txt @@ -22,8 +22,16 @@ add_library( v8runtime/JSIV8ValueConverter.cpp v8runtime/HostProxy.cpp v8runtime/JSIRuntime.cpp + v8runtime/V8ExportShims.cpp ) +# Inline V8 header code reads object layouts, so these must match the V8 build +# inside libNativeScript.so (NativeScript/android test-app/runtime/CMakeLists.txt). +target_compile_definitions(nativescript-jsi PUBLIC V8_31BIT_SMIS_ON_64BIT_ARCH) +if(ANDROID_ABI MATCHES "arm64-v8a$" OR ANDROID_ABI MATCHES "x86_64$") + target_compile_definitions(nativescript-jsi PUBLIC V8_COMPRESS_POINTERS) +endif() + target_include_directories( nativescript-jsi PUBLIC @@ -67,6 +75,14 @@ if(DEFINED NATIVESCRIPT_SO_PATH AND EXISTS "${NATIVESCRIPT_SO_PATH}") NativeScriptRuntime ) else() + # Android's dynamic linker binds a library's undefined symbols only against + # its own DT_NEEDED tree, never against libraries the process loaded earlier. + # Linking against an empty stub whose soname is libNativeScript.so records + # that dependency, so the V8 symbols bind to the runtime already in memory. + add_library(nativescript-runtime-stub SHARED v8runtime/NativeScriptRuntimeStub.cpp) + set_target_properties(nativescript-runtime-stub PROPERTIES NO_SONAME ON) + target_link_options(nativescript-runtime-stub PRIVATE "-Wl,-soname,libNativeScript.so") + string(REGEX REPLACE "(^| )-Wl,--no-undefined( |$)" " " CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") target_link_options( nativescript-jsi @@ -78,5 +94,6 @@ else() nativescript-jsi ${android-log-lib} ${android-lib} + nativescript-runtime-stub ) endif() diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/NativeScriptRuntimeStub.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/NativeScriptRuntimeStub.cpp new file mode 100644 index 000000000..42162500d --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/NativeScriptRuntimeStub.cpp @@ -0,0 +1,3 @@ +// Intentionally empty. Linked under the soname libNativeScript.so so that +// libnativescript-jsi.so records a DT_NEEDED entry for the NativeScript runtime; +// see CMakeLists.txt. diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8ExportShims.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/V8ExportShims.cpp new file mode 100644 index 000000000..6663362b0 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8ExportShims.cpp @@ -0,0 +1,8 @@ +#include "v8.h" + +// Inline V8 header code (e.g. Object::GetInternalField) calls this V8_EXPORT +// function, but libNativeScript.so does not export it. V8 defines it as the +// current isolate, which libNativeScript.so does export. +v8::Isolate* v8::internal::Internals::GetCurrentIsolate() { + return v8::Isolate::GetCurrent(); +} From fdd24ce4a7497abc5c483b42c970c2b116cce02f Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 23 Oct 2026 11:44:30 -0700 Subject: [PATCH 07/10] perf(jsi): skip re-entering the isolate and context on every JSI call Each V8Runtime method took a v8::Locker and entered the isolate and context, and every released jsi::Value did the same in V8PointerValue::invalidate(). NativeScript never takes a Locker itself, so each one was a real mutex acquire. JSI calls almost always come from host functions that are already inside the isolate and context, so JSIScope now enters only what is missing. Nitro method calls on Android go from ~860 ns to ~415 ns. --- .../android/cpp/v8runtime/V8PointerValue.cpp | 4 +- .../android/cpp/v8runtime/V8Runtime.cpp | 425 +++++------------- .../v8runtime/V8PointerValue.cpp | 4 +- .../NativeScriptJSI/v8runtime/V8Runtime.cpp | 425 +++++------------- 4 files changed, 224 insertions(+), 634 deletions(-) diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp index 6c5a98e2b..3f7144103 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp @@ -60,7 +60,9 @@ V8PointerValue* V8PointerValue::createFromUtf8(v8::Isolate* isolate, } void V8PointerValue::invalidate() noexcept { - { + if (v8::Isolate::TryGetCurrent() == isolate_) { + value_.Reset(); + } else { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); value_.Reset(); diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp index b517ae82f..807216f58 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp @@ -18,6 +18,7 @@ // #include #include #include +#include #include #include "HostProxy.h" @@ -126,6 +127,30 @@ struct RetainedMutableBuffers { } }; +// Enters the isolate and its context only when the caller has not already. +// JSI calls nearly always come from host functions already running inside +// them, and re-entering costs a v8::Locker (a real mutex acquire, since +// NativeScript itself never takes one) plus scope bookkeeping on every call. +class JSIScope { + public: + JSIScope(v8::Isolate* isolate, const v8::Global& context) { + if (v8::Isolate::TryGetCurrent() != isolate) { + locker_.emplace(isolate); + isolateScope_.emplace(isolate); + } + handleScope_.emplace(isolate); + if (!isolate->InContext()) { + contextScope_.emplace(context.Get(isolate)); + } + } + + private: + std::optional locker_; + std::optional isolateScope_; + std::optional handleScope_; + std::optional contextScope_; +}; + } // namespace // static @@ -162,9 +187,7 @@ V8Runtime::V8Runtime() { // createParams.snapshot_blob = snapshotBlob_.get(); // } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); + JSIScope scope(isolate_, context_); context_.Reset(isolate_, isolate_->GetCurrentContext()); v8::Context::Scope scopedContext(context_.Get(isolate_)); arrayBufferAllocator_.reset( @@ -281,11 +304,8 @@ V8Runtime::~V8Runtime() { void V8Runtime::ExecuteTask( const std::function& task) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Local context = context_.Get(isolate_); - v8::Context::Scope scopedContext(context); + JSIScope scope(isolate_, context_); + v8::Local context = isolate_->GetCurrentContext(); // Running the task inside a host function turns C++ exceptions into JS // exceptions, which the verbose TryCatch below then reports. @@ -579,30 +599,21 @@ jsi::Value V8Runtime::evaluatePreparedJavaScript( } void V8Runtime::queueMicrotask(const jsi::Function& callback) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); isolate_->EnqueueMicrotask( JSIV8ValueConverter::ToV8Function(*this, callback)); } bool V8Runtime::drainMicrotasks(int maxMicrotasksHint) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); isolate_->PerformMicrotaskCheckpoint(); return true; } jsi::Object V8Runtime::global() { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); return make( new V8PointerValue(isolate_, context_.Get(isolate_)->Global())); @@ -622,10 +633,7 @@ jsi::Runtime::PointerValue* V8Runtime::cloneSymbol( if (!pv) { return nullptr; } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(pv); assert(v8PointerValue->Get(isolate_)->IsSymbol()); @@ -638,10 +646,7 @@ jsi::Runtime::PointerValue* V8Runtime::cloneBigInt( return nullptr; } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(pv); assert(v8PointerValue->Get(isolate_)->IsBigInt()); @@ -654,10 +659,7 @@ jsi::Runtime::PointerValue* V8Runtime::cloneString( return nullptr; } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(pv); assert(v8PointerValue->Get(isolate_)->IsString()); @@ -670,10 +672,7 @@ jsi::Runtime::PointerValue* V8Runtime::cloneObject( return nullptr; } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(pv); assert(v8PointerValue->Get(isolate_)->IsObject()); @@ -687,10 +686,7 @@ jsi::Runtime::PointerValue* V8Runtime::clonePropNameID( jsi::PropNameID V8Runtime::createPropNameIDFromAscii(const char* str, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); V8PointerValue* value = V8PointerValue::createFromOneByte(isolate_, str, length); if (!value) { @@ -702,10 +698,7 @@ jsi::PropNameID V8Runtime::createPropNameIDFromAscii(const char* str, jsi::PropNameID V8Runtime::createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); V8PointerValue* value = V8PointerValue::createFromUtf8(isolate_, utf8, length); if (!value) { @@ -716,10 +709,7 @@ jsi::PropNameID V8Runtime::createPropNameIDFromUtf8(const uint8_t* utf8, } jsi::PropNameID V8Runtime::createPropNameIDFromString(const jsi::String& str) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(str)); @@ -732,10 +722,7 @@ jsi::PropNameID V8Runtime::createPropNameIDFromString(const jsi::String& str) { jsi::PropNameID V8Runtime::createPropNameIDFromSymbol( const facebook::jsi::Symbol& sym) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); assert(static_cast(getPointerValue(sym)) ->Get(isolate_) @@ -745,10 +732,7 @@ jsi::PropNameID V8Runtime::createPropNameIDFromSymbol( } std::string V8Runtime::utf8(const jsi::PropNameID& sym) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(sym)); @@ -757,10 +741,7 @@ std::string V8Runtime::utf8(const jsi::PropNameID& sym) { } bool V8Runtime::compare(const jsi::PropNameID& a, const jsi::PropNameID& b) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValueA = static_cast(getPointerValue(a)); @@ -779,30 +760,21 @@ std::string V8Runtime::symbolToString(const jsi::Symbol& symbol) { } jsi::BigInt V8Runtime::createBigIntFromInt64(int64_t value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8BigInt = v8::BigInt::New(isolate_, value); return make(new V8PointerValue(isolate_, v8BigInt)); } jsi::BigInt V8Runtime::createBigIntFromUint64(uint64_t value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8BigInt = v8::BigInt::NewFromUnsigned(isolate_, value); return make(new V8PointerValue(isolate_, v8BigInt)); } bool V8Runtime::bigintIsInt64(const jsi::BigInt& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(value)); @@ -815,10 +787,7 @@ bool V8Runtime::bigintIsInt64(const jsi::BigInt& value) { } bool V8Runtime::bigintIsUint64(const jsi::BigInt& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(value)); @@ -831,10 +800,7 @@ bool V8Runtime::bigintIsUint64(const jsi::BigInt& value) { } uint64_t V8Runtime::truncate(const jsi::BigInt& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(value)); @@ -845,10 +811,7 @@ uint64_t V8Runtime::truncate(const jsi::BigInt& value) { } jsi::String V8Runtime::bigintToString(const jsi::BigInt& value, int radix) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(value)); @@ -885,10 +848,7 @@ jsi::String V8Runtime::bigintToString(const jsi::BigInt& value, int radix) { } jsi::String V8Runtime::createStringFromAscii(const char* str, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); V8PointerValue* value = V8PointerValue::createFromOneByte(isolate_, str, length); @@ -900,10 +860,7 @@ jsi::String V8Runtime::createStringFromAscii(const char* str, size_t length) { } jsi::String V8Runtime::createStringFromUtf8(const uint8_t* str, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); V8PointerValue* value = V8PointerValue::createFromUtf8(isolate_, str, length); if (!value) { @@ -914,10 +871,7 @@ jsi::String V8Runtime::createStringFromUtf8(const uint8_t* str, size_t length) { } std::string V8Runtime::utf8(const jsi::String& str) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(str)); @@ -987,10 +941,7 @@ void forStringData(v8::Isolate* isolate, v8::Local string, jsi::String V8Runtime::createStringFromUtf16(const char16_t* utf16, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); return make(new V8PointerValue( isolate_, newStringFromUtf16(isolate_, utf16, length))); @@ -998,27 +949,20 @@ jsi::String V8Runtime::createStringFromUtf16(const char16_t* utf16, jsi::PropNameID V8Runtime::createPropNameIDFromUtf16(const char16_t* utf16, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); return make(new V8PointerValue( isolate_, newStringFromUtf16(isolate_, utf16, length))); } std::u16string V8Runtime::utf16(const jsi::String& str) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); + JSIScope scope(isolate_, context_); return stringToUtf16(isolate_, JSIV8ValueConverter::ToV8String(*this, str)); } std::u16string V8Runtime::utf16(const jsi::PropNameID& sym) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); + JSIScope scope(isolate_, context_); return stringToUtf16(isolate_, JSIV8ValueConverter::ToV8String(*this, sym)); } @@ -1026,9 +970,7 @@ std::u16string V8Runtime::utf16(const jsi::PropNameID& sym) { void V8Runtime::getStringData(const jsi::String& str, void* ctx, void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); + JSIScope scope(isolate_, context_); forStringData(isolate_, JSIV8ValueConverter::ToV8String(*this, str), ctx, cb); @@ -1037,19 +979,14 @@ void V8Runtime::getStringData(const jsi::String& str, void* ctx, void V8Runtime::getPropNameIdData(const jsi::PropNameID& sym, void* ctx, void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); + JSIScope scope(isolate_, context_); forStringData(isolate_, JSIV8ValueConverter::ToV8String(*this, sym), ctx, cb); } jsi::Object V8Runtime::createObject() { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local object = v8::Object::New(isolate_); return make(new V8PointerValue(isolate_, object)); @@ -1057,10 +994,7 @@ jsi::Object V8Runtime::createObject() { jsi::Object V8Runtime::createObject( std::shared_ptr hostObject) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); HostObjectProxy* hostObjectProxy = new HostObjectProxy(*this, isolate_, hostObject); @@ -1095,10 +1029,7 @@ std::shared_ptr V8Runtime::getHostObject( // We are guarenteed at this point to have isHostObject(obj) == true // so the internal data should be HostObjectMetadata - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1115,10 +1046,7 @@ jsi::HostFunctionType& V8Runtime::getHostFunction( assert(isHostFunction(function)); // We know that isHostFunction(function) is true here, so its safe to proceed - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(function)); @@ -1141,11 +1069,8 @@ jsi::HostFunctionType& V8Runtime::getHostFunction( } jsi::Object V8Runtime::createObjectWithPrototype(const jsi::Value& prototype) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Local context = context_.Get(isolate_); - v8::Context::Scope scopedContext(context); + JSIScope scope(isolate_, context_); + v8::Local context = isolate_->GetCurrentContext(); v8::Local objectTemplate = v8::ObjectTemplate::New(isolate_); @@ -1171,10 +1096,7 @@ jsi::Object V8Runtime::createObjectWithPrototype(const jsi::Value& prototype) { } bool V8Runtime::hasNativeState(const jsi::Object& object) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1194,10 +1116,7 @@ std::shared_ptr V8Runtime::getNativeState( } assert(hasNativeState(object)); - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1213,11 +1132,8 @@ void V8Runtime::setNativeState(const jsi::Object& object, throw jsi::JSINativeException("native state unsupported on HostObject"); } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Local context = context_.Get(isolate_); - v8::Context::Scope scopedContext(context); + JSIScope scope(isolate_, context_); + v8::Local context = isolate_->GetCurrentContext(); v8::Local v8ObjectOriginal = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1307,11 +1223,8 @@ void V8Runtime::setNativeState(const jsi::Object& object, void V8Runtime::setPrototypeOf(const jsi::Object& object, const jsi::Value& prototype) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Local context = context_.Get(isolate_); - v8::Context::Scope scopedContext(context); + JSIScope scope(isolate_, context_); + v8::Local context = isolate_->GetCurrentContext(); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1323,10 +1236,7 @@ void V8Runtime::setPrototypeOf(const jsi::Object& object, } jsi::Value V8Runtime::getPrototypeOf(const jsi::Object& object) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1335,10 +1245,7 @@ jsi::Value V8Runtime::getPrototypeOf(const jsi::Object& object) { jsi::Value V8Runtime::getProperty(const jsi::Object& object, const jsi::PropNameID& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1359,10 +1266,7 @@ jsi::Value V8Runtime::getProperty(const jsi::Object& object, jsi::Value V8Runtime::getProperty(const jsi::Object& object, const jsi::String& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1382,10 +1286,7 @@ jsi::Value V8Runtime::getProperty(const jsi::Object& object, bool V8Runtime::hasProperty(const jsi::Object& object, const jsi::PropNameID& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1406,10 +1307,7 @@ bool V8Runtime::hasProperty(const jsi::Object& object, bool V8Runtime::hasProperty(const jsi::Object& object, const jsi::String& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1431,10 +1329,7 @@ bool V8Runtime::hasProperty(const jsi::Object& object, void V8Runtime::setPropertyValue(const jsi::Object& object, const jsi::PropNameID& name, const jsi::Value& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1451,10 +1346,7 @@ void V8Runtime::setPropertyValue(const jsi::Object& object, void V8Runtime::setPropertyValue(const jsi::Object& object, const jsi::String& name, const jsi::Value& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1470,10 +1362,7 @@ void V8Runtime::setPropertyValue(const jsi::Object& object, jsi::Value V8Runtime::getProperty(const jsi::Object& object, const jsi::Value& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1493,10 +1382,7 @@ jsi::Value V8Runtime::getProperty(const jsi::Object& object, bool V8Runtime::hasProperty(const jsi::Object& object, const jsi::Value& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1514,10 +1400,7 @@ bool V8Runtime::hasProperty(const jsi::Object& object, void V8Runtime::setPropertyValue(const jsi::Object& object, const jsi::Value& name, const jsi::Value& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1545,10 +1428,7 @@ void V8Runtime::DeleteV8Property(v8::Local object, void V8Runtime::deleteProperty(const jsi::Object& object, const jsi::PropNameID& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), JSIV8ValueConverter::ToV8String(*this, name)); @@ -1556,10 +1436,7 @@ void V8Runtime::deleteProperty(const jsi::Object& object, void V8Runtime::deleteProperty(const jsi::Object& object, const jsi::String& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), JSIV8ValueConverter::ToV8String(*this, name)); @@ -1567,10 +1444,7 @@ void V8Runtime::deleteProperty(const jsi::Object& object, void V8Runtime::deleteProperty(const jsi::Object& object, const jsi::Value& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), JSIV8ValueConverter::ToV8Value(*this, name)); @@ -1593,10 +1467,7 @@ const void* V8Runtime::getRuntimeDataImpl(const jsi::UUID& uuid) { } bool V8Runtime::isArray(const jsi::Object& object) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1604,10 +1475,7 @@ bool V8Runtime::isArray(const jsi::Object& object) const { } bool V8Runtime::isArrayBuffer(const jsi::Object& object) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1615,10 +1483,7 @@ bool V8Runtime::isArrayBuffer(const jsi::Object& object) const { } bool V8Runtime::isFunction(const jsi::Object& object) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1626,10 +1491,7 @@ bool V8Runtime::isFunction(const jsi::Object& object) const { } bool V8Runtime::isHostObject(const jsi::Object& object) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1637,10 +1499,7 @@ bool V8Runtime::isHostObject(const jsi::Object& object) const { } bool V8Runtime::isHostFunction(const jsi::Function& function) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Function = JSIV8ValueConverter::ToV8Function(*this, function); @@ -1653,10 +1512,7 @@ bool V8Runtime::isHostFunction(const jsi::Function& function) const { } jsi::Array V8Runtime::getPropertyNames(const jsi::Object& object) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1676,10 +1532,7 @@ jsi::Array V8Runtime::getPropertyNames(const jsi::Object& object) { } jsi::WeakObject V8Runtime::createWeakObject(const jsi::Object& weakObject) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(weakObject)); @@ -1693,10 +1546,7 @@ jsi::WeakObject V8Runtime::createWeakObject(const jsi::Object& weakObject) { } jsi::Value V8Runtime::lockWeakObject(const jsi::WeakObject& weakObject) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(weakObject)); @@ -1706,10 +1556,7 @@ jsi::Value V8Runtime::lockWeakObject(const jsi::WeakObject& weakObject) { } jsi::Array V8Runtime::createArray(size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Array = v8::Array::New(isolate_, static_cast(length)); @@ -1719,10 +1566,7 @@ jsi::Array V8Runtime::createArray(size_t length) { jsi::ArrayBuffer V8Runtime::createArrayBuffer( std::shared_ptr buffer) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); if (!buffer) { throw jsi::JSINativeException( @@ -1765,20 +1609,14 @@ jsi::ArrayBuffer V8Runtime::createArrayBuffer( } size_t V8Runtime::size(const jsi::Array& array) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); return v8Array->Length(); } size_t V8Runtime::size(const jsi::ArrayBuffer& arrayBuffer) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); @@ -1788,10 +1626,7 @@ size_t V8Runtime::size(const jsi::ArrayBuffer& arrayBuffer) { } uint8_t* V8Runtime::data(const jsi::ArrayBuffer& arrayBuffer) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); @@ -1802,10 +1637,7 @@ uint8_t* V8Runtime::data(const jsi::ArrayBuffer& arrayBuffer) { std::shared_ptr V8Runtime::tryGetMutableBuffer( const jsi::ArrayBuffer& arrayBuffer) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); @@ -1826,10 +1658,7 @@ std::shared_ptr V8Runtime::tryGetMutableBuffer( } bool V8Runtime::detached(const jsi::ArrayBuffer& arrayBuffer) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); @@ -1838,10 +1667,7 @@ bool V8Runtime::detached(const jsi::ArrayBuffer& arrayBuffer) { size_t V8Runtime::push(const jsi::Array& array, const jsi::Value* elements, size_t count) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local context = isolate_->GetCurrentContext(); v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); @@ -1858,10 +1684,7 @@ size_t V8Runtime::push(const jsi::Array& array, const jsi::Value* elements, } jsi::Value V8Runtime::getValueAtIndex(const jsi::Array& array, size_t i) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); v8::MaybeLocal result = @@ -1874,10 +1697,7 @@ jsi::Value V8Runtime::getValueAtIndex(const jsi::Array& array, size_t i) { void V8Runtime::setValueAtIndexImpl(const jsi::Array& array, size_t i, const jsi::Value& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); v8::Maybe result = @@ -1891,10 +1711,7 @@ void V8Runtime::setValueAtIndexImpl(const jsi::Array& array, size_t i, jsi::Function V8Runtime::createFunctionFromHostFunction( const jsi::PropNameID& name, unsigned int paramCount, jsi::HostFunctionType func) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); HostFunctionProxy* hostFunctionProxy = new HostFunctionProxy(*this, isolate_, std::move(func)); @@ -1933,10 +1750,7 @@ jsi::Function V8Runtime::createFunctionFromHostFunction( jsi::Value V8Runtime::call(const jsi::Function& function, const jsi::Value& jsThis, const jsi::Value* args, size_t count) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Function = @@ -1973,10 +1787,7 @@ jsi::Value V8Runtime::call(const jsi::Function& function, jsi::Value V8Runtime::callAsConstructor(const jsi::Function& function, const jsi::Value* args, size_t count) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Function = @@ -2004,10 +1815,7 @@ jsi::Value V8Runtime::callAsConstructor(const jsi::Function& function, } bool V8Runtime::strictEquals(const jsi::Symbol& a, const jsi::Symbol& b) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8SymbolA = JSIV8ValueConverter::ToV8Symbol(*this, a); @@ -2021,10 +1829,7 @@ bool V8Runtime::strictEquals(const jsi::Symbol& a, const jsi::Symbol& b) const { } bool V8Runtime::strictEquals(const jsi::BigInt& a, const jsi::BigInt& b) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8ValueA = @@ -2040,10 +1845,7 @@ bool V8Runtime::strictEquals(const jsi::BigInt& a, const jsi::BigInt& b) const { } bool V8Runtime::strictEquals(const jsi::String& a, const jsi::String& b) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8StringA = JSIV8ValueConverter::ToV8String(*this, a); @@ -2057,10 +1859,7 @@ bool V8Runtime::strictEquals(const jsi::String& a, const jsi::String& b) const { } bool V8Runtime::strictEquals(const jsi::Object& a, const jsi::Object& b) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8ObjectA = JSIV8ValueConverter::ToV8Object(*this, a); @@ -2074,10 +1873,7 @@ bool V8Runtime::strictEquals(const jsi::Object& a, const jsi::Object& b) const { } bool V8Runtime::instanceOf(const jsi::Object& o, const jsi::Function& f) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, o); @@ -2093,11 +1889,8 @@ bool V8Runtime::instanceOf(const jsi::Object& o, const jsi::Function& f) { void V8Runtime::setExternalMemoryPressure(const jsi::Object& obj, size_t amount) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Local context = context_.Get(isolate_); - v8::Context::Scope scopedContext(context); + JSIScope scope(isolate_, context_); + v8::Local context = isolate_->GetCurrentContext(); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, obj); v8::Local privateKey = v8::Private::ForApi( diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.cpp index 6c5a98e2b..3f7144103 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.cpp @@ -60,7 +60,9 @@ V8PointerValue* V8PointerValue::createFromUtf8(v8::Isolate* isolate, } void V8PointerValue::invalidate() noexcept { - { + if (v8::Isolate::TryGetCurrent() == isolate_) { + value_.Reset(); + } else { v8::Locker locker(isolate_); v8::Isolate::Scope scopedIsolate(isolate_); value_.Reset(); diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp index b517ae82f..807216f58 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp @@ -18,6 +18,7 @@ // #include #include #include +#include #include #include "HostProxy.h" @@ -126,6 +127,30 @@ struct RetainedMutableBuffers { } }; +// Enters the isolate and its context only when the caller has not already. +// JSI calls nearly always come from host functions already running inside +// them, and re-entering costs a v8::Locker (a real mutex acquire, since +// NativeScript itself never takes one) plus scope bookkeeping on every call. +class JSIScope { + public: + JSIScope(v8::Isolate* isolate, const v8::Global& context) { + if (v8::Isolate::TryGetCurrent() != isolate) { + locker_.emplace(isolate); + isolateScope_.emplace(isolate); + } + handleScope_.emplace(isolate); + if (!isolate->InContext()) { + contextScope_.emplace(context.Get(isolate)); + } + } + + private: + std::optional locker_; + std::optional isolateScope_; + std::optional handleScope_; + std::optional contextScope_; +}; + } // namespace // static @@ -162,9 +187,7 @@ V8Runtime::V8Runtime() { // createParams.snapshot_blob = snapshotBlob_.get(); // } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); + JSIScope scope(isolate_, context_); context_.Reset(isolate_, isolate_->GetCurrentContext()); v8::Context::Scope scopedContext(context_.Get(isolate_)); arrayBufferAllocator_.reset( @@ -281,11 +304,8 @@ V8Runtime::~V8Runtime() { void V8Runtime::ExecuteTask( const std::function& task) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Local context = context_.Get(isolate_); - v8::Context::Scope scopedContext(context); + JSIScope scope(isolate_, context_); + v8::Local context = isolate_->GetCurrentContext(); // Running the task inside a host function turns C++ exceptions into JS // exceptions, which the verbose TryCatch below then reports. @@ -579,30 +599,21 @@ jsi::Value V8Runtime::evaluatePreparedJavaScript( } void V8Runtime::queueMicrotask(const jsi::Function& callback) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); isolate_->EnqueueMicrotask( JSIV8ValueConverter::ToV8Function(*this, callback)); } bool V8Runtime::drainMicrotasks(int maxMicrotasksHint) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); isolate_->PerformMicrotaskCheckpoint(); return true; } jsi::Object V8Runtime::global() { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); return make( new V8PointerValue(isolate_, context_.Get(isolate_)->Global())); @@ -622,10 +633,7 @@ jsi::Runtime::PointerValue* V8Runtime::cloneSymbol( if (!pv) { return nullptr; } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(pv); assert(v8PointerValue->Get(isolate_)->IsSymbol()); @@ -638,10 +646,7 @@ jsi::Runtime::PointerValue* V8Runtime::cloneBigInt( return nullptr; } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(pv); assert(v8PointerValue->Get(isolate_)->IsBigInt()); @@ -654,10 +659,7 @@ jsi::Runtime::PointerValue* V8Runtime::cloneString( return nullptr; } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(pv); assert(v8PointerValue->Get(isolate_)->IsString()); @@ -670,10 +672,7 @@ jsi::Runtime::PointerValue* V8Runtime::cloneObject( return nullptr; } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(pv); assert(v8PointerValue->Get(isolate_)->IsObject()); @@ -687,10 +686,7 @@ jsi::Runtime::PointerValue* V8Runtime::clonePropNameID( jsi::PropNameID V8Runtime::createPropNameIDFromAscii(const char* str, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); V8PointerValue* value = V8PointerValue::createFromOneByte(isolate_, str, length); if (!value) { @@ -702,10 +698,7 @@ jsi::PropNameID V8Runtime::createPropNameIDFromAscii(const char* str, jsi::PropNameID V8Runtime::createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); V8PointerValue* value = V8PointerValue::createFromUtf8(isolate_, utf8, length); if (!value) { @@ -716,10 +709,7 @@ jsi::PropNameID V8Runtime::createPropNameIDFromUtf8(const uint8_t* utf8, } jsi::PropNameID V8Runtime::createPropNameIDFromString(const jsi::String& str) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(str)); @@ -732,10 +722,7 @@ jsi::PropNameID V8Runtime::createPropNameIDFromString(const jsi::String& str) { jsi::PropNameID V8Runtime::createPropNameIDFromSymbol( const facebook::jsi::Symbol& sym) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); assert(static_cast(getPointerValue(sym)) ->Get(isolate_) @@ -745,10 +732,7 @@ jsi::PropNameID V8Runtime::createPropNameIDFromSymbol( } std::string V8Runtime::utf8(const jsi::PropNameID& sym) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(sym)); @@ -757,10 +741,7 @@ std::string V8Runtime::utf8(const jsi::PropNameID& sym) { } bool V8Runtime::compare(const jsi::PropNameID& a, const jsi::PropNameID& b) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValueA = static_cast(getPointerValue(a)); @@ -779,30 +760,21 @@ std::string V8Runtime::symbolToString(const jsi::Symbol& symbol) { } jsi::BigInt V8Runtime::createBigIntFromInt64(int64_t value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8BigInt = v8::BigInt::New(isolate_, value); return make(new V8PointerValue(isolate_, v8BigInt)); } jsi::BigInt V8Runtime::createBigIntFromUint64(uint64_t value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8BigInt = v8::BigInt::NewFromUnsigned(isolate_, value); return make(new V8PointerValue(isolate_, v8BigInt)); } bool V8Runtime::bigintIsInt64(const jsi::BigInt& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(value)); @@ -815,10 +787,7 @@ bool V8Runtime::bigintIsInt64(const jsi::BigInt& value) { } bool V8Runtime::bigintIsUint64(const jsi::BigInt& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(value)); @@ -831,10 +800,7 @@ bool V8Runtime::bigintIsUint64(const jsi::BigInt& value) { } uint64_t V8Runtime::truncate(const jsi::BigInt& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(value)); @@ -845,10 +811,7 @@ uint64_t V8Runtime::truncate(const jsi::BigInt& value) { } jsi::String V8Runtime::bigintToString(const jsi::BigInt& value, int radix) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(value)); @@ -885,10 +848,7 @@ jsi::String V8Runtime::bigintToString(const jsi::BigInt& value, int radix) { } jsi::String V8Runtime::createStringFromAscii(const char* str, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); V8PointerValue* value = V8PointerValue::createFromOneByte(isolate_, str, length); @@ -900,10 +860,7 @@ jsi::String V8Runtime::createStringFromAscii(const char* str, size_t length) { } jsi::String V8Runtime::createStringFromUtf8(const uint8_t* str, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); V8PointerValue* value = V8PointerValue::createFromUtf8(isolate_, str, length); if (!value) { @@ -914,10 +871,7 @@ jsi::String V8Runtime::createStringFromUtf8(const uint8_t* str, size_t length) { } std::string V8Runtime::utf8(const jsi::String& str) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(str)); @@ -987,10 +941,7 @@ void forStringData(v8::Isolate* isolate, v8::Local string, jsi::String V8Runtime::createStringFromUtf16(const char16_t* utf16, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); return make(new V8PointerValue( isolate_, newStringFromUtf16(isolate_, utf16, length))); @@ -998,27 +949,20 @@ jsi::String V8Runtime::createStringFromUtf16(const char16_t* utf16, jsi::PropNameID V8Runtime::createPropNameIDFromUtf16(const char16_t* utf16, size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); return make(new V8PointerValue( isolate_, newStringFromUtf16(isolate_, utf16, length))); } std::u16string V8Runtime::utf16(const jsi::String& str) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); + JSIScope scope(isolate_, context_); return stringToUtf16(isolate_, JSIV8ValueConverter::ToV8String(*this, str)); } std::u16string V8Runtime::utf16(const jsi::PropNameID& sym) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); + JSIScope scope(isolate_, context_); return stringToUtf16(isolate_, JSIV8ValueConverter::ToV8String(*this, sym)); } @@ -1026,9 +970,7 @@ std::u16string V8Runtime::utf16(const jsi::PropNameID& sym) { void V8Runtime::getStringData(const jsi::String& str, void* ctx, void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); + JSIScope scope(isolate_, context_); forStringData(isolate_, JSIV8ValueConverter::ToV8String(*this, str), ctx, cb); @@ -1037,19 +979,14 @@ void V8Runtime::getStringData(const jsi::String& str, void* ctx, void V8Runtime::getPropNameIdData(const jsi::PropNameID& sym, void* ctx, void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); + JSIScope scope(isolate_, context_); forStringData(isolate_, JSIV8ValueConverter::ToV8String(*this, sym), ctx, cb); } jsi::Object V8Runtime::createObject() { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local object = v8::Object::New(isolate_); return make(new V8PointerValue(isolate_, object)); @@ -1057,10 +994,7 @@ jsi::Object V8Runtime::createObject() { jsi::Object V8Runtime::createObject( std::shared_ptr hostObject) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); HostObjectProxy* hostObjectProxy = new HostObjectProxy(*this, isolate_, hostObject); @@ -1095,10 +1029,7 @@ std::shared_ptr V8Runtime::getHostObject( // We are guarenteed at this point to have isHostObject(obj) == true // so the internal data should be HostObjectMetadata - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1115,10 +1046,7 @@ jsi::HostFunctionType& V8Runtime::getHostFunction( assert(isHostFunction(function)); // We know that isHostFunction(function) is true here, so its safe to proceed - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(function)); @@ -1141,11 +1069,8 @@ jsi::HostFunctionType& V8Runtime::getHostFunction( } jsi::Object V8Runtime::createObjectWithPrototype(const jsi::Value& prototype) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Local context = context_.Get(isolate_); - v8::Context::Scope scopedContext(context); + JSIScope scope(isolate_, context_); + v8::Local context = isolate_->GetCurrentContext(); v8::Local objectTemplate = v8::ObjectTemplate::New(isolate_); @@ -1171,10 +1096,7 @@ jsi::Object V8Runtime::createObjectWithPrototype(const jsi::Value& prototype) { } bool V8Runtime::hasNativeState(const jsi::Object& object) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1194,10 +1116,7 @@ std::shared_ptr V8Runtime::getNativeState( } assert(hasNativeState(object)); - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1213,11 +1132,8 @@ void V8Runtime::setNativeState(const jsi::Object& object, throw jsi::JSINativeException("native state unsupported on HostObject"); } - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Local context = context_.Get(isolate_); - v8::Context::Scope scopedContext(context); + JSIScope scope(isolate_, context_); + v8::Local context = isolate_->GetCurrentContext(); v8::Local v8ObjectOriginal = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1307,11 +1223,8 @@ void V8Runtime::setNativeState(const jsi::Object& object, void V8Runtime::setPrototypeOf(const jsi::Object& object, const jsi::Value& prototype) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Local context = context_.Get(isolate_); - v8::Context::Scope scopedContext(context); + JSIScope scope(isolate_, context_); + v8::Local context = isolate_->GetCurrentContext(); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1323,10 +1236,7 @@ void V8Runtime::setPrototypeOf(const jsi::Object& object, } jsi::Value V8Runtime::getPrototypeOf(const jsi::Object& object) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1335,10 +1245,7 @@ jsi::Value V8Runtime::getPrototypeOf(const jsi::Object& object) { jsi::Value V8Runtime::getProperty(const jsi::Object& object, const jsi::PropNameID& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1359,10 +1266,7 @@ jsi::Value V8Runtime::getProperty(const jsi::Object& object, jsi::Value V8Runtime::getProperty(const jsi::Object& object, const jsi::String& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1382,10 +1286,7 @@ jsi::Value V8Runtime::getProperty(const jsi::Object& object, bool V8Runtime::hasProperty(const jsi::Object& object, const jsi::PropNameID& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1406,10 +1307,7 @@ bool V8Runtime::hasProperty(const jsi::Object& object, bool V8Runtime::hasProperty(const jsi::Object& object, const jsi::String& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1431,10 +1329,7 @@ bool V8Runtime::hasProperty(const jsi::Object& object, void V8Runtime::setPropertyValue(const jsi::Object& object, const jsi::PropNameID& name, const jsi::Value& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1451,10 +1346,7 @@ void V8Runtime::setPropertyValue(const jsi::Object& object, void V8Runtime::setPropertyValue(const jsi::Object& object, const jsi::String& name, const jsi::Value& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1470,10 +1362,7 @@ void V8Runtime::setPropertyValue(const jsi::Object& object, jsi::Value V8Runtime::getProperty(const jsi::Object& object, const jsi::Value& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1493,10 +1382,7 @@ jsi::Value V8Runtime::getProperty(const jsi::Object& object, bool V8Runtime::hasProperty(const jsi::Object& object, const jsi::Value& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = @@ -1514,10 +1400,7 @@ bool V8Runtime::hasProperty(const jsi::Object& object, void V8Runtime::setPropertyValue(const jsi::Object& object, const jsi::Value& name, const jsi::Value& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1545,10 +1428,7 @@ void V8Runtime::DeleteV8Property(v8::Local object, void V8Runtime::deleteProperty(const jsi::Object& object, const jsi::PropNameID& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), JSIV8ValueConverter::ToV8String(*this, name)); @@ -1556,10 +1436,7 @@ void V8Runtime::deleteProperty(const jsi::Object& object, void V8Runtime::deleteProperty(const jsi::Object& object, const jsi::String& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), JSIV8ValueConverter::ToV8String(*this, name)); @@ -1567,10 +1444,7 @@ void V8Runtime::deleteProperty(const jsi::Object& object, void V8Runtime::deleteProperty(const jsi::Object& object, const jsi::Value& name) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), JSIV8ValueConverter::ToV8Value(*this, name)); @@ -1593,10 +1467,7 @@ const void* V8Runtime::getRuntimeDataImpl(const jsi::UUID& uuid) { } bool V8Runtime::isArray(const jsi::Object& object) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1604,10 +1475,7 @@ bool V8Runtime::isArray(const jsi::Object& object) const { } bool V8Runtime::isArrayBuffer(const jsi::Object& object) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1615,10 +1483,7 @@ bool V8Runtime::isArrayBuffer(const jsi::Object& object) const { } bool V8Runtime::isFunction(const jsi::Object& object) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1626,10 +1491,7 @@ bool V8Runtime::isFunction(const jsi::Object& object) const { } bool V8Runtime::isHostObject(const jsi::Object& object) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1637,10 +1499,7 @@ bool V8Runtime::isHostObject(const jsi::Object& object) const { } bool V8Runtime::isHostFunction(const jsi::Function& function) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Function = JSIV8ValueConverter::ToV8Function(*this, function); @@ -1653,10 +1512,7 @@ bool V8Runtime::isHostFunction(const jsi::Function& function) const { } jsi::Array V8Runtime::getPropertyNames(const jsi::Object& object) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1676,10 +1532,7 @@ jsi::Array V8Runtime::getPropertyNames(const jsi::Object& object) { } jsi::WeakObject V8Runtime::createWeakObject(const jsi::Object& weakObject) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(weakObject)); @@ -1693,10 +1546,7 @@ jsi::WeakObject V8Runtime::createWeakObject(const jsi::Object& weakObject) { } jsi::Value V8Runtime::lockWeakObject(const jsi::WeakObject& weakObject) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); const V8PointerValue* v8PointerValue = static_cast(getPointerValue(weakObject)); @@ -1706,10 +1556,7 @@ jsi::Value V8Runtime::lockWeakObject(const jsi::WeakObject& weakObject) { } jsi::Array V8Runtime::createArray(size_t length) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Array = v8::Array::New(isolate_, static_cast(length)); @@ -1719,10 +1566,7 @@ jsi::Array V8Runtime::createArray(size_t length) { jsi::ArrayBuffer V8Runtime::createArrayBuffer( std::shared_ptr buffer) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); if (!buffer) { throw jsi::JSINativeException( @@ -1765,20 +1609,14 @@ jsi::ArrayBuffer V8Runtime::createArrayBuffer( } size_t V8Runtime::size(const jsi::Array& array) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); return v8Array->Length(); } size_t V8Runtime::size(const jsi::ArrayBuffer& arrayBuffer) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); @@ -1788,10 +1626,7 @@ size_t V8Runtime::size(const jsi::ArrayBuffer& arrayBuffer) { } uint8_t* V8Runtime::data(const jsi::ArrayBuffer& arrayBuffer) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); @@ -1802,10 +1637,7 @@ uint8_t* V8Runtime::data(const jsi::ArrayBuffer& arrayBuffer) { std::shared_ptr V8Runtime::tryGetMutableBuffer( const jsi::ArrayBuffer& arrayBuffer) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); @@ -1826,10 +1658,7 @@ std::shared_ptr V8Runtime::tryGetMutableBuffer( } bool V8Runtime::detached(const jsi::ArrayBuffer& arrayBuffer) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); @@ -1838,10 +1667,7 @@ bool V8Runtime::detached(const jsi::ArrayBuffer& arrayBuffer) { size_t V8Runtime::push(const jsi::Array& array, const jsi::Value* elements, size_t count) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local context = isolate_->GetCurrentContext(); v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); @@ -1858,10 +1684,7 @@ size_t V8Runtime::push(const jsi::Array& array, const jsi::Value* elements, } jsi::Value V8Runtime::getValueAtIndex(const jsi::Array& array, size_t i) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); v8::MaybeLocal result = @@ -1874,10 +1697,7 @@ jsi::Value V8Runtime::getValueAtIndex(const jsi::Array& array, size_t i) { void V8Runtime::setValueAtIndexImpl(const jsi::Array& array, size_t i, const jsi::Value& value) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); v8::Maybe result = @@ -1891,10 +1711,7 @@ void V8Runtime::setValueAtIndexImpl(const jsi::Array& array, size_t i, jsi::Function V8Runtime::createFunctionFromHostFunction( const jsi::PropNameID& name, unsigned int paramCount, jsi::HostFunctionType func) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); HostFunctionProxy* hostFunctionProxy = new HostFunctionProxy(*this, isolate_, std::move(func)); @@ -1933,10 +1750,7 @@ jsi::Function V8Runtime::createFunctionFromHostFunction( jsi::Value V8Runtime::call(const jsi::Function& function, const jsi::Value& jsThis, const jsi::Value* args, size_t count) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Function = @@ -1973,10 +1787,7 @@ jsi::Value V8Runtime::call(const jsi::Function& function, jsi::Value V8Runtime::callAsConstructor(const jsi::Function& function, const jsi::Value* args, size_t count) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Function = @@ -2004,10 +1815,7 @@ jsi::Value V8Runtime::callAsConstructor(const jsi::Function& function, } bool V8Runtime::strictEquals(const jsi::Symbol& a, const jsi::Symbol& b) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8SymbolA = JSIV8ValueConverter::ToV8Symbol(*this, a); @@ -2021,10 +1829,7 @@ bool V8Runtime::strictEquals(const jsi::Symbol& a, const jsi::Symbol& b) const { } bool V8Runtime::strictEquals(const jsi::BigInt& a, const jsi::BigInt& b) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8ValueA = @@ -2040,10 +1845,7 @@ bool V8Runtime::strictEquals(const jsi::BigInt& a, const jsi::BigInt& b) const { } bool V8Runtime::strictEquals(const jsi::String& a, const jsi::String& b) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8StringA = JSIV8ValueConverter::ToV8String(*this, a); @@ -2057,10 +1859,7 @@ bool V8Runtime::strictEquals(const jsi::String& a, const jsi::String& b) const { } bool V8Runtime::strictEquals(const jsi::Object& a, const jsi::Object& b) const { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8ObjectA = JSIV8ValueConverter::ToV8Object(*this, a); @@ -2074,10 +1873,7 @@ bool V8Runtime::strictEquals(const jsi::Object& a, const jsi::Object& b) const { } bool V8Runtime::instanceOf(const jsi::Object& o, const jsi::Function& f) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::TryCatch tryCatch(isolate_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, o); @@ -2093,11 +1889,8 @@ bool V8Runtime::instanceOf(const jsi::Object& o, const jsi::Function& f) { void V8Runtime::setExternalMemoryPressure(const jsi::Object& obj, size_t amount) { - v8::Locker locker(isolate_); - v8::Isolate::Scope scopedIsolate(isolate_); - v8::HandleScope scopedHandle(isolate_); - v8::Local context = context_.Get(isolate_); - v8::Context::Scope scopedContext(context); + JSIScope scope(isolate_, context_); + v8::Local context = isolate_->GetCurrentContext(); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, obj); v8::Local privateKey = v8::Private::ForApi( From 84b0d53895d73e5c0c93a9badbddc0152b5cecb3 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 23 Oct 2026 12:04:24 -0700 Subject: [PATCH 08/10] perf(jsi): unwrap NativeState in one pass and stop allocating per host call getNativeState() entered the isolate and resolved the object three times (isHostObject, the hasNativeState assert, then the lookup), and Nitro calls it on every method call to unwrap `this`. It now reads the internal field type once. The host function container copied its arguments into a heap-allocated std::vector on every call; up to eight now use the stack. --- .../android/cpp/v8runtime/V8Runtime.cpp | 33 ++++++++++++------- .../NativeScriptJSI/v8runtime/V8Runtime.cpp | 33 ++++++++++++------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp index 807216f58..4ffcb8c1d 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp @@ -1111,15 +1111,17 @@ bool V8Runtime::hasNativeState(const jsi::Object& object) { std::shared_ptr V8Runtime::getNativeState( const jsi::Object& object) { - if (isHostObject(object)) { - throw jsi::JSINativeException("native state unsupported on HostObject"); - } - assert(hasNativeState(object)); - JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); + InternalFieldType type = GetInternalFieldType(v8Object); + if (type == InternalFieldType::kHostObject) { + throw jsi::JSINativeException("native state unsupported on HostObject"); + } + if (type != InternalFieldType::kNativeState) { + return nullptr; + } auto* holder = reinterpret_cast( v8Object->GetAlignedPointerFromInternalField( 1, v8::kEmbedderDataTypeTagDefault)); @@ -1981,13 +1983,20 @@ void V8Runtime::OnHostFuncionContainerCallback( v8::Local v8HostFunction = v8::Local::Cast(args.Data()); - std::vector> argv; - for (size_t i = 0; i < args.Length(); ++i) { - argv.push_back(args[(int)i]); - } - v8::MaybeLocal result = - v8HostFunction->Call(args.GetIsolate()->GetCurrentContext(), args.This(), - args.Length(), argv.data()); + constexpr int kStackArgCount = 8; + const int argc = args.Length(); + v8::Local stackArgv[kStackArgCount]; + std::vector> heapArgv; + v8::Local* argv = stackArgv; + if (argc > kStackArgCount) { + heapArgv.resize(argc); + argv = heapArgv.data(); + } + for (int i = 0; i < argc; ++i) { + argv[i] = args[i]; + } + v8::MaybeLocal result = v8HostFunction->Call( + args.GetIsolate()->GetCurrentContext(), args.This(), argc, argv); if (!result.IsEmpty()) { args.GetReturnValue().Set(result.ToLocalChecked()); diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp index 807216f58..4ffcb8c1d 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp @@ -1111,15 +1111,17 @@ bool V8Runtime::hasNativeState(const jsi::Object& object) { std::shared_ptr V8Runtime::getNativeState( const jsi::Object& object) { - if (isHostObject(object)) { - throw jsi::JSINativeException("native state unsupported on HostObject"); - } - assert(hasNativeState(object)); - JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); + InternalFieldType type = GetInternalFieldType(v8Object); + if (type == InternalFieldType::kHostObject) { + throw jsi::JSINativeException("native state unsupported on HostObject"); + } + if (type != InternalFieldType::kNativeState) { + return nullptr; + } auto* holder = reinterpret_cast( v8Object->GetAlignedPointerFromInternalField( 1, v8::kEmbedderDataTypeTagDefault)); @@ -1981,13 +1983,20 @@ void V8Runtime::OnHostFuncionContainerCallback( v8::Local v8HostFunction = v8::Local::Cast(args.Data()); - std::vector> argv; - for (size_t i = 0; i < args.Length(); ++i) { - argv.push_back(args[(int)i]); - } - v8::MaybeLocal result = - v8HostFunction->Call(args.GetIsolate()->GetCurrentContext(), args.This(), - args.Length(), argv.data()); + constexpr int kStackArgCount = 8; + const int argc = args.Length(); + v8::Local stackArgv[kStackArgCount]; + std::vector> heapArgv; + v8::Local* argv = stackArgv; + if (argc > kStackArgCount) { + heapArgv.resize(argc); + argv = heapArgv.data(); + } + for (int i = 0; i < argc; ++i) { + argv[i] = args[i]; + } + v8::MaybeLocal result = v8HostFunction->Call( + args.GetIsolate()->GetCurrentContext(), args.This(), argc, argv); if (!result.IsEmpty()) { args.GetReturnValue().Set(result.ToLocalChecked()); From 8b814849049c59ad2bf752865c786a3cda0c3ae4 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 23 Oct 2026 12:04:37 -0700 Subject: [PATCH 09/10] fix(jsi): pass the receiver to host functions as this Host functions received undefined as thisVal whenever the receiver had no internal fields, i.e. for every plain JS object, so a host function installed on an ordinary object or prototype could not see the object it was called on. --- .../android-jsi/platforms/android/cpp/v8runtime/HostProxy.cpp | 2 +- .../ios-jsi/Sources/NativeScriptJSI/v8runtime/HostProxy.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/HostProxy.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/HostProxy.cpp index 10c8eb779..1e54cdba8 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/HostProxy.cpp +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/HostProxy.cpp @@ -227,7 +227,7 @@ void HostFunctionProxy::FunctionCallback( v8::Local result; v8::Local receiver = info.This(); jsi::Value thisVal = - (receiver.IsEmpty() || receiver->InternalFieldCount() == 0) + receiver.IsEmpty() ? jsi::Value::undefined() : JSIV8ValueConverter::ToJSIValue(info.GetIsolate(), receiver); try { diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/HostProxy.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/HostProxy.cpp index 10c8eb779..1e54cdba8 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/HostProxy.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/HostProxy.cpp @@ -227,7 +227,7 @@ void HostFunctionProxy::FunctionCallback( v8::Local result; v8::Local receiver = info.This(); jsi::Value thisVal = - (receiver.IsEmpty() || receiver->InternalFieldCount() == 0) + receiver.IsEmpty() ? jsi::Value::undefined() : JSIV8ValueConverter::ToJSIValue(info.GetIsolate(), receiver); try { From f578a7d50778ab2943735a1d54669b30a96ca0af Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 23 Oct 2026 12:09:36 -0700 Subject: [PATCH 10/10] fix(android-jsi): stop compiling JSIRuntime.java into the app twice The NativeScript CLI already builds platforms/android/java into the plugin's AAR, and include.gradle added the same directory to the app's sources, so release builds failed dexing with "Type org.nativescript.jsi.JSIRuntime is defined multiple times". --- packages/android-jsi/platforms/android/include.gradle | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 packages/android-jsi/platforms/android/include.gradle diff --git a/packages/android-jsi/platforms/android/include.gradle b/packages/android-jsi/platforms/android/include.gradle deleted file mode 100644 index 5cac14ae9..000000000 --- a/packages/android-jsi/platforms/android/include.gradle +++ /dev/null @@ -1,9 +0,0 @@ -android { - sourceSets { - main { - java { - srcDirs += ["${rootDir}/../../node_modules/@nativescript/android-jsi/platforms/android/java"] - } - } - } -}