Add remote API to uninstall the global error handler in RN
Reviewed By: fromcelticpark Differential Revision: D6426209 fbshipit-source-id: 804e73e0dc4e4b85b336e3627c00840d2ff3c9d6
This commit is contained in:
parent
ed2bfcb35a
commit
1d16923063
|
@ -12,7 +12,21 @@
|
|||
'use strict';
|
||||
|
||||
const MessageQueue = require('MessageQueue');
|
||||
const BatchedBridge = new MessageQueue();
|
||||
|
||||
// MessageQueue can install a global handler to catch all exceptions where JS users can register their own behavior
|
||||
// This handler makes all exceptions to be handled inside MessageQueue rather than by the VM at its origin
|
||||
// This makes stacktraces to be placed at MessageQueue rather than at where they were launched
|
||||
// The parameter __fbUninstallRNGlobalErrorHandler is passed to MessageQueue to prevent the handler from being installed
|
||||
//
|
||||
// __fbUninstallRNGlobalErrorHandler is conditionally set by the Inspector while the VM is paused for intialization
|
||||
// If the Inspector isn't present it defaults to undefined and the global error handler is installed
|
||||
// The Inspector can still call MessageQueue#uninstallGlobalErrorHandler to uninstalled on attach
|
||||
|
||||
const BatchedBridge = new MessageQueue(
|
||||
// $FlowFixMe
|
||||
typeof __fbUninstallRNGlobalErrorHandler !== 'undefined' &&
|
||||
__fbUninstallRNGlobalErrorHandler === true, // eslint-disable-line no-undef
|
||||
);
|
||||
|
||||
// Wire up the batched bridge on the global object so that we can call into it.
|
||||
// Ideally, this would be the inverse relationship. I.e. the native environment
|
||||
|
|
|
@ -59,7 +59,9 @@ class MessageQueue {
|
|||
|
||||
__spy: ?(data: SpyData) => void;
|
||||
|
||||
constructor() {
|
||||
__guard: (() => void) => void;
|
||||
|
||||
constructor(shouldUninstallGlobalErrorHandler: boolean = false) {
|
||||
this._lazyCallableModules = {};
|
||||
this._queue = [[], [], [], 0];
|
||||
this._successCallbacks = [];
|
||||
|
@ -67,6 +69,11 @@ class MessageQueue {
|
|||
this._callID = 0;
|
||||
this._lastFlush = 0;
|
||||
this._eventLoopStartTime = new Date().getTime();
|
||||
if (shouldUninstallGlobalErrorHandler) {
|
||||
this.uninstallGlobalErrorHandler();
|
||||
} else {
|
||||
this.installGlobalErrorHandler();
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
this._debugInfo = {};
|
||||
|
@ -252,11 +259,26 @@ class MessageQueue {
|
|||
}
|
||||
}
|
||||
|
||||
uninstallGlobalErrorHandler() {
|
||||
this.__guard = this.__guardUnsafe;
|
||||
}
|
||||
|
||||
installGlobalErrorHandler() {
|
||||
this.__guard = this.__guardSafe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private methods
|
||||
*/
|
||||
|
||||
__guard(fn: () => void) {
|
||||
// Lets exceptions propagate to be handled by the VM at the origin
|
||||
__guardUnsafe(fn: () => void) {
|
||||
this._inCall++;
|
||||
fn();
|
||||
this._inCall--;
|
||||
}
|
||||
|
||||
__guardSafe(fn: () => void) {
|
||||
this._inCall++;
|
||||
try {
|
||||
fn();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails oncall+react_native
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
@ -38,10 +39,12 @@ describe('MessageQueue', function() {
|
|||
queue = new MessageQueue();
|
||||
queue.registerCallableModule(
|
||||
'MessageQueueTestModule',
|
||||
MessageQueueTestModule
|
||||
MessageQueueTestModule,
|
||||
);
|
||||
queue.createDebugLookup(0, 'MessageQueueTestModule',
|
||||
['testHook1', 'testHook2']);
|
||||
queue.createDebugLookup(0, 'MessageQueueTestModule', [
|
||||
'testHook1',
|
||||
'testHook2',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should enqueue native calls', () => {
|
||||
|
@ -65,7 +68,15 @@ describe('MessageQueue', function() {
|
|||
|
||||
it('should call the stored callback', () => {
|
||||
let done = false;
|
||||
queue.enqueueNativeCall(0, 1, [], () => {}, () => { done = true; });
|
||||
queue.enqueueNativeCall(
|
||||
0,
|
||||
1,
|
||||
[],
|
||||
() => {},
|
||||
() => {
|
||||
done = true;
|
||||
},
|
||||
);
|
||||
queue.__invokeCallback(1, []);
|
||||
expect(done).toEqual(true);
|
||||
});
|
||||
|
@ -83,32 +94,92 @@ describe('MessageQueue', function() {
|
|||
});
|
||||
|
||||
it('should throw when calling with unknown module', () => {
|
||||
const unknownModule = 'UnknownModule', unknownMethod = 'UnknownMethod';
|
||||
const unknownModule = 'UnknownModule',
|
||||
unknownMethod = 'UnknownMethod';
|
||||
expect(() => queue.__callFunction(unknownModule, unknownMethod)).toThrow(
|
||||
`Module ${unknownModule} is not a registered callable module (calling ${unknownMethod})`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return lazily registered module', () => {
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
const dummyModule = {},
|
||||
name = 'modulesName';
|
||||
queue.registerLazyCallableModule(name, () => dummyModule);
|
||||
|
||||
expect(queue.getCallableModule(name)).toEqual(dummyModule);
|
||||
});
|
||||
|
||||
it('should not initialize lazily registered module before it was used for the first time', () => {
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
const dummyModule = {},
|
||||
name = 'modulesName';
|
||||
const factory = jest.fn(() => dummyModule);
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
expect(factory).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should initialize lazily registered module only once', () => {
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
const dummyModule = {},
|
||||
name = 'modulesName';
|
||||
const factory = jest.fn(() => dummyModule);
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
queue.getCallableModule(name);
|
||||
queue.getCallableModule(name);
|
||||
expect(factory).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should catch all exceptions if the global error handler is installed', () => {
|
||||
const errorMessage = 'intentional error';
|
||||
const errorModule = {
|
||||
explode: function() {
|
||||
throw new Error(errorMessage);
|
||||
},
|
||||
};
|
||||
const name = 'errorModuleName';
|
||||
const factory = jest.fn(() => errorModule);
|
||||
queue.__guardSafe = jest.fn(() => {});
|
||||
queue.__guardUnsafe = jest.fn(() => {});
|
||||
queue.installGlobalErrorHandler();
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
queue.callFunctionReturnFlushedQueue(name, 'explode', []);
|
||||
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(0);
|
||||
expect(queue.__guardSafe).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should propagate exceptions if the global error handler is uninstalled', () => {
|
||||
queue.uninstallGlobalErrorHandler();
|
||||
const errorMessage = 'intentional error';
|
||||
const errorModule = {
|
||||
explode: function() {
|
||||
throw new Error(errorMessage);
|
||||
},
|
||||
};
|
||||
const name = 'errorModuleName';
|
||||
const factory = jest.fn(() => errorModule);
|
||||
queue.__guardUnsafe = jest.fn(() => {});
|
||||
queue.__guardSafe = jest.fn(() => {});
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
queue.uninstallGlobalErrorHandler();
|
||||
queue.callFunctionReturnFlushedQueue(name, 'explode');
|
||||
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(2);
|
||||
expect(queue.__guardSafe).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should catch all exceptions if the global error handler is re-installed', () => {
|
||||
const errorMessage = 'intentional error';
|
||||
const errorModule = {
|
||||
explode: function() {
|
||||
throw new Error(errorMessage);
|
||||
},
|
||||
};
|
||||
const name = 'errorModuleName';
|
||||
const factory = jest.fn(() => errorModule);
|
||||
queue.__guardUnsafe = jest.fn(() => {});
|
||||
queue.__guardSafe = jest.fn(() => {});
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
queue.uninstallGlobalErrorHandler();
|
||||
queue.installGlobalErrorHandler();
|
||||
queue.callFunctionReturnFlushedQueue(name, 'explode');
|
||||
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(0);
|
||||
expect(queue.__guardSafe).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue