diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index 980fd2d55aa..a31edf4a353 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -100,11 +100,17 @@ const onPerformanceEntry = () => { const durationThreshold = observerConfig.entryTypes.get(entry.entryType); return entry.duration >= (durationThreshold ?? 0); }); - observerConfig.callback( - new PerformanceObserverEntryList(entriesForObserver), - observer, - droppedEntriesCount, - ); + if (entriesForObserver.length !== 0) { + try { + observerConfig.callback( + new PerformanceObserverEntryList(entriesForObserver), + observer, + droppedEntriesCount, + ); + } catch (error) { + console.error(error); + } + } } }; diff --git a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js index cf46dcbfd0e..06cfbff59cd 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js @@ -205,4 +205,58 @@ describe('PerformanceObserver', () => { 'mark7', ]); }); + + it('should guard against errors in observer callbacks', () => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + + const observer1Callback = jest.fn(() => { + throw new Error('observer 1 callback'); + }); + const observer1 = new PerformanceObserver(observer1Callback); + + const observer2Callback = jest.fn(); + const observer2 = new PerformanceObserver(observer2Callback); + + observer1.observe({type: 'mark'}); + observer2.observe({type: 'mark'}); + + NativePerformanceObserver.logRawEntry({ + name: 'mark1', + entryType: RawPerformanceEntryTypeValues.MARK, + startTime: 0, + duration: 200, + }); + + jest.runAllTicks(); + + expect(observer1Callback).toHaveBeenCalled(); + expect(observer2Callback).toHaveBeenCalled(); + + expect(console.error).toHaveBeenCalledWith( + new Error('observer 1 callback'), + ); + }); + + it('should not invoke observers with non-matching entries', () => { + const observer1Callback = jest.fn(); + const observer1 = new PerformanceObserver(observer1Callback); + + const observer2Callback = jest.fn(); + const observer2 = new PerformanceObserver(observer2Callback); + + observer1.observe({type: 'mark'}); + observer2.observe({type: 'measure'}); + + NativePerformanceObserver.logRawEntry({ + name: 'mark1', + entryType: RawPerformanceEntryTypeValues.MARK, + startTime: 0, + duration: 200, + }); + + jest.runAllTicks(); + + expect(observer1Callback).toHaveBeenCalled(); + expect(observer2Callback).not.toHaveBeenCalled(); + }); });