2015-02-20 07:10:52 +03:00
|
|
|
/**
|
2018-09-12 01:27:47 +03:00
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
2015-03-23 23:35:08 +03:00
|
|
|
*
|
2018-02-17 05:24:55 +03:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2015-02-20 07:10:52 +03:00
|
|
|
*
|
2016-09-23 21:12:51 +03:00
|
|
|
* @flow
|
2017-09-28 23:11:08 +03:00
|
|
|
* @format
|
2015-02-20 07:10:52 +03:00
|
|
|
*/
|
|
|
|
|
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 17:51:48 +03:00
|
|
|
'use strict';
|
2015-04-29 21:54:26 +03:00
|
|
|
|
2019-05-08 18:44:25 +03:00
|
|
|
const ErrorUtils = require('../vendor/core/ErrorUtils');
|
|
|
|
const Systrace = require('../Performance/Systrace');
|
2015-03-24 03:09:14 +03:00
|
|
|
|
2019-05-08 18:44:25 +03:00
|
|
|
const deepFreezeAndThrowOnMutationInDev = require('../Utilities/deepFreezeAndThrowOnMutationInDev');
|
2018-12-03 10:49:12 +03:00
|
|
|
const invariant = require('invariant');
|
2019-05-08 18:44:25 +03:00
|
|
|
const stringifySafe = require('../Utilities/stringifySafe');
|
2019-09-23 20:12:45 +03:00
|
|
|
const warnOnce = require('../Utilities/warnOnce');
|
2015-03-24 03:09:14 +03:00
|
|
|
|
2016-09-23 21:12:51 +03:00
|
|
|
export type SpyData = {
|
|
|
|
type: number,
|
|
|
|
module: ?string,
|
2017-09-28 23:11:08 +03:00
|
|
|
method: string | number,
|
2017-10-11 02:20:15 +03:00
|
|
|
args: any[],
|
2019-11-21 20:38:13 +03:00
|
|
|
...
|
2017-09-28 23:11:08 +03:00
|
|
|
};
|
2016-09-23 21:12:51 +03:00
|
|
|
|
|
|
|
const TO_JS = 0;
|
|
|
|
const TO_NATIVE = 1;
|
|
|
|
|
2016-06-15 17:50:20 +03:00
|
|
|
const MODULE_IDS = 0;
|
|
|
|
const METHOD_IDS = 1;
|
|
|
|
const PARAMS = 2;
|
|
|
|
const MIN_TIME_BETWEEN_FLUSHES_MS = 5;
|
2015-02-20 07:10:52 +03:00
|
|
|
|
2017-10-11 02:20:15 +03:00
|
|
|
// eslint-disable-next-line no-bitwise
|
2016-08-04 18:38:49 +03:00
|
|
|
const TRACE_TAG_REACT_APPS = 1 << 17;
|
2015-07-09 19:12:53 +03:00
|
|
|
|
2017-09-08 00:23:13 +03:00
|
|
|
const DEBUG_INFO_LIMIT = 32;
|
2016-08-27 04:44:07 +03:00
|
|
|
|
2016-05-06 14:13:30 +03:00
|
|
|
class MessageQueue {
|
2019-11-21 20:38:13 +03:00
|
|
|
_lazyCallableModules: {[key: string]: (void) => Object, ...};
|
2017-10-11 02:20:15 +03:00
|
|
|
_queue: [number[], number[], any[], number];
|
2019-07-18 00:05:43 +03:00
|
|
|
_successCallbacks: Map<number, ?Function>;
|
|
|
|
_failureCallbacks: Map<number, ?Function>;
|
2016-09-23 21:12:51 +03:00
|
|
|
_callID: number;
|
|
|
|
_lastFlush: number;
|
|
|
|
_eventLoopStartTime: number;
|
2018-06-16 03:00:53 +03:00
|
|
|
_immediatesCallback: ?() => void;
|
2016-09-23 21:12:51 +03:00
|
|
|
|
2019-11-21 20:38:13 +03:00
|
|
|
_debugInfo: {[number]: [number, number], ...};
|
|
|
|
_remoteModuleTable: {[number]: string, ...};
|
|
|
|
_remoteMethodTable: {[number]: $ReadOnlyArray<string>, ...};
|
2016-09-23 21:12:51 +03:00
|
|
|
|
|
|
|
__spy: ?(data: SpyData) => void;
|
|
|
|
|
2018-04-03 15:59:07 +03:00
|
|
|
constructor() {
|
2017-06-06 17:03:10 +03:00
|
|
|
this._lazyCallableModules = {};
|
2016-01-04 13:15:19 +03:00
|
|
|
this._queue = [[], [], [], 0];
|
2019-07-18 00:05:43 +03:00
|
|
|
this._successCallbacks = new Map();
|
|
|
|
this._failureCallbacks = new Map();
|
2016-01-04 13:15:19 +03:00
|
|
|
this._callID = 0;
|
2015-10-13 18:00:36 +03:00
|
|
|
this._lastFlush = 0;
|
2018-09-21 20:59:19 +03:00
|
|
|
this._eventLoopStartTime = Date.now();
|
2018-06-16 03:00:53 +03:00
|
|
|
this._immediatesCallback = null;
|
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 17:51:48 +03:00
|
|
|
|
2016-06-15 17:50:20 +03:00
|
|
|
if (__DEV__) {
|
|
|
|
this._debugInfo = {};
|
|
|
|
this._remoteModuleTable = {};
|
|
|
|
this._remoteMethodTable = {};
|
|
|
|
}
|
2017-09-08 00:23:13 +03:00
|
|
|
|
2017-09-28 23:11:08 +03:00
|
|
|
(this: any).callFunctionReturnFlushedQueue = this.callFunctionReturnFlushedQueue.bind(
|
|
|
|
this,
|
|
|
|
);
|
|
|
|
(this: any).flushedQueue = this.flushedQueue.bind(this);
|
|
|
|
(this: any).invokeCallbackAndReturnFlushedQueue = this.invokeCallbackAndReturnFlushedQueue.bind(
|
|
|
|
this,
|
|
|
|
);
|
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 17:51:48 +03:00
|
|
|
}
|
2015-02-20 07:10:52 +03:00
|
|
|
|
|
|
|
/**
|
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 17:51:48 +03:00
|
|
|
* Public APIs
|
2015-02-20 07:10:52 +03:00
|
|
|
*/
|
2017-09-08 00:23:13 +03:00
|
|
|
|
2017-09-28 23:11:08 +03:00
|
|
|
static spy(spyOrToggle: boolean | ((data: SpyData) => void)) {
|
|
|
|
if (spyOrToggle === true) {
|
2016-09-23 21:12:51 +03:00
|
|
|
MessageQueue.prototype.__spy = info => {
|
2017-09-28 23:11:08 +03:00
|
|
|
console.log(
|
|
|
|
`${info.type === TO_JS ? 'N->JS' : 'JS->N'} : ` +
|
|
|
|
`${info.module ? info.module + '.' : ''}${info.method}` +
|
|
|
|
`(${JSON.stringify(info.args)})`,
|
|
|
|
);
|
2016-08-09 16:32:41 +03:00
|
|
|
};
|
2016-08-04 18:38:49 +03:00
|
|
|
} else if (spyOrToggle === false) {
|
|
|
|
MessageQueue.prototype.__spy = null;
|
|
|
|
} else {
|
|
|
|
MessageQueue.prototype.__spy = spyOrToggle;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-09 20:06:53 +03:00
|
|
|
callFunctionReturnFlushedQueue(
|
|
|
|
module: string,
|
|
|
|
method: string,
|
|
|
|
args: any[],
|
|
|
|
): null | [Array<number>, Array<number>, Array<any>, number] {
|
2017-03-28 17:11:45 +03:00
|
|
|
this.__guard(() => {
|
2015-10-13 13:22:40 +03:00
|
|
|
this.__callFunction(module, method, args);
|
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 17:51:48 +03:00
|
|
|
});
|
2015-09-29 13:14:14 +03:00
|
|
|
|
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 17:51:48 +03:00
|
|
|
return this.flushedQueue();
|
2017-09-08 00:23:13 +03:00
|
|
|
}
|
2015-02-20 07:10:52 +03:00
|
|
|
|
2020-02-06 14:33:35 +03:00
|
|
|
// Deprecated. T61834641: Remove me once native clients have updated
|
|
|
|
callFunctionReturnResultAndFlushedQueue(
|
|
|
|
module: string,
|
|
|
|
method: string,
|
|
|
|
args: any[],
|
|
|
|
): void {}
|
|
|
|
|
2019-08-09 20:06:53 +03:00
|
|
|
invokeCallbackAndReturnFlushedQueue(
|
|
|
|
cbID: number,
|
|
|
|
args: any[],
|
|
|
|
): null | [Array<number>, Array<number>, Array<any>, number] {
|
2017-03-28 17:11:45 +03:00
|
|
|
this.__guard(() => {
|
2015-10-13 13:22:40 +03:00
|
|
|
this.__invokeCallback(cbID, args);
|
|
|
|
});
|
|
|
|
|
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 17:51:48 +03:00
|
|
|
return this.flushedQueue();
|
2017-09-08 00:23:13 +03:00
|
|
|
}
|
2015-02-20 07:10:52 +03:00
|
|
|
|
2019-08-09 20:06:53 +03:00
|
|
|
flushedQueue(): null | [Array<number>, Array<number>, Array<any>, number] {
|
2017-06-02 18:16:04 +03:00
|
|
|
this.__guard(() => {
|
|
|
|
this.__callImmediates();
|
|
|
|
});
|
2015-10-13 13:22:40 +03:00
|
|
|
|
2016-06-15 17:50:20 +03:00
|
|
|
const queue = this._queue;
|
2016-01-04 13:15:19 +03:00
|
|
|
this._queue = [[], [], [], this._callID];
|
2015-10-13 13:22:40 +03:00
|
|
|
return queue[0].length ? queue : null;
|
2017-09-08 00:23:13 +03:00
|
|
|
}
|
2015-09-29 13:14:14 +03:00
|
|
|
|
2019-08-09 20:06:53 +03:00
|
|
|
getEventLoopRunningTime(): number {
|
2018-09-21 20:59:19 +03:00
|
|
|
return Date.now() - this._eventLoopStartTime;
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 03:10:39 +03:00
|
|
|
}
|
|
|
|
|
2016-09-23 21:12:51 +03:00
|
|
|
registerCallableModule(name: string, module: Object) {
|
2017-06-06 17:03:10 +03:00
|
|
|
this._lazyCallableModules[name] = () => module;
|
|
|
|
}
|
|
|
|
|
|
|
|
registerLazyCallableModule(name: string, factory: void => Object) {
|
|
|
|
let module: Object;
|
2017-09-28 23:11:08 +03:00
|
|
|
let getValue: ?(void) => Object = factory;
|
2017-06-06 17:03:10 +03:00
|
|
|
this._lazyCallableModules[name] = () => {
|
|
|
|
if (getValue) {
|
|
|
|
module = getValue();
|
|
|
|
getValue = null;
|
|
|
|
}
|
|
|
|
return module;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-08-09 20:06:53 +03:00
|
|
|
getCallableModule(name: string): any | null {
|
2017-06-08 22:40:19 +03:00
|
|
|
const getValue = this._lazyCallableModules[name];
|
|
|
|
return getValue ? getValue() : null;
|
2016-09-08 13:59:32 +03:00
|
|
|
}
|
|
|
|
|
2019-04-08 19:10:44 +03:00
|
|
|
callNativeSyncHook(
|
|
|
|
moduleID: number,
|
|
|
|
methodID: number,
|
|
|
|
params: any[],
|
|
|
|
onFail: ?Function,
|
|
|
|
onSucc: ?Function,
|
2019-08-09 20:06:53 +03:00
|
|
|
): any {
|
2019-04-08 19:10:44 +03:00
|
|
|
if (__DEV__) {
|
|
|
|
invariant(
|
|
|
|
global.nativeCallSyncHook,
|
|
|
|
'Calling synchronous methods on native ' +
|
|
|
|
'modules is not supported in Chrome.\n\n Consider providing alternative ' +
|
|
|
|
'methods to expose this method in debug mode, e.g. by exposing constants ' +
|
|
|
|
'ahead-of-time.',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this.processCallbacks(moduleID, methodID, params, onFail, onSucc);
|
2019-10-09 15:46:34 +03:00
|
|
|
return global.nativeCallSyncHook(moduleID, methodID, params);
|
2019-04-08 19:10:44 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
processCallbacks(
|
2017-09-28 23:11:08 +03:00
|
|
|
moduleID: number,
|
|
|
|
methodID: number,
|
2017-10-11 02:20:15 +03:00
|
|
|
params: any[],
|
2017-09-28 23:11:08 +03:00
|
|
|
onFail: ?Function,
|
|
|
|
onSucc: ?Function,
|
|
|
|
) {
|
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 17:51:48 +03:00
|
|
|
if (onFail || onSucc) {
|
2016-06-15 17:50:20 +03:00
|
|
|
if (__DEV__) {
|
2017-04-28 13:47:19 +03:00
|
|
|
this._debugInfo[this._callID] = [moduleID, methodID];
|
|
|
|
if (this._callID > DEBUG_INFO_LIMIT) {
|
|
|
|
delete this._debugInfo[this._callID - DEBUG_INFO_LIMIT];
|
2016-08-27 04:44:07 +03:00
|
|
|
}
|
2019-07-18 00:05:43 +03:00
|
|
|
if (this._successCallbacks.size > 500) {
|
|
|
|
const info = {};
|
|
|
|
this._successCallbacks.forEach((_, callID) => {
|
|
|
|
const debug = this._debugInfo[callID];
|
|
|
|
const module = debug && this._remoteModuleTable[debug[0]];
|
|
|
|
const method = debug && this._remoteMethodTable[debug[0]][debug[1]];
|
|
|
|
info[callID] = {module, method};
|
|
|
|
});
|
2019-09-23 20:12:45 +03:00
|
|
|
warnOnce(
|
|
|
|
'excessive-number-of-pending-callbacks',
|
2019-07-18 00:05:43 +03:00
|
|
|
`Please report: Excessive number of pending callbacks: ${
|
|
|
|
this._successCallbacks.size
|
2019-09-23 20:12:45 +03:00
|
|
|
}. Some pending callbacks that might have leaked by never being called from native code: ${stringifySafe(
|
|
|
|
info,
|
|
|
|
)}`,
|
2019-07-18 00:05:43 +03:00
|
|
|
);
|
|
|
|
}
|
2016-06-15 17:50:20 +03:00
|
|
|
}
|
2017-04-28 13:47:19 +03:00
|
|
|
// Encode callIDs into pairs of callback identifiers by shifting left and using the rightmost bit
|
|
|
|
// to indicate fail (0) or success (1)
|
2017-10-11 02:20:15 +03:00
|
|
|
// eslint-disable-next-line no-bitwise
|
2017-04-28 13:47:19 +03:00
|
|
|
onFail && params.push(this._callID << 1);
|
2017-10-11 02:20:15 +03:00
|
|
|
// eslint-disable-next-line no-bitwise
|
2017-04-28 13:47:19 +03:00
|
|
|
onSucc && params.push((this._callID << 1) | 1);
|
2019-07-18 00:05:43 +03:00
|
|
|
this._successCallbacks.set(this._callID, onSucc);
|
|
|
|
this._failureCallbacks.set(this._callID, onFail);
|
2015-06-15 23:01:39 +03:00
|
|
|
}
|
2016-06-21 22:02:31 +03:00
|
|
|
if (__DEV__) {
|
|
|
|
global.nativeTraceBeginAsyncFlow &&
|
2017-09-28 23:11:08 +03:00
|
|
|
global.nativeTraceBeginAsyncFlow(
|
|
|
|
TRACE_TAG_REACT_APPS,
|
|
|
|
'native',
|
|
|
|
this._callID,
|
|
|
|
);
|
2016-06-21 22:02:31 +03:00
|
|
|
}
|
2016-01-04 13:15:19 +03:00
|
|
|
this._callID++;
|
2019-04-08 19:10:44 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
enqueueNativeCall(
|
|
|
|
moduleID: number,
|
|
|
|
methodID: number,
|
|
|
|
params: any[],
|
|
|
|
onFail: ?Function,
|
|
|
|
onSucc: ?Function,
|
|
|
|
) {
|
|
|
|
this.processCallbacks(moduleID, methodID, params, onFail, onSucc);
|
2016-01-04 13:15:19 +03:00
|
|
|
|
2016-09-23 21:12:51 +03:00
|
|
|
this._queue[MODULE_IDS].push(moduleID);
|
|
|
|
this._queue[METHOD_IDS].push(methodID);
|
2016-09-08 13:59:32 +03:00
|
|
|
|
2016-09-19 14:43:09 +03:00
|
|
|
if (__DEV__) {
|
2017-12-14 22:05:35 +03:00
|
|
|
// Validate that parameters passed over the bridge are
|
|
|
|
// folly-convertible. As a special case, if a prop value is a
|
|
|
|
// function it is permitted here, and special-cased in the
|
|
|
|
// conversion.
|
|
|
|
const isValidArgument = val => {
|
|
|
|
const t = typeof val;
|
|
|
|
if (
|
|
|
|
t === 'undefined' ||
|
|
|
|
t === 'null' ||
|
|
|
|
t === 'boolean' ||
|
|
|
|
t === 'string'
|
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
2018-09-13 04:56:27 +03:00
|
|
|
if (t === 'number') {
|
|
|
|
return isFinite(val);
|
|
|
|
}
|
2017-12-14 22:05:35 +03:00
|
|
|
if (t === 'function' || t !== 'object') {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (Array.isArray(val)) {
|
|
|
|
return val.every(isValidArgument);
|
|
|
|
}
|
|
|
|
for (const k in val) {
|
|
|
|
if (typeof val[k] !== 'function' && !isValidArgument(val[k])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2018-09-13 04:56:27 +03:00
|
|
|
// Replacement allows normally non-JSON-convertible values to be
|
|
|
|
// seen. There is ambiguity with string values, but in context,
|
|
|
|
// it should at least be a strong hint.
|
|
|
|
const replacer = (key, val) => {
|
|
|
|
const t = typeof val;
|
|
|
|
if (t === 'function') {
|
|
|
|
return '<<Function ' + val.name + '>>';
|
|
|
|
} else if (t === 'number' && !isFinite(val)) {
|
|
|
|
return '<<' + val.toString() + '>>';
|
|
|
|
} else {
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Note that JSON.stringify
|
2017-12-14 22:05:35 +03:00
|
|
|
invariant(
|
|
|
|
isValidArgument(params),
|
|
|
|
'%s is not usable as a native method argument',
|
2018-09-13 04:56:27 +03:00
|
|
|
JSON.stringify(params, replacer),
|
2017-12-14 22:05:35 +03:00
|
|
|
);
|
2016-09-19 14:43:09 +03:00
|
|
|
|
|
|
|
// The params object should not be mutated after being queued
|
2017-09-28 23:11:08 +03:00
|
|
|
deepFreezeAndThrowOnMutationInDev((params: any));
|
2016-09-19 14:43:09 +03:00
|
|
|
}
|
|
|
|
this._queue[PARAMS].push(params);
|
2015-10-13 18:00:36 +03:00
|
|
|
|
2018-09-21 20:59:19 +03:00
|
|
|
const now = Date.now();
|
2017-09-28 23:11:08 +03:00
|
|
|
if (
|
|
|
|
global.nativeFlushQueueImmediate &&
|
2018-05-10 20:45:36 +03:00
|
|
|
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS
|
2017-09-28 23:11:08 +03:00
|
|
|
) {
|
2018-05-11 05:06:39 +03:00
|
|
|
const queue = this._queue;
|
2016-01-04 13:15:19 +03:00
|
|
|
this._queue = [[], [], [], this._callID];
|
2015-10-13 18:00:36 +03:00
|
|
|
this._lastFlush = now;
|
2017-03-31 10:46:14 +03:00
|
|
|
global.nativeFlushQueueImmediate(queue);
|
2015-10-13 18:00:36 +03:00
|
|
|
}
|
2015-12-30 10:36:34 +03:00
|
|
|
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
|
2017-09-08 00:23:13 +03:00
|
|
|
if (__DEV__ && this.__spy && isFinite(moduleID)) {
|
2017-09-28 23:11:08 +03:00
|
|
|
this.__spy({
|
|
|
|
type: TO_NATIVE,
|
|
|
|
module: this._remoteModuleTable[moduleID],
|
|
|
|
method: this._remoteMethodTable[moduleID][methodID],
|
|
|
|
args: params,
|
|
|
|
});
|
2017-09-08 00:23:13 +03:00
|
|
|
} else if (this.__spy) {
|
2017-09-28 23:11:08 +03:00
|
|
|
this.__spy({
|
|
|
|
type: TO_NATIVE,
|
|
|
|
module: moduleID + '',
|
|
|
|
method: methodID,
|
|
|
|
args: params,
|
|
|
|
});
|
2015-07-13 19:37:05 +03:00
|
|
|
}
|
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 17:51:48 +03:00
|
|
|
}
|
2015-02-20 07:10:52 +03:00
|
|
|
|
2019-06-19 19:09:57 +03:00
|
|
|
createDebugLookup(
|
|
|
|
moduleID: number,
|
|
|
|
name: string,
|
|
|
|
methods: ?$ReadOnlyArray<string>,
|
|
|
|
) {
|
2016-09-23 21:12:54 +03:00
|
|
|
if (__DEV__) {
|
|
|
|
this._remoteModuleTable[moduleID] = name;
|
2019-06-19 19:09:57 +03:00
|
|
|
this._remoteMethodTable[moduleID] = methods || [];
|
2016-09-23 21:12:54 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-16 03:00:53 +03:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2016-09-23 21:12:54 +03:00
|
|
|
/**
|
2017-03-28 17:11:45 +03:00
|
|
|
* Private methods
|
2016-09-23 21:12:54 +03:00
|
|
|
*/
|
|
|
|
|
2018-04-03 15:59:07 +03:00
|
|
|
__guard(fn: () => void) {
|
|
|
|
if (this.__shouldPauseOnThrow()) {
|
|
|
|
fn();
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
fn();
|
|
|
|
} catch (error) {
|
|
|
|
ErrorUtils.reportFatalError(error);
|
|
|
|
}
|
|
|
|
}
|
2017-12-11 18:59:45 +03:00
|
|
|
}
|
|
|
|
|
2018-04-03 15:59:07 +03:00
|
|
|
// MessageQueue installs a global handler to catch all exceptions where JS users can register their own behavior
|
|
|
|
// This handler makes all exceptions to be propagated from inside MessageQueue rather than by the VM at their origin
|
|
|
|
// This makes stacktraces to be placed at MessageQueue rather than at where they were launched
|
|
|
|
// The parameter DebuggerInternal.shouldPauseOnThrow is used to check before catching all exceptions and
|
|
|
|
// can be configured by the VM or any Inspector
|
2019-08-09 20:06:53 +03:00
|
|
|
__shouldPauseOnThrow(): boolean {
|
2018-04-03 15:59:07 +03:00
|
|
|
return (
|
|
|
|
// $FlowFixMe
|
2018-05-21 21:08:25 +03:00
|
|
|
typeof DebuggerInternal !== 'undefined' &&
|
|
|
|
DebuggerInternal.shouldPauseOnThrow === true // eslint-disable-line no-undef
|
2018-04-03 15:59:07 +03:00
|
|
|
);
|
2017-03-28 17:11:45 +03:00
|
|
|
}
|
|
|
|
|
2016-09-23 21:12:54 +03:00
|
|
|
__callImmediates() {
|
2017-06-22 19:46:28 +03:00
|
|
|
Systrace.beginEvent('JSTimers.callImmediates()');
|
2018-06-16 03:00:53 +03:00
|
|
|
if (this._immediatesCallback != null) {
|
|
|
|
this._immediatesCallback();
|
2017-06-22 19:46:28 +03:00
|
|
|
}
|
2016-09-23 21:12:54 +03:00
|
|
|
Systrace.endEvent();
|
|
|
|
}
|
|
|
|
|
2020-02-05 23:59:41 +03:00
|
|
|
__callFunction(module: string, method: string, args: any[]): void {
|
2018-09-21 20:59:19 +03:00
|
|
|
this._lastFlush = Date.now();
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 03:10:39 +03:00
|
|
|
this._eventLoopStartTime = this._lastFlush;
|
2018-04-30 19:52:34 +03:00
|
|
|
if (__DEV__ || this.__spy) {
|
|
|
|
Systrace.beginEvent(`${module}.${method}(${stringifySafe(args)})`);
|
|
|
|
} else {
|
|
|
|
Systrace.beginEvent(`${module}.${method}(...)`);
|
|
|
|
}
|
2017-02-07 04:39:15 +03:00
|
|
|
if (this.__spy) {
|
2017-09-28 23:11:08 +03:00
|
|
|
this.__spy({type: TO_JS, module, method, args});
|
2015-07-09 19:12:53 +03:00
|
|
|
}
|
2017-07-03 17:44:38 +03:00
|
|
|
const moduleMethods = this.getCallableModule(module);
|
2015-12-11 10:15:38 +03:00
|
|
|
invariant(
|
|
|
|
!!moduleMethods,
|
2016-08-10 17:41:54 +03:00
|
|
|
'Module %s is not a registered callable module (calling %s)',
|
2017-09-28 23:11:08 +03:00
|
|
|
module,
|
|
|
|
method,
|
2015-12-11 10:15:38 +03:00
|
|
|
);
|
2016-08-02 21:06:19 +03:00
|
|
|
invariant(
|
|
|
|
!!moduleMethods[method],
|
|
|
|
'Method %s does not exist on module %s',
|
2017-09-28 23:11:08 +03:00
|
|
|
method,
|
|
|
|
module,
|
2016-08-02 21:06:19 +03:00
|
|
|
);
|
2020-02-05 23:59:41 +03:00
|
|
|
moduleMethods[method].apply(moduleMethods, args);
|
2015-12-11 14:49:15 +03:00
|
|
|
Systrace.endEvent();
|
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 17:51:48 +03:00
|
|
|
}
|
2015-02-20 07:10:52 +03:00
|
|
|
|
2017-10-11 02:20:15 +03:00
|
|
|
__invokeCallback(cbID: number, args: any[]) {
|
2018-09-21 20:59:19 +03:00
|
|
|
this._lastFlush = Date.now();
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 03:10:39 +03:00
|
|
|
this._eventLoopStartTime = this._lastFlush;
|
2017-04-28 13:47:19 +03:00
|
|
|
|
|
|
|
// The rightmost bit of cbID indicates fail (0) or success (1), the other bits are the callID shifted left.
|
2017-10-11 02:20:15 +03:00
|
|
|
// eslint-disable-next-line no-bitwise
|
2017-04-28 13:47:19 +03:00
|
|
|
const callID = cbID >>> 1;
|
2017-10-11 02:20:15 +03:00
|
|
|
// eslint-disable-next-line no-bitwise
|
|
|
|
const isSuccess = cbID & 1;
|
|
|
|
const callback = isSuccess
|
2019-07-18 00:05:43 +03:00
|
|
|
? this._successCallbacks.get(callID)
|
|
|
|
: this._failureCallbacks.get(callID);
|
2016-06-15 17:50:20 +03:00
|
|
|
|
|
|
|
if (__DEV__) {
|
2017-04-28 13:47:19 +03:00
|
|
|
const debug = this._debugInfo[callID];
|
2016-06-15 17:50:20 +03:00
|
|
|
const module = debug && this._remoteModuleTable[debug[0]];
|
|
|
|
const method = debug && this._remoteMethodTable[debug[0]][debug[1]];
|
2019-04-08 19:10:44 +03:00
|
|
|
invariant(
|
|
|
|
callback,
|
|
|
|
`No callback found with cbID ${cbID} and callID ${callID} for ` +
|
|
|
|
(method
|
|
|
|
? ` ${module}.${method} - most likely the callback was already invoked`
|
|
|
|
: `module ${module || '<unknown>'}`) +
|
|
|
|
`. Args: '${stringifySafe(args)}'`,
|
|
|
|
);
|
2017-09-28 23:11:08 +03:00
|
|
|
const profileName = debug
|
|
|
|
? '<callback for ' + module + '.' + method + '>'
|
|
|
|
: cbID;
|
2017-09-08 00:23:13 +03:00
|
|
|
if (callback && this.__spy) {
|
2017-09-28 23:11:08 +03:00
|
|
|
this.__spy({type: TO_JS, module: null, method: profileName, args});
|
2016-06-15 17:50:20 +03:00
|
|
|
}
|
|
|
|
Systrace.beginEvent(
|
2017-09-28 23:11:08 +03:00
|
|
|
`MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`,
|
|
|
|
);
|
2015-02-20 07:10:52 +03:00
|
|
|
}
|
2016-06-15 17:50:20 +03:00
|
|
|
|
2017-04-28 13:47:19 +03:00
|
|
|
if (!callback) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-07-18 00:05:43 +03:00
|
|
|
this._successCallbacks.delete(callID);
|
|
|
|
this._failureCallbacks.delete(callID);
|
2017-10-11 02:20:15 +03:00
|
|
|
callback(...args);
|
2016-06-15 17:50:20 +03:00
|
|
|
|
|
|
|
if (__DEV__) {
|
|
|
|
Systrace.endEvent();
|
|
|
|
}
|
[ReactNative] Refactor BatchedBridge and MessageQueue
Summary:
@public
The current implementation of `MessageQueue` is huge, over-complicated and spread
across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory`
Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge`
is only an instance of it.
Test Plan:
I had to make some updates to the tests, but no real update to the native side.
There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer.
Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable.
Manually testing: Create a big hierarchy, like `<ListView>` example. Use the `TimerMixin` example to generate multiple calls.
Test the failure callback on the `Geolocation` example.
All the calls go through this entry point, so it's hard to miss if it's broken.
2015-06-17 17:51:48 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-20 07:10:52 +03:00
|
|
|
module.exports = MessageQueue;
|