From 10f3b7bbdc477cfc47bf188679c462fdbafb9f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 22 Jul 2024 02:55:35 -0700 Subject: [PATCH] Restrict durationThreshold to event entry types only (#45524) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/45524 Changelog: [internal] The `durationThreshold` option is only meant to be used with `event` entry types. `mark`, `measure`, `longtask`, etc. shouldn't take that option into account, as per the spec. Reviewed By: mdvacca Differential Revision: D59918519 --- .../performance/PerformanceObserver.js | 61 ++++----- .../__tests__/PerformanceObserver-test.js | 117 +++++++++++------- 2 files changed, 93 insertions(+), 85 deletions(-) diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index 7bf0c1e3ebb3..440d0b55cf88 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -71,8 +71,8 @@ export type PerformanceObserverInit = type PerformanceObserverConfig = {| callback: PerformanceObserverCallback, - // Map of {entryType: durationThreshold} - entryTypes: $ReadOnlyMap, + entryTypes: $ReadOnlySet, + durationThreshold: ?number, |}; const observerCountPerEntryType: Map = new Map(); @@ -97,8 +97,15 @@ const onPerformanceEntry = () => { if (!observerConfig.entryTypes.has(entry.entryType)) { return false; } - const durationThreshold = observerConfig.entryTypes.get(entry.entryType); - return entry.duration >= (durationThreshold ?? 0); + + if ( + entry.entryType === 'event' && + observerConfig.durationThreshold != null + ) { + return entry.duration >= observerConfig.durationThreshold; + } + + return true; }); if (entriesForObserver.length !== 0) { try { @@ -122,21 +129,11 @@ export function warnNoNativePerformanceObserver() { } function applyDurationThresholds() { - const durationThresholds: Map = Array.from( - registeredObservers.values(), - ) - .map(config => config.entryTypes) - .reduce( - (accumulator, currentValue) => union(accumulator, currentValue), - new Map(), - ); - - for (const [entryType, durationThreshold] of durationThresholds) { - NativePerformanceObserver?.setDurationThreshold( - performanceEntryTypeToRaw(entryType), - durationThreshold ?? 0, - ); - } + const durationThresholds = Array.from(registeredObservers.values()) + .map(observerConfig => observerConfig.durationThreshold) + .filter(Boolean); + + return Math.min(...durationThresholds); } function getSupportedPerformanceEntryTypes(): $ReadOnlyArray { @@ -194,14 +191,10 @@ export default class PerformanceObserver { if (options.entryTypes) { this.#type = 'multiple'; - requestedEntryTypes = new Map( - options.entryTypes.map(t => [t, undefined]), - ); + requestedEntryTypes = new Set(options.entryTypes); } else { this.#type = 'single'; - requestedEntryTypes = new Map([ - [options.type, options.durationThreshold], - ]); + requestedEntryTypes = new Set([options.type]); } // The same observer may receive multiple calls to "observe", so we need @@ -218,6 +211,8 @@ export default class PerformanceObserver { registeredObservers.set(this, { callback: this.#callback, + durationThreshold: + options.type === 'event' ? options.durationThreshold : undefined, entryTypes: nextEntryTypes, }); @@ -322,20 +317,8 @@ export default class PerformanceObserver { getSupportedPerformanceEntryTypes(); } -// As a Set union, except if value exists in both, we take minimum -function union( - a: $ReadOnlyMap, - b: $ReadOnlyMap, -): Map { - const res = new Map(); - for (const [k, v] of a) { - if (!b.has(k)) { - res.set(k, v); - } else { - res.set(k, Math.min(v ?? 0, b.get(k) ?? 0)); - } - } - return res; +function union(a: $ReadOnlySet, b: $ReadOnlySet): Set { + return new Set([...a, ...b]); } function difference(a: $ReadOnlySet, b: $ReadOnlySet): Set { 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 06cfbff59cd8..35cf1e1062c7 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 @@ -52,49 +52,74 @@ describe('PerformanceObserver', () => { const observer = new PerformanceObserver((list, _observer) => {}); expect(() => - observer.observe({entryTypes: ['mark'], durationThreshold: 100}), + observer.observe({entryTypes: ['event', 'mark'], durationThreshold: 100}), ).toThrow(); }); + it('ignores durationThreshold when used with marks or measures', async () => { + let entries = []; + + const observer = new PerformanceObserver((list, _observer) => { + entries = [...entries, ...list.getEntries()]; + }); + + observer.observe({type: 'measure', durationThreshold: 100}); + + NativePerformanceObserver.logRawEntry({ + name: 'measure1', + entryType: RawPerformanceEntryTypeValues.MEASURE, + startTime: 0, + duration: 200, + }); + + await jest.runAllTicks(); + expect(entries).toHaveLength(1); + expect(entries.map(e => e.name)).toStrictEqual(['measure1']); + }); + it('handles durationThreshold argument as expected', async () => { let entries = []; const observer = new PerformanceObserver((list, _observer) => { entries = [...entries, ...list.getEntries()]; }); - observer.observe({type: 'mark', durationThreshold: 100}); + observer.observe({type: 'event', durationThreshold: 100}); NativePerformanceObserver.logRawEntry({ - name: 'mark1', - entryType: RawPerformanceEntryTypeValues.MARK, + name: 'event1', + entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, duration: 200, }); NativePerformanceObserver.logRawEntry({ - name: 'mark2', - entryType: RawPerformanceEntryTypeValues.MARK, + name: 'event2', + entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, duration: 20, }); NativePerformanceObserver.logRawEntry({ - name: 'mark3', - entryType: RawPerformanceEntryTypeValues.MARK, + name: 'event3', + entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, duration: 100, }); NativePerformanceObserver.logRawEntry({ - name: 'mark4', - entryType: RawPerformanceEntryTypeValues.MARK, + name: 'event4', + entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, duration: 500, }); await jest.runAllTicks(); expect(entries).toHaveLength(3); - expect(entries.map(e => e.name)).toStrictEqual(['mark1', 'mark3', 'mark4']); + expect(entries.map(e => e.name)).toStrictEqual([ + 'event1', + 'event3', + 'event4', + ]); }); it('correctly works with multiple PerformanceObservers with durationThreshold', async () => { @@ -118,36 +143,36 @@ describe('PerformanceObserver', () => { entries4 = [...entries4, ...list.getEntries()]; }); - observer2.observe({type: 'mark', durationThreshold: 200}); - observer1.observe({type: 'mark', durationThreshold: 100}); - observer3.observe({type: 'mark', durationThreshold: 300}); - observer3.observe({type: 'measure', durationThreshold: 500}); - observer4.observe({entryTypes: ['mark', 'measure']}); + observer2.observe({type: 'event', durationThreshold: 200}); + observer1.observe({type: 'event', durationThreshold: 100}); + observer3.observe({type: 'event', durationThreshold: 300}); + observer3.observe({type: 'event', durationThreshold: 500}); + observer4.observe({entryTypes: ['event']}); NativePerformanceObserver.logRawEntry({ - name: 'mark1', - entryType: RawPerformanceEntryTypeValues.MARK, + name: 'event1', + entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, duration: 200, }); NativePerformanceObserver.logRawEntry({ - name: 'mark2', - entryType: RawPerformanceEntryTypeValues.MARK, + name: 'event2', + entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, duration: 20, }); NativePerformanceObserver.logRawEntry({ - name: 'mark3', - entryType: RawPerformanceEntryTypeValues.MARK, + name: 'event3', + entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, duration: 100, }); NativePerformanceObserver.logRawEntry({ - name: 'mark4', - entryType: RawPerformanceEntryTypeValues.MARK, + name: 'event4', + entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, duration: 500, }); @@ -156,15 +181,15 @@ describe('PerformanceObserver', () => { observer1.disconnect(); NativePerformanceObserver.logRawEntry({ - name: 'mark5', - entryType: RawPerformanceEntryTypeValues.MARK, + name: 'event5', + entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, duration: 200, }); NativePerformanceObserver.logRawEntry({ - name: 'mark6', - entryType: RawPerformanceEntryTypeValues.MARK, + name: 'event6', + entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, duration: 300, }); @@ -173,8 +198,8 @@ describe('PerformanceObserver', () => { observer3.disconnect(); NativePerformanceObserver.logRawEntry({ - name: 'mark7', - entryType: RawPerformanceEntryTypeValues.MARK, + name: 'event7', + entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, duration: 200, }); @@ -183,26 +208,26 @@ describe('PerformanceObserver', () => { observer4.disconnect(); expect(entries1.map(e => e.name)).toStrictEqual([ - 'mark1', - 'mark3', - 'mark4', + 'event1', + 'event3', + 'event4', ]); expect(entries2.map(e => e.name)).toStrictEqual([ - 'mark1', - 'mark4', - 'mark5', - 'mark6', - 'mark7', + 'event1', + 'event4', + 'event5', + 'event6', + 'event7', ]); - expect(entries3.map(e => e.name)).toStrictEqual(['mark4', 'mark6']); + expect(entries3.map(e => e.name)).toStrictEqual(['event4', 'event6']); expect(entries4.map(e => e.name)).toStrictEqual([ - 'mark1', - 'mark2', - 'mark3', - 'mark4', - 'mark5', - 'mark6', - 'mark7', + 'event1', + 'event2', + 'event3', + 'event4', + 'event5', + 'event6', + 'event7', ]); });