Rename immediate to ReactNativeMicrotask in Bridge

Summary:
Changelog: [Internal]

This diff replaced all the internal occurrences of "Immediate" with
"ReactNativeMicrotask" in the legacy bridge and then polyfilled the
original immediate APIs during the timer setup phases as aliases of them.

Note that this diff is part of a larger refactoring.

Reviewed By: RSNara

Differential Revision: D29785430

fbshipit-source-id: 7325d2a7358a6c9baa3e9abb8acf90414de5072f
This commit is contained in:
Xuan Huang
2021-07-27 00:51:34 -07:00
committed by Facebook GitHub Bot
parent 893aff2e20
commit 37dc1d44a5
5 changed files with 95 additions and 76 deletions
+10 -10
View File
@@ -47,7 +47,7 @@ class MessageQueue {
_callID: number;
_lastFlush: number;
_eventLoopStartTime: number;
_immediatesCallback: ?() => void;
_reactNativeMicrotasksCallback: ?() => void;
_debugInfo: {[number]: [number, number], ...};
_remoteModuleTable: {[number]: string, ...};
@@ -63,7 +63,7 @@ class MessageQueue {
this._callID = 0;
this._lastFlush = 0;
this._eventLoopStartTime = Date.now();
this._immediatesCallback = null;
this._reactNativeMicrotasksCallback = null;
if (__DEV__) {
this._debugInfo = {};
@@ -132,7 +132,7 @@ class MessageQueue {
flushedQueue(): null | [Array<number>, Array<number>, Array<mixed>, number] {
this.__guard(() => {
this.__callImmediates();
this.__callReactNativeMicrotasks();
});
const queue = this._queue;
@@ -354,8 +354,8 @@ class MessageQueue {
// For JSTimers to register its callback. Otherwise a circular dependency
// between modules is introduced. Note that only one callback may be
// registered at a time.
setImmediatesCallback(fn: () => void) {
this._immediatesCallback = fn;
setReactNativeMicrotasksCallback(fn: () => void) {
this._reactNativeMicrotasksCallback = fn;
}
/**
@@ -387,10 +387,10 @@ class MessageQueue {
);
}
__callImmediates() {
Systrace.beginEvent('JSTimers.callImmediates()');
if (this._immediatesCallback != null) {
this._immediatesCallback();
__callReactNativeMicrotasks() {
Systrace.beginEvent('JSTimers.callReactNativeMicrotasks()');
if (this._reactNativeMicrotasksCallback != null) {
this._reactNativeMicrotasksCallback();
}
Systrace.endEvent();
}
@@ -409,7 +409,7 @@ class MessageQueue {
const moduleMethods = this.getCallableModule(module);
invariant(
!!moduleMethods,
`Module ${module} is not a registered callable module (calling ${method}). A frequent cause of the error is that the application entry file path is incorrect.
`Module ${module} is not a registered callable module (calling ${method}). A frequent cause of the error is that the application entry file path is incorrect.
This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native.`,
);
invariant(
@@ -108,7 +108,7 @@ describe('MessageQueue', function() {
const unknownModule = 'UnknownModule',
unknownMethod = 'UnknownMethod';
expect(() => queue.__callFunction(unknownModule, unknownMethod)).toThrow(
`Module ${unknownModule} is not a registered callable module (calling ${unknownMethod}). A frequent cause of the error is that the application entry file path is incorrect.
`Module ${unknownModule} is not a registered callable module (calling ${unknownMethod}). A frequent cause of the error is that the application entry file path is incorrect.
This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native.`,
);
});
+37 -28
View File
@@ -25,7 +25,7 @@ export type JSTimerType =
| 'setTimeout'
| 'setInterval'
| 'requestAnimationFrame'
| 'setImmediate'
| 'queueReactNativeMicrotask'
| 'requestIdleCallback';
// These timing constants should be kept in sync with the ones in native ios and
@@ -37,7 +37,7 @@ const IDLE_CALLBACK_FRAME_DEADLINE = 1;
const callbacks: Array<?Function> = [];
const types: Array<?JSTimerType> = [];
const timerIDs: Array<?number> = [];
let immediates: Array<number> = [];
let reactNativeMicrotasks: Array<number> = [];
let requestIdleCallbacks: Array<number> = [];
const requestIdleCallbackTimeouts: {[number]: number, ...} = {};
@@ -107,7 +107,7 @@ function _callTimer(timerID: number, frameTime: number, didTimeout: ?boolean) {
if (
type === 'setTimeout' ||
type === 'setInterval' ||
type === 'setImmediate'
type === 'queueReactNativeMicrotask'
) {
callback();
} else if (type === 'requestAnimationFrame') {
@@ -143,33 +143,33 @@ function _callTimer(timerID: number, frameTime: number, didTimeout: ?boolean) {
}
/**
* Performs a single pass over the enqueued immediates. Returns whether
* more immediates are queued up (can be used as a condition a while loop).
* Performs a single pass over the enqueued reactNativeMicrotasks. Returns whether
* more reactNativeMicrotasks are queued up (can be used as a condition a while loop).
*/
function _callImmediatesPass() {
if (immediates.length === 0) {
function _callReactNativeMicrotasksPass() {
if (reactNativeMicrotasks.length === 0) {
return false;
}
if (__DEV__) {
Systrace.beginEvent('callImmediatesPass()');
Systrace.beginEvent('callReactNativeMicrotasksPass()');
}
// The main reason to extract a single pass is so that we can track
// in the system trace
const passImmediates = immediates;
immediates = [];
const passReactNativeMicrotasks = reactNativeMicrotasks;
reactNativeMicrotasks = [];
// 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);
for (let i = 0; i < passReactNativeMicrotasks.length; ++i) {
_callTimer(passReactNativeMicrotasks[i], 0);
}
if (__DEV__) {
Systrace.endEvent();
}
return immediates.length > 0;
return reactNativeMicrotasks.length > 0;
}
function _clearIndex(i: number) {
@@ -190,7 +190,10 @@ function _freeCallback(timerID: number) {
if (index !== -1) {
const type = types[index];
_clearIndex(index);
if (type !== 'setImmediate' && type !== 'requestIdleCallback') {
if (
type !== 'queueReactNativeMicrotask' &&
type !== 'requestIdleCallback'
) {
deleteTimer(timerID);
}
}
@@ -233,15 +236,19 @@ const JSTimers = {
},
/**
* The React Native microtask mechanism is used to back public APIs e.g.
* `queueMicrotask`, `clearImmediate`, and `setImmediate` (which is used by
* the Promise polyfill) when the JSVM microtask mechanism is not used.
*
* @param {function} func Callback to be invoked before the end of the
* current JavaScript execution loop.
*/
setImmediate: function(func: Function, ...args: any) {
queueReactNativeMicrotask: function(func: Function, ...args: any) {
const id = _allocateCallback(
() => func.apply(undefined, args),
'setImmediate',
'queueReactNativeMicrotask',
);
immediates.push(id);
reactNativeMicrotasks.push(id);
return id;
},
@@ -323,11 +330,11 @@ const JSTimers = {
_freeCallback(timerID);
},
clearImmediate: function(timerID: number) {
clearReactNativeMicrotask: function(timerID: number) {
_freeCallback(timerID);
const index = immediates.indexOf(timerID);
const index = reactNativeMicrotasks.indexOf(timerID);
if (index !== -1) {
immediates.splice(index, 1);
reactNativeMicrotasks.splice(index, 1);
}
},
@@ -406,9 +413,9 @@ const JSTimers = {
* This is called after we execute any command we receive from native but
* before we hand control back to native.
*/
callImmediates() {
callReactNativeMicrotasks() {
errors = (null: ?Array<Error>);
while (_callImmediatesPass()) {}
while (_callReactNativeMicrotasksPass()) {}
if (errors) {
errors.forEach(error =>
JSTimers.setTimeout(() => {
@@ -452,17 +459,17 @@ function setSendIdleEvents(sendIdleEvents: boolean): void {
let ExportedJSTimers: {|
callIdleCallbacks: (frameTime: number) => any | void,
callImmediates: () => void,
callReactNativeMicrotasks: () => void,
callTimers: (timersToCall: Array<number>) => any | void,
cancelAnimationFrame: (timerID: number) => void,
cancelIdleCallback: (timerID: number) => void,
clearImmediate: (timerID: number) => void,
clearReactNativeMicrotask: (timerID: number) => void,
clearInterval: (timerID: number) => void,
clearTimeout: (timerID: number) => void,
emitTimeDriftWarning: (warningMessage: string) => any | void,
requestAnimationFrame: (func: any) => any | number,
requestIdleCallback: (func: any, options: ?any) => any | number,
setImmediate: (func: any, ...args: any) => number,
queueReactNativeMicrotask: (func: any, ...args: any) => number,
setInterval: (func: any, duration: number, ...args: any) => number,
setTimeout: (func: any, duration: number, ...args: any) => number,
|};
@@ -471,13 +478,15 @@ if (!NativeTiming) {
console.warn("Timing native module is not available, can't set timers.");
// $FlowFixMe[prop-missing] : we can assume timers are generally available
ExportedJSTimers = ({
callImmediates: JSTimers.callImmediates,
setImmediate: JSTimers.setImmediate,
callReactNativeMicrotasks: JSTimers.callReactNativeMicrotasks,
queueReactNativeMicrotask: JSTimers.queueReactNativeMicrotask,
}: typeof JSTimers);
} else {
ExportedJSTimers = JSTimers;
}
BatchedBridge.setImmediatesCallback(JSTimers.callImmediates);
BatchedBridge.setReactNativeMicrotasksCallback(
JSTimers.callReactNativeMicrotasks,
);
module.exports = ExportedJSTimers;
@@ -65,15 +65,15 @@ describe('JSTimers', function() {
expect(callCount).toBe(1);
});
it('should call nested setImmediate when cleared', function() {
it('should call nested queueReactNativeMicrotask when cleared', function() {
let id1, id2, id3;
let callCount = 0;
id1 = JSTimers.setImmediate(function() {
JSTimers.clearImmediate(id1);
id2 = JSTimers.setImmediate(function() {
JSTimers.clearImmediate(id2);
id3 = JSTimers.setImmediate(function() {
id1 = JSTimers.queueReactNativeMicrotask(function() {
JSTimers.clearReactNativeMicrotask(id1);
id2 = JSTimers.queueReactNativeMicrotask(function() {
JSTimers.clearReactNativeMicrotask(id2);
id3 = JSTimers.queueReactNativeMicrotask(function() {
callCount += 1;
});
});
@@ -132,64 +132,64 @@ describe('JSTimers', function() {
expect(callback).toBeCalledTimes(1);
});
it('should call function with setImmediate', function() {
it('should call function with queueReactNativeMicrotask', function() {
const callback = jest.fn();
JSTimers.setImmediate(callback);
JSTimers.callImmediates();
JSTimers.queueReactNativeMicrotask(callback);
JSTimers.callReactNativeMicrotasks();
expect(callback).toBeCalledTimes(1);
});
it('should not call function with clearImmediate', function() {
it('should not call function with clearReactNativeMicrotask', function() {
const callback = jest.fn();
const id = JSTimers.setImmediate(callback);
JSTimers.clearImmediate(id);
JSTimers.callImmediates();
const id = JSTimers.queueReactNativeMicrotask(callback);
JSTimers.clearReactNativeMicrotask(id);
JSTimers.callReactNativeMicrotasks();
expect(callback).not.toBeCalled();
});
it('should call functions in the right order with setImmediate', function() {
it('should call functions in the right order with queueReactNativeMicrotask', function() {
let count = 0;
let firstCalled = null;
let secondCalled = null;
JSTimers.setImmediate(function() {
JSTimers.queueReactNativeMicrotask(function() {
firstCalled = count++;
});
JSTimers.setImmediate(function() {
JSTimers.queueReactNativeMicrotask(function() {
secondCalled = count++;
});
JSTimers.callImmediates();
JSTimers.callReactNativeMicrotasks();
expect(firstCalled).toBe(0);
expect(secondCalled).toBe(1);
});
it('should call functions in the right order with nested setImmediate', function() {
it('should call functions in the right order with nested queueReactNativeMicrotask', function() {
let count = 0;
let firstCalled = null;
let secondCalled = null;
let thirdCalled = null;
JSTimers.setImmediate(function() {
JSTimers.queueReactNativeMicrotask(function() {
firstCalled = count++;
JSTimers.setImmediate(function() {
JSTimers.queueReactNativeMicrotask(function() {
thirdCalled = count++;
});
secondCalled = count++;
});
JSTimers.callImmediates();
JSTimers.callReactNativeMicrotasks();
expect(firstCalled).toBe(0);
expect(secondCalled).toBe(1);
expect(thirdCalled).toBe(2);
});
it('should call nested setImmediate', function() {
it('should call nested queueReactNativeMicrotask', function() {
let firstCalled = false;
let secondCalled = false;
JSTimers.setImmediate(function() {
JSTimers.queueReactNativeMicrotask(function() {
firstCalled = true;
JSTimers.setImmediate(function() {
JSTimers.queueReactNativeMicrotask(function() {
secondCalled = true;
});
});
JSTimers.callImmediates();
JSTimers.callReactNativeMicrotasks();
expect(firstCalled).toBe(true);
expect(secondCalled).toBe(true);
});
@@ -319,34 +319,34 @@ describe('JSTimers', function() {
);
});
it('should pass along errors thrown from setImmediate', function() {
JSTimers.setImmediate(function() {
throw new Error('error within setImmediate');
it('should pass along errors thrown from queueReactNativeMicrotask', function() {
JSTimers.queueReactNativeMicrotask(function() {
throw new Error('error within queueReactNativeMicrotask');
});
NativeTiming.createTimer = jest.fn();
JSTimers.callImmediates();
JSTimers.callReactNativeMicrotasks();
// The remaining errors should be called within setTimeout, in case there
// are a series of them
expect(NativeTiming.createTimer).toBeCalled();
const timerID = NativeTiming.createTimer.mock.calls[0][0];
expect(JSTimers.callTimers.bind(null, [timerID])).toThrowError(
'error within setImmediate',
'error within queueReactNativeMicrotask',
);
});
it('should throw all errors from setImmediate', function() {
JSTimers.setImmediate(function() {
it('should throw all errors from queueReactNativeMicrotask', function() {
JSTimers.queueReactNativeMicrotask(function() {
throw new Error('first error');
});
JSTimers.setImmediate(function() {
JSTimers.queueReactNativeMicrotask(function() {
throw new Error('second error');
});
NativeTiming.createTimer = jest.fn();
JSTimers.callImmediates();
JSTimers.callReactNativeMicrotasks();
expect(NativeTiming.createTimer.mock.calls.length).toBe(2);
+13 -3
View File
@@ -22,13 +22,23 @@ if (!global.RN$Bridgeless) {
polyfillGlobal(name, () => require('./Timers/JSTimers')[name]);
};
defineLazyTimer('setTimeout');
defineLazyTimer('setInterval');
defineLazyTimer('setImmediate');
defineLazyTimer('clearTimeout');
defineLazyTimer('setInterval');
defineLazyTimer('clearInterval');
defineLazyTimer('clearImmediate');
defineLazyTimer('requestAnimationFrame');
defineLazyTimer('cancelAnimationFrame');
defineLazyTimer('requestIdleCallback');
defineLazyTimer('cancelIdleCallback');
/**
* Set up immediate APIs as aliases to the ReactNativeMicrotask APIs.
*/
polyfillGlobal(
'setImmediate',
() => require('./Timers/JSTimers').queueReactNativeMicrotask,
);
polyfillGlobal(
'clearImmediate',
() => require('./Timers/JSTimers').clearReactNativeMicrotask,
);
}