diff --git a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js index 75b1e23be..ce83778ed 100644 --- a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js +++ b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js @@ -7,116 +7,117 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule JSTimers + * @flow */ 'use strict'; // Note that the module JSTimers is split into two in order to solve a cycle // in dependencies. NativeModules > BatchedBridge > MessageQueue > JSTimersExecution -var RCTTiming = require('NativeModules').Timing; -var JSTimersExecution = require('JSTimersExecution'); +const RCTTiming = require('NativeModules').Timing; +const JSTimersExecution = require('JSTimersExecution'); + +// 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; +} + +function _allocateCallback(func: Function, type: $Keys): number { + const id = JSTimersExecution.GUID++; + const freeIndex = _getFreeIndex(); + JSTimersExecution.timerIDs[freeIndex] = id; + JSTimersExecution.callbacks[freeIndex] = func; + JSTimersExecution.types[freeIndex] = type; + return id; +} + +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 !== JSTimersExecution.Type.setImmediate && + type !== JSTimersExecution.Type.requestIdleCallback) { + RCTTiming.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 * callback. */ -var JSTimers = { - Types: JSTimersExecution.Types, - - /** - * Returns a free index if one is available, and the next consecutive index - * otherwise. - */ - _getFreeIndex: function() { - var freeIndex = JSTimersExecution.timerIDs.indexOf(null); - if (freeIndex === -1) { - freeIndex = JSTimersExecution.timerIDs.length; - } - return freeIndex; - }, - +const JSTimers = { /** * @param {function} func Callback to be invoked after `duration` ms. * @param {number} duration Number of milliseconds. */ - setTimeout: function(func, duration, ...args) { - var newID = JSTimersExecution.GUID++; - var freeIndex = JSTimers._getFreeIndex(); - JSTimersExecution.timerIDs[freeIndex] = newID; - JSTimersExecution.callbacks[freeIndex] = function() { - return func.apply(undefined, args); - }; - JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.setTimeout; - RCTTiming.createTimer(newID, duration || 0, Date.now(), /** recurring */ false); - return newID; + setTimeout: function(func: Function, duration: number, ...args?: any): number { + const id = _allocateCallback(() => func.apply(undefined, args), + JSTimersExecution.Type.setTimeout); + RCTTiming.createTimer(id, duration || 0, Date.now(), /* recurring */ false); + return id; }, /** * @param {function} func Callback to be invoked every `duration` ms. * @param {number} duration Number of milliseconds. */ - setInterval: function(func, duration, ...args) { - var newID = JSTimersExecution.GUID++; - var freeIndex = JSTimers._getFreeIndex(); - JSTimersExecution.timerIDs[freeIndex] = newID; - JSTimersExecution.callbacks[freeIndex] = function() { - return func.apply(undefined, args); - }; - JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.setInterval; - RCTTiming.createTimer(newID, duration || 0, Date.now(), /** recurring */ true); - return newID; + setInterval: function(func: Function, duration: number, ...args?: any): number { + const id = _allocateCallback(() => func.apply(undefined, args), + JSTimersExecution.Type.setInterval); + RCTTiming.createTimer(id, duration || 0, Date.now(), /* recurring */ true); + return id; }, /** * @param {function} func Callback to be invoked before the end of the * current JavaScript execution loop. */ - setImmediate: function(func, ...args) { - var newID = JSTimersExecution.GUID++; - var freeIndex = JSTimers._getFreeIndex(); - JSTimersExecution.timerIDs[freeIndex] = newID; - JSTimersExecution.callbacks[freeIndex] = function() { - return func.apply(undefined, args); - }; - JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.setImmediate; - JSTimersExecution.immediates.push(newID); - return newID; + setImmediate: function(func: Function, ...args?: any) { + const id = _allocateCallback(() => func.apply(undefined, args), + JSTimersExecution.Type.setImmediate); + JSTimersExecution.immediates.push(id); + return id; }, /** * @param {function} func Callback to be invoked every frame. */ - requestAnimationFrame: function(func) { - var newID = JSTimersExecution.GUID++; - var freeIndex = JSTimers._getFreeIndex(); - JSTimersExecution.timerIDs[freeIndex] = newID; - JSTimersExecution.callbacks[freeIndex] = func; - JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.requestAnimationFrame; - RCTTiming.createTimer(newID, 1, Date.now(), /** recurring */ false); - return newID; + requestAnimationFrame: function(func : Function) { + const id = _allocateCallback(func, JSTimersExecution.Type.requestAnimationFrame); + RCTTiming.createTimer(id, 1, Date.now(), /* recurring */ false); + return id; }, /** * @param {function} func Callback to be invoked every frame and provided * with time remaining in frame. */ - requestIdleCallback: function(func) { + requestIdleCallback: function(func : Function) { if (JSTimersExecution.requestIdleCallbacks.length === 0) { RCTTiming.setSendIdleEvents(true); } - var newID = JSTimersExecution.GUID++; - var freeIndex = JSTimers._getFreeIndex(); - JSTimersExecution.timerIDs[freeIndex] = newID; - JSTimersExecution.callbacks[freeIndex] = func; - JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.requestIdleCallback; - JSTimersExecution.requestIdleCallbacks.push(newID); - return newID; + const id = _allocateCallback(func, JSTimersExecution.Type.requestIdleCallback); + JSTimersExecution.requestIdleCallbacks.push(id); + return id; }, - cancelIdleCallback: function(timerID) { - JSTimers._clearTimerID(timerID); - var index = JSTimersExecution.requestIdleCallbacks.indexOf(timerID); + cancelIdleCallback: function(timerID: number) { + _freeCallback(timerID); + const index = JSTimersExecution.requestIdleCallbacks.indexOf(timerID); if (index !== -1) { JSTimersExecution.requestIdleCallbacks.splice(index, 1); } @@ -126,43 +127,24 @@ var JSTimers = { } }, - clearTimeout: function(timerID) { - JSTimers._clearTimerID(timerID); + clearTimeout: function(timerID: number) { + _freeCallback(timerID); }, - clearInterval: function(timerID) { - JSTimers._clearTimerID(timerID); + clearInterval: function(timerID: number) { + _freeCallback(timerID); }, - clearImmediate: function(timerID) { - JSTimers._clearTimerID(timerID); - var index = JSTimersExecution.immediates.indexOf(timerID); + clearImmediate: function(timerID: number) { + _freeCallback(timerID); + const index = JSTimersExecution.immediates.indexOf(timerID); if (index !== -1) { JSTimersExecution.immediates.splice(index, 1); } }, - cancelAnimationFrame: function(timerID) { - JSTimers._clearTimerID(timerID); - }, - - _clearTimerID: function(timerID) { - // JSTimersExecution.timerIDs contains nulls after timers have been removed; - // ignore nulls upfront so indexOf doesn't find them - if (timerID == null) { - return; - } - - var index = JSTimersExecution.timerIDs.indexOf(timerID); - // See corresponding comment in `callTimers` for reasoning behind this - if (index !== -1) { - JSTimersExecution._clearIndex(index); - var type = JSTimersExecution.types[index]; - if (type !== JSTimersExecution.Type.setImmediate && - type !== JSTimersExecution.Type.requestIdleCallback) { - RCTTiming.deleteTimer(timerID); - } - } + cancelAnimationFrame: function(timerID: number) { + _freeCallback(timerID); }, }; diff --git a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js index 146018771..e306cc033 100644 --- a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js +++ b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule JSTimersExecution + * @flow */ 'use strict'; @@ -39,35 +40,43 @@ const JSTimersExecution = { requestIdleCallback: null, }), - // Parallel arrays: + // Parallel arrays callbacks: [], types: [], timerIDs: [], immediates: [], requestIdleCallbacks: [], + errors: (null : ?[Error]), + /** * 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, frameTime) { + callTimer(timerID: number, frameTime: number) { warning( timerID <= JSTimersExecution.GUID, 'Tried to call timer with ID %s but no such timer exists.', timerID ); - const timerIndex = JSTimersExecution.timerIDs.indexOf(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; + } // Clear the metadata if (type === JSTimersExecution.Type.setTimeout || @@ -83,8 +92,7 @@ const JSTimersExecution = { type === JSTimersExecution.Type.setImmediate) { callback(); } else if (type === JSTimersExecution.Type.requestAnimationFrame) { - const currentTime = performanceNow(); - callback(currentTime); + callback(performanceNow()); } else if (type === JSTimersExecution.Type.requestIdleCallback) { callback({ timeRemaining: function() { @@ -96,12 +104,14 @@ const JSTimersExecution = { }); } else { console.error('Tried to call a callback with invalid type: ' + type); - return; } } catch (e) { - // Don't rethrow so that we can run every other timer. - JSTimersExecution.errors = JSTimersExecution.errors || []; - JSTimersExecution.errors.push(e); + // Don't rethrow so that we can run all timers. + if (!JSTimersExecution.errors) { + JSTimersExecution.errors = [e]; + } else { + JSTimersExecution.errors.push(e); + } } }, @@ -109,14 +119,16 @@ const JSTimersExecution = { * This is called from the native side. We are passed an array of timerIDs, * and */ - callTimers(timerIDs) { + callTimers(timerIDs: [number]) { invariant( timerIDs.length !== 0, 'Cannot call `callTimers` with an empty list of IDs.' ); JSTimersExecution.errors = null; - timerIDs.forEach((id) => { JSTimersExecution.callTimer(id); }); + for (let i = 0; i < timerIDs.length; i++) { + JSTimersExecution.callTimer(timerIDs[i], 0); + } const errors = JSTimersExecution.errors; if (errors) { @@ -135,15 +147,12 @@ const JSTimersExecution = { } }, - callIdleCallbacks: function(frameTime) { - const { Timing } = require('NativeModules'); - + 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 = []; @@ -154,6 +163,7 @@ const JSTimersExecution = { } if (JSTimersExecution.requestIdleCallbacks.length === 0) { + const { Timing } = require('NativeModules'); Timing.setSendIdleEvents(false); } @@ -180,7 +190,7 @@ const JSTimersExecution = { // 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]); + JSTimersExecution.callTimer(passImmediates[i], 0); } } @@ -206,7 +216,7 @@ const JSTimersExecution = { /** * Called from native (in development) when environment times are out-of-sync. */ - emitTimeDriftWarning(warningMessage) { + emitTimeDriftWarning(warningMessage: string) { if (hasEmittedTimeDriftWarning) { return; } @@ -214,7 +224,7 @@ const JSTimersExecution = { console.warn(warningMessage); }, - _clearIndex(i) { + _clearIndex(i: number) { JSTimersExecution.timerIDs[i] = null; JSTimersExecution.callbacks[i] = null; JSTimersExecution.types[i] = null;