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
This commit is contained in:
Jhen 2017-06-01 10:47:18 -07:00 committed by Facebook Github Bot
parent b975342e7b
commit cf51aee9a0
3 changed files with 55 additions and 3 deletions

View File

@ -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);
}

View File

@ -45,6 +45,7 @@ const JSTimersExecution = {
timerIDs: ([] : Array<?number>),
immediates: [],
requestIdleCallbacks: [],
requestIdleCallbackTimeouts: (new Map() : Map<number, number>),
identifiers: ([] : Array<null | {methodName: string}>),
errors: (null : ?Array<Error>),
@ -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);

View File

@ -52,6 +52,10 @@ class RequestIdleCallbackTester extends React.Component {
Burn CPU inside of requestIdleCallback
</RNTesterButton>
<RNTesterButton onPress={this._runWithTimeout.bind(this)}>
Run requestIdleCallback with timeout option
</RNTesterButton>
<RNTesterButton onPress={this._runBackground}>
Run background task
</RNTesterButton>
@ -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) => {