Skip to content

Commit

Permalink
Add flow-typing to MessageQueue
Browse files Browse the repository at this point in the history
Reviewed By: majak

Differential Revision: D3901545

fbshipit-source-id: a9ccc3d11794830e21c778df0ae7100d79f7ee73
  • Loading branch information
javache authored and Facebook Github Bot 8 committed Sep 23, 2016
1 parent 53c9dba commit acdd08a
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 83 deletions.
174 changes: 91 additions & 83 deletions Libraries/Utilities/MessageQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,52 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule MessageQueue
* @flow
*/

/*eslint no-bitwise: 0*/

'use strict';

const Systrace = require('Systrace');
const ErrorUtils = require('ErrorUtils');
const JSTimersExecution = require('JSTimersExecution');
const Systrace = require('Systrace');

const invariant = require('fbjs/lib/invariant');
const keyMirror = require('fbjs/lib/keyMirror');
const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
const defineLazyObjectProperty = require('defineLazyObjectProperty');
const invariant = require('fbjs/lib/invariant');
const stringifySafe = require('stringifySafe');

export type ConfigProvider = () => {
remoteModuleConfig: Array<ModuleConfig>,
};
export type MethodType = 'async' | 'promise' | 'sync';
export type ModuleConfig = [
string, /* name */
?Object, /* constants */
Array<string>, /* functions */
Array<number>, /* promise method IDs */
Array<number>, /* sync method IDs */
];
export type SpyData = {
type: number,
module: ?string,
method: string|number,
args: any
}

const TO_JS = 0;
const TO_NATIVE = 1;

const MODULE_IDS = 0;
const METHOD_IDS = 1;
const PARAMS = 2;
const MIN_TIME_BETWEEN_FLUSHES_MS = 5;

const TO_NATIVE = 1;
const TO_JS = 0;

const TRACE_TAG_REACT_APPS = 1 << 17;

const DEBUG_INFO_LIMIT = 32;

const MethodTypes = keyMirror({
async: null,
promise: null,
sync: null,
});

const guard = (fn) => {
try {
fn();
Expand All @@ -48,12 +61,24 @@ const guard = (fn) => {
}
};

type Config = {
remoteModuleConfig: Object,
};

class MessageQueue {
constructor(configProvider: () => Config) {
_callableModules: {[key: string]: Object};
_queue: [Array<number>, Array<number>, Array<any>];
_callbacks: [];
_callbackID: number;
_callID: number;
_lastFlush: number;
_eventLoopStartTime: number;

RemoteModules: Object;

_debugInfo: Object;
_remoteModuleTable: Object;
_remoteMethodTable: Object;

__spy: ?(data: SpyData) => void;

constructor(configProvider: ConfigProvider) {
this._callableModules = {};
this._queue = [[], [], [], 0];
this._callbacks = [];
Expand All @@ -68,27 +93,24 @@ class MessageQueue {
this._remoteMethodTable = {};
}

[
'invokeCallbackAndReturnFlushedQueue',
'callFunctionReturnFlushedQueue',
'callFunctionReturnResultAndFlushedQueue',
'flushedQueue',
].forEach((fn) => (this[fn] = this[fn].bind(this)));
(this:any).callFunctionReturnFlushedQueue = this.callFunctionReturnFlushedQueue.bind(this);
(this:any).callFunctionReturnResultAndFlushedQueue = this.callFunctionReturnResultAndFlushedQueue.bind(this);
(this:any).flushedQueue = this.flushedQueue.bind(this);
(this:any).invokeCallbackAndReturnFlushedQueue = this.invokeCallbackAndReturnFlushedQueue.bind(this);

lazyProperty(this, 'RemoteModules', () => {
defineLazyObjectProperty(this, 'RemoteModules', {get: () => {
const {remoteModuleConfig} = configProvider();
const modulesConfig = remoteModuleConfig;
return this._genModules(modulesConfig);
});
return this._genModules(remoteModuleConfig);
}});
}

/**
* Public APIs
*/

static spy(spyOrToggle){
static spy(spyOrToggle: boolean|(data: SpyData) => void){
if (spyOrToggle === true){
MessageQueue.prototype.__spy = (info)=>{
MessageQueue.prototype.__spy = info => {
console.log(`${info.type == TO_JS ? 'N->JS' : 'JS->N'} : ` +
`${info.module ? (info.module + '.') : ''}${info.method}` +
`(${JSON.stringify(info.args)})`);
Expand All @@ -100,7 +122,7 @@ class MessageQueue {
}
}

callFunctionReturnFlushedQueue(module, method, args) {
callFunctionReturnFlushedQueue(module: string, method: string, args: Array<any>) {
guard(() => {
this.__callFunction(module, method, args);
this.__callImmediates();
Expand All @@ -109,7 +131,7 @@ class MessageQueue {
return this.flushedQueue();
}

callFunctionReturnResultAndFlushedQueue(module, method, args) {
callFunctionReturnResultAndFlushedQueue(module: string, method: string, args: Array<any>) {
let result;
guard(() => {
result = this.__callFunction(module, method, args);
Expand All @@ -119,7 +141,7 @@ class MessageQueue {
return [result, this.flushedQueue()];
}

invokeCallbackAndReturnFlushedQueue(cbID, args) {
invokeCallbackAndReturnFlushedQueue(cbID: number, args: Array<any>) {
guard(() => {
this.__invokeCallback(cbID, args);
this.__callImmediates();
Expand All @@ -136,11 +158,15 @@ class MessageQueue {
return queue[0].length ? queue : null;
}

processModuleConfig(config, moduleID) {
processModuleConfig(config: ModuleConfig, moduleID: number) {
const info = this._genModule(config, moduleID);
if (!info) {
return null;
}

this.RemoteModules[info.name] = info.module;
if (__DEV__) {
this._createDebugLookup(config, moduleID, this._remoteModuleTable, this._remoteMethodTable);
this._createDebugLookup(config, moduleID);
}
return info.module;
}
Expand All @@ -149,8 +175,8 @@ class MessageQueue {
return new Date().getTime() - this._eventLoopStartTime;
}

registerCallableModule(name, methods) {
this._callableModules[name] = methods;
registerCallableModule(name: string, module: Object) {
this._callableModules[name] = module;
}

/**
Expand All @@ -162,11 +188,11 @@ class MessageQueue {
Systrace.endEvent();
}

__nativeCall(module, method, params, onFail, onSucc) {
__nativeCall(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) {
if (onFail || onSucc) {
if (__DEV__) {
const callId = this._callbackID >> 1;
this._debugInfo[callId] = [module, method];
this._debugInfo[callId] = [moduleID, methodID];
if (callId > DEBUG_INFO_LIMIT) {
delete this._debugInfo[callId - DEBUG_INFO_LIMIT];
}
Expand All @@ -183,15 +209,15 @@ class MessageQueue {
}
this._callID++;

this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);

if (__DEV__) {
// Any params sent over the bridge should be encodable as JSON
JSON.stringify(params);

// The params object should not be mutated after being queued
deepFreezeAndThrowOnMutationInDev(params);
deepFreezeAndThrowOnMutationInDev((params:any));
}
this._queue[PARAMS].push(params);

Expand All @@ -203,17 +229,17 @@ class MessageQueue {
this._lastFlush = now;
}
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
if (__DEV__ && this.__spy && isFinite(module)) {
if (__DEV__ && this.__spy && isFinite(moduleID)) {
this.__spy(
{ type: TO_NATIVE,
module: this._remoteModuleTable[module],
method: this._remoteMethodTable[module][method],
module: this._remoteModuleTable[moduleID],
method: this._remoteMethodTable[moduleID][methodID],
args: params }
);
}
}

__callFunction(module: string, method: string, args: any) {
__callFunction(module: string, method: string, args: Array<any>) {
this._lastFlush = new Date().getTime();
this._eventLoopStartTime = this._lastFlush;
Systrace.beginEvent(`${module}.${method}()`);
Expand All @@ -236,7 +262,7 @@ class MessageQueue {
return result;
}

__invokeCallback(cbID, args) {
__invokeCallback(cbID: number, args: Array<any>) {
this._lastFlush = new Date().getTime();
this._eventLoopStartTime = this._lastFlush;
const callback = this._callbacks[cbID];
Expand Down Expand Up @@ -293,13 +319,13 @@ class MessageQueue {
}

if (__DEV__) {
this._createDebugLookup(config, moduleID, this._remoteModuleTable, this._remoteMethodTable);
this._createDebugLookup(config, moduleID);
}
});
return modules;
}

_genModule(config, moduleID): ?Object {
_genModule(config: ModuleConfig, moduleID: number): ?{name: string, module: Object} {
if (!config) {
return null;
}
Expand All @@ -308,16 +334,15 @@ class MessageQueue {
if (moduleHasConstants(config)) {
[moduleName, constants, methods, promiseMethods, syncMethods] = config;
} else {
[moduleName, methods, promiseMethods, syncMethods] = config;
[moduleName, methods, promiseMethods, syncMethods] = (config:any);
}

const module = {};
methods && methods.forEach((methodName, methodID) => {
const isPromise = promiseMethods && arrayContains(promiseMethods, methodID);
const isSync = syncMethods && arrayContains(syncMethods, methodID);
invariant(!isPromise || !isSync, 'Cannot have a method that is both async and a sync hook');
const methodType = isPromise ? MethodTypes.promise :
isSync ? MethodTypes.sync : MethodTypes.async;
const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
module[methodName] = this._genMethod(moduleID, methodID, methodType);
});
Object.assign(module, constants);
Expand All @@ -329,23 +354,23 @@ class MessageQueue {
return { name: moduleName, module };
}

_genMethod(module, method, type) {
_genMethod(moduleID: number, methodID: number, type: MethodType) {
let fn = null;
const self = this;
if (type === MethodTypes.promise) {
fn = function(...args) {
if (type === 'promise') {
fn = function(...args: Array<any>) {
return new Promise((resolve, reject) => {
self.__nativeCall(module, method, args,
self.__nativeCall(moduleID, methodID, args,
(data) => resolve(data),
(errorData) => reject(createErrorFromErrorData(errorData)));
});
};
} else if (type === MethodTypes.sync) {
fn = function(...args) {
return global.nativeCallSyncHook(module, method, args);
} else if (type === 'sync') {
fn = function(...args: Array<any>) {
return global.nativeCallSyncHook(moduleID, methodID, args);
};
} else {
fn = function(...args) {
fn = function(...args: Array<any>) {
const lastArg = args.length > 0 ? args[args.length - 1] : null;
const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
const hasSuccessCallback = typeof lastArg === 'function';
Expand All @@ -358,14 +383,14 @@ class MessageQueue {
const onFail = hasErrorCallback ? secondLastArg : null;
const callbackCount = hasSuccessCallback + hasErrorCallback;
args = args.slice(0, args.length - callbackCount);
return self.__nativeCall(module, method, args, onFail, onSuccess);
return self.__nativeCall(moduleID, methodID, args, onFail, onSuccess);
};
}
fn.type = type;
return fn;
}

_createDebugLookup(config, moduleID, moduleTable, methodTable) {
_createDebugLookup(config: ModuleConfig, moduleID: number) {
if (!config) {
return;
}
Expand All @@ -374,15 +399,15 @@ class MessageQueue {
if (moduleHasConstants(config)) {
[moduleName, , methods] = config;
} else {
[moduleName, methods] = config;
[moduleName, methods] = (config:any);
}

moduleTable[moduleID] = moduleName;
methodTable[moduleID] = Object.assign({}, methods);
this._remoteModuleTable[moduleID] = moduleName;
this._remoteMethodTable[moduleID] = methods;
}
}

function moduleHasConstants(moduleArray: Array<Object|Array<>>): boolean {
function moduleHasConstants(moduleArray: ModuleConfig): boolean {
return !Array.isArray(moduleArray[1]);
}

Expand All @@ -396,25 +421,8 @@ function createErrorFromErrorData(errorData: {message: string}): Error {
...extraErrorInfo,
} = errorData;
const error = new Error(message);
error.framesToPop = 1;
(error:any).framesToPop = 1;
return Object.assign(error, extraErrorInfo);
}

function lazyProperty(target: Object, name: string, f: () => any) {
Object.defineProperty(target, name, {
configurable: true,
enumerable: true,
get() {
const value = f();
Object.defineProperty(target, name, {
configurable: true,
enumerable: true,
writeable: true,
value: value,
});
return value;
}
});
}

module.exports = MessageQueue;
1 change: 1 addition & 0 deletions Libraries/Utilities/__tests__/MessageQueue-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

const MessageQueueTestConfig = require('MessageQueueTestConfig');
jest.unmock('MessageQueue');
jest.unmock('defineLazyObjectProperty');

let MessageQueue;
let MessageQueueTestModule1;
Expand Down

0 comments on commit acdd08a

Please sign in to comment.