diff --git a/packages/android-jsi/README.md b/packages/android-jsi/README.md index 128ce56d..94283276 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 252065ef..b8eaef23 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 @@ -49,6 +57,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,9 +71,18 @@ if(DEFINED NATIVESCRIPT_SO_PATH AND EXISTS "${NATIVESCRIPT_SO_PATH}") target_link_libraries( nativescript-jsi ${android-log-lib} + ${android-lib} 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 @@ -75,5 +93,7 @@ else() target_link_libraries( nativescript-jsi ${android-log-lib} + ${android-lib} + nativescript-runtime-stub ) endif() diff --git a/packages/android-jsi/platforms/android/cpp/jsi/decorator.h b/packages/android-jsi/platforms/android/cpp/jsi/decorator.h index deaa86b7..f101c183 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 db22f4a4..4a88951f 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 8f30c056..6a29f647 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 5e3aac99..792b5bf5 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 7d760ef5..bf98028f 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 42fa1a09..b47d0d4a 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 0dad2ee8..c94de89f 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 768edb4a..cb10a335 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/HostProxy.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/HostProxy.cpp index 10c8eb77..1e54cdba 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/android-jsi/platforms/android/cpp/v8runtime/JSIRuntime.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/JSIRuntime.cpp index 961d160e..ad1b33f5 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 31976e67..2fcf4de6 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/NativeScriptRuntimeStub.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/NativeScriptRuntimeStub.cpp new file mode 100644 index 00000000..42162500 --- /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 00000000..6663362b --- /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(); +} diff --git a/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp index d8aaa584..3f714410 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.cpp @@ -59,8 +59,10 @@ V8PointerValue* V8PointerValue::createFromUtf8(v8::Isolate* isolate, return new V8PointerValue(isolate, v8String); } -void V8PointerValue::invalidate() { - { +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/V8PointerValue.h b/packages/android-jsi/platforms/android/cpp/v8runtime/V8PointerValue.h index 0ed63ff0..47831850 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/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.cpp index 03a87040..4ffcb8c1 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" @@ -111,6 +112,45 @@ 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; + } +}; + +// 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 @@ -147,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( @@ -247,19 +285,57 @@ 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(); + taskTrampoline_.Reset(); if (inspectorClient_) { inspectorClient_.reset(); } context_.Reset(); } - if (!isSharedRuntime_) { - isolate_->Dispose(); - } + // The isolate belongs to the NativeScript runtime; never dispose it here. // v8::V8::Dispose(); // v8::V8::DisposePlatform(); } +void V8Runtime::ExecuteTask( + const std::function& task) { + 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. + 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_); @@ -523,24 +599,21 @@ jsi::Value V8Runtime::evaluatePreparedJavaScript( } void V8Runtime::queueMicrotask(const jsi::Function& callback) { - // TODO: add this when we revisit new architecture support + 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())); @@ -560,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()); @@ -576,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()); @@ -592,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()); @@ -608,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()); @@ -625,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) { @@ -640,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) { @@ -654,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)); @@ -670,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_) @@ -683,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)); @@ -695,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)); @@ -717,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)); @@ -753,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)); @@ -769,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)); @@ -783,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)); @@ -823,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); @@ -838,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) { @@ -852,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)); @@ -865,11 +881,112 @@ 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) { + JSIScope scope(isolate_, context_); + + return make(new V8PointerValue( + isolate_, newStringFromUtf16(isolate_, utf16, length))); +} + +jsi::PropNameID V8Runtime::createPropNameIDFromUtf16(const char16_t* utf16, + size_t length) { + JSIScope scope(isolate_, context_); + + return make(new V8PointerValue( + isolate_, newStringFromUtf16(isolate_, utf16, length))); +} + +std::u16string V8Runtime::utf16(const jsi::String& str) { + JSIScope scope(isolate_, context_); + + return stringToUtf16(isolate_, JSIV8ValueConverter::ToV8String(*this, str)); +} + +std::u16string V8Runtime::utf16(const jsi::PropNameID& sym) { + JSIScope scope(isolate_, context_); + + 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)) { + JSIScope scope(isolate_, context_); + + 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)) { + 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)); @@ -877,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); @@ -915,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); @@ -935,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)); @@ -961,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_); @@ -991,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); @@ -1009,18 +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)); - - 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); + 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)); @@ -1033,11 +1134,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); @@ -1127,11 +1225,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); @@ -1143,10 +1238,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); @@ -1155,10 +1247,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 = @@ -1179,10 +1268,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 = @@ -1202,10 +1288,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 = @@ -1226,10 +1309,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 = @@ -1251,10 +1331,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); @@ -1271,10 +1348,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); @@ -1288,11 +1362,114 @@ void V8Runtime::setPropertyValue(const jsi::Object& object, } } +jsi::Value V8Runtime::getProperty(const jsi::Object& object, + const jsi::Value& name) { + JSIScope scope(isolate_, context_); + + 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) { + JSIScope scope(isolate_, context_); + + 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) { + JSIScope scope(isolate_, context_); + + 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) { + JSIScope scope(isolate_, context_); + + DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), + JSIV8ValueConverter::ToV8String(*this, name)); +} + +void V8Runtime::deleteProperty(const jsi::Object& object, + const jsi::String& name) { + JSIScope scope(isolate_, context_); + + DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), + JSIV8ValueConverter::ToV8String(*this, name)); +} + +void V8Runtime::deleteProperty(const jsi::Object& object, + const jsi::Value& name) { + JSIScope scope(isolate_, context_); + + 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_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1300,10 +1477,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); @@ -1311,10 +1485,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); @@ -1322,10 +1493,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); @@ -1333,10 +1501,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); @@ -1349,10 +1514,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); @@ -1372,10 +1534,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)); @@ -1389,10 +1548,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)); @@ -1402,10 +1558,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)); @@ -1415,10 +1568,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( @@ -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); @@ -1446,20 +1611,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); @@ -1469,10 +1628,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); @@ -1481,11 +1637,56 @@ uint8_t* V8Runtime::data(const jsi::ArrayBuffer& arrayBuffer) { return reinterpret_cast(v8ArrayBuffer->GetBackingStore()->Data()); } +std::shared_ptr V8Runtime::tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) { + JSIScope scope(isolate_, context_); + + 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) { + JSIScope scope(isolate_, context_); + + 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) { + JSIScope scope(isolate_, context_); + + 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_); - 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 = @@ -1498,10 +1699,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 = @@ -1515,10 +1713,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)); @@ -1557,10 +1752,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 = @@ -1597,10 +1789,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 = @@ -1628,10 +1817,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); @@ -1645,10 +1831,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 = @@ -1664,10 +1847,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); @@ -1681,10 +1861,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); @@ -1698,10 +1875,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); @@ -1717,11 +1891,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( @@ -1812,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]); + 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(); } - v8::MaybeLocal result = - v8HostFunction->Call(args.GetIsolate()->GetCurrentContext(), args.This(), - args.Length(), argv.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/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h index e2159c0a..481f2e4c 100644 --- a/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h +++ b/packages/android-jsi/platforms/android/cpp/v8runtime/V8Runtime.h @@ -7,6 +7,9 @@ #pragma once +#include +#include + // #include #include "V8RuntimeConfig.h" #include "jsi/jsi.h" @@ -33,12 +36,18 @@ 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, 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); @@ -116,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; @@ -154,6 +176,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 +212,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, @@ -230,7 +274,18 @@ 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; + void (*deleter)(const void* data); + }; + // Only touched on the runtime's JS thread, so it needs no lock. + std::unordered_map + runtimeData_; + + v8::Global taskTrampoline_; + const std::function* currentTask_ = nullptr; // std::shared_ptr jsQueue_; }; diff --git a/packages/android-jsi/platforms/android/include.gradle b/packages/android-jsi/platforms/android/include.gradle deleted file mode 100644 index 5cac14ae..00000000 --- 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"] - } - } - } -} diff --git a/packages/ios-jsi/README.md b/packages/ios-jsi/README.md index 34aa1424..806f2133 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 f5792077..597337a3 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 e2159c0a..481f2e4c 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h +++ b/packages/ios-jsi/Sources/NativeScriptJSI/include/V8Runtime.h @@ -7,6 +7,9 @@ #pragma once +#include +#include + // #include #include "V8RuntimeConfig.h" #include "jsi/jsi.h" @@ -33,12 +36,18 @@ 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, 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); @@ -116,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; @@ -154,6 +176,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 +212,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, @@ -230,7 +274,18 @@ 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; + void (*deleter)(const void* data); + }; + // Only touched on the runtime's JS thread, so it needs no lock. + std::unordered_map + runtimeData_; + + v8::Global taskTrampoline_; + const std::function* currentTask_ = nullptr; // std::shared_ptr jsQueue_; }; diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/decorator.h b/packages/ios-jsi/Sources/NativeScriptJSI/include/jsi/decorator.h index deaa86b7..f101c183 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 db22f4a4..4a88951f 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 8f30c056..6a29f647 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 7d760ef5..bf98028f 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 0dad2ee8..c94de89f 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 768edb4a..cb10a335 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 5e3aac99..792b5bf5 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 42fa1a09..b47d0d4a 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/HostProxy.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/HostProxy.cpp index 10c8eb77..1e54cdba 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 { diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/JSIRuntime.mm b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/JSIRuntime.mm index 2f2939e6..c74902e5 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/V8PointerValue.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.cpp index d8aaa584..3f714410 100644 --- a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.cpp +++ b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.cpp @@ -59,8 +59,10 @@ V8PointerValue* V8PointerValue::createFromUtf8(v8::Isolate* isolate, return new V8PointerValue(isolate, v8String); } -void V8PointerValue::invalidate() { - { +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/V8PointerValue.h b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8PointerValue.h index 0ed63ff0..47831850 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; diff --git a/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp b/packages/ios-jsi/Sources/NativeScriptJSI/v8runtime/V8Runtime.cpp index 03a87040..4ffcb8c1 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" @@ -111,6 +112,45 @@ 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; + } +}; + +// 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 @@ -147,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( @@ -247,19 +285,57 @@ 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(); + taskTrampoline_.Reset(); if (inspectorClient_) { inspectorClient_.reset(); } context_.Reset(); } - if (!isSharedRuntime_) { - isolate_->Dispose(); - } + // The isolate belongs to the NativeScript runtime; never dispose it here. // v8::V8::Dispose(); // v8::V8::DisposePlatform(); } +void V8Runtime::ExecuteTask( + const std::function& task) { + 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. + 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_); @@ -523,24 +599,21 @@ jsi::Value V8Runtime::evaluatePreparedJavaScript( } void V8Runtime::queueMicrotask(const jsi::Function& callback) { - // TODO: add this when we revisit new architecture support + 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())); @@ -560,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()); @@ -576,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()); @@ -592,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()); @@ -608,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()); @@ -625,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) { @@ -640,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) { @@ -654,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)); @@ -670,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_) @@ -683,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)); @@ -695,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)); @@ -717,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)); @@ -753,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)); @@ -769,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)); @@ -783,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)); @@ -823,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); @@ -838,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) { @@ -852,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)); @@ -865,11 +881,112 @@ 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) { + JSIScope scope(isolate_, context_); + + return make(new V8PointerValue( + isolate_, newStringFromUtf16(isolate_, utf16, length))); +} + +jsi::PropNameID V8Runtime::createPropNameIDFromUtf16(const char16_t* utf16, + size_t length) { + JSIScope scope(isolate_, context_); + + return make(new V8PointerValue( + isolate_, newStringFromUtf16(isolate_, utf16, length))); +} + +std::u16string V8Runtime::utf16(const jsi::String& str) { + JSIScope scope(isolate_, context_); + + return stringToUtf16(isolate_, JSIV8ValueConverter::ToV8String(*this, str)); +} + +std::u16string V8Runtime::utf16(const jsi::PropNameID& sym) { + JSIScope scope(isolate_, context_); + + 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)) { + JSIScope scope(isolate_, context_); + + 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)) { + 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)); @@ -877,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); @@ -915,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); @@ -935,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)); @@ -961,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_); @@ -991,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); @@ -1009,18 +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)); - - 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); + 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)); @@ -1033,11 +1134,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); @@ -1127,11 +1225,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); @@ -1143,10 +1238,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); @@ -1155,10 +1247,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 = @@ -1179,10 +1268,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 = @@ -1202,10 +1288,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 = @@ -1226,10 +1309,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 = @@ -1251,10 +1331,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); @@ -1271,10 +1348,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); @@ -1288,11 +1362,114 @@ void V8Runtime::setPropertyValue(const jsi::Object& object, } } +jsi::Value V8Runtime::getProperty(const jsi::Object& object, + const jsi::Value& name) { + JSIScope scope(isolate_, context_); + + 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) { + JSIScope scope(isolate_, context_); + + 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) { + JSIScope scope(isolate_, context_); + + 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) { + JSIScope scope(isolate_, context_); + + DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), + JSIV8ValueConverter::ToV8String(*this, name)); +} + +void V8Runtime::deleteProperty(const jsi::Object& object, + const jsi::String& name) { + JSIScope scope(isolate_, context_); + + DeleteV8Property(JSIV8ValueConverter::ToV8Object(*this, object), + JSIV8ValueConverter::ToV8String(*this, name)); +} + +void V8Runtime::deleteProperty(const jsi::Object& object, + const jsi::Value& name) { + JSIScope scope(isolate_, context_); + + 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_); - v8::HandleScope scopedHandle(isolate_); - v8::Context::Scope scopedContext(context_.Get(isolate_)); + JSIScope scope(isolate_, context_); v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, object); @@ -1300,10 +1477,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); @@ -1311,10 +1485,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); @@ -1322,10 +1493,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); @@ -1333,10 +1501,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); @@ -1349,10 +1514,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); @@ -1372,10 +1534,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)); @@ -1389,10 +1548,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)); @@ -1402,10 +1558,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)); @@ -1415,10 +1568,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( @@ -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); @@ -1446,20 +1611,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); @@ -1469,10 +1628,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); @@ -1481,11 +1637,56 @@ uint8_t* V8Runtime::data(const jsi::ArrayBuffer& arrayBuffer) { return reinterpret_cast(v8ArrayBuffer->GetBackingStore()->Data()); } +std::shared_ptr V8Runtime::tryGetMutableBuffer( + const jsi::ArrayBuffer& arrayBuffer) { + JSIScope scope(isolate_, context_); + + 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) { + JSIScope scope(isolate_, context_); + + 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) { + JSIScope scope(isolate_, context_); + + 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_); - 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 = @@ -1498,10 +1699,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 = @@ -1515,10 +1713,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)); @@ -1557,10 +1752,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 = @@ -1597,10 +1789,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 = @@ -1628,10 +1817,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); @@ -1645,10 +1831,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 = @@ -1664,10 +1847,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); @@ -1681,10 +1861,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); @@ -1698,10 +1875,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); @@ -1717,11 +1891,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( @@ -1812,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]); + 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(); } - v8::MaybeLocal result = - v8HostFunction->Call(args.GetIsolate()->GetCurrentContext(), args.This(), - args.Length(), argv.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());