diff --git a/packages/react-native/React/Base/RCTBridge+Private.h b/packages/react-native/React/Base/RCTBridge+Private.h index 3a5141fe47a7..d01973195a1b 100644 --- a/packages/react-native/React/Base/RCTBridge+Private.h +++ b/packages/react-native/React/Base/RCTBridge+Private.h @@ -84,12 +84,6 @@ RCT_EXTERN void RCTRegisterModule(Class); */ - (void)start; -/** - * Used by RCTModuleData to register the module for frame updates after it is - * lazily initialized. - */ -- (void)registerModuleForFrameUpdates:(id)module withModuleData:(RCTModuleData *)moduleData; - /** * Dispatch work to a module's queue - this is also supports the fake RCTJSThread * queue. Exposed for the RCTProfiler diff --git a/packages/react-native/React/Base/RCTDisplayLink.h b/packages/react-native/React/Base/RCTDisplayLink.h index 636f374254ae..87c1d6b65749 100644 --- a/packages/react-native/React/Base/RCTDisplayLink.h +++ b/packages/react-native/React/Base/RCTDisplayLink.h @@ -7,21 +7,13 @@ #import -@protocol RCTBridgeModule; -@class RCTModuleData; - -@protocol RCTDisplayLinkModuleHolder -- (id)instance; -- (Class)moduleClass; -- (dispatch_queue_t)methodQueue; -@end +@protocol RCTFrameUpdateObserver; @interface RCTDisplayLink : NSObject - (instancetype)init; - (void)invalidate; -- (void)registerModuleForFrameUpdates:(id)module - withModuleHolder:(id)moduleHolder; +- (void)registerTimingForFrameUpdates:(id)module; - (void)addToRunLoop:(NSRunLoop *)runLoop; @end diff --git a/packages/react-native/React/Base/RCTDisplayLink.m b/packages/react-native/React/Base/RCTDisplayLink.m index de7c8700f3e5..4280aa3164f6 100644 --- a/packages/react-native/React/Base/RCTDisplayLink.m +++ b/packages/react-native/React/Base/RCTDisplayLink.m @@ -11,9 +11,7 @@ #import #import "RCTAssert.h" -#import "RCTBridgeModule.h" #import "RCTFrameUpdate.h" -#import "RCTModuleData.h" #import "RCTProfile.h" #define RCTAssertRunLoop() \ @@ -21,7 +19,7 @@ @implementation RCTDisplayLink { CADisplayLink *_jsDisplayLink; - NSMutableSet> *_frameUpdateObservers; + NSMutableSet> *_frameUpdateObservers; NSRunLoop *_runLoop; } @@ -35,18 +33,11 @@ - (instancetype)init return self; } -- (void)registerModuleForFrameUpdates:(id)module - withModuleHolder:(id)moduleHolder +- (void)registerTimingForFrameUpdates:(id)timing { - if (![moduleHolder.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)] || - [_frameUpdateObservers containsObject:moduleHolder]) { - return; - } - - [_frameUpdateObservers addObject:moduleHolder]; + [_frameUpdateObservers addObject:timing]; - // Don't access the module instance via moduleHolder, as this will cause deadlock - id observer = (id)module; + id observer = timing; __weak typeof(self) weakSelf = self; observer.pauseCallback = ^{ typeof(self) strongSelf = weakSelf; @@ -97,8 +88,7 @@ - (void)dealloc - (void)invalidate { // ensure observer callbacks do not hold a reference to weak self via pauseCallback - for (id moduleHolder in _frameUpdateObservers) { - id observer = (id)moduleHolder.instance; + for (id observer in _frameUpdateObservers) { [observer setPauseCallback:nil]; } [_frameUpdateObservers removeAllObjects]; // just to be explicit @@ -106,36 +96,18 @@ - (void)invalidate [_jsDisplayLink invalidate]; } -- (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue -{ - if (queue == RCTJSThread) { - block(); - } else if (queue) { - dispatch_async(queue, block); - } -} - - (void)_jsThreadUpdate:(CADisplayLink *)displayLink { RCTAssertRunLoop(); RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTDisplayLink _jsThreadUpdate:]", nil); + // This always runs on the JS thread run loop, which is the queue the frame + // update observers expect their callbacks on, so dispatch inline. RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; - for (id moduleHolder in _frameUpdateObservers) { - id observer = (id)moduleHolder.instance; + for (id observer in _frameUpdateObservers) { if (!observer.paused) { - if (moduleHolder.methodQueue) { - RCTProfileBeginFlowEvent(); - [self - dispatchBlock:^{ - RCTProfileEndFlowEvent(); - [observer didUpdateFrame:frameUpdate]; - } - queue:moduleHolder.methodQueue]; - } else { - [observer didUpdateFrame:frameUpdate]; - } + [observer didUpdateFrame:frameUpdate]; } } @@ -151,8 +123,7 @@ - (void)updateJSDisplayLinkState RCTAssertRunLoop(); BOOL pauseDisplayLink = YES; - for (id moduleHolder in _frameUpdateObservers) { - id observer = (id)moduleHolder.instance; + for (id observer in _frameUpdateObservers) { if (!observer.paused) { pauseDisplayLink = NO; break; diff --git a/packages/react-native/React/CoreModules/CoreModulesPlugins.h b/packages/react-native/React/CoreModules/CoreModulesPlugins.h index e62d4d157bb0..a9842abde418 100644 --- a/packages/react-native/React/CoreModules/CoreModulesPlugins.h +++ b/packages/react-native/React/CoreModules/CoreModulesPlugins.h @@ -49,7 +49,6 @@ Class RCTPlatformCls(void) __attribute__((used)); Class RCTRedBoxCls(void) __attribute__((used)); Class RCTSourceCodeCls(void) __attribute__((used)); Class RCTStatusBarManagerCls(void) __attribute__((used)); -Class RCTTimingCls(void) __attribute__((used)); Class RCTWebSocketModuleCls(void) __attribute__((used)); Class RCTBlobManagerCls(void) __attribute__((used)); diff --git a/packages/react-native/React/CoreModules/CoreModulesPlugins.mm b/packages/react-native/React/CoreModules/CoreModulesPlugins.mm index f0844ee8afdf..005f75065a63 100644 --- a/packages/react-native/React/CoreModules/CoreModulesPlugins.mm +++ b/packages/react-native/React/CoreModules/CoreModulesPlugins.mm @@ -100,10 +100,6 @@ Class RCTCoreModulesClassProvider(const char *name) return RCTStatusBarManagerCls(); } - if (name == "Timing"sv) { - return RCTTimingCls(); - } - if (name == "WebSocketModule"sv) { return RCTWebSocketModuleCls(); } diff --git a/packages/react-native/React/CoreModules/RCTTiming.h b/packages/react-native/React/CoreModules/RCTTiming.h index 2a7519afb1b6..6b2fe638f3af 100644 --- a/packages/react-native/React/CoreModules/RCTTiming.h +++ b/packages/react-native/React/CoreModules/RCTTiming.h @@ -7,7 +7,6 @@ #import -#import #import #import #import @@ -22,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface RCTTiming : NSObject +@interface RCTTiming : NSObject - (instancetype)initWithDelegate:(id)delegate; - (void)createTimerForNextFrame:(NSNumber *)callbackID diff --git a/packages/react-native/React/CoreModules/RCTTiming.mm b/packages/react-native/React/CoreModules/RCTTiming.mm index 14ee7844c7fe..52a3dd7b3c08 100644 --- a/packages/react-native/React/CoreModules/RCTTiming.mm +++ b/packages/react-native/React/CoreModules/RCTTiming.mm @@ -7,17 +7,11 @@ #import "RCTTiming.h" -#import - #import -#import -#import #import #import #import -#import "CoreModulesPlugins.h" - static const NSTimeInterval kMinimumSleepInterval = 1; // These timing contants should be kept in sync with the ones in `JSTimers.js`. @@ -82,7 +76,7 @@ @implementation _RCTTimingProxy { + (instancetype)proxyWithTarget:(id)target { _RCTTimingProxy *proxy = [self new]; - if (proxy) { + if (proxy != nullptr) { proxy->_target = target; } return proxy; @@ -103,12 +97,9 @@ @implementation RCTTiming { id _timingDelegate; } -@synthesize bridge = _bridge; @synthesize paused = _paused; @synthesize pauseCallback = _pauseCallback; -RCT_EXPORT_MODULE() - - (instancetype)initWithDelegate:(id)delegate { if (self = [super init]) { @@ -165,15 +156,9 @@ - (void)dealloc [_sleepTimer invalidate]; } -- (dispatch_queue_t)methodQueue -{ - return RCTJSThread; -} - - (void)invalidate { [self stopTimers]; - _bridge = nil; _timingDelegate = nil; } @@ -220,7 +205,7 @@ - (void)stopTimers - (void)startTimers { - if ((!_bridge && !_timingDelegate) || _inBackground || ![self hasPendingTimers]) { + if ((!_timingDelegate) || _inBackground || ![self hasPendingTimers]) { return; } @@ -259,11 +244,7 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update NSArray *sortedTimers = [[timersToCall sortedArrayUsingComparator:^(_RCTTimer *a, _RCTTimer *b) { return [a.target compare:b.target]; }] valueForKey:@"callbackID"]; - if (_bridge) { - [_bridge enqueueJSCall:@"JSTimers" method:@"callTimers" args:@[ sortedTimers ] completion:NULL]; - } else { - [_timingDelegate callTimers:sortedTimers]; - } + [_timingDelegate callTimers:sortedTimers]; } for (_RCTTimer *timer in timersToCall) { @@ -282,23 +263,19 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update if (kFrameDuration - frameElapsed >= kIdleCallbackFrameDeadline) { NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970]; NSNumber *absoluteFrameStartMS = @((currentTimestamp - frameElapsed) * 1000); - if (_bridge) { - [_bridge enqueueJSCall:@"JSTimers" method:@"callIdleCallbacks" args:@[ absoluteFrameStartMS ] completion:NULL]; - } else { - [_timingDelegate callIdleCallbacks:absoluteFrameStartMS]; - } + [_timingDelegate callIdleCallbacks:absoluteFrameStartMS]; } } // Switch to a paused state only if we didn't call any timer this frame, so if // in response to this timer another timer is scheduled, we don't pause and unpause // the displaylink frivolously. - NSUInteger timerCount; + NSUInteger timerCount = 0; @synchronized(_timers) { timerCount = _timers.count; } if (_inBackground) { - if (timerCount) { + if (timerCount != 0u) { [self scheduleSleepTimer:nextScheduledTarget]; } } else if (!_sendIdleEvents && timersToCall.count == 0) { @@ -318,7 +295,7 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update - (void)scheduleSleepTimer:(NSDate *)sleepTarget { @synchronized(self) { - if (!_sleepTimer || !_sleepTimer.valid) { + if ((_sleepTimer == nullptr) || !_sleepTimer.valid) { _sleepTimer = [[NSTimer alloc] initWithFireDate:sleepTarget interval:0 target:[_RCTTimingProxy proxyWithTarget:self] @@ -343,35 +320,6 @@ - (void)timerDidFire } } -/** - * A method used for asynchronously creating a timer. If the timer has already expired, - * (based on the provided jsSchedulingTime) then it will be immediately invoked. - * - * There's a small difference between the time when we call - * setTimeout/setInterval/requestAnimation frame and the time it actually makes - * it here. This is important and needs to be taken into account when - * calculating the timer's target time. We calculate this by passing in - * Date.now() from JS and then subtracting that from the current time here. - */ -RCT_EXPORT_METHOD( - createTimer : (double)callbackID duration : (NSTimeInterval)jsDuration jsSchedulingTime : (double) - jsSchedulingTime repeats : (BOOL)repeats) -{ - NSNumber *callbackIdObjc = [NSNumber numberWithDouble:callbackID]; - NSDate *schedulingTime = [RCTConvert NSDate:[NSNumber numberWithDouble:jsSchedulingTime]]; - if (jsDuration == 0 && repeats == NO) { - // For super fast, one-off timers, just enqueue them immediately rather than waiting a frame. - if (_bridge) { - [_bridge _immediatelyCallTimer:callbackIdObjc]; - } else { - [_timingDelegate immediatelyCallTimer:callbackIdObjc]; - } - return; - } - - [self createTimerForNextFrame:callbackIdObjc duration:jsDuration jsSchedulingTime:schedulingTime repeats:repeats]; -} - /** * A method used for synchronously creating a timer. The timer will not be invoked until the * next frame, regardless of whether it has already expired (i.e. jsSchedulingTime is 0). @@ -407,29 +355,14 @@ - (void)createTimerForNextFrame:(nonnull NSNumber *)callbackID } } -RCT_EXPORT_METHOD(deleteTimer : (double)timerID) +- (void)deleteTimer:(double)timerID { @synchronized(_timers) { - [_timers removeObjectForKey:[NSNumber numberWithDouble:timerID]]; + [_timers removeObjectForKey:@(timerID)]; } if (![self hasPendingTimers]) { [self stopTimers]; } } -RCT_EXPORT_METHOD(setSendIdleEvents : (BOOL)sendIdleEvents) -{ - _sendIdleEvents = sendIdleEvents; - if (sendIdleEvents) { - [self startTimers]; - } else if (![self hasPendingTimers]) { - [self stopTimers]; - } -} - @end - -Class RCTTimingCls(void) -{ - return RCTTiming.class; -} diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index ffcfb0b074a3..73a2dd22266b 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -2595,22 +2595,6 @@ public final class com/facebook/react/modules/core/ReactChoreographer$Companion public final fun initialize (Lcom/facebook/react/internal/ChoreographerProvider;)V } -public final class com/facebook/react/modules/core/TimingModule : com/facebook/fbreact/specs/NativeTimingSpec, com/facebook/react/modules/core/JavaScriptTimerExecutor { - public static final field Companion Lcom/facebook/react/modules/core/TimingModule$Companion; - public static final field NAME Ljava/lang/String; - public fun (Lcom/facebook/react/bridge/ReactApplicationContext;Lcom/facebook/react/devsupport/interfaces/DevSupportManager;)V - public fun callIdleCallbacks (D)V - public fun callTimers (Lcom/facebook/react/bridge/WritableArray;)V - public fun createTimer (DDDZ)V - public fun deleteTimer (D)V - public fun emitTimeDriftWarning (Ljava/lang/String;)V - public fun invalidate ()V - public fun setSendIdleEvents (Z)V -} - -public final class com/facebook/react/modules/core/TimingModule$Companion { -} - public final class com/facebook/react/modules/debug/DevSettingsModule : com/facebook/fbreact/specs/NativeDevSettingsSpec { public static final field Companion Lcom/facebook/react/modules/debug/DevSettingsModule$Companion; public static final field NAME Ljava/lang/String; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt index 9c3df748c476..c1a57ee244a5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt @@ -32,8 +32,6 @@ import kotlin.math.sign /** * This class is the native implementation for JS timer execution on Android. It schedules JS timers * to be invoked on frame boundaries using [ReactChoreographer]. - * - * This is used by the NativeModule [TimingModule]. */ public open class JavaTimerManager( private val reactApplicationContext: ReactApplicationContext, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/TimingModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/TimingModule.kt deleted file mode 100644 index 67761e2bdf1b..000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/TimingModule.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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. - */ - -package com.facebook.react.modules.core - -import com.facebook.fbreact.specs.NativeTimingSpec -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.WritableArray -import com.facebook.react.common.annotations.VisibleForTesting -import com.facebook.react.devsupport.interfaces.DevSupportManager -import com.facebook.react.module.annotations.ReactModule - -/** Native module for JS timer execution. Timers fire on frame boundaries. */ -@ReactModule(name = NativeTimingSpec.NAME) -public class TimingModule( - reactContext: ReactApplicationContext, - devSupportManager: DevSupportManager, -) : com.facebook.fbreact.specs.NativeTimingSpec(reactContext), JavaScriptTimerExecutor { - private val javaTimerManager: JavaTimerManager = - JavaTimerManager(reactContext, this, ReactChoreographer.getInstance(), devSupportManager) - - override fun createTimer( - callbackIDDouble: Double, - durationDouble: Double, - jsSchedulingTime: Double, - repeat: Boolean, - ) { - val callbackID = callbackIDDouble.toInt() - val duration = durationDouble.toInt() - javaTimerManager.createAndMaybeCallTimer(callbackID, duration, jsSchedulingTime, repeat) - } - - override fun deleteTimer(timerIdDouble: Double) { - val timerId = timerIdDouble.toInt() - javaTimerManager.deleteTimer(timerId) - } - - override fun setSendIdleEvents(sendIdleEvents: Boolean) { - javaTimerManager.setSendIdleEvents(sendIdleEvents) - } - - override fun callTimers(timerIDs: WritableArray) { - getReactApplicationContextIfActiveOrWarn() - ?.getJSModule(JSTimers::class.java) - ?.callTimers(timerIDs) - } - - override fun callIdleCallbacks(frameTime: Double) { - getReactApplicationContextIfActiveOrWarn() - ?.getJSModule(JSTimers::class.java) - ?.callIdleCallbacks(frameTime) - } - - override fun emitTimeDriftWarning(warningMessage: String) { - getReactApplicationContextIfActiveOrWarn() - ?.getJSModule(JSTimers::class.java) - ?.emitTimeDriftWarning(warningMessage) - } - - override fun invalidate() { - javaTimerManager.onInstanceDestroy() - } - - @VisibleForTesting - public fun hasActiveTimersInRange(rangeMs: Long): Boolean = - javaTimerManager.hasActiveTimersInRange(rangeMs) - - public companion object { - public const val NAME: String = NativeTimingSpec.NAME - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.kt deleted file mode 100644 index 89f43788f841..000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.kt +++ /dev/null @@ -1,307 +0,0 @@ -/* - * 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. - */ - -// TODO T207169925: Migrate CatalystInstance to Reacthost and remove the Suppress("DEPRECATION") -// annotation -@file:Suppress("DEPRECATION") - -package com.facebook.react.modules.timing - -import android.content.Context -import android.os.Looper -import android.view.Choreographer.FrameCallback -import com.facebook.react.bridge.BridgeReactContext -import com.facebook.react.bridge.CatalystInstance -import com.facebook.react.bridge.JavaOnlyArray -import com.facebook.react.bridge.JavaOnlyMap -import com.facebook.react.common.SystemClock -import com.facebook.react.devsupport.interfaces.DevSupportManager -import com.facebook.react.jstasks.HeadlessJsTaskConfig -import com.facebook.react.jstasks.HeadlessJsTaskContext -import com.facebook.react.modules.appregistry.AppRegistry -import com.facebook.react.modules.core.JSTimers -import com.facebook.react.modules.core.ReactChoreographer -import com.facebook.react.modules.core.ReactChoreographer.CallbackType -import com.facebook.react.modules.core.TimingModule -import com.facebook.testutils.shadows.ShadowArguments -import org.assertj.core.api.Assertions.assertThat -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers -import org.mockito.MockedStatic -import org.mockito.Mockito.mockStatic -import org.mockito.invocation.InvocationOnMock -import org.mockito.kotlin.doAnswer -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock -import org.mockito.kotlin.reset -import org.mockito.kotlin.spy -import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyNoMoreInteractions -import org.mockito.kotlin.whenever -import org.mockito.stubbing.Answer -import org.robolectric.RobolectricTestRunner -import org.robolectric.Shadows.shadowOf -import org.robolectric.annotation.Config - -object MockCompat { - // Same as Mockito's 'eq()', but works for non-nullable types - fun eq(value: T): T = ArgumentMatchers.eq(value) ?: value - - // Same as Mockito's 'any()', but works for non-nullable types - fun any(): T { - ArgumentMatchers.any() - return uninitialized() - } - - @Suppress("UNCHECKED_CAST") fun uninitialized(): T = null as T -} - -@Config(shadows = [ShadowArguments::class]) -@RunWith(RobolectricTestRunner::class) -class TimingModuleTest { - companion object { - const val FRAME_TIME_NS = 17 * 1000 * 1000 - } - - private lateinit var reactContext: BridgeReactContext - private lateinit var headlessContext: HeadlessJsTaskContext - private lateinit var timingModule: TimingModule - private lateinit var postFrameCallbackHandler: PostFrameCallbackHandler - private lateinit var idlePostFrameCallbackHandler: PostFrameCallbackHandler - private lateinit var jsTimersMock: JSTimers - private lateinit var systemClock: MockedStatic - private lateinit var reactChoreographerMock: ReactChoreographer - - private var currentTimeNs = 0L - private var reactChoreographerOriginal: ReactChoreographer? = null - - @Before - fun prepareModules() { - systemClock = mockStatic(SystemClock::class.java) - systemClock - .`when` { SystemClock.uptimeMillis() } - .thenAnswer { - return@thenAnswer currentTimeNs / 1000000 - } - systemClock - .`when` { SystemClock.currentTimeMillis() } - .thenAnswer { - return@thenAnswer currentTimeNs / 1000000 - } - systemClock - .`when` { SystemClock.nanoTime() } - .thenAnswer { - return@thenAnswer currentTimeNs - } - - reactChoreographerMock = mock() - reactChoreographerOriginal = ReactChoreographer.overrideInstanceForTest(reactChoreographerMock) - - val reactInstance = mock() - reactContext = spy(BridgeReactContext(mock())) - doReturn(reactInstance).`when`(reactContext).catalystInstance - doReturn(true).`when`(reactContext).hasActiveReactInstance() - - headlessContext = HeadlessJsTaskContext.getInstance(reactContext) - - postFrameCallbackHandler = PostFrameCallbackHandler() - idlePostFrameCallbackHandler = PostFrameCallbackHandler() - - whenever( - reactChoreographerMock.postFrameCallback( - MockCompat.eq(CallbackType.TIMERS_EVENTS), - MockCompat.any(), - ) - ) - .thenAnswer { - return@thenAnswer postFrameCallbackHandler.answer(it) - } - whenever( - reactChoreographerMock.postFrameCallback( - MockCompat.eq(CallbackType.IDLE_EVENT), - MockCompat.any(), - ) - ) - .thenAnswer { - return@thenAnswer idlePostFrameCallbackHandler.answer(it) - } - - timingModule = TimingModule(reactContext, mock()) - jsTimersMock = mock() - doReturn(jsTimersMock).`when`(reactContext).getJSModule(JSTimers::class.java) - doReturn(mock()).`when`(reactContext).getJSModule(AppRegistry::class.java) - doAnswer({ invocation -> - (invocation.arguments[0] as Runnable).run() - return@doAnswer true - }) - .`when`(reactContext) - .runOnJSQueueThread(MockCompat.any()) - - timingModule.initialize() - } - - @After - fun tearDown() { - systemClock.close() - ReactChoreographer.overrideInstanceForTest(reactChoreographerOriginal) - } - - private fun stepChoreographerFrame() { - shadowOf(Looper.getMainLooper()).idle() - - val callback = postFrameCallbackHandler.getAndResetFrameCallback() - val idleCallback = idlePostFrameCallbackHandler.getAndResetFrameCallback() - currentTimeNs += FRAME_TIME_NS - whenever(SystemClock.uptimeMillis()).thenAnswer { - return@thenAnswer currentTimeNs / 1000000 - } - callback?.doFrame(currentTimeNs) - idleCallback?.doFrame(currentTimeNs) - } - - @Test - fun testSimpleTimer() { - reactContext.onHostResume(null) - timingModule.createTimer(1.0, 1.0, 0.0, false) - stepChoreographerFrame() - verify(jsTimersMock).callTimers(JavaOnlyArray.of(1.0)) - reset(jsTimersMock) - stepChoreographerFrame() - verifyNoMoreInteractions(jsTimersMock) - } - - @Test - fun testSimpleRecurringTimer() { - timingModule.createTimer(100.0, 1.0, 0.0, true) - reactContext.onHostResume(null) - stepChoreographerFrame() - verify(jsTimersMock).callTimers(JavaOnlyArray.of(100.0)) - reset(jsTimersMock) - stepChoreographerFrame() - verify(jsTimersMock).callTimers(JavaOnlyArray.of(100.0)) - } - - @Test - fun testCancelRecurringTimer() { - reactContext.onHostResume(null) - timingModule.createTimer(105.0, 1.0, 0.0, true) - stepChoreographerFrame() - verify(jsTimersMock).callTimers(JavaOnlyArray.of(105.0)) - reset(jsTimersMock) - timingModule.deleteTimer(105.0) - stepChoreographerFrame() - verifyNoMoreInteractions(jsTimersMock) - } - - @Test - fun testPausingAndResuming() { - reactContext.onHostResume(null) - timingModule.createTimer(41.0, 1.0, 0.0, true) - stepChoreographerFrame() - verify(jsTimersMock).callTimers(JavaOnlyArray.of(41.0)) - reset(jsTimersMock) - reactContext.onHostPause() - stepChoreographerFrame() - verifyNoMoreInteractions(jsTimersMock) - reset(jsTimersMock) - reactContext.onHostResume(null) - stepChoreographerFrame() - verify(jsTimersMock).callTimers(JavaOnlyArray.of(41.0)) - } - - @Test - fun testHeadlessJsTaskInBackground() { - reactContext.onHostPause() - val taskConfig = HeadlessJsTaskConfig("foo", JavaOnlyMap()) - val taskId = headlessContext.startTask(taskConfig) - timingModule.createTimer(41.0, 1.0, 0.0, true) - stepChoreographerFrame() - verify(jsTimersMock).callTimers(JavaOnlyArray.of(41.0)) - reset(jsTimersMock) - headlessContext.finishTask(taskId) - stepChoreographerFrame() - verifyNoMoreInteractions(jsTimersMock) - } - - @Test - fun testHeadlessJsTaskInForeground() { - val taskConfig = HeadlessJsTaskConfig("foo", JavaOnlyMap()) - val taskId = headlessContext.startTask(taskConfig) - reactContext.onHostResume(null) - timingModule.createTimer(41.0, 1.0, 0.0, true) - stepChoreographerFrame() - verify(jsTimersMock).callTimers(JavaOnlyArray.of(41.0)) - reset(jsTimersMock) - headlessContext.finishTask(taskId) - stepChoreographerFrame() - verify(jsTimersMock).callTimers(JavaOnlyArray.of(41.0)) - reset(jsTimersMock) - reactContext.onHostPause() - verifyNoMoreInteractions(jsTimersMock) - } - - @Test - fun testHeadlessJsTaskIntertwine() { - timingModule.createTimer(41.0, 1.0, 0.0, true) - reactContext.onHostPause() - val taskConfig = HeadlessJsTaskConfig("foo", JavaOnlyMap()) - val taskId = headlessContext.startTask(taskConfig) - stepChoreographerFrame() - verify(jsTimersMock).callTimers(JavaOnlyArray.of(41.0)) - reset(jsTimersMock) - reactContext.onHostResume(null) - headlessContext.finishTask(taskId) - stepChoreographerFrame() - verify(jsTimersMock).callTimers(JavaOnlyArray.of(41.0)) - reset(jsTimersMock) - reactContext.onHostPause() - stepChoreographerFrame() - verifyNoMoreInteractions(jsTimersMock) - } - - @Test - fun testSetTimeoutZero() { - timingModule.createTimer(100.0, 0.0, 0.0, false) - verify(jsTimersMock).callTimers(JavaOnlyArray.of(100.0)) - } - - @Test - fun testActiveTimersInRange() { - reactContext.onHostResume(null) - assertThat(timingModule.hasActiveTimersInRange(100)).isFalse - timingModule.createTimer(41.0, 1.0, 0.0, true) - assertThat(timingModule.hasActiveTimersInRange(100)).isFalse // Repeating - timingModule.createTimer(42.0, 150.0, 0.0, false) - assertThat(timingModule.hasActiveTimersInRange(100)).isFalse // Out of range - assertThat(timingModule.hasActiveTimersInRange(200)).isTrue // In range - } - - @Test - fun testIdleCallback() { - timingModule.setSendIdleEvents(true) - reactContext.onHostResume(null) - stepChoreographerFrame() - verify(jsTimersMock).callIdleCallbacks(SystemClock.currentTimeMillis().toDouble()) - } - - private class PostFrameCallbackHandler : Answer { - private var frameCallback: FrameCallback? = null - - override fun answer(invocation: InvocationOnMock) { - invocation.arguments[1]?.let { frameCallback = it as FrameCallback } - } - - fun getAndResetFrameCallback(): FrameCallback? { - val callback = frameCallback - frameCallback = null - return callback - } - } -} diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm index 13ebbe825393..a9f069a83c36 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm @@ -72,35 +72,6 @@ void RCTInstanceSetRuntimeDiagnosticFlags(NSString *flags) sRuntimeDiagnosticFlags = [flags copy]; } -@interface RCTBridgelessDisplayLinkModuleHolder : NSObject -- (instancetype)initWithModule:(id)module; -@end - -@implementation RCTBridgelessDisplayLinkModuleHolder { - id _module; -} -- (instancetype)initWithModule:(id)module -{ - _module = module; - return self; -} - -- (id)instance -{ - return _module; -} - -- (Class)moduleClass -{ - return [_module class]; -} - -- (dispatch_queue_t)methodQueue -{ - return _module.methodQueue; -} -@end - @interface RCTInstance () @end @@ -454,8 +425,7 @@ - (void)_start [strongSelf->_delegate instance:strongSelf didInitializeRuntime:runtime]; // Set up Display Link - id moduleHolder = [[RCTBridgelessDisplayLinkModuleHolder alloc] initWithModule:timing]; - [strongSelf->_displayLink registerModuleForFrameUpdates:timing withModuleHolder:moduleHolder]; + [strongSelf->_displayLink registerTimingForFrameUpdates:timing]; [strongSelf->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]]; // Attempt to load bundle synchronously, fallback to asynchronously. diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 9ff8dcb229e6..eed3cba30e49 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -1026,7 +1026,7 @@ interface RCTDisplayLink : public NSObject { public virtual instancetype init(); public virtual void addToRunLoop:(NSRunLoop* runLoop); public virtual void invalidate(); - public virtual void registerModuleForFrameUpdates:withModuleHolder:(id module, id moduleHolder); + public virtual void registerTimingForFrameUpdates:(id module); } interface RCTDisplayWeakRefreshable : public NSObject { @@ -1974,7 +1974,7 @@ interface RCTThirdPartyComponentsProvider : public NSObject { public virtual static NSDictionary>* thirdPartyFabricComponents(); } -interface RCTTiming : public NSObject { +interface RCTTiming : public NSObject { public virtual instancetype initWithDelegate:(id delegate); public virtual void createTimerForNextFrame:duration:jsSchedulingTime:repeats:(NSNumber* callbackID, NSTimeInterval jsDuration, _Nullable NSDate* jsSchedulingTime, BOOL repeats); public virtual void deleteTimer:(double timerID); @@ -2771,12 +2771,6 @@ protocol RCTDevSettingsInspectable : public NSObject { public @property (assign) BOOL isInspectable; } -protocol RCTDisplayLinkModuleHolder { - public virtual Class moduleClass(); - public virtual dispatch_queue_t methodQueue(); - public virtual id instance(); -} - protocol RCTDisplayRefreshable { public virtual void displayDidRefresh:(CADisplayLink* displayLink); } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 7de39e2e86d2..f01a8f0be37c 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -1024,7 +1024,7 @@ interface RCTDisplayLink : public NSObject { public virtual instancetype init(); public virtual void addToRunLoop:(NSRunLoop* runLoop); public virtual void invalidate(); - public virtual void registerModuleForFrameUpdates:withModuleHolder:(id module, id moduleHolder); + public virtual void registerTimingForFrameUpdates:(id module); } interface RCTDisplayWeakRefreshable : public NSObject { @@ -1962,7 +1962,7 @@ interface RCTThirdPartyComponentsProvider : public NSObject { public virtual static NSDictionary>* thirdPartyFabricComponents(); } -interface RCTTiming : public NSObject { +interface RCTTiming : public NSObject { public virtual instancetype initWithDelegate:(id delegate); public virtual void createTimerForNextFrame:duration:jsSchedulingTime:repeats:(NSNumber* callbackID, NSTimeInterval jsDuration, _Nullable NSDate* jsSchedulingTime, BOOL repeats); public virtual void deleteTimer:(double timerID); @@ -2758,12 +2758,6 @@ protocol RCTDevSettingsInspectable : public NSObject { public @property (assign) BOOL isInspectable; } -protocol RCTDisplayLinkModuleHolder { - public virtual Class moduleClass(); - public virtual dispatch_queue_t methodQueue(); - public virtual id instance(); -} - protocol RCTDisplayRefreshable { public virtual void displayDidRefresh:(CADisplayLink* displayLink); } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index f1e8fb0dd9b0..b52e48ae39d5 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -1026,7 +1026,7 @@ interface RCTDisplayLink : public NSObject { public virtual instancetype init(); public virtual void addToRunLoop:(NSRunLoop* runLoop); public virtual void invalidate(); - public virtual void registerModuleForFrameUpdates:withModuleHolder:(id module, id moduleHolder); + public virtual void registerTimingForFrameUpdates:(id module); } interface RCTDisplayWeakRefreshable : public NSObject { @@ -1974,7 +1974,7 @@ interface RCTThirdPartyComponentsProvider : public NSObject { public virtual static NSDictionary>* thirdPartyFabricComponents(); } -interface RCTTiming : public NSObject { +interface RCTTiming : public NSObject { public virtual instancetype initWithDelegate:(id delegate); public virtual void createTimerForNextFrame:duration:jsSchedulingTime:repeats:(NSNumber* callbackID, NSTimeInterval jsDuration, _Nullable NSDate* jsSchedulingTime, BOOL repeats); public virtual void deleteTimer:(double timerID); @@ -2771,12 +2771,6 @@ protocol RCTDevSettingsInspectable : public NSObject { public @property (assign) BOOL isInspectable; } -protocol RCTDisplayLinkModuleHolder { - public virtual Class moduleClass(); - public virtual dispatch_queue_t methodQueue(); - public virtual id instance(); -} - protocol RCTDisplayRefreshable { public virtual void displayDidRefresh:(CADisplayLink* displayLink); }