diff --git a/packages/react-native/ReactCommon/react/bridging/CallbackWrapper.h b/packages/react-native/ReactCommon/react/bridging/CallbackWrapper.h index 445b4a0dccf4..efad212bc4fa 100644 --- a/packages/react-native/ReactCommon/react/bridging/CallbackWrapper.h +++ b/packages/react-native/ReactCommon/react/bridging/CallbackWrapper.h @@ -19,7 +19,10 @@ namespace facebook::react { class CallInvoker; -// Helper for passing jsi::Function arg to other methods. +/** + * @deprecated Use LongLivedObjectHandle instead. + * Access and release this legacy wrapper only on the JavaScript thread. + */ class CallbackWrapper : public LongLivedObject { private: CallbackWrapper(jsi::Function &&callback, jsi::Runtime &runtime, std::shared_ptr jsInvoker) diff --git a/packages/react-native/ReactCommon/react/bridging/Function.h b/packages/react-native/ReactCommon/react/bridging/Function.h index 19aaec53c006..511416965571 100644 --- a/packages/react-native/ReactCommon/react/bridging/Function.h +++ b/packages/react-native/ReactCommon/react/bridging/Function.h @@ -10,7 +10,7 @@ #include #include -#include +#include // Retain the legacy transitive include. #include #include @@ -61,16 +61,18 @@ class AsyncCallback { void callWithArgs(std::optional priority, Args... args) const noexcept { - if (auto wrapper = callback_->wrapper_.lock()) { - auto fn = [callback = callback_, + auto callback = callback_; + if (auto jsInvoker = callback->function_.lockInvoker()) { + // The queued function owns the tuple; its lifetime is opaque to the analyzer. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + auto fn = [callback = std::move(callback), argsPtr = std::make_shared>(std::make_tuple(std::forward(args)...))]( jsi::Runtime &) { callback->apply(std::move(*argsPtr)); }; - auto &jsInvoker = wrapper->jsInvoker(); if (priority) { - jsInvoker.invokeAsync(*priority, std::move(fn)); + jsInvoker->invokeAsync(*priority, std::move(fn)); } else { - jsInvoker.invokeAsync(std::move(fn)); + jsInvoker->invokeAsync(std::move(fn)); } } } @@ -79,57 +81,42 @@ class AsyncCallback { std::optional priority, std::function &&callImpl) const noexcept { - if (auto wrapper = callback_->wrapper_.lock()) { - // Capture callback_ and not wrapper_. If callback_ is deallocated or the - // JSVM is shutdown before the async task is scheduled, the underlying - // function will have been deallocated. - auto fn = [callback = callback_, callImpl = std::move(callImpl)](jsi::Runtime &rt) { - if (auto wrapper2 = callback->wrapper_.lock()) { - callImpl(rt, wrapper2->callback()); + auto callback = callback_; + if (auto jsInvoker = callback->function_.lockInvoker()) { + // Retain native ownership until this task is destroyed, without retaining + // a JavaScript value while the task is queued or discarded. + auto fn = [callback = std::move(callback), callImpl = std::move(callImpl)](jsi::Runtime &) { + if (!callback->function_.expired()) { + auto &rt = *callback->runtime_; + if (auto function = callback->function_.lockJSValue(rt)) { + callImpl(rt, function->value); + } } }; - auto &jsInvoker = wrapper->jsInvoker(); if (priority) { - jsInvoker.invokeAsync(*priority, std::move(fn)); + jsInvoker->invokeAsync(*priority, std::move(fn)); } else { - jsInvoker.invokeAsync(std::move(fn)); + jsInvoker->invokeAsync(std::move(fn)); } } } }; -// You must ensure that when invoking this you're located on the JS thread, or -// have exclusive control of the JS VM context. If you cannot ensure this, use -// AsyncCallback instead. +// Invoke only on the JavaScript thread. Use AsyncCallback from other threads. template class SyncCallback { public: SyncCallback(jsi::Runtime &rt, jsi::Function function, std::shared_ptr jsInvoker) - : wrapper_(CallbackWrapper::createWeak(std::move(function), rt, std::move(jsInvoker))) + : runtime_(&rt), function_(rt, std::move(function), std::move(jsInvoker)) { } - // Disallow copying, as we can no longer safely destroy the callback - // from the destructor if there's multiple copies SyncCallback(const SyncCallback &) = delete; SyncCallback &operator=(const SyncCallback &) = delete; - - // Allow move - SyncCallback(SyncCallback &&other) noexcept : wrapper_(std::move(other.wrapper_)) {} - - SyncCallback &operator=(SyncCallback &&other) noexcept - { - wrapper_ = std::move(other.wrapper_); - return *this; - } - - ~SyncCallback() - { - if (auto wrapper = wrapper_.lock()) { - wrapper->destroy(); - } - } + SyncCallback(SyncCallback &&) noexcept = default; + SyncCallback &operator=(SyncCallback &&) noexcept = default; + ~SyncCallback() = default; R operator()(Args... args) const { @@ -138,11 +125,12 @@ class SyncCallback { R call(Args... args) const { - auto wrapper = wrapper_.lock(); + auto jsInvoker = function_.lockInvoker(); + auto function = jsInvoker ? function_.lockJSValue(*runtime_) : nullptr; - // If the wrapper has been deallocated, we can no longer provide a return - // value consistently, so our only option is to throw - if (!wrapper) { + // If the function has been deallocated, we can no longer provide a return + // value consistently, so our only option is to throw. + if (!function) { if constexpr (std::is_void_v) { return; } else { @@ -150,9 +138,10 @@ class SyncCallback { } } - auto &callback = wrapper->callback(); - auto &rt = wrapper->runtime(); - auto jsInvoker = wrapper->jsInvokerPtr(); + // JavaScript can destroy this native callback during the call; use only + // the retained value and local runtime/invoker references afterward. + auto &callback = function->value; + auto &rt = *runtime_; if constexpr (std::is_void_v) { callback.call(rt, bridging::toJs(rt, std::forward(args), jsInvoker)...); @@ -177,8 +166,8 @@ class SyncCallback { return call(std::move(std::get(args))...); } - // Held weakly so lifetime is managed by LongLivedObjectCollection. - std::weak_ptr wrapper_; + jsi::Runtime *runtime_; + LongLivedObjectHandle function_; }; template @@ -191,7 +180,7 @@ struct Bridging> { static jsi::Function toJs(jsi::Runtime &rt, const AsyncCallback &value) { - return value.callback_->function_.getFunction(rt); + return Bridging>::toJs(rt, *value.callback_); } }; @@ -205,7 +194,10 @@ struct Bridging> { static jsi::Function toJs(jsi::Runtime &rt, const SyncCallback &value) { - return value.function_.getFunction(rt); + if (auto function = value.function_.lockJSValue(rt)) { + return function->value.getFunction(rt); + } + throw jsi::JSError(rt, "Failed to get invalidated callback"); } }; diff --git a/packages/react-native/ReactCommon/react/bridging/LongLivedObject.cpp b/packages/react-native/ReactCommon/react/bridging/LongLivedObject.cpp index aa4b1fcdb589..2bd26f35a44d 100644 --- a/packages/react-native/ReactCommon/react/bridging/LongLivedObject.cpp +++ b/packages/react-native/ReactCommon/react/bridging/LongLivedObject.cpp @@ -6,6 +6,8 @@ */ #include "LongLivedObject.h" + +#include #include namespace facebook::react { @@ -35,22 +37,31 @@ void LongLivedObjectCollection::add(std::shared_ptr so) { } void LongLivedObjectCollection::remove(const LongLivedObject* o) { - std::scoped_lock lock(collectionMutex_); - // Empty owner: lookup-only alias, with no ownership or control block. - const std::shared_ptr lookupKey{ - std::shared_ptr{}, const_cast(o)}; + decltype(collection_)::node_type retired; + { + std::scoped_lock lock(collectionMutex_); + // Empty owner: lookup-only alias, with no ownership or control block. + const std::shared_ptr lookupKey{ + std::shared_ptr{}, + // Lookup never dereferences or mutates the object. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + const_cast(o)}; - // A shared_ptr hashes and compares by the pointer it stores, so a - // non-owning shared_ptr to `o` finds its entry without walking the set. - auto entry = collection_.find(lookupKey); - if (entry != collection_.end()) { - collection_.erase(entry); + // A shared_ptr hashes and compares by the pointer it stores, so a + // non-owning shared_ptr to `o` finds its entry without walking the set. + auto entry = collection_.find(lookupKey); + if (entry != collection_.end()) { + retired = collection_.extract(entry); + } } } void LongLivedObjectCollection::clear() { - std::scoped_lock lock(collectionMutex_); - collection_.clear(); + decltype(collection_) retired; + { + std::scoped_lock lock(collectionMutex_); + retired.swap(collection_); + } } size_t LongLivedObjectCollection::size() const { @@ -64,4 +75,22 @@ void LongLivedObject::allowRelease() { LongLivedObjectCollection::get(runtime_).remove(this); } +void detail::releaseLongLivedObjectOnJS( + std::weak_ptr object, + std::weak_ptr jsInvoker) noexcept { + if (object.expired()) { + return; + } + if (auto invoker = jsInvoker.lock()) { + invoker->invokeAsync([object = std::move(object)](jsi::Runtime&) { + if (auto locked = object.lock()) { + locked->allowRelease(); + } + }); + } + // If dispatch is dropped, runtime cleanup releases the collection. Never + // fall back to acquiring or releasing a strong reference on the caller + // thread. +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/bridging/LongLivedObject.h b/packages/react-native/ReactCommon/react/bridging/LongLivedObject.h index 9af43f4598e7..6cb27aeb17ba 100644 --- a/packages/react-native/ReactCommon/react/bridging/LongLivedObject.h +++ b/packages/react-native/ReactCommon/react/bridging/LongLivedObject.h @@ -12,10 +12,14 @@ #include #include #include +#include #include +#include namespace facebook::react { +class CallInvoker; + /** * A simple wrapper class that can be registered to a collection that keep it * alive for extended period of time. This object can be removed from the @@ -23,7 +27,8 @@ namespace facebook::react { * * The subclass of this class must be created using std::make_shared(). * After creation, add it to the `LongLivedObjectCollection`. When done with the - * object, call `allowRelease()` to reclaim its memory. + * object, call `allowRelease()` to reclaim its memory. Prefer + * `LongLivedObjectHandle` for automatic JavaScript-value ownership and release. * * When using LongLivedObject to keep JS values alive, ensure you only hold weak * references to the object outside the JS thread to avoid accessing deallocated @@ -41,6 +46,8 @@ class LongLivedObject { /** * A singleton, thread-safe, write-only collection for the `LongLivedObject`s. + * Clear with exclusive access to the still-live runtime during teardown, even + * when native handles outlive it or queued release tasks cannot execute. */ class LongLivedObjectCollection { public: @@ -60,4 +67,103 @@ class LongLivedObjectCollection { mutable std::mutex collectionMutex_; }; +namespace detail { +void releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; + +// A derived weak pointer can transiently acquire strong ownership when converted. +template +void releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +} // namespace detail + +/** + * Owns a collection registration without retaining its JavaScript value on a + * native thread. Construct and lock on the JavaScript thread; move and destroy + * on any thread. The runtime host owns the call invoker; this handle retains it + * weakly. The collection must be cleared before its runtime is destroyed. + */ +template +class LongLivedObjectHandle { + public: + // All strong references to this holder, including copies, must stay on the JavaScript thread. + struct Holder : LongLivedObject { + Holder(jsi::Runtime &runtime, T value) : LongLivedObject(runtime), value(std::move(value)) {} + + T value; + }; + + LongLivedObjectHandle() = default; + + LongLivedObjectHandle(jsi::Runtime &runtime, T value, std::shared_ptr jsInvoker) + { + if (!jsInvoker) { + throw jsi::JSError(runtime, "LongLivedObjectHandle requires a call invoker"); + } + jsThread_ = std::this_thread::get_id(); + jsInvoker_ = jsInvoker; + auto holder = std::make_shared(runtime, std::move(value)); + LongLivedObjectCollection::get(runtime).add(holder); + holder_ = holder; + } + + LongLivedObjectHandle(const LongLivedObjectHandle &) = delete; + LongLivedObjectHandle &operator=(const LongLivedObjectHandle &) = delete; + LongLivedObjectHandle(LongLivedObjectHandle &&) noexcept = default; + + LongLivedObjectHandle &operator=(LongLivedObjectHandle &&other) noexcept + { + if (this != &other) { + reset(); + holder_ = std::move(other.holder_); + jsInvoker_ = std::move(other.jsInvoker_); + jsThread_ = std::exchange(other.jsThread_, {}); + } + return *this; + } + + ~LongLivedObjectHandle() + { + reset(); + } + + bool expired() const noexcept + { + return holder_.expired(); + } + + std::shared_ptr lockInvoker() const noexcept + { + return holder_.expired() ? nullptr : jsInvoker_.lock(); + } + + // Require this handle's runtime as proof that the caller has access to it; + // runtime identity is not checked. Keep the returned strong reference on the + // JavaScript thread, including its destruction. + std::shared_ptr lockJSValue(jsi::Runtime & /*runtime*/) const noexcept + { + return std::static_pointer_cast(holder_.lock()); + } + + void reset() noexcept + { + auto holder = std::exchange(holder_, {}); + auto jsInvoker = std::exchange(jsInvoker_, {}); + auto jsThread = std::exchange(jsThread_, {}); + if (std::this_thread::get_id() == jsThread) { + if (auto locked = holder.lock()) { + locked->allowRelease(); + } + } else { + detail::releaseLongLivedObjectOnJS(std::move(holder), std::move(jsInvoker)); + } + } + + private: + // Converting weak pointers can promote the object; keep the helper's base type + // so off-thread release only moves weak references. + std::weak_ptr holder_; + std::weak_ptr jsInvoker_; + // Cache affinity without promoting the JavaScript-value holder on a native thread. + std::thread::id jsThread_; +}; + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/bridging/Promise.h b/packages/react-native/ReactCommon/react/bridging/Promise.h index cdd023ad9549..ce3c3969ed6c 100644 --- a/packages/react-native/ReactCommon/react/bridging/Promise.h +++ b/packages/react-native/ReactCommon/react/bridging/Promise.h @@ -39,61 +39,52 @@ class AsyncPromise { }, jsInvoker)); - auto promiseHolder = std::make_shared(rt, promise.asObject(rt)); - LongLivedObjectCollection::get(rt).add(promiseHolder); - - // The shared state can retain the promise holder weakly now. - state_->promiseHolder = promiseHolder; + state_->promiseHandle = LongLivedObjectHandle(rt, promise.asObject(rt), jsInvoker); } void resolve(T... value) { - std::lock_guard lock(state_->mutex); + std::optional> resolve; + std::optional> reject; + { + std::lock_guard lock(state_->mutex); + resolve = std::exchange(state_->resolve, std::nullopt); + reject = std::exchange(state_->reject, std::nullopt); + } - if (state_->resolve) { - state_->resolve->call(std::forward(value)...); - state_->resolve.reset(); - state_->reject.reset(); + if (resolve) { + resolve->call(std::forward(value)...); } } void reject(Error error) { - std::lock_guard lock(state_->mutex); + std::optional> resolve; + std::optional> reject; + { + std::lock_guard lock(state_->mutex); + resolve = std::exchange(state_->resolve, std::nullopt); + reject = std::exchange(state_->reject, std::nullopt); + } - if (state_->reject) { - state_->reject->call(std::move(error)); - state_->reject.reset(); - state_->resolve.reset(); + if (reject) { + reject->call(std::move(error)); } } jsi::Object get(jsi::Runtime &rt) const { - if (auto holder = state_->promiseHolder.lock()) { - return jsi::Value(rt, holder->promise).asObject(rt); + if (auto holder = state_->promiseHandle.lockJSValue(rt)) { + return jsi::Value(rt, holder->value).asObject(rt); } else { throw jsi::JSError(rt, "Failed to get invalidated promise"); } } private: - struct PromiseHolder : LongLivedObject { - PromiseHolder(jsi::Runtime &runtime, jsi::Object p) : LongLivedObject(runtime), promise(std::move(p)) {} - - jsi::Object promise; - }; - struct SharedState { - ~SharedState() - { - if (auto holder = promiseHolder.lock()) { - holder->allowRelease(); - } - } - std::mutex mutex; - std::weak_ptr promiseHolder; + LongLivedObjectHandle promiseHandle; std::optional> resolve; std::optional> reject; }; diff --git a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp index 0178c7904e83..cb0b08b98783 100644 --- a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp +++ b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp @@ -5,8 +5,16 @@ * LICENSE file in the root directory of this source tree. */ +#include #include +#include +#include +#include +#include +#include +#include #include +#include #include #include "BridgingTest.h" @@ -1038,4 +1046,906 @@ TEST_F(BridgingTest, highResTimeStampTest) { 1.000001, bridging::toJs(rt, HighResDuration::fromNanoseconds(1e6 + 1))); } +namespace { + +template +constexpr bool canReleaseLongLivedObjectOnJS = + requires(std::weak_ptr object, std::weak_ptr invoker) { + detail::releaseLongLivedObjectOnJS(std::move(object), std::move(invoker)); + }; + +static_assert(canReleaseLongLivedObjectOnJS); +static_assert(!canReleaseLongLivedObjectOnJS< + LongLivedObjectHandle::Holder>); + +// `TestCallInvoker` keeps its queue in a bare `std::list`, so it cannot be used +// by the tests below, which schedule work from worker threads. Jobs are taken +// out of the queue under the lock and run (and destroyed) outside it, so a +// release scheduled while the queue drains is picked up by the same flush. +class ConcurrentTestCallInvoker : public CallInvoker { + public: + explicit ConcurrentTestCallInvoker(jsi::Runtime& runtime) + : runtime_(runtime) {} + + void invokeAsync(CallFunc&& func) noexcept override { + std::unique_lock lock(mutex_); + ++dispatchCount_; + dispatched_.notify_all(); + gateOpened_.wait(lock, [this] { return gateOpen_; }); + queue_.push_back(std::move(func)); + } + + void invokeAsync(SchedulerPriority priority, CallFunc&& func) noexcept + override { + { + std::scoped_lock lock(mutex_); + lastPriority_ = priority; + } + invokeAsync(std::move(func)); + } + + void invokeSync(CallFunc&& func) override { + func(runtime_); + } + + void flushQueue() { + while (auto job = takeNextJob()) { + (*job)(runtime_); + job.reset(); + runtime_.drainMicrotasks(); + } + } + + // Throws queued work away without running it, the way a JavaScript queue + // torn down at virtual machine shutdown would. + void discardQueue() { + while (auto job = takeNextJob()) { + job.reset(); + } + } + + size_t queueSize() { + std::scoped_lock lock(mutex_); + return queue_.size(); + } + + std::optional lastPriority() { + std::scoped_lock lock(mutex_); + return lastPriority_; + } + + // Parks every dispatching thread inside `invokeAsync` until `openGate()`, + // which makes the window between "the callback is still alive" and "the task + // is queued" a deterministic point to interleave the JavaScript thread with. + void closeGate() { + std::scoped_lock lock(mutex_); + gateOpen_ = false; + } + + void openGate() { + std::scoped_lock lock(mutex_); + gateOpen_ = true; + gateOpened_.notify_all(); + } + + void waitForDispatch(size_t count) { + std::unique_lock lock(mutex_); + dispatched_.wait(lock, [&] { return dispatchCount_ >= count; }); + } + + std::optional takeNextJob() { + std::scoped_lock lock(mutex_); + if (queue_.empty()) { + return std::nullopt; + } + auto job = std::move(queue_.front()); + queue_.pop_front(); + return job; + } + + private: + jsi::Runtime& runtime_; + std::mutex mutex_; + std::condition_variable gateOpened_; + std::condition_variable dispatched_; + std::list queue_; + std::optional lastPriority_; + size_t dispatchCount_{0}; + bool gateOpen_{true}; +}; + +// Where and how often a value owned by a long lived object was destroyed. +struct ReleaseRecord { + void record() { + std::scoped_lock lock(mutex); + ++count; + thread = std::this_thread::get_id(); + } + + int releaseCount() { + std::scoped_lock lock(mutex); + return count; + } + + std::thread::id releaseThread() { + std::scoped_lock lock(mutex); + return thread; + } + + std::mutex mutex; + int count{0}; + std::thread::id thread{}; +}; + +// A payload that reports its own destruction. Moving it hands the report over, +// so a moved-from temporary never counts as a release. +class ProbePayload { + public: + explicit ProbePayload(std::function onDestroy) + : onDestroy_(std::move(onDestroy)) {} + + ProbePayload(ProbePayload&& other) noexcept + : onDestroy_(std::exchange(other.onDestroy_, nullptr)) {} + + ProbePayload& operator=(ProbePayload&&) = delete; + + ProbePayload(const ProbePayload&) = delete; + ProbePayload& operator=(const ProbePayload&) = delete; + + ~ProbePayload() { + if (onDestroy_) { + onDestroy_(); + } + } + + private: + std::function onDestroy_; +}; + +ProbePayload recordingPayload(const std::shared_ptr& record) { + return ProbePayload([record] { record->record(); }); +} + +} // namespace + +TEST_F(BridgingTest, syncCallbackDestroyedOffJsThreadReleasesOnJsThread) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + + { + // Kept on the heap so that a cleanup job reaching back into the destroyed + // native owner is a use-after-free the sanitizers report, rather than a + // read of stack storage that is still mapped. + auto callback = std::make_unique>( + rt, function("() => {}"), jsInvoker); + EXPECT_EQ(1, collection.size()); + + std::thread worker([&] { callback.reset(); }); + worker.join(); + + // Dropping the last native owner away from the JavaScript thread must not + // reach into the collection: the function stays rooted until a JavaScript + // task releases it. + EXPECT_EQ(1, collection.size()); + } + + jsInvoker->flushQueue(); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, syncCallbackMoveAssignmentReleasesReplacedCallback) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + + { + SyncCallback callback( + rt, function("() => 'first'"), jsInvoker); + + { + SyncCallback replacement( + rt, function("() => 'second'"), jsInvoker); + EXPECT_EQ(2, collection.size()); + + callback = std::move(replacement); + + // On the JavaScript thread the replaced root goes immediately and no + // cleanup job is left behind. + EXPECT_EQ(1, collection.size()); + EXPECT_EQ(0, jsInvoker->queueSize()); + EXPECT_EQ("second"s, callback()); + } + + // Destroying the moved-from source must not release what it handed over. + EXPECT_EQ(1, collection.size()); + EXPECT_EQ("second"s, callback()); + + // Assignment resets the destination before taking ownership, so self + // assignment has to be guarded. + auto& alias = callback; + callback = std::move(alias); + EXPECT_EQ(1, collection.size()); + EXPECT_EQ("second"s, callback()); + } + + EXPECT_EQ(0, jsInvoker->queueSize()); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, asyncCallbackClearedDuringDispatchNeverCallsJs) { + enum class Path { + Arguments, + ArgumentsWithPriority, + Function, + FunctionWithPriority, + }; + + for (auto path : + {Path::Arguments, + Path::ArgumentsWithPriority, + Path::Function, + Path::FunctionWithPriority}) { + auto jsInvoker = std::make_shared(rt); + std::string output; + auto nativeFunc = std::function( + [&](auto str) { output = std::move(str); }); + + { + auto callback = + bridging::fromJs>( + rt, function("(func, str) => func(str)"), jsInvoker); + + auto callImpl = [nativeFunc, jsInvoker]( + jsi::Runtime& runtime, jsi::Function& jsFunc) { + jsFunc.call( + runtime, + bridging::toJs(runtime, nativeFunc, jsInvoker), + bridging::toJs(runtime, "called", jsInvoker)); + }; + + jsInvoker->closeGate(); + + std::thread worker([&] { + switch (path) { + case Path::Arguments: + callback(nativeFunc, "called"); + break; + case Path::ArgumentsWithPriority: + callback.callWithPriority( + SchedulerPriority::IdlePriority, nativeFunc, "called"); + break; + case Path::Function: + callback.call(callImpl); + break; + case Path::FunctionWithPriority: + callback.callWithPriority( + SchedulerPriority::IdlePriority, callImpl); + break; + } + }); + + // The dispatching thread is now parked inside the invoker, past the + // point where it found the callback alive. + jsInvoker->waitForDispatch(1); + LongLivedObjectCollection::get(rt).clear(); + jsInvoker->openGate(); + worker.join(); + + jsInvoker->flushQueue(); + EXPECT_EQ(""s, output) << "dispatch path " << static_cast(path); + } + + jsInvoker->flushQueue(); + } +} + +TEST_F(BridgingTest, asyncCallbackDispatchAfterInvalidationEnqueuesNothing) { + auto jsInvoker = std::make_shared(rt); + std::string output; + auto nativeFunc = std::function( + [&](auto str) { output = std::move(str); }); + + { + auto callback = + bridging::fromJs>( + rt, function("(func, str) => func(str)"), jsInvoker); + + auto callImpl = [nativeFunc, jsInvoker]( + jsi::Runtime& runtime, jsi::Function& jsFunc) { + jsFunc.call( + runtime, + bridging::toJs(runtime, nativeFunc, jsInvoker), + bridging::toJs(runtime, "called", jsInvoker)); + }; + + LongLivedObjectCollection::get(rt).clear(); + + callback(nativeFunc, "called"); + callback.callWithPriority( + SchedulerPriority::IdlePriority, nativeFunc, "called"); + callback.call(callImpl); + callback.callWithPriority(SchedulerPriority::IdlePriority, callImpl); + + EXPECT_EQ(0, jsInvoker->queueSize()); + } + + jsInvoker->flushQueue(); + EXPECT_EQ(""s, output); +} + +TEST_F(BridgingTest, asyncCallbackQueuedInvocationOutlivesItsOwner) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + std::string output; + auto nativeFunc = std::function( + [&](auto str) { output = std::move(str); }); + + { + auto callback = + bridging::fromJs>( + rt, function("(func, str) => func(str)"), jsInvoker); + callback.callWithPriority( + SchedulerPriority::IdlePriority, nativeFunc, "late"); + } + + // The queued task owns the callback state, so dropping every public owner + // before it runs neither releases the function nor schedules a release. + EXPECT_EQ(SchedulerPriority::IdlePriority, jsInvoker->lastPriority()); + EXPECT_EQ(1, jsInvoker->queueSize()); + EXPECT_EQ(1, collection.size()); + + jsInvoker->flushQueue(); + EXPECT_EQ("late"s, output); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, asyncPromiseDroppedOffJsThreadReleasesOnJsThread) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + + { + auto promise = std::make_optional>(rt, jsInvoker); + const auto rooted = collection.size(); + EXPECT_GT(rooted, 0u); + + std::thread worker([&] { promise.reset(); }); + worker.join(); + + EXPECT_EQ(rooted, collection.size()); + } + + jsInvoker->flushQueue(); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, asyncPromiseSettlementSurvivesBeingDroppedOffJsThread) { + auto observe = function( + "(promise, obj) => {" + " promise.then(" + " (res) => { obj.res = res; }," + " (err) => { obj.err = err.message; }" + " )" + "}"); + + for (bool rejects : {false, true}) { + auto jsInvoker = std::make_shared(rt); + auto output = jsi::Object(rt); + + { + auto promise = std::make_optional>(rt, jsInvoker); + observe.call(rt, bridging::toJs(rt, *promise, jsInvoker), output); + + if (rejects) { + promise->reject("boom"); + } else { + promise->resolve(42); + } + + // Every public copy is dropped away from the JavaScript thread while + // the settlement is still queued. + std::thread worker([&] { promise.reset(); }); + worker.join(); + } + + jsInvoker->flushQueue(); + + if (rejects) { + ASSERT_TRUE(output.getProperty(rt, "err").isString()); + EXPECT_EQ("boom"s, output.getProperty(rt, "err").asString(rt).utf8(rt)); + } else { + ASSERT_TRUE(output.getProperty(rt, "res").isNumber()); + EXPECT_EQ(42, output.getProperty(rt, "res").asNumber()); + } + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); + } +} + +TEST_F(BridgingTest, asyncPromiseCopiesShareOneSettlement) { + auto jsInvoker = std::make_shared(rt); + auto observe = function( + "(promise, obj) => {" + " obj.count = 0;" + " promise.then(" + " (res) => { obj.count++; obj.res = res; }," + " () => { obj.count++; obj.rejected = true; }" + " )" + "}"); + auto output = jsi::Object(rt); + + { + auto promise = AsyncPromise(rt, jsInvoker); + observe.call(rt, bridging::toJs(rt, promise, jsInvoker), output); + + { + auto copy = promise; + copy.resolve(1); + } + + // Dropping a copy leaves the shared state, and only the first settlement + // counts. + EXPECT_NO_THROW(promise.get(rt)); + EXPECT_NO_THROW(promise.resolve(2)); + EXPECT_NO_THROW(promise.reject("ignored")); + } + + jsInvoker->flushQueue(); + + EXPECT_EQ(1, output.getProperty(rt, "count").asNumber()); + EXPECT_EQ(1, output.getProperty(rt, "res").asNumber()); + EXPECT_FALSE(output.hasProperty(rt, "rejected")); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, asyncPromiseGetFailsAfterInvalidation) { + auto jsInvoker = std::make_shared(rt); + + { + auto promise = AsyncPromise(rt, jsInvoker); + EXPECT_NO_THROW(promise.get(rt)); + + LongLivedObjectCollection::get(rt).clear(); + + EXPECT_JSI_THROW(promise.get(rt)); + EXPECT_NO_THROW(promise.resolve(1)); + EXPECT_NO_THROW(promise.reject("ignored")); + EXPECT_EQ(0, jsInvoker->queueSize()); + } + + jsInvoker->flushQueue(); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, longLivedObjectHandleReleasesPayloadOnJsThreadOnce) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + auto record = std::make_shared(); + + { + LongLivedObjectHandle handle( + rt, recordingPayload(record), jsInvoker); + EXPECT_EQ(1, collection.size()); + + std::thread worker([&] { + handle.reset(); + handle.reset(); + }); + worker.join(); + EXPECT_EQ(1, jsInvoker->queueSize()); + + // The payload belongs to the collection, so nothing is destroyed on the + // worker thread however often the handle is reset. + EXPECT_EQ(0, record->releaseCount()); + EXPECT_EQ(1, collection.size()); + } + + jsInvoker->flushQueue(); + + EXPECT_EQ(1, record->releaseCount()); + EXPECT_EQ(std::this_thread::get_id(), record->releaseThread()); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, longLivedObjectHandleAccessExpiresWithItsRoot) { + auto jsInvoker = std::make_shared(rt); + + LongLivedObjectHandle empty; + EXPECT_TRUE(empty.expired()); + EXPECT_EQ(nullptr, empty.lockInvoker()); + EXPECT_FALSE(static_cast(empty.lockJSValue(rt))); + + { + LongLivedObjectHandle handle(rt, "payload"s, jsInvoker); + EXPECT_FALSE(handle.expired()); + EXPECT_EQ( + static_cast(jsInvoker.get()), handle.lockInvoker().get()); + + { + auto locked = handle.lockJSValue(rt); + ASSERT_TRUE(locked); + locked->value += "!"; + } + + { + // The payload lives in the collection rather than in the handle, so the + // mutation is visible through a second lock. + auto locked = handle.lockJSValue(rt); + ASSERT_TRUE(locked); + EXPECT_EQ("payload!"s, locked->value); + } + + LongLivedObjectCollection::get(rt).clear(); + + // An invoker retained elsewhere must not allow dispatch through an + // invalidated handle. + EXPECT_TRUE(handle.expired()); + EXPECT_EQ(nullptr, handle.lockInvoker()); + EXPECT_FALSE(static_cast(handle.lockJSValue(rt))); + } + + jsInvoker->flushQueue(); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, longLivedObjectHandleDestroyedOnJsThreadReleasesInline) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + auto record = std::make_shared(); + + { + LongLivedObjectHandle handle( + rt, recordingPayload(record), jsInvoker); + EXPECT_EQ(1, collection.size()); + + handle.reset(); + + // Released from the thread that built it, so the root and the value go + // inline and the queue never sees a cleanup job. + EXPECT_EQ(0, jsInvoker->queueSize()); + EXPECT_EQ(1, record->releaseCount()); + EXPECT_EQ(std::this_thread::get_id(), record->releaseThread()); + EXPECT_EQ(0, collection.size()); + } + + // Destroying the emptied handle schedules nothing either. + EXPECT_EQ(0, jsInvoker->queueSize()); + EXPECT_EQ(1, record->releaseCount()); +} + +TEST_F(BridgingTest, longLivedObjectHandleReleaseTaskHoldsNoStrongReference) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + auto record = std::make_shared(); + + { + auto handle = std::make_optional>( + rt, recordingPayload(record), jsInvoker); + + // Only a handle dropped away from the JavaScript thread leaves a queued + // cleanup job to inspect. + std::thread owner([&] { handle.reset(); }); + owner.join(); + } + EXPECT_EQ(1, jsInvoker->queueSize()); + + // A queue that is thrown away destroys its tasks on whichever thread tears + // it down, so a scheduled release must not own the payload. + std::thread worker([&] { jsInvoker->discardQueue(); }); + worker.join(); + + EXPECT_EQ(0, record->releaseCount()); + EXPECT_EQ(1, collection.size()); + + collection.clear(); + + EXPECT_EQ(1, record->releaseCount()); + EXPECT_EQ(std::this_thread::get_id(), record->releaseThread()); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, longLivedObjectCollectionClearDestroysPayloadsUnlocked) { + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + int reentrantClears = 0; + + { + LongLivedObjectHandle handle( + rt, + ProbePayload([&] { + ++reentrantClears; + LongLivedObjectCollection::get(rt).clear(); + }), + jsInvoker); + + // A payload destructor is allowed to reach back into the collection, which + // it cannot do if clear() still holds the lock while destroying. + collection.clear(); + } + + EXPECT_EQ(1, reentrantClears); + EXPECT_EQ(0, collection.size()); + + jsInvoker->flushQueue(); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, asyncCallbackRuntimeDestroyedDuringDispatch) { + for (bool customCall : {false, true}) { + auto standaloneRuntime = hermes::makeHermesRuntime(); + auto jsInvoker = + std::make_shared(*standaloneRuntime); + auto callback = std::make_optional>( + *standaloneRuntime, + jsi::Function::createFromHostFunction( + *standaloneRuntime, + jsi::PropNameID::forAscii(*standaloneRuntime, "callback"), + 1, + [](jsi::Runtime&, const jsi::Value&, const jsi::Value*, size_t) { + return jsi::Value::undefined(); + }), + jsInvoker); + + jsInvoker->closeGate(); + std::thread worker([&] { + if (customCall) { + callback->call([](jsi::Runtime& runtime, jsi::Function& function) { + function.call(runtime, 1); + }); + } else { + callback->call(1); + } + }); + + jsInvoker->waitForDispatch(1); + // A native-side strong reference would survive clear() while dispatch is + // parked; address sanitizer detects its invalidation after runtime + // disposal. + LongLivedObjectCollection::get(*standaloneRuntime).clear(); + standaloneRuntime.reset(); + jsInvoker->openGate(); + worker.join(); + EXPECT_EQ(1, jsInvoker->queueSize()); + + callback.reset(); + std::thread discard([&] { jsInvoker->discardQueue(); }); + discard.join(); + EXPECT_EQ(0, jsInvoker->queueSize()); + } +} + +TEST_F(BridgingTest, callbackRoundTripPreservesFunctionIdentity) { + auto original = function("(value) => value + 1"); + auto sync = bridging::fromJs>( + rt, original.getFunction(rt), invoker); + auto async = bridging::fromJs>( + rt, original.getFunction(rt), invoker); + + EXPECT_TRUE( + jsi::Object::strictEquals( + rt, original, Bridging::toJs(rt, sync))); + EXPECT_TRUE( + jsi::Object::strictEquals( + rt, original, Bridging::toJs(rt, async))); + + LongLivedObjectCollection::get(rt).clear(); + EXPECT_JSI_THROW(Bridging::toJs(rt, sync)); + EXPECT_JSI_THROW(Bridging::toJs(rt, async)); +} + +TEST_F(BridgingTest, longLivedObjectHandleRejectsMissingInvoker) { + EXPECT_JSI_THROW( + LongLivedObjectHandle(rt, jsi::Object(rt), nullptr)); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, syncCallbackMoveConstructionTransfersOwnership) { + { + auto source = std::make_optional>( + rt, function("() => 'moved'"), invoker); + auto moved = std::move(*source); + source.reset(); + flushQueue(); + EXPECT_EQ(1, LongLivedObjectCollection::get(rt).size()); + EXPECT_EQ("moved"s, moved()); + } + flushQueue(); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, syncCallbackCanDestroyItsNativeOwnerDuringCall) { + // Heap allocated so that touching a member after the owner is gone is a + // use-after-free the sanitizers report, not a read of live stack storage. + std::unique_ptr> callback; + auto destroyOwner = std::function([&] { + callback.reset(); + return 42; + }); + callback = std::make_unique>( + rt, bridging::toJs(rt, destroyOwner, invoker), invoker); + + EXPECT_EQ(42, callback->call()); + EXPECT_EQ(nullptr, callback); + flushQueue(); + EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size()); +} + +TEST_F(BridgingTest, longLivedObjectCollectionRemoveDestroysPayloadsUnlocked) { + struct ReentrantObject : LongLivedObject { + ReentrantObject(jsi::Runtime& runtime, int& releases) + : LongLivedObject(runtime), releases_(releases) {} + + ~ReentrantObject() override { + ++releases_; + LongLivedObjectCollection::get(runtime_).clear(); + } + + int& releases_; + }; + + auto& collection = LongLivedObjectCollection::get(rt); + int releases = 0; + auto object = std::make_shared(rt, releases); + auto address = object.get(); + collection.add(std::move(object)); + collection.remove(address); + + EXPECT_EQ(1, releases); + EXPECT_EQ(0, collection.size()); +} + +TEST_F(BridgingTest, asyncCallbackInvalidationReleasesQueuedArguments) { + for (bool discard : {false, true}) { + auto jsInvoker = std::make_shared(rt); + auto lifetime = std::make_shared(1); + std::weak_ptr weakLifetime = lifetime; + auto argument = + std::function([lifetime] { EXPECT_EQ(1, *lifetime); }); + auto callback = AsyncCallback( + rt, function("(callback, value) => callback()"), jsInvoker); + + callback.callWithPriority( + SchedulerPriority::IdlePriority, argument, "queued"); + argument = {}; + lifetime.reset(); + EXPECT_FALSE(weakLifetime.expired()); + EXPECT_EQ(1, jsInvoker->queueSize()); + + LongLivedObjectCollection::get(rt).clear(); + if (discard) { + std::thread worker([&] { jsInvoker->discardQueue(); }); + worker.join(); + } else { + jsInvoker->flushQueue(); + } + EXPECT_TRUE(weakLifetime.expired()); + EXPECT_EQ(0, jsInvoker->queueSize()); + } +} + +TEST_F(BridgingTest, longLivedObjectHandleDoesNotRetainInvoker) { + auto jsInvoker = std::make_shared(rt); + std::weak_ptr weakInvoker = jsInvoker; + auto record = std::make_shared(); + LongLivedObjectHandle handle( + rt, recordingPayload(record), jsInvoker); + + jsInvoker.reset(); + EXPECT_TRUE(weakInvoker.expired()); + EXPECT_FALSE(handle.expired()); + + std::thread worker([&] { handle.reset(); }); + worker.join(); + EXPECT_EQ(0, record->releaseCount()); + + LongLivedObjectCollection::get(rt).clear(); + EXPECT_EQ(1, record->releaseCount()); + EXPECT_EQ(std::this_thread::get_id(), record->releaseThread()); +} + +TEST_F(BridgingTest, asyncCallbackQueuedCallStopsWhenInvokerExpires) { + auto jsInvoker = std::make_shared(rt); + std::weak_ptr weakInvoker = jsInvoker; + int calls = 0; + auto nativeCallback = std::function([&] { ++calls; }); + auto callback = AsyncCallback( + rt, function("(callback) => callback()"), jsInvoker); + callback.call(nativeCallback); + + auto queued = jsInvoker->takeNextJob(); + ASSERT_TRUE(queued.has_value()); + jsInvoker.reset(); + ASSERT_TRUE(weakInvoker.expired()); + + EXPECT_NO_THROW((*queued)(rt)); + EXPECT_EQ(0, calls); +} + +TEST_F(BridgingTest, asyncCallbackQueuedCustomCallDoesNotRequireInvoker) { + auto jsInvoker = std::make_shared(rt); + std::weak_ptr weakInvoker = jsInvoker; + int calls = 0; + auto callback = AsyncCallback<>(rt, function("() => {}"), jsInvoker); + callback.call([&](jsi::Runtime&, jsi::Function&) { ++calls; }); + + auto queued = jsInvoker->takeNextJob(); + ASSERT_TRUE(queued.has_value()); + jsInvoker.reset(); + ASSERT_TRUE(weakInvoker.expired()); + + (*queued)(rt); + EXPECT_EQ(1, calls); +} + +TEST_F(BridgingTest, syncCallbackStopsWhenInvokerExpires) { + auto jsInvoker = std::make_shared(rt); + SyncCallback callback(rt, function("() => 42"), jsInvoker); + + jsInvoker.reset(); + EXPECT_THROW(callback.call(), std::runtime_error); +} + +TEST_F(BridgingTest, asyncCallbackUsesItsStoredRuntime) { + auto otherRuntime = hermes::makeHermesRuntime(); + for (bool customCall : {false, true}) { + auto jsInvoker = std::make_shared(rt); + int calls = 0; + auto nativeCallback = std::function([&] { ++calls; }); + auto callback = AsyncCallback( + rt, function("(callback) => callback()"), jsInvoker); + + if (customCall) { + callback.call([&](jsi::Runtime& runtime, jsi::Function& function) { + EXPECT_EQ(&rt, &runtime); + function.call( + runtime, bridging::toJs(runtime, nativeCallback, jsInvoker)); + }); + } else { + callback.call(nativeCallback); + } + + auto queued = jsInvoker->takeNextJob(); + ASSERT_TRUE(queued.has_value()); + (*queued)(*otherRuntime); + EXPECT_EQ(1, calls); + } +} + +TEST_F(BridgingTest, longLivedObjectHandleResetRacesRuntimeCleanup) { + constexpr size_t iterations = 10000; + auto jsInvoker = std::make_shared(rt); + auto& collection = LongLivedObjectCollection::get(rt); + std::optional> handle; + std::barrier phase(2); + + std::thread worker([&] { + for (size_t iteration = 0; iteration < iterations; ++iteration) { + phase.arrive_and_wait(); + handle->reset(); + phase.arrive_and_wait(); + } + }); + + size_t wrongReleaseCount = 0; + size_t wrongReleaseThread = 0; + for (size_t iteration = 0; iteration < iterations; ++iteration) { + auto record = std::make_shared(); + handle.emplace(rt, recordingPayload(record), jsInvoker); + + // Repeatedly overlap teardown with native release to cover short-lived + // strong references hidden inside converting weak-pointer constructors. + phase.arrive_and_wait(); + collection.clear(); + phase.arrive_and_wait(); + + wrongReleaseCount += static_cast(record->releaseCount() != 1); + wrongReleaseThread += static_cast( + record->releaseThread() != std::this_thread::get_id()); + jsInvoker->discardQueue(); + } + worker.join(); + + EXPECT_EQ(0, wrongReleaseCount); + EXPECT_EQ(0, wrongReleaseThread); + EXPECT_EQ(0, collection.size()); + EXPECT_EQ(0, jsInvoker->queueSize()); +} + } // namespace facebook::react diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 675923cec7c0..9006213bd524 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -4149,10 +4149,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -4208,6 +4208,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -5402,6 +5422,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index 0d1f0fd94a91..90a6070cc531 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -7272,10 +7272,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -7338,6 +7338,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -8381,8 +8401,11 @@ template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 4b15d66312e8..bff627286ab7 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -4142,10 +4142,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -4201,6 +4201,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -5251,6 +5271,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 4b15d66312e8..bff627286ab7 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -4142,10 +4142,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -4201,6 +4201,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -5251,6 +5271,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 101ef554362f..44b434799ab1 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -6019,10 +6019,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -6060,6 +6060,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -7134,6 +7154,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api index e89ec1a6c6df..82124a4087bc 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api @@ -9157,10 +9157,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -9205,6 +9205,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -10211,8 +10231,11 @@ template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index a396e261d86c..3c6f1c033e70 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -6005,10 +6005,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -6046,6 +6046,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -6986,6 +7006,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 1255a7730e0a..badac943e3e9 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -6012,10 +6012,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -6053,6 +6053,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template class facebook::react::NativeAccessibilityInfoCxxSpec : public facebook::react::TurboModule { protected NativeAccessibilityInfoCxxSpec(std::shared_ptr jsInvoker); @@ -6993,6 +7013,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 507e71d72698..870be9eb0ec4 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -2197,10 +2197,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -2238,6 +2238,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template struct facebook::react::CascadedRectangleCorners { public bool operator==(const facebook::react::CascadedRectangleCorners& rhs) const = default; @@ -2648,6 +2668,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api index 2772d8e63b9e..894eb8cb1f42 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api @@ -5069,10 +5069,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -5117,6 +5117,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template struct facebook::react::AnimatedProp : public facebook::react::AnimatedPropBase { public AnimatedProp() = default; @@ -5600,8 +5620,11 @@ template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 004abd9081df..2b9c7135b886 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -2190,10 +2190,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -2231,6 +2231,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template struct facebook::react::CascadedRectangleCorners { public bool operator==(const facebook::react::CascadedRectangleCorners& rhs) const = default; @@ -2641,6 +2661,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 004abd9081df..2b9c7135b886 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -2190,10 +2190,10 @@ class facebook::react::SyncCallback { public R operator()(Args... args) const; public SyncCallback(const facebook::react::SyncCallback&) = delete; public SyncCallback(facebook::jsi::Runtime& rt, facebook::jsi::Function function, std::shared_ptr jsInvoker); - public SyncCallback(facebook::react::SyncCallback&& other) noexcept; + public SyncCallback(facebook::react::SyncCallback&&) noexcept = default; public facebook::react::SyncCallback& operator=(const facebook::react::SyncCallback&) = delete; - public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&& other) noexcept; - public ~SyncCallback(); + public facebook::react::SyncCallback& operator=(facebook::react::SyncCallback&&) noexcept = default; + public ~SyncCallback() = default; } template @@ -2231,6 +2231,26 @@ template struct facebook::react::Bridging { } +template +class facebook::react::LongLivedObjectHandle { + public LongLivedObjectHandle() = default; + public LongLivedObjectHandle(const facebook::react::LongLivedObjectHandle&) = delete; + public LongLivedObjectHandle(facebook::jsi::Runtime& runtime, T value, std::shared_ptr jsInvoker); + public LongLivedObjectHandle(facebook::react::LongLivedObjectHandle&&) noexcept = default; + public bool expired() const noexcept; + public facebook::react::LongLivedObjectHandle& operator=(const facebook::react::LongLivedObjectHandle&) = delete; + public facebook::react::LongLivedObjectHandle& operator=(facebook::react::LongLivedObjectHandle&& other) noexcept; + public std::shared_ptr lockInvoker() const noexcept; + public std::shared_ptr lockJSValue(facebook::jsi::Runtime&) const noexcept; + public void reset() noexcept; + public ~LongLivedObjectHandle(); +} + +struct facebook::react::LongLivedObjectHandle::Holder : public facebook::react::LongLivedObject { + public Holder(facebook::jsi::Runtime& runtime, T value); + public T value; +} + template struct facebook::react::CascadedRectangleCorners { public bool operator==(const facebook::react::CascadedRectangleCorners& rhs) const = default; @@ -2641,6 +2661,9 @@ struct facebook::react::array_detail::BridgingDynamic { template C facebook::react::detail::memberFunctionClass(T C::*); +template +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr, std::weak_ptr) = delete; +void facebook::react::detail::releaseLongLivedObjectOnJS(std::weak_ptr object, std::weak_ptr jsInvoker) noexcept; class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept;