/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "NativeIdleCallbacks.h" #include #include #include #include #include #ifdef RN_DISABLE_OSS_PLUGIN_HEADER #include "Plugins.h" #endif std::shared_ptr NativeIdleCallbacksModuleProvider( std::shared_ptr jsInvoker) { return std::make_shared( std::move(jsInvoker)); } namespace facebook::react { namespace { class IdleTaskRef : public jsi::NativeState { public: IdleTaskRef(std::shared_ptr task) : task(std::move(task)) {} std::shared_ptr task; }; jsi::Function makeTimeRemainingFunction( jsi::Runtime& runtime, std::shared_ptr runtimeScheduler, RuntimeSchedulerTimePoint deadline) { return jsi::Function::createFromHostFunction( runtime, jsi::PropNameID::forAscii(runtime, "timeRemaining"), 0, [runtimeScheduler, deadline, expired = false]( jsi::Runtime& runtime, const jsi::Value& /* unused */, const jsi::Value* /* unused */, size_t /* unused */) mutable { double remainingTime = 0; // No need to access the runtime scheduler if this idle callback expired // already. if (!expired) { if (runtimeScheduler->getShouldYield()) { expired = true; } else { auto now = runtimeScheduler->now(); remainingTime = std::max( static_cast( std::chrono::duration_cast( deadline - now) .count()), 0.0); if (remainingTime == 0) { expired = true; } } } return jsi::Value(runtime, remainingTime); }); } } // namespace NativeIdleCallbacks::NativeIdleCallbacks(std::shared_ptr jsInvoker) : NativeIdleCallbacksCxxSpec(std::move(jsInvoker)) {} CallbackHandle NativeIdleCallbacks::requestIdleCallback( jsi::Runtime& runtime, SyncCallback&& userCallback, std::optional options) { auto binding = RuntimeSchedulerBinding::getBinding(runtime); auto runtimeScheduler = binding->getRuntimeScheduler(); // handle timeout parameter std::optional timeout; std::optional expirationTime; if (options.has_value() && options.value().timeout.has_value()) { auto userTimeout = (options.value().timeout.value()); if (userTimeout > 0) { timeout = std::chrono::duration_cast( std::chrono::duration(userTimeout)); expirationTime = runtimeScheduler->now() + timeout.value(); } } auto userCallbackShared = std::make_shared>( std::move(userCallback)); auto wrappedCallback = [runtimeScheduler, expirationTime, userCallbackShared]( jsi::Runtime& runtime) -> void { // This implementation gives each idle callback a 50ms deadline, instead of // being shared by all idle callbacks. This is ok because we don't really // have idle periods, and if a higher priority task comes in while we're // executing an idle callback, we don't execute any more idle callbacks and // we interrupt the current one. The general outcome should be the same. auto executionStartTime = runtimeScheduler->now(); auto deadline = executionStartTime + std::chrono::milliseconds(50); auto didTimeout = expirationTime.has_value() ? executionStartTime > expirationTime : false; jsi::Object idleDeadline{runtime}; idleDeadline.setProperty(runtime, "didTimeout", didTimeout); idleDeadline.setProperty( runtime, "timeRemaining", makeTimeRemainingFunction(runtime, runtimeScheduler, deadline)); userCallbackShared->call(std::move(idleDeadline)); }; std::shared_ptr task; if (timeout.has_value()) { task = runtimeScheduler->scheduleIdleTask( std::move(wrappedCallback), timeout.value()); } else { task = runtimeScheduler->scheduleIdleTask(std::move(wrappedCallback)); } if (task == nullptr) { throw jsi::JSError( runtime, "requestIdleCallback is not supported in legacy runtime scheduler"); } jsi::Object taskHandle{runtime}; auto taskNativeState = std::make_shared(task); taskHandle.setNativeState(runtime, std::move(taskNativeState)); return taskHandle; } void NativeIdleCallbacks::cancelIdleCallback( jsi::Runtime& runtime, jsi::Object handle) { auto binding = RuntimeSchedulerBinding::getBinding(runtime); auto runtimeScheduler = binding->getRuntimeScheduler(); if (!handle.hasNativeState(runtime)) { return; } auto taskHandle = std::dynamic_pointer_cast(handle.getNativeState(runtime)); if (!taskHandle) { return; } runtimeScheduler->cancelTask(*taskHandle->task); } } // namespace facebook::react