diff --git a/Libraries/Core/Timers/JSTimers.js b/Libraries/Core/Timers/JSTimers.js index a236c07cb..8109a96bc 100644 --- a/Libraries/Core/Timers/JSTimers.js +++ b/Libraries/Core/Timers/JSTimers.js @@ -17,6 +17,7 @@ const JSTimersExecution = require('JSTimersExecution'); const Platform = require('Platform'); const {Timing} = require('NativeModules'); +const performanceNow = require('fbjs/lib/performanceNow'); import type {JSTimerType} from 'JSTimersExecution'; @@ -131,14 +132,43 @@ const JSTimers = { /** * @param {function} func Callback to be invoked every frame and provided * with time remaining in frame. + * @param {?object} options */ - requestIdleCallback: function(func : Function) { + requestIdleCallback: function(func : Function, options : ?Object) { if (JSTimersExecution.requestIdleCallbacks.length === 0) { Timing.setSendIdleEvents(true); } - const id = _allocateCallback(func, 'requestIdleCallback'); + const timeout = options && options.timeout; + const id = _allocateCallback( + timeout != null ? + deadline => { + const timeoutId = JSTimersExecution.requestIdleCallbackTimeouts.get(id); + if (timeoutId) { + JSTimers.clearTimeout(timeoutId); + JSTimersExecution.requestIdleCallbackTimeouts.delete(id); + } + return func(deadline); + } : + func, + 'requestIdleCallback' + ); JSTimersExecution.requestIdleCallbacks.push(id); + + if (timeout != null) { + const timeoutId = JSTimers.setTimeout(() => { + const index = JSTimersExecution.requestIdleCallbacks.indexOf(id); + if (index > -1) { + JSTimersExecution.requestIdleCallbacks.splice(index, 1); + JSTimersExecution.callTimer(id, performanceNow(), true); + } + JSTimersExecution.requestIdleCallbackTimeouts.delete(id); + if (JSTimersExecution.requestIdleCallbacks.length === 0) { + Timing.setSendIdleEvents(false); + } + }, timeout); + JSTimersExecution.requestIdleCallbackTimeouts.set(id, timeoutId); + } return id; }, @@ -149,6 +179,12 @@ const JSTimers = { JSTimersExecution.requestIdleCallbacks.splice(index, 1); } + const timeoutId = JSTimersExecution.requestIdleCallbackTimeouts.get(timerID); + if (timeoutId) { + JSTimers.clearTimeout(timeoutId); + JSTimersExecution.requestIdleCallbackTimeouts.delete(timerID); + } + if (JSTimersExecution.requestIdleCallbacks.length === 0) { Timing.setSendIdleEvents(false); } diff --git a/Libraries/Core/Timers/JSTimersExecution.js b/Libraries/Core/Timers/JSTimersExecution.js index ee2275c33..cbb01aa54 100644 --- a/Libraries/Core/Timers/JSTimersExecution.js +++ b/Libraries/Core/Timers/JSTimersExecution.js @@ -45,6 +45,7 @@ const JSTimersExecution = { timerIDs: ([] : Array), immediates: [], requestIdleCallbacks: [], + requestIdleCallbackTimeouts: (new Map() : Map), identifiers: ([] : Array), errors: (null : ?Array), @@ -54,7 +55,7 @@ const JSTimersExecution = { * if it was a one time timer (setTimeout), and not unregister it if it was * recurring (setInterval). */ - callTimer(timerID: number, frameTime: number) { + callTimer(timerID: number, frameTime: number, didTimeout: ?boolean) { warning( timerID <= JSTimersExecution.GUID, 'Tried to call timer with ID %s but no such timer exists.', @@ -103,6 +104,7 @@ const JSTimersExecution = { // 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); diff --git a/RNTester/js/TimerExample.js b/RNTester/js/TimerExample.js index 6f2705b70..a4efe3cfc 100644 --- a/RNTester/js/TimerExample.js +++ b/RNTester/js/TimerExample.js @@ -52,6 +52,10 @@ class RequestIdleCallbackTester extends React.Component { Burn CPU inside of requestIdleCallback + + Run requestIdleCallback with timeout option + + Run background task @@ -78,6 +82,16 @@ class RequestIdleCallbackTester extends React.Component { }); }; + _runWithTimeout = () => { + cancelIdleCallback(this._idleTimer); + this._idleTimer = requestIdleCallback((deadline) => { + this.setState({ + message: `${deadline.timeRemaining()}ms remaining in frame, it did timeout: ${deadline.didTimeout ? 'yes' : 'no'}` + }); + }, { timeout: 100 }); + burnCPU(100); + }; + _runBackground = () => { cancelIdleCallback(this._idleTimer); const handler = (deadline) => {