mirror of
https://github.com/status-im/react-native.git
synced 2025-02-19 12:57:00 +00:00
Summary: public We're sending a lot of module config data when the app first starts, and much of this is redundant. UIExplorer current sends 19061 bytes of module config JSON. This diff reduces that to 16104 (15% saving) by stripping modules that have no methods or constants, and removing method types unless method is async. Reviewed By: tadeuzagallo, javache Differential Revision: D2570010 fb-gh-sync-id: 8c0abbd1cdee3264b37a4f52e852008caaffb9c5
278 lines
8.0 KiB
JavaScript
278 lines
8.0 KiB
JavaScript
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule MessageQueue
|
|
*/
|
|
|
|
/*eslint no-bitwise: 0*/
|
|
|
|
'use strict';
|
|
|
|
let BridgeProfiling = require('BridgeProfiling');
|
|
let ErrorUtils = require('ErrorUtils');
|
|
let JSTimersExecution = require('JSTimersExecution');
|
|
let ReactUpdates = require('ReactUpdates');
|
|
|
|
let invariant = require('invariant');
|
|
let keyMirror = require('keyMirror');
|
|
let stringifySafe = require('stringifySafe');
|
|
|
|
let MODULE_IDS = 0;
|
|
let METHOD_IDS = 1;
|
|
let PARAMS = 2;
|
|
let MIN_TIME_BETWEEN_FLUSHES_MS = 5;
|
|
|
|
let SPY_MODE = false;
|
|
|
|
let MethodTypes = keyMirror({
|
|
local: null,
|
|
remote: null,
|
|
remoteAsync: null,
|
|
});
|
|
|
|
var guard = (fn) => {
|
|
try {
|
|
fn();
|
|
} catch (error) {
|
|
ErrorUtils.reportFatalError(error);
|
|
}
|
|
};
|
|
|
|
class MessageQueue {
|
|
|
|
constructor(remoteModules, localModules, customRequire) {
|
|
this.RemoteModules = {};
|
|
|
|
this._require = customRequire || require;
|
|
this._queue = [[],[],[]];
|
|
this._moduleTable = {};
|
|
this._methodTable = {};
|
|
this._callbacks = [];
|
|
this._callbackID = 0;
|
|
this._lastFlush = 0;
|
|
|
|
[
|
|
'invokeCallbackAndReturnFlushedQueue',
|
|
'callFunctionReturnFlushedQueue',
|
|
'flushedQueue',
|
|
].forEach((fn) => this[fn] = this[fn].bind(this));
|
|
|
|
this._genModules(remoteModules);
|
|
localModules && this._genLookupTables(
|
|
localModules, this._moduleTable, this._methodTable);
|
|
|
|
this._debugInfo = {};
|
|
this._remoteModuleTable = {};
|
|
this._remoteMethodTable = {};
|
|
this._genLookupTables(
|
|
remoteModules, this._remoteModuleTable, this._remoteMethodTable);
|
|
}
|
|
|
|
/**
|
|
* Public APIs
|
|
*/
|
|
callFunctionReturnFlushedQueue(module, method, args) {
|
|
guard(() => {
|
|
this.__callFunction(module, method, args);
|
|
this.__callImmediates();
|
|
});
|
|
|
|
return this.flushedQueue();
|
|
}
|
|
|
|
invokeCallbackAndReturnFlushedQueue(cbID, args) {
|
|
guard(() => {
|
|
this.__invokeCallback(cbID, args);
|
|
this.__callImmediates();
|
|
});
|
|
|
|
return this.flushedQueue();
|
|
}
|
|
|
|
flushedQueue() {
|
|
this.__callImmediates();
|
|
|
|
let queue = this._queue;
|
|
this._queue = [[],[],[]];
|
|
return queue[0].length ? queue : null;
|
|
}
|
|
|
|
/**
|
|
* "Private" methods
|
|
*/
|
|
|
|
__callImmediates() {
|
|
BridgeProfiling.profile('JSTimersExecution.callImmediates()');
|
|
guard(() => JSTimersExecution.callImmediates());
|
|
BridgeProfiling.profileEnd();
|
|
}
|
|
|
|
__nativeCall(module, method, params, onFail, onSucc) {
|
|
if (onFail || onSucc) {
|
|
// eventually delete old debug info
|
|
(this._callbackID > (1 << 5)) &&
|
|
(this._debugInfo[this._callbackID >> 5] = null);
|
|
|
|
this._debugInfo[this._callbackID >> 1] = [module, method];
|
|
onFail && params.push(this._callbackID);
|
|
this._callbacks[this._callbackID++] = onFail;
|
|
onSucc && params.push(this._callbackID);
|
|
this._callbacks[this._callbackID++] = onSucc;
|
|
}
|
|
this._queue[MODULE_IDS].push(module);
|
|
this._queue[METHOD_IDS].push(method);
|
|
this._queue[PARAMS].push(params);
|
|
|
|
var now = new Date().getTime();
|
|
if (global.nativeFlushQueueImmediate &&
|
|
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
|
|
global.nativeFlushQueueImmediate(this._queue);
|
|
this._queue = [[],[],[]];
|
|
this._lastFlush = now;
|
|
}
|
|
if (__DEV__ && SPY_MODE && isFinite(module)) {
|
|
console.log('JS->N : ' + this._remoteModuleTable[module] + '.' +
|
|
this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')');
|
|
}
|
|
}
|
|
|
|
__callFunction(module, method, args) {
|
|
BridgeProfiling.profile(() => `${module}.${method}(${stringifySafe(args)})`);
|
|
this._lastFlush = new Date().getTime();
|
|
if (isFinite(module)) {
|
|
method = this._methodTable[module][method];
|
|
module = this._moduleTable[module];
|
|
}
|
|
if (__DEV__ && SPY_MODE) {
|
|
console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')');
|
|
}
|
|
module = this._require(module);
|
|
module[method].apply(module, args);
|
|
BridgeProfiling.profileEnd();
|
|
}
|
|
|
|
__invokeCallback(cbID, args) {
|
|
BridgeProfiling.profile(
|
|
() => `MessageQueue.invokeCallback(${cbID}, ${stringifySafe(args)})`);
|
|
this._lastFlush = new Date().getTime();
|
|
let callback = this._callbacks[cbID];
|
|
if (!callback || __DEV__) {
|
|
let debug = this._debugInfo[cbID >> 1];
|
|
let module = debug && this._remoteModuleTable[debug[0]];
|
|
let method = debug && this._remoteMethodTable[debug[0]][debug[1]];
|
|
invariant(
|
|
callback,
|
|
`Callback with id ${cbID}: ${module}.${method}() not found`
|
|
);
|
|
if (callback && SPY_MODE) {
|
|
console.log('N->JS : <callback for ' + module + '.' + method + '>(' + JSON.stringify(args) + ')');
|
|
}
|
|
}
|
|
this._callbacks[cbID & ~1] = null;
|
|
this._callbacks[cbID | 1] = null;
|
|
callback.apply(null, args);
|
|
BridgeProfiling.profileEnd();
|
|
}
|
|
|
|
/**
|
|
* Private helper methods
|
|
*/
|
|
_genLookupTables(localModules, moduleTable, methodTable) {
|
|
let moduleNames = Object.keys(localModules);
|
|
for (var i = 0, l = moduleNames.length; i < l; i++) {
|
|
let moduleName = moduleNames[i];
|
|
let methods = localModules[moduleName].methods || {};
|
|
let moduleID = localModules[moduleName].moduleID;
|
|
moduleTable[moduleID] = moduleName;
|
|
methodTable[moduleID] = {};
|
|
|
|
let methodNames = Object.keys(methods);
|
|
for (var j = 0, k = methodNames.length; j < k; j++) {
|
|
let methodName = methodNames[j];
|
|
let methodConfig = methods[methodName];
|
|
methodTable[moduleID][methodConfig.methodID] = methodName;
|
|
}
|
|
}
|
|
}
|
|
|
|
_genModules(remoteModules) {
|
|
let moduleNames = Object.keys(remoteModules);
|
|
for (var i = 0, l = moduleNames.length; i < l; i++) {
|
|
let moduleName = moduleNames[i];
|
|
let moduleConfig = remoteModules[moduleName];
|
|
this.RemoteModules[moduleName] = this._genModule({}, moduleConfig);
|
|
}
|
|
}
|
|
|
|
_genModule(module, moduleConfig) {
|
|
let methods = moduleConfig.methods || {};
|
|
let methodNames = Object.keys(methods);
|
|
for (var i = 0, l = methodNames.length; i < l; i++) {
|
|
let methodName = methodNames[i];
|
|
let methodConfig = methods[methodName];
|
|
module[methodName] = this._genMethod(
|
|
moduleConfig.moduleID,
|
|
methodConfig.methodID,
|
|
methodConfig.type || MethodTypes.remote
|
|
);
|
|
}
|
|
Object.assign(module, moduleConfig.constants);
|
|
return module;
|
|
}
|
|
|
|
_genMethod(module, method, type) {
|
|
if (type === MethodTypes.local) {
|
|
return null;
|
|
}
|
|
|
|
let fn = null;
|
|
let self = this;
|
|
if (type === MethodTypes.remoteAsync) {
|
|
fn = function(...args) {
|
|
return new Promise((resolve, reject) => {
|
|
self.__nativeCall(module, method, args, resolve, (errorData) => {
|
|
var error = createErrorFromErrorData(errorData);
|
|
reject(error);
|
|
});
|
|
});
|
|
};
|
|
} else {
|
|
fn = function(...args) {
|
|
let lastArg = args.length > 0 ? args[args.length - 1] : null;
|
|
let secondLastArg = args.length > 1 ? args[args.length - 2] : null;
|
|
let hasSuccCB = typeof lastArg === 'function';
|
|
let hasErrorCB = typeof secondLastArg === 'function';
|
|
hasErrorCB && invariant(
|
|
hasSuccCB,
|
|
'Cannot have a non-function arg after a function arg.'
|
|
);
|
|
let numCBs = hasSuccCB + hasErrorCB;
|
|
let onSucc = hasSuccCB ? lastArg : null;
|
|
let onFail = hasErrorCB ? secondLastArg : null;
|
|
args = args.slice(0, args.length - numCBs);
|
|
return self.__nativeCall(module, method, args, onFail, onSucc);
|
|
};
|
|
}
|
|
fn.type = type;
|
|
return fn;
|
|
}
|
|
|
|
}
|
|
|
|
function createErrorFromErrorData(errorData: {message: string}): Error {
|
|
var {
|
|
message,
|
|
...extraErrorInfo,
|
|
} = errorData;
|
|
var error = new Error(message);
|
|
error.framesToPop = 1;
|
|
return Object.assign(error, extraErrorInfo);
|
|
}
|
|
|
|
module.exports = MessageQueue;
|