From 0ae11f12f3a6e1d526287f2ec3c4e900bda498ae Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 22 Jun 2017 09:46:28 -0700 Subject: [PATCH] Merge JSTimers and JSTimersExecution Reviewed By: mmmulani Differential Revision: D5292923 fbshipit-source-id: d8331cbac28ba3bbf47c9746238a755b707903ea --- Libraries/BatchedBridge/MessageQueue.js | 11 +- Libraries/Core/InitializeCore.js | 2 +- Libraries/Core/Timers/JSTimers.js | 432 +++++++++++++++++---- Libraries/Core/Timers/JSTimersExecution.js | 246 ------------ 4 files changed, 358 insertions(+), 333 deletions(-) delete mode 100644 Libraries/Core/Timers/JSTimersExecution.js diff --git a/Libraries/BatchedBridge/MessageQueue.js b/Libraries/BatchedBridge/MessageQueue.js index 579dc30653..46bec14e3e 100644 --- a/Libraries/BatchedBridge/MessageQueue.js +++ b/Libraries/BatchedBridge/MessageQueue.js @@ -15,7 +15,6 @@ 'use strict'; const ErrorUtils = require('ErrorUtils'); -const JSTimersExecution = require('JSTimersExecution'); const Systrace = require('Systrace'); const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); @@ -41,6 +40,9 @@ const TRACE_TAG_REACT_APPS = 1 << 17; const DEBUG_INFO_LIMIT = 32; +// Work around an initialization order issue +let JSTimers = null; + class MessageQueue { _lazyCallableModules: {[key: string]: void => Object}; _queue: [Array, Array, Array, number]; @@ -235,8 +237,11 @@ class MessageQueue { } __callImmediates() { - Systrace.beginEvent('JSTimersExecution.callImmediates()'); - JSTimersExecution.callImmediates(); + Systrace.beginEvent('JSTimers.callImmediates()'); + if (!JSTimers) { + JSTimers = require('JSTimers'); + } + JSTimers.callImmediates(); Systrace.endEvent(); } diff --git a/Libraries/Core/InitializeCore.js b/Libraries/Core/InitializeCore.js index 60c68b2d80..8dd2251c2e 100644 --- a/Libraries/Core/InitializeCore.js +++ b/Libraries/Core/InitializeCore.js @@ -112,7 +112,7 @@ ExceptionsManager.installConsoleErrorReporter(); // TODO: Move these around to solve the cycle in a cleaner way const BatchedBridge = require('BatchedBridge'); BatchedBridge.registerLazyCallableModule('Systrace', () => require('Systrace')); -BatchedBridge.registerLazyCallableModule('JSTimersExecution', () => require('JSTimersExecution')); +BatchedBridge.registerLazyCallableModule('JSTimersExecution', () => require('JSTimers')); BatchedBridge.registerLazyCallableModule('HeapCapture', () => require('HeapCapture')); BatchedBridge.registerLazyCallableModule('SamplingProfiler', () => require('SamplingProfiler')); BatchedBridge.registerLazyCallableModule('RCTLog', () => require('RCTLog')); diff --git a/Libraries/Core/Timers/JSTimers.js b/Libraries/Core/Timers/JSTimers.js index 7373a024f2..f9c10ca253 100644 --- a/Libraries/Core/Timers/JSTimers.js +++ b/Libraries/Core/Timers/JSTimers.js @@ -7,66 +7,39 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule JSTimers + * @format * @flow */ 'use strict'; -// Note that the module JSTimers is split into two in order to solve a cycle -// in dependencies. NativeModules > BatchedBridge > MessageQueue > JSTimersExecution -const JSTimersExecution = require('JSTimersExecution'); const Platform = require('Platform'); +const Systrace = require('Systrace'); +const invariant = require('fbjs/lib/invariant'); const performanceNow = require('fbjs/lib/performanceNow'); +const warning = require('fbjs/lib/warning'); const {Timing} = require('NativeModules'); -import type {JSTimerType} from 'JSTimersExecution'; import type {ExtendedError} from 'parseErrorStack'; -// Returns a free index if one is available, and the next consecutive index otherwise. -function _getFreeIndex(): number { - let freeIndex = JSTimersExecution.timerIDs.indexOf(null); - if (freeIndex === -1) { - freeIndex = JSTimersExecution.timerIDs.length; - } - return freeIndex; -} +/** + * JS implementation of timer functions. Must be completely driven by an + * external clock signal, all that's stored here is timerID, timer type, and + * callback. + */ -function _allocateCallback(func: Function, type: JSTimerType): number { - const id = JSTimersExecution.GUID++; - const freeIndex = _getFreeIndex(); - JSTimersExecution.timerIDs[freeIndex] = id; - JSTimersExecution.callbacks[freeIndex] = func; - JSTimersExecution.types[freeIndex] = type; - if (__DEV__) { - const parseErrorStack = require('parseErrorStack'); - const error : ExtendedError = new Error(); - error.framesToPop = 1; - const stack = parseErrorStack(error); - if (stack) { - JSTimersExecution.identifiers[freeIndex] = stack.shift(); - } - } - return id; -} +export type JSTimerType = + | 'setTimeout' + | 'setInterval' + | 'requestAnimationFrame' + | 'setImmediate' + | 'requestIdleCallback'; -function _freeCallback(timerID: number) { - // JSTimersExecution.timerIDs contains nulls after timers have been removed; - // ignore nulls upfront so indexOf doesn't find them - if (timerID == null) { - return; - } - - const index = JSTimersExecution.timerIDs.indexOf(timerID); - // See corresponding comment in `callTimers` for reasoning behind this - if (index !== -1) { - JSTimersExecution._clearIndex(index); - const type = JSTimersExecution.types[index]; - if (type !== 'setImmediate' && type !== 'requestIdleCallback') { - Timing.deleteTimer(timerID); - } - } -} +// These timing contants should be kept in sync with the ones in native ios and +// android `RCTTiming` module. +const FRAME_DURATION = 1000 / 60; +const IDLE_CALLBACK_FRAME_DEADLINE = 1; const MAX_TIMER_DURATION_MS = 60 * 1000; const IS_ANDROID = Platform.OS === 'android'; @@ -76,6 +49,152 @@ const ANDROID_LONG_TIMER_MESSAGE = 'module awake, and timers can only be called when the app is in the foreground. ' + 'See https://github.com/facebook/react-native/issues/12981 for more info.'; +// Parallel arrays +const callbacks: Array = []; +const types: Array = []; +const timerIDs: Array = []; +let immediates: Array = []; +let requestIdleCallbacks: Array = []; +const requestIdleCallbackTimeouts: {[number]: number} = {}; +const identifiers: Array = []; + +let GUID = 1; +let errors: ?Array = null; + +let hasEmittedTimeDriftWarning = false; + +// Returns a free index if one is available, and the next consecutive index otherwise. +function _getFreeIndex(): number { + let freeIndex = timerIDs.indexOf(null); + if (freeIndex === -1) { + freeIndex = timerIDs.length; + } + return freeIndex; +} + +function _allocateCallback(func: Function, type: JSTimerType): number { + const id = GUID++; + const freeIndex = _getFreeIndex(); + timerIDs[freeIndex] = id; + callbacks[freeIndex] = func; + types[freeIndex] = type; + if (__DEV__) { + const parseErrorStack = require('parseErrorStack'); + const error: ExtendedError = new Error(); + error.framesToPop = 1; + const stack = parseErrorStack(error); + if (stack) { + identifiers[freeIndex] = stack.shift(); + } + } + return id; +} + +/** + * Calls the callback associated with the ID. Also unregister that callback + * if it was a one time timer (setTimeout), and not unregister it if it was + * recurring (setInterval). + */ +function _callTimer(timerID: number, frameTime: number, didTimeout: ?boolean) { + warning( + timerID <= GUID, + 'Tried to call timer with ID %s but no such timer exists.', + timerID, + ); + + // timerIndex of -1 means that no timer with that ID exists. There are + // two situations when this happens, when a garbage timer ID was given + // and when a previously existing timer was deleted before this callback + // fired. In both cases we want to ignore the timer id, but in the former + // case we warn as well. + const timerIndex = timerIDs.indexOf(timerID); + if (timerIndex === -1) { + return; + } + + const type = types[timerIndex]; + const callback = callbacks[timerIndex]; + if (!callback || !type) { + console.error('No callback found for timerID ' + timerID); + return; + } + + if (__DEV__) { + const identifier = identifiers[timerIndex] || {}; + Systrace.beginEvent('Systrace.callTimer: ' + identifier.methodName); + } + + // Clear the metadata + if ( + type === 'setTimeout' || + type === 'setImmediate' || + type === 'requestAnimationFrame' || + type === 'requestIdleCallback' + ) { + _clearIndex(timerIndex); + } + + try { + if ( + type === 'setTimeout' || + type === 'setInterval' || + type === 'setImmediate' + ) { + callback(); + } else if (type === 'requestAnimationFrame') { + callback(performanceNow()); + } else if (type === 'requestIdleCallback') { + callback({ + timeRemaining: function() { + // TODO: Optimisation: allow running for longer than one frame if + // there are no pending JS calls on the bridge from native. This + // would require a way to check the bridge queue synchronously. + return Math.max(0, FRAME_DURATION - (performanceNow() - frameTime)); + }, + didTimeout: !!didTimeout, + }); + } else { + console.error('Tried to call a callback with invalid type: ' + type); + } + } catch (e) { + // Don't rethrow so that we can run all timers. + if (!errors) { + errors = [e]; + } else { + errors.push(e); + } + } + + if (__DEV__) { + Systrace.endEvent(); + } +} + +function _clearIndex(i: number) { + timerIDs[i] = null; + callbacks[i] = null; + types[i] = null; + identifiers[i] = null; +} + +function _freeCallback(timerID: number) { + // timerIDs contains nulls after timers have been removed; + // ignore nulls upfront so indexOf doesn't find them + if (timerID == null) { + return; + } + + const index = timerIDs.indexOf(timerID); + // See corresponding comment in `callTimers` for reasoning behind this + if (index !== -1) { + _clearIndex(index); + const type = types[index]; + if (type !== 'setImmediate' && type !== 'requestIdleCallback') { + Timing.deleteTimer(timerID); + } + } +} + /** * JS implementation of timer functions. Must be completely driven by an * external clock signal, all that's stored here is timerID, timer type, and @@ -86,13 +205,24 @@ const JSTimers = { * @param {function} func Callback to be invoked after `duration` ms. * @param {number} duration Number of milliseconds. */ - setTimeout: function(func: Function, duration: number, ...args?: any): number { + setTimeout: function( + func: Function, + duration: number, + ...args?: any + ): number { if (__DEV__ && IS_ANDROID && duration > MAX_TIMER_DURATION_MS) { console.warn( - ANDROID_LONG_TIMER_MESSAGE + '\n' + '(Saw setTimeout with duration ' + - duration + 'ms)'); + ANDROID_LONG_TIMER_MESSAGE + + '\n' + + '(Saw setTimeout with duration ' + + duration + + 'ms)', + ); } - const id = _allocateCallback(() => func.apply(undefined, args), 'setTimeout'); + const id = _allocateCallback( + () => func.apply(undefined, args), + 'setTimeout', + ); Timing.createTimer(id, duration || 0, Date.now(), /* recurring */ false); return id; }, @@ -101,13 +231,24 @@ const JSTimers = { * @param {function} func Callback to be invoked every `duration` ms. * @param {number} duration Number of milliseconds. */ - setInterval: function(func: Function, duration: number, ...args?: any): number { + setInterval: function( + func: Function, + duration: number, + ...args?: any + ): number { if (__DEV__ && IS_ANDROID && duration > MAX_TIMER_DURATION_MS) { console.warn( - ANDROID_LONG_TIMER_MESSAGE + '\n' + '(Saw setInterval with duration ' + - duration + 'ms)'); + ANDROID_LONG_TIMER_MESSAGE + + '\n' + + '(Saw setInterval with duration ' + + duration + + 'ms)', + ); } - const id = _allocateCallback(() => func.apply(undefined, args), 'setInterval'); + const id = _allocateCallback( + () => func.apply(undefined, args), + 'setInterval', + ); Timing.createTimer(id, duration || 0, Date.now(), /* recurring */ true); return id; }, @@ -117,15 +258,18 @@ const JSTimers = { * current JavaScript execution loop. */ setImmediate: function(func: Function, ...args?: any) { - const id = _allocateCallback(() => func.apply(undefined, args), 'setImmediate'); - JSTimersExecution.immediates.push(id); + const id = _allocateCallback( + () => func.apply(undefined, args), + 'setImmediate', + ); + immediates.push(id); return id; }, /** * @param {function} func Callback to be invoked every frame. */ - requestAnimationFrame: function(func : Function) { + requestAnimationFrame: function(func: Function) { const id = _allocateCallback(func, 'requestAnimationFrame'); Timing.createTimer(id, 1, Date.now(), /* recurring */ false); return id; @@ -136,58 +280,58 @@ const JSTimers = { * with time remaining in frame. * @param {?object} options */ - requestIdleCallback: function(func : Function, options : ?Object) { - if (JSTimersExecution.requestIdleCallbacks.length === 0) { + requestIdleCallback: function(func: Function, options: ?Object) { + if (requestIdleCallbacks.length === 0) { Timing.setSendIdleEvents(true); } const timeout = options && options.timeout; const id = _allocateCallback( - timeout != null ? - deadline => { - const timeoutId = JSTimersExecution.requestIdleCallbackTimeouts[id]; - if (timeoutId) { - JSTimers.clearTimeout(timeoutId); - JSTimersExecution.requestIdleCallbackTimeouts[id]; + timeout != null + ? deadline => { + const timeoutId = requestIdleCallbackTimeouts[id]; + if (timeoutId) { + JSTimers.clearTimeout(timeoutId); + requestIdleCallbackTimeouts[id]; + } + return func(deadline); } - return func(deadline); - } : - func, - 'requestIdleCallback' + : func, + 'requestIdleCallback', ); - JSTimersExecution.requestIdleCallbacks.push(id); + requestIdleCallbacks.push(id); if (timeout != null) { const timeoutId = JSTimers.setTimeout(() => { - const index = JSTimersExecution.requestIdleCallbacks.indexOf(id); + const index = requestIdleCallbacks.indexOf(id); if (index > -1) { - JSTimersExecution.requestIdleCallbacks.splice(index, 1); - JSTimersExecution.callTimer(id, performanceNow(), true); + requestIdleCallbacks.splice(index, 1); + _callTimer(id, performanceNow(), true); } - delete JSTimersExecution.requestIdleCallbackTimeouts[id]; - if (JSTimersExecution.requestIdleCallbacks.length === 0) { + delete requestIdleCallbackTimeouts[id]; + if (requestIdleCallbacks.length === 0) { Timing.setSendIdleEvents(false); } }, timeout); - JSTimersExecution.requestIdleCallbackTimeouts[id] = timeoutId; + requestIdleCallbackTimeouts[id] = timeoutId; } return id; }, cancelIdleCallback: function(timerID: number) { _freeCallback(timerID); - const index = JSTimersExecution.requestIdleCallbacks.indexOf(timerID); + const index = requestIdleCallbacks.indexOf(timerID); if (index !== -1) { - JSTimersExecution.requestIdleCallbacks.splice(index, 1); + requestIdleCallbacks.splice(index, 1); } - const timeoutId = JSTimersExecution.requestIdleCallbackTimeouts[timerID]; + const timeoutId = requestIdleCallbackTimeouts[timerID]; if (timeoutId) { JSTimers.clearTimeout(timeoutId); - delete JSTimersExecution.requestIdleCallbackTimeouts[timerID]; + delete requestIdleCallbackTimeouts[timerID]; } - if (JSTimersExecution.requestIdleCallbacks.length === 0) { + if (requestIdleCallbacks.length === 0) { Timing.setSendIdleEvents(false); } }, @@ -202,15 +346,137 @@ const JSTimers = { clearImmediate: function(timerID: number) { _freeCallback(timerID); - const index = JSTimersExecution.immediates.indexOf(timerID); + const index = immediates.indexOf(timerID); if (index !== -1) { - JSTimersExecution.immediates.splice(index, 1); + immediates.splice(index, 1); } }, cancelAnimationFrame: function(timerID: number) { _freeCallback(timerID); }, + + /** + * This is called from the native side. We are passed an array of timerIDs, + * and + */ + callTimers: function(timersToCall: Array) { + invariant( + timersToCall.length !== 0, + 'Cannot call `callTimers` with an empty list of IDs.', + ); + + // $FlowFixMe: optionals do not allow assignment from null + errors = null; + for (let i = 0; i < timersToCall.length; i++) { + _callTimer(timersToCall[i], 0); + } + + if (errors) { + const errorCount = errors.length; + if (errorCount > 1) { + // Throw all the other errors in a setTimeout, which will throw each + // error one at a time + for (let ii = 1; ii < errorCount; ii++) { + JSTimers.setTimeout( + (error => { + throw error; + }).bind(null, errors[ii]), + 0, + ); + } + } + throw errors[0]; + } + }, + + callIdleCallbacks: function(frameTime: number) { + if ( + FRAME_DURATION - (performanceNow() - frameTime) < + IDLE_CALLBACK_FRAME_DEADLINE + ) { + return; + } + + // $FlowFixMe: optionals do not allow assignment from null + errors = null; + if (requestIdleCallbacks.length > 0) { + const passIdleCallbacks = requestIdleCallbacks.slice(); + requestIdleCallbacks = []; + + for (let i = 0; i < passIdleCallbacks.length; ++i) { + _callTimer(passIdleCallbacks[i], frameTime); + } + } + + if (requestIdleCallbacks.length === 0) { + Timing.setSendIdleEvents(false); + } + + if (errors) { + errors.forEach(error => + JSTimers.setTimeout(() => { + throw error; + }, 0), + ); + } + }, + + /** + * Performs a single pass over the enqueued immediates. Returns whether + * more immediates are queued up (can be used as a condition a while loop). + */ + callImmediatesPass() { + if (__DEV__) { + Systrace.beginEvent('callImmediatesPass()'); + } + + // The main reason to extract a single pass is so that we can track + // in the system trace + if (immediates.length > 0) { + const passImmediates = immediates.slice(); + immediates = []; + + // Use for loop rather than forEach as per @vjeux's advice + // https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051 + for (let i = 0; i < passImmediates.length; ++i) { + _callTimer(passImmediates[i], 0); + } + } + + if (__DEV__) { + Systrace.endEvent(); + } + return immediates.length > 0; + }, + + /** + * This is called after we execute any command we receive from native but + * before we hand control back to native. + */ + callImmediates() { + errors = null; + while (JSTimers.callImmediatesPass()) { + } + if (errors) { + errors.forEach(error => + JSTimers.setTimeout(() => { + throw error; + }, 0), + ); + } + }, + + /** + * Called from native (in development) when environment times are out-of-sync. + */ + emitTimeDriftWarning(warningMessage: string) { + if (hasEmittedTimeDriftWarning) { + return; + } + hasEmittedTimeDriftWarning = true; + console.warn(warningMessage); + }, }; module.exports = JSTimers; diff --git a/Libraries/Core/Timers/JSTimersExecution.js b/Libraries/Core/Timers/JSTimersExecution.js deleted file mode 100644 index 02b38bb7ee..0000000000 --- a/Libraries/Core/Timers/JSTimersExecution.js +++ /dev/null @@ -1,246 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule JSTimersExecution - * @flow - */ -'use strict'; - -const Systrace = require('Systrace'); - -const invariant = require('fbjs/lib/invariant'); -const performanceNow = require('fbjs/lib/performanceNow'); -const warning = require('fbjs/lib/warning'); - -// These timing contants should be kept in sync with the ones in native ios and -// android `RCTTiming` module. -const FRAME_DURATION = 1000 / 60; -const IDLE_CALLBACK_FRAME_DEADLINE = 1; - -let hasEmittedTimeDriftWarning = false; - -export type JSTimerType = - 'setTimeout' | - 'setInterval' | - 'requestAnimationFrame' | - 'setImmediate' | - 'requestIdleCallback'; - -/** - * JS implementation of timer functions. Must be completely driven by an - * external clock signal, all that's stored here is timerID, timer type, and - * callback. - */ -const JSTimersExecution = { - GUID: 1, - - // Parallel arrays - callbacks: ([] : Array), - types: ([] : Array), - timerIDs: ([] : Array), - immediates: [], - requestIdleCallbacks: [], - requestIdleCallbackTimeouts: ({} : {[number]: number}), - identifiers: ([] : Array), - - errors: (null : ?Array), - - /** - * Calls the callback associated with the ID. Also unregister that callback - * if it was a one time timer (setTimeout), and not unregister it if it was - * recurring (setInterval). - */ - callTimer(timerID: number, frameTime: number, didTimeout: ?boolean) { - warning( - timerID <= JSTimersExecution.GUID, - 'Tried to call timer with ID %s but no such timer exists.', - timerID - ); - - // timerIndex of -1 means that no timer with that ID exists. There are - // two situations when this happens, when a garbage timer ID was given - // and when a previously existing timer was deleted before this callback - // fired. In both cases we want to ignore the timer id, but in the former - // case we warn as well. - const timerIndex = JSTimersExecution.timerIDs.indexOf(timerID); - if (timerIndex === -1) { - return; - } - - const type = JSTimersExecution.types[timerIndex]; - const callback = JSTimersExecution.callbacks[timerIndex]; - if (!callback || !type) { - console.error('No callback found for timerID ' + timerID); - return; - } - - if (__DEV__) { - const identifier = JSTimersExecution.identifiers[timerIndex] || {}; - Systrace.beginEvent('Systrace.callTimer: ' + identifier.methodName); - } - - // Clear the metadata - if (type === 'setTimeout' || type === 'setImmediate' || - type === 'requestAnimationFrame' || type === 'requestIdleCallback') { - JSTimersExecution._clearIndex(timerIndex); - } - - try { - if (type === 'setTimeout' || type === 'setInterval' || - type === 'setImmediate') { - callback(); - } else if (type === 'requestAnimationFrame') { - callback(performanceNow()); - } else if (type === 'requestIdleCallback') { - callback({ - timeRemaining: function() { - // TODO: Optimisation: allow running for longer than one frame if - // there are no pending JS calls on the bridge from native. This - // would require a way to check the bridge queue synchronously. - return Math.max(0, FRAME_DURATION - (performanceNow() - frameTime)); - }, - didTimeout: !!didTimeout, - }); - } else { - console.error('Tried to call a callback with invalid type: ' + type); - } - } catch (e) { - // Don't rethrow so that we can run all timers. - if (!JSTimersExecution.errors) { - JSTimersExecution.errors = [e]; - } else { - JSTimersExecution.errors.push(e); - } - } - - if (__DEV__) { - Systrace.endEvent(); - } - }, - - /** - * This is called from the native side. We are passed an array of timerIDs, - * and - */ - callTimers(timerIDs: [number]) { - invariant( - timerIDs.length !== 0, - 'Cannot call `callTimers` with an empty list of IDs.' - ); - - JSTimersExecution.errors = null; - for (let i = 0; i < timerIDs.length; i++) { - JSTimersExecution.callTimer(timerIDs[i], 0); - } - - const errors = JSTimersExecution.errors; - if (errors) { - const errorCount = errors.length; - if (errorCount > 1) { - // Throw all the other errors in a setTimeout, which will throw each - // error one at a time - for (let ii = 1; ii < errorCount; ii++) { - require('JSTimers').setTimeout( - ((error) => { throw error; }).bind(null, errors[ii]), - 0 - ); - } - } - throw errors[0]; - } - }, - - callIdleCallbacks: function(frameTime: number) { - if (FRAME_DURATION - (performanceNow() - frameTime) < IDLE_CALLBACK_FRAME_DEADLINE) { - return; - } - - JSTimersExecution.errors = null; - if (JSTimersExecution.requestIdleCallbacks.length > 0) { - const passIdleCallbacks = JSTimersExecution.requestIdleCallbacks.slice(); - JSTimersExecution.requestIdleCallbacks = []; - - for (let i = 0; i < passIdleCallbacks.length; ++i) { - JSTimersExecution.callTimer(passIdleCallbacks[i], frameTime); - } - } - - if (JSTimersExecution.requestIdleCallbacks.length === 0) { - const { Timing } = require('NativeModules'); - Timing.setSendIdleEvents(false); - } - - if (JSTimersExecution.errors) { - JSTimersExecution.errors.forEach((error) => - require('JSTimers').setTimeout(() => { throw error; }, 0) - ); - } - }, - - /** - * Performs a single pass over the enqueued immediates. Returns whether - * more immediates are queued up (can be used as a condition a while loop). - */ - callImmediatesPass() { - if (__DEV__) { - Systrace.beginEvent('JSTimersExecution.callImmediatesPass()'); - } - - // The main reason to extract a single pass is so that we can track - // in the system trace - if (JSTimersExecution.immediates.length > 0) { - const passImmediates = JSTimersExecution.immediates.slice(); - JSTimersExecution.immediates = []; - - // Use for loop rather than forEach as per @vjeux's advice - // https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051 - for (let i = 0; i < passImmediates.length; ++i) { - JSTimersExecution.callTimer(passImmediates[i], 0); - } - } - - if (__DEV__) { - Systrace.endEvent(); - } - return JSTimersExecution.immediates.length > 0; - }, - - /** - * This is called after we execute any command we receive from native but - * before we hand control back to native. - */ - callImmediates() { - JSTimersExecution.errors = null; - while (JSTimersExecution.callImmediatesPass()) {} - if (JSTimersExecution.errors) { - JSTimersExecution.errors.forEach((error) => - require('JSTimers').setTimeout(() => { throw error; }, 0) - ); - } - }, - - /** - * Called from native (in development) when environment times are out-of-sync. - */ - emitTimeDriftWarning(warningMessage: string) { - if (hasEmittedTimeDriftWarning) { - return; - } - hasEmittedTimeDriftWarning = true; - console.warn(warningMessage); - }, - - _clearIndex(i: number) { - JSTimersExecution.timerIDs[i] = null; - JSTimersExecution.callbacks[i] = null; - JSTimersExecution.types[i] = null; - JSTimersExecution.identifiers[i] = null; - }, -}; - -module.exports = JSTimersExecution;