From 09d9a1851352eedcccce0f7a069e895eb9fbac83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 9 Apr 2024 04:26:16 -0700 Subject: [PATCH 1/6] Fix problems in PerformanceObserver notifications Differential Revision: D55646390 --- .../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 980fd2d55aaa27..a31edf4a3534db 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 cf46dcbfd0e2d8..06cfbff59cd8d5 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(); + }); }); From 2522fa7251a7f2f7a2ffbf0763f89bee94c987a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 9 Apr 2024 04:26:16 -0700 Subject: [PATCH 2/6] Add example of Event Timing API in RNTester examples Differential Revision: D55646393 --- .../Performance/PerformanceApiExample.js | 74 ++++++++++++++++++- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/packages/rn-tester/js/examples/Performance/PerformanceApiExample.js b/packages/rn-tester/js/examples/Performance/PerformanceApiExample.js index 243f7145f46d67..609d721b2b21d4 100644 --- a/packages/rn-tester/js/examples/Performance/PerformanceApiExample.js +++ b/packages/rn-tester/js/examples/Performance/PerformanceApiExample.js @@ -19,6 +19,7 @@ import {Button, StyleSheet, Text, View} from 'react-native'; import Performance from 'react-native/src/private/webapis/performance/Performance'; import PerformanceObserver, { type PerformanceEntry, + type PerformanceEventTiming, } from 'react-native/src/private/webapis/performance/PerformanceObserver'; const {useState, useCallback} = React; @@ -132,12 +133,13 @@ function PerformanceObserverUserTimingExample(): React.Node { {entries.map((entry, index) => entry.entryType === 'mark' ? ( - Mark {entry.name}: {entry.startTime} + Mark {entry.name}: {entry.startTime.toFixed(2)} ) : ( - Measure {entry.name}: {entry.startTime} -{' '} - {entry.startTime + entry.duration} ({entry.duration}ms) + Measure {entry.name}: {entry.startTime.toFixed(2)} -{' '} + {(entry.startTime + entry.duration).toFixed(2)} ( + {entry.duration.toFixed(2)}ms) ), )} @@ -146,6 +148,66 @@ function PerformanceObserverUserTimingExample(): React.Node { ); } +function PerformanceObserverEventTimingExample(): React.Node { + const theme = useContext(RNTesterThemeContext); + + const [count, setCount] = useState(0); + + const [entries, setEntries] = useState< + $ReadOnlyArray, + >([]); + + useEffect(() => { + const observer = new PerformanceObserver(list => { + const newEntries: $ReadOnlyArray = + // $FlowExpectedError[incompatible-type] This is guaranteed because we're only observing `event` entry types. + list.getEntries(); + setEntries(newEntries); + }); + + observer.observe({entryTypes: ['event']}); + + return () => observer.disconnect(); + }, []); + + const onPress = useCallback(() => { + busyWait(500); + // Force a state update to show how/if we're reporting paint times as well. + setCount(currentCount => currentCount + 1); + }, []); + + return ( + +