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

facebook / react-native / f4551ac8-5bd8-4f75-8b5b-af4fabfc4d3c

pending completion
f4551ac8-5bd8-4f75-8b5b-af4fabfc4d3c

push

CircleCI

Facebook GitHub Bot
Remove "first-input" event type from Event Timing API logging implementation (#35771)

3633 of 26406 branches covered (13.76%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

7424 of 44590 relevant lines covered (16.65%)

204.26 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
    default:
35
      throw new TypeError(
×
36
        `unexpected performance entry type received: ${type}`,
37
      );
38
  }
39
}
40

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

61
export type PerformanceEntryList = $ReadOnlyArray<PerformanceEntry>;
62

63
export class PerformanceObserverEntryList {
×
64
  _entries: PerformanceEntryList;
65

66
  constructor(entries: PerformanceEntryList) {
×
67
    this._entries = entries;
×
68
  }
×
69

70
  getEntries(): PerformanceEntryList {
71
    return this._entries;
×
72
  }
73

74
  getEntriesByType(type: PerformanceEntryType): PerformanceEntryList {
75
    return this._entries.filter(entry => entry.entryType === type);
×
76
  }
77

78
  getEntriesByName(
79
    name: string,
80
    type?: PerformanceEntryType,
81
  ): PerformanceEntryList {
82
    if (type === undefined) {
×
83
      return this._entries.filter(entry => entry.name === name);
×
84
    } else {
85
      return this._entries.filter(
×
86
        entry => entry.name === name && entry.entryType === type,
×
87
      );
88
    }
89
  }
×
90
}
91

92
export type PerformanceObserverCallback = (
93
  list: PerformanceObserverEntryList,
94
  observer: PerformanceObserver,
95
  // The number of buffered entries which got dropped from the buffer due to the buffer being full:
96
  droppedEntryCount?: number,
97
) => void;
98

99
export type PerformanceObserverInit =
100
  | {
101
      entryTypes: Array<PerformanceEntryType>,
102
    }
103
  | {
104
      type: PerformanceEntryType,
105
    };
106

107
type PerformanceObserverConfig = {|
108
  callback: PerformanceObserverCallback,
109
  entryTypes: $ReadOnlySet<PerformanceEntryType>,
110
|};
111

112
const observerCountPerEntryType: Map<PerformanceEntryType, number> = new Map();
×
113
const registeredObservers: Map<PerformanceObserver, PerformanceObserverConfig> =
114
  new Map();
×
115
let isOnPerformanceEntryCallbackSet: boolean = false;
×
116

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

141
function warnNoNativePerformanceObserver() {
142
  warnOnce(
×
143
    'missing-native-performance-observer',
144
    'Missing native implementation of PerformanceObserver',
145
  );
146
}
147

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

172
  constructor(callback: PerformanceObserverCallback) {
×
173
    this._callback = callback;
×
174
  }
×
175

176
  observe(options: PerformanceObserverInit): void {
177
    if (!NativePerformanceObserver) {
×
178
      warnNoNativePerformanceObserver();
×
179
      return;
×
180
    }
181

182
    this._validateObserveOptions(options);
×
183

184
    let requestedEntryTypes;
185

186
    if (options.entryTypes) {
×
187
      this._type = 'multiple';
×
188
      requestedEntryTypes = new Set(options.entryTypes);
×
189
    } else {
190
      this._type = 'single';
×
191
      requestedEntryTypes = new Set([options.type]);
×
192
    }
193

194
    // The same observer may receive multiple calls to "observe", so we need
195
    // to check what is new on this call vs. previous ones.
196
    const currentEntryTypes = registeredObservers.get(this)?.entryTypes;
×
197
    const nextEntryTypes = currentEntryTypes
×
198
      ? union(requestedEntryTypes, currentEntryTypes)
199
      : requestedEntryTypes;
200

201
    // This `observe` call is a no-op because there are no new things to observe.
202
    if (currentEntryTypes && currentEntryTypes.size === nextEntryTypes.size) {
×
203
      return;
×
204
    }
205

206
    registeredObservers.set(this, {
×
207
      callback: this._callback,
208
      entryTypes: nextEntryTypes,
209
    });
210

211
    if (!isOnPerformanceEntryCallbackSet) {
×
212
      NativePerformanceObserver.setOnPerformanceEntryCallback(
×
213
        onPerformanceEntry,
214
      );
215
      isOnPerformanceEntryCallbackSet = true;
×
216
    }
217

218
    // We only need to start listenening to new entry types being observed in
219
    // this observer.
220
    const newEntryTypes = currentEntryTypes
×
221
      ? difference(requestedEntryTypes, currentEntryTypes)
222
      : requestedEntryTypes;
223
    for (const type of newEntryTypes) {
×
224
      if (!observerCountPerEntryType.has(type)) {
×
225
        NativePerformanceObserver.startReporting(type);
×
226
      }
227
      observerCountPerEntryType.set(
×
228
        type,
229
        (observerCountPerEntryType.get(type) ?? 0) + 1,
×
230
      );
231
    }
232
  }
233

234
  disconnect(): void {
235
    if (!NativePerformanceObserver) {
×
236
      warnNoNativePerformanceObserver();
×
237
      return;
×
238
    }
239

240
    const observerConfig = registeredObservers.get(this);
×
241
    if (!observerConfig) {
×
242
      return;
×
243
    }
244

245
    // Disconnect this observer
246
    for (const type of observerConfig.entryTypes) {
×
247
      const numberOfObserversForThisType =
×
248
        observerCountPerEntryType.get(type) ?? 0;
249
      if (numberOfObserversForThisType === 1) {
×
250
        observerCountPerEntryType.delete(type);
×
251
        NativePerformanceObserver.stopReporting(type);
×
252
      } else if (numberOfObserversForThisType !== 0) {
×
253
        observerCountPerEntryType.set(type, numberOfObserversForThisType - 1);
×
254
      }
255
    }
256

257
    // Disconnect all observers if this was the last one
258
    registeredObservers.delete(this);
×
259
    if (registeredObservers.size === 0) {
×
260
      NativePerformanceObserver.setOnPerformanceEntryCallback(undefined);
×
261
      isOnPerformanceEntryCallbackSet = false;
×
262
    }
263
  }
264

265
  _validateObserveOptions(options: PerformanceObserverInit): void {
266
    const {type, entryTypes} = options;
×
267

268
    if (!type && !entryTypes) {
×
269
      throw new TypeError(
×
270
        "Failed to execute 'observe' on 'PerformanceObserver': An observe() call must not include both entryTypes and type arguments.",
271
      );
272
    }
273

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

280
    if (this._type === 'multiple' && type) {
×
281
      throw new Error(
×
282
        "Failed to execute 'observe' on 'PerformanceObserver': This observer has performed observe({entryTypes:...}, therefore it cannot perform observe({type:...})",
283
      );
284
    }
285

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

293
  static supportedEntryTypes: $ReadOnlyArray<PerformanceEntryType> =
294
    Object.freeze(['mark', 'measure', 'event']);
295
}
296

297
function union<T>(a: $ReadOnlySet<T>, b: $ReadOnlySet<T>): Set<T> {
298
  return new Set([...a, ...b]);
×
299
}
300

301
function difference<T>(a: $ReadOnlySet<T>, b: $ReadOnlySet<T>): Set<T> {
302
  return new Set([...a].filter(x => !b.has(x)));
×
303
}
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

© 2025 Coveralls, Inc