diff --git a/Libraries/BatchedBridge/MessageQueue.js b/Libraries/BatchedBridge/MessageQueue.js index 57c9434f87f..7347df36121 100644 --- a/Libraries/BatchedBridge/MessageQueue.js +++ b/Libraries/BatchedBridge/MessageQueue.js @@ -182,7 +182,19 @@ class MessageQueue { ); } this.processCallbacks(moduleID, methodID, params, onFail, onSucc); - return global.nativeCallSyncHook(moduleID, methodID, params); + try { + return global.nativeCallSyncHook(moduleID, methodID, params); + } catch (e) { + if ( + typeof e === 'object' && + e != null && + typeof e.framesToPop === 'undefined' && + /^Exception in HostFunction: /.test(e.message) + ) { + e.framesToPop = 2; + } + throw e; + } } processCallbacks( diff --git a/Libraries/BatchedBridge/__tests__/NativeModules-test.js b/Libraries/BatchedBridge/__tests__/NativeModules-test.js index a2fb9bb5d72..04909c8209b 100644 --- a/Libraries/BatchedBridge/__tests__/NativeModules-test.js +++ b/Libraries/BatchedBridge/__tests__/NativeModules-test.js @@ -211,6 +211,36 @@ describe('MessageQueue', function() { }); }); + it('throwing a "native" exception gets framesToPop = 2', function() { + global.nativeCallSyncHook = () => { + throw new Error('Exception in HostFunction: foo'); + }; + let error; + try { + NativeModules.RemoteModule1.syncMethod('paloAlto', 'menloPark'); + } catch (e) { + error = e; + } + // We can't test this behaviour with `getLineFromFrame` because our mock + // function adds an extra frame, so check `framesToPop` directly instead. + expect(error.framesToPop).toBe(2); + }); + + it('throwing a "native" exception preserves framesToPop if set', function() { + global.nativeCallSyncHook = () => { + const e = new Error('Exception in HostFunction: foo'); + e.framesToPop = 42; + throw e; + }; + let error; + try { + NativeModules.RemoteModule1.syncMethod('paloAlto', 'menloPark'); + } catch (e) { + error = e; + } + expect(error.framesToPop).toBe(42); + }); + it('returning a value', function() { global.nativeCallSyncHook = jest.fn(() => { return 'secondSucc';