Skip to content

Commit 65c561e

Browse files
rubennortemeta-codesync[bot]
authored andcommitted
PerformanceObserver: default durationThreshold to 104ms (#56506)
Summary: Pull Request resolved: #56506 React Native's `PerformanceObserver` previously defaulted `durationThreshold` to 0 for `event` entries, while the W3C Event Timing spec (https://www.w3.org/TR/event-timing/#sec-modifications-perf-timeline) mandates a default of 104ms. The previous default flooded observers with short events (clicks, pointer up/down) that are not interesting for responsiveness measurement. This change brings React Native in line with the spec by making 104ms the default. The default is applied at the JSI bridge boundary in `NativePerformance.cpp` (the JS API boundary on the C++ side) when JS does not provide an explicit `durationThreshold`. The C++ public observer API (`PerformanceObserverObserveSingleOptions::durationThreshold`) and the global event buffer (`eventBuffer_.durationThreshold`) keep their `HighResDuration::zero()` default — applying 104ms to the global buffer would drop sub-104ms events at the source, breaking observers that explicitly request `durationThreshold: 0`. Changes in `react-native`: - `NativePerformance.cpp`: apply 104ms default at the JSI bridge boundary. - `PerformanceApiExample.js` (rn-tester): pass `durationThreshold: 0` to preserve previous behavior. - `EventTimingAPI-itest.js`: migrate 4 existing call sites to `{type: 'event', durationThreshold: 0}` and add a new test verifying the 104ms default. Sites using `{entryTypes: ['event']}` were converted to `{type: 'event', durationThreshold: 0}` since the spec (and existing JS validation in `PerformanceObserver.js`) disallows `durationThreshold` together with `entryTypes`. Changelog: [General][Fixed] - PerformanceObserver: `observe({type: 'event'})` now correctly defaults `durationThreshold` to 104ms per the W3C Event Timing spec instead of reporting all events. Reviewed By: hoxyq Differential Revision: D101629586 fbshipit-source-id: 897914e8582a3d16db9e9388606be5d0aebd19ab
1 parent 8944bb3 commit 65c561e

3 files changed

Lines changed: 61 additions & 6 deletions

File tree

packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ namespace facebook::react {
3434

3535
namespace {
3636

37+
/**
38+
* Default `durationThreshold` (in milliseconds) applied to `event` entries when
39+
* the JS API does not provide an explicit value. Per the W3C Event Timing
40+
* spec, observers for `event` entries default to a 104ms threshold:
41+
* https://www.w3.org/TR/event-timing/#sec-modifications-perf-timeline
42+
*/
43+
constexpr HighResDuration DEFAULT_EVENT_DURATION_THRESHOLD =
44+
HighResDuration::fromMilliseconds(104);
45+
3746
class PerformanceObserverWrapper : public jsi::NativeState {
3847
public:
3948
explicit PerformanceObserverWrapper(
@@ -337,7 +346,7 @@ void NativePerformance::observe(
337346
}
338347

339348
auto durationThreshold =
340-
options.durationThreshold.value_or(HighResDuration::zero());
349+
options.durationThreshold.value_or(DEFAULT_EVENT_DURATION_THRESHOLD);
341350

342351
// observer of type multiple
343352
if (options.entryTypes.has_value()) {

packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe('Event Timing API', () => {
3838
const callback = jest.fn();
3939

4040
const observer = new PerformanceObserver(callback);
41-
observer.observe({entryTypes: ['event']});
41+
observer.observe({type: 'event', durationThreshold: 0});
4242

4343
const root = Fantom.createRoot();
4444
Fantom.runTask(() => {
@@ -79,7 +79,7 @@ describe('Event Timing API', () => {
7979
const callback = jest.fn();
8080

8181
const observer = new PerformanceObserver(callback);
82-
observer.observe({entryTypes: ['event']});
82+
observer.observe({type: 'event', durationThreshold: 0});
8383

8484
const SIMULATED_PROCESSING_DELAY = 50;
8585

@@ -132,7 +132,7 @@ describe('Event Timing API', () => {
132132
const callback = jest.fn();
133133

134134
const observer = new PerformanceObserver(callback);
135-
observer.observe({entryTypes: ['event']});
135+
observer.observe({type: 'event', durationThreshold: 0});
136136

137137
function MyComponent() {
138138
const [count, setCount] = useState(0);
@@ -188,7 +188,7 @@ describe('Event Timing API', () => {
188188
const callback = jest.fn();
189189

190190
const observer = new PerformanceObserver(callback);
191-
observer.observe({entryTypes: ['event']});
191+
observer.observe({type: 'event', durationThreshold: 0});
192192

193193
let eventTimeStamp;
194194

@@ -341,6 +341,52 @@ describe('Event Timing API', () => {
341341
expect(callback).toHaveBeenCalledTimes(1);
342342
});
343343

344+
it('defaults to 104ms for event entries per W3C spec', () => {
345+
const callback = jest.fn();
346+
347+
const observer = new PerformanceObserver(callback);
348+
observer.observe({type: 'event'});
349+
350+
let forceDelay = false;
351+
352+
function MyComponent() {
353+
const [count, setCount] = useState(0);
354+
355+
return (
356+
<View
357+
onClick={event => {
358+
if (forceDelay) {
359+
sleep(104);
360+
}
361+
setCount(count + 1);
362+
}}>
363+
<Text>{count}</Text>
364+
</View>
365+
);
366+
}
367+
368+
const root = Fantom.createRoot();
369+
Fantom.runTask(() => {
370+
root.render(<MyComponent />);
371+
});
372+
373+
const element = nullthrows(
374+
root.document.documentElement.firstElementChild,
375+
);
376+
377+
expect(callback).not.toHaveBeenCalled();
378+
379+
Fantom.dispatchNativeEvent(element, 'click');
380+
381+
expect(callback).toHaveBeenCalledTimes(0);
382+
383+
forceDelay = true;
384+
385+
Fantom.dispatchNativeEvent(element, 'click');
386+
387+
expect(callback).toHaveBeenCalledTimes(1);
388+
});
389+
344390
it('throws when used together with `entryTypes`', () => {
345391
const observer = new PerformanceObserver(() => {});
346392

packages/rn-tester/js/examples/Performance/PerformanceApiExample.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ function PerformanceObserverEventTimingExample(): React.Node {
143143
setEntries(newEntries);
144144
});
145145

146-
observer.observe({type: 'event'});
146+
observer.observe({type: 'event', durationThreshold: 0});
147147

148148
return () => observer.disconnect();
149149
}, []);

0 commit comments

Comments
 (0)