From af27d29553763b6d240e642e76b2b526b01ec2ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 9 Apr 2024 07:51:03 -0700 Subject: [PATCH] Fix problems in PerformanceObserver notifications (#43847) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/43847 Changelog: [internal] (This is an internal change because the API hasn't been released in OSS yet) This fixes 2 problems in how we dispatch `PerformanceObserver` notifications: 1. If an observer callback throws an error, the remaining observers don't receive notifications. 2. We're notifying observers with an empty list of events when they don't match the filters. Reviewed By: javache Differential Revision: D55646390 fbshipit-source-id: 6511c7babd45517baf42076308268ea89afe1265 --- .../performance/PerformanceObserver.js | 16 ++++-- .../__tests__/PerformanceObserver-test.js | 54 +++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) 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(); + }); });