From cf51aee9a0922bb43b57d2b35dd25a8420cd97bb Mon Sep 17 00:00:00 2001 From: Jhen Date: Thu, 1 Jun 2017 10:47:18 -0700 Subject: [PATCH] Support `options` param for `requestIdleCallback` Summary: The `requestIdleCallback` sometimes doesn't work in `Debug JS Remotely` mode if I use real device, the callback will never called. I guess it may be debugger worker and device caused by the time gap, or some cause websocket blocking, so it's just sometimes happening. I think we can support [options](https://developer.mozilla.org/zh-TW/docs/Web/API/Window/requestIdleCallback#Parameters) for that. Added an example `Run requestIdleCallback with timeout option` for Timers of UIExplorer, it use `{ timeout: 100 }` option with burn CPU 100ms, we can see `didTimeout` is true. Closes https://github.com/facebook/react-native/pull/13116 Differential Revision: D4894348 Pulled By: hramos fbshipit-source-id: 29c4c2fe5634b30a8bf8d3495305cd8f635ed922 --- Libraries/Core/Timers/JSTimers.js | 40 ++++++++++++++++++++-- Libraries/Core/Timers/JSTimersExecution.js | 4 ++- RNTester/js/TimerExample.js | 14 ++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) 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) => {