• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facebook / react-native / 4d9a7017-b8a7-4b9f-88c7-fb35cd561d4b

pending completion
4d9a7017-b8a7-4b9f-88c7-fb35cd561d4b

push

CircleCI

Facebook GitHub Bot
Add PerformanceEventTiming API, according to the standard

3615 of 26397 branches covered (13.69%)

Branch coverage included in aggregate %.

23 of 23 new or added lines in 4 files covered. (100.0%)

7397 of 44564 relevant lines covered (16.6%)

258.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/Libraries/WebPerformance/PerformanceObserver.js
1
/**
2
 * Copyright (c) Meta Platforms, Inc. and affiliates.
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 *
7
 * @format
8
 * @flow strict
9
 */
10

11
import type {
12
  RawPerformanceEntry,
13
  RawPerformanceEntryType,
14
} from './NativePerformanceObserver';
15
import type {PerformanceEntryType} from './PerformanceEntry';
16

17
import warnOnce from '../Utilities/warnOnce';
×
18
import NativePerformanceObserver, {
×
19
  RawPerformanceEntryTypeValues,
20
} from './NativePerformanceObserver';
21
import {PerformanceEntry} from './PerformanceEntry';
×
22
import {PerformanceEventTiming} from './PerformanceEventTiming';
×
23

24
function rawToPerformanceEntryType(
25
  type: RawPerformanceEntryType,
26
): PerformanceEntryType {
27
  switch (type) {
×
28
    case RawPerformanceEntryTypeValues.MARK:
29
      return 'mark';
×
30
    case RawPerformanceEntryTypeValues.MEASURE:
31
      return 'measure';
×
32
    case RawPerformanceEntryTypeValues.EVENT:
33
      return 'event';
×
34
    case RawPerformanceEntryTypeValues.FIRST_INPUT:
35
      return 'first-input';
×
36
    default:
37
      throw new TypeError(
×
38
        `unexpected performance entry type received: ${type}`,
39
      );
40
  }
41
}
42

43
function rawToPerformanceEntry(entry: RawPerformanceEntry): PerformanceEntry {
44
  if (
×
45
    entry.entryType === RawPerformanceEntryTypeValues.EVENT ||
×
46
    entry.entryType === RawPerformanceEntryTypeValues.FIRST_INPUT
47
  ) {
48
    return new PerformanceEventTiming({
×
49
      name: entry.name,
50
      startTime: entry.startTime,
51
      duration: entry.duration,
52
      processingStart: entry.processingStart,
53
      processingEnd: entry.processingEnd,
54
      interactionId: entry.interactionId,
55
      isFirstInput:
56
        entry.entryType === RawPerformanceEntryTypeValues.FIRST_INPUT,
57
    });
58
  } else {
59
    return new PerformanceEntry({
×
60
      name: entry.name,
61
      entryType: rawToPerformanceEntryType(entry.entryType),
62
      startTime: entry.startTime,
63
      duration: entry.duration,
64
    });
65
  }
66
}
67

68
export type PerformanceEntryList = $ReadOnlyArray<PerformanceEntry>;
69

70
export class PerformanceObserverEntryList {
×
71
  _entries: PerformanceEntryList;
72

73
  constructor(entries: PerformanceEntryList) {
×
74
    this._entries = entries;
×
75
  }
×
76

77
  getEntries(): PerformanceEntryList {
78
    return this._entries;
×
79
  }
80

81
  getEntriesByType(type: PerformanceEntryType): PerformanceEntryList {
82
    return this._entries.filter(entry => entry.entryType === type);
×
83
  }
84

85
  getEntriesByName(
86
    name: string,
87
    type?: PerformanceEntryType,
88
  ): PerformanceEntryList {
89
    if (type === undefined) {
×
90
      return this._entries.filter(entry => entry.name === name);
×
91
    } else {
92
      return this._entries.filter(
×
93
        entry => entry.name === name && entry.entryType === type,
×
94
      );
95
    }
96
  }
×
97
}
98

99
export type PerformanceObserverCallback = (
100
  list: PerformanceObserverEntryList,
101
  observer: PerformanceObserver,
102
  // The number of buffered entries which got dropped from the buffer due to the buffer being full:
103
  droppedEntryCount?: number,
104
) => void;
105

106
export type PerformanceObserverInit =
107
  | {
108
      entryTypes: Array<PerformanceEntryType>,
109
    }
110
  | {
111
      type: PerformanceEntryType,
112
    };
113

114
type PerformanceObserverConfig = {|
115
  callback: PerformanceObserverCallback,
116
  entryTypes: $ReadOnlySet<PerformanceEntryType>,
117
|};
118

119
const observerCountPerEntryType: Map<PerformanceEntryType, number> = new Map();
×
120
const registeredObservers: Map<PerformanceObserver, PerformanceObserverConfig> =
121
  new Map();
×
122
let isOnPerformanceEntryCallbackSet: boolean = false;
×
123

124
// This is a callback that gets scheduled and periodically called from the native side
125
const onPerformanceEntry = () => {
×
126
  if (!NativePerformanceObserver) {
×
127
    return;
×
128
  }
129
  const entryResult = NativePerformanceObserver.popPendingEntries();
×
130
  const rawEntries = entryResult?.entries ?? [];
×
131
  const droppedEntriesCount = entryResult?.droppedEntriesCount;
×
132
  if (rawEntries.length === 0) {
×
133
    return;
×
134
  }
135
  const entries = rawEntries.map(rawToPerformanceEntry);
×
136
  for (const [observer, observerConfig] of registeredObservers.entries()) {
×
137
    const entriesForObserver: PerformanceEntryList = entries.filter(
×
138
      entry => observerConfig.entryTypes.has(entry.entryType) !== -1,
×
139
    );
140
    observerConfig.callback(
×
141
      new PerformanceObserverEntryList(entriesForObserver),
142
      observer,
143
      droppedEntriesCount,
144
    );
145
  }
146
};
147

148
function warnNoNativePerformanceObserver() {
149
  warnOnce(
×
150
    'missing-native-performance-observer',
151
    'Missing native implementation of PerformanceObserver',
152
  );
153
}
154

155
/**
156
 * Implementation of the PerformanceObserver interface for RN,
157
 * corresponding to the standard in https://www.w3.org/TR/performance-timeline/
158
 *
159
 * @example
160
 * const observer = new PerformanceObserver((list, _observer) => {
161
 *   const entries = list.getEntries();
162
 *   entries.forEach(entry => {
163
 *     reportEvent({
164
 *       eventName: entry.name,
165
 *       startTime: entry.startTime,
166
 *       endTime: entry.startTime + entry.duration,
167
 *       processingStart: entry.processingStart,
168
 *       processingEnd: entry.processingEnd,
169
 *       interactionId: entry.interactionId,
170
 *     });
171
 *   });
172
 * });
173
 * observer.observe({ type: "event" });
174
 */
175
export default class PerformanceObserver {
×
176
  _callback: PerformanceObserverCallback;
177
  _type: 'single' | 'multiple' | void;
178

179
  constructor(callback: PerformanceObserverCallback) {
×
180
    this._callback = callback;
×
181
  }
×
182

183
  observe(options: PerformanceObserverInit): void {
184
    if (!NativePerformanceObserver) {
×
185
      warnNoNativePerformanceObserver();
×
186
      return;
×
187
    }
188

189
    this._validateObserveOptions(options);
×
190

191
    let requestedEntryTypes;
192

193
    if (options.entryTypes) {
×
194
      this._type = 'multiple';
×
195
      requestedEntryTypes = new Set(options.entryTypes);
×
196
    } else {
197
      this._type = 'single';
×
198
      requestedEntryTypes = new Set([options.type]);
×
199
    }
200

201
    // The same observer may receive multiple calls to "observe", so we need
202
    // to check what is new on this call vs. previous ones.
203
    const currentEntryTypes = registeredObservers.get(this)?.entryTypes;
×
204
    const nextEntryTypes = currentEntryTypes
×
205
      ? union(requestedEntryTypes, currentEntryTypes)
206
      : requestedEntryTypes;
207

208
    // This `observe` call is a no-op because there are no new things to observe.
209
    if (currentEntryTypes && currentEntryTypes.size === nextEntryTypes.size) {
×
210
      return;
×
211
    }
212

213
    registeredObservers.set(this, {
×
214
      callback: this._callback,
215
      entryTypes: nextEntryTypes,
216
    });
217

218
    if (!isOnPerformanceEntryCallbackSet) {
×
219
      NativePerformanceObserver.setOnPerformanceEntryCallback(
×
220
        onPerformanceEntry,
221
      );
222
      isOnPerformanceEntryCallbackSet = true;
×
223
    }
224

225
    // We only need to start listenening to new entry types being observed in
226
    // this observer.
227
    const newEntryTypes = currentEntryTypes
×
228
      ? difference(requestedEntryTypes, currentEntryTypes)
229
      : requestedEntryTypes;
230
    for (const type of newEntryTypes) {
×
231
      if (!observerCountPerEntryType.has(type)) {
×
232
        NativePerformanceObserver.startReporting(type);
×
233
      }
234
      observerCountPerEntryType.set(
×
235
        type,
236
        (observerCountPerEntryType.get(type) ?? 0) + 1,
×
237
      );
238
    }
239
  }
240

241
  disconnect(): void {
242
    if (!NativePerformanceObserver) {
×
243
      warnNoNativePerformanceObserver();
×
244
      return;
×
245
    }
246

247
    const observerConfig = registeredObservers.get(this);
×
248
    if (!observerConfig) {
×
249
      return;
×
250
    }
251

252
    // Disconnect this observer
253
    for (const type of observerConfig.entryTypes) {
×
254
      const numberOfObserversForThisType =
×
255
        observerCountPerEntryType.get(type) ?? 0;
256
      if (numberOfObserversForThisType === 1) {
×
257
        observerCountPerEntryType.delete(type);
×
258
        NativePerformanceObserver.stopReporting(type);
×
259
      } else if (numberOfObserversForThisType !== 0) {
×
260
        observerCountPerEntryType.set(type, numberOfObserversForThisType - 1);
×
261
      }
262
    }
263

264
    // Disconnect all observers if this was the last one
265
    registeredObservers.delete(this);
×
266
    if (registeredObservers.size === 0) {
×
267
      NativePerformanceObserver.setOnPerformanceEntryCallback(undefined);
×
268
      isOnPerformanceEntryCallbackSet = false;
×
269
    }
270
  }
271

272
  _validateObserveOptions(options: PerformanceObserverInit): void {
273
    const {type, entryTypes} = options;
×
274

275
    if (!type && !entryTypes) {
×
276
      throw new TypeError(
×
277
        "Failed to execute 'observe' on 'PerformanceObserver': An observe() call must not include both entryTypes and type arguments.",
278
      );
279
    }
280

281
    if (entryTypes && type) {
×
282
      throw new TypeError(
×
283
        "Failed to execute 'observe' on 'PerformanceObserver': An observe() call must include either entryTypes or type arguments.",
284
      );
285
    }
286

287
    if (this._type === 'multiple' && type) {
×
288
      throw new Error(
×
289
        "Failed to execute 'observe' on 'PerformanceObserver': This observer has performed observe({entryTypes:...}, therefore it cannot perform observe({type:...})",
290
      );
291
    }
292

293
    if (this._type === 'single' && entryTypes) {
×
294
      throw new Error(
×
295
        "Failed to execute 'observe' on 'PerformanceObserver': This PerformanceObserver has performed observe({type:...}, therefore it cannot perform observe({entryTypes:...})",
296
      );
297
    }
298
  }
×
299

300
  static supportedEntryTypes: $ReadOnlyArray<PerformanceEntryType> =
301
    Object.freeze(['mark', 'measure', 'event', 'first-input']);
302
}
303

304
function union<T>(a: $ReadOnlySet<T>, b: $ReadOnlySet<T>): Set<T> {
305
  return new Set([...a, ...b]);
×
306
}
307

308
function difference<T>(a: $ReadOnlySet<T>, b: $ReadOnlySet<T>): Set<T> {
309
  return new Set([...a].filter(x => !b.has(x)));
×
310
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc