Skip to content

Commit cf55e4a

Browse files
rshestfacebook-github-bot
authored andcommitted
JS side implementation of PerformanceObserver API
Summary: [Changelog][Internal] This adds module specs for the native part of PerformanceObserver, as well as the interaction logic vs the NativePerformanceObserver API. See https://fb.quip.com/MdqgAk1Eb2dV for more detail. Reviewed By: rubennorte Differential Revision: D40897006 fbshipit-source-id: 77475f21dad9ee9dbe15df5a989eb08d314e6db2
1 parent d03a29c commit cf55e4a

File tree

2 files changed

+159
-8
lines changed

2 files changed

+159
-8
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
* @flow strict
8+
* @format
9+
*/
10+
11+
import type {TurboModule} from '../../TurboModule/RCTExport';
12+
13+
import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry';
14+
15+
export type RawTimeStamp = number;
16+
17+
export const RawPerformanceEntryTypeValues = {
18+
UNDEFINED: 0,
19+
};
20+
21+
export type RawPerformanceEntryType = number;
22+
23+
export type RawPerformanceEntry = $ReadOnly<{
24+
name: string,
25+
entryType: RawPerformanceEntryType,
26+
startTime: RawTimeStamp,
27+
duration: number,
28+
29+
// For "event" entries only:
30+
processingStart?: RawTimeStamp,
31+
processingEnd?: RawTimeStamp,
32+
interactionId?: RawTimeStamp,
33+
}>;
34+
35+
export type RawPerformanceEntryList = $ReadOnlyArray<RawPerformanceEntry>;
36+
37+
export interface Spec extends TurboModule {
38+
+startReporting: (entryType: string) => void;
39+
+stopReporting: (entryType: string) => void;
40+
+getPendingEntries: () => RawPerformanceEntryList;
41+
+setOnPerformanceEntryCallback: (callback?: () => void) => void;
42+
}
43+
44+
export default (TurboModuleRegistry.get<Spec>('PerformanceObserver'): ?Spec);

Libraries/WebPerformance/PerformanceObserver.js

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,38 @@
88
* @flow strict
99
*/
1010

11+
import type {
12+
RawPerformanceEntry,
13+
RawPerformanceEntryList,
14+
RawPerformanceEntryType,
15+
} from '../NativeModules/specs/NativePerformanceObserverCxx';
16+
17+
import NativePerformanceObserver from '../NativeModules/specs/NativePerformanceObserverCxx';
18+
import warnOnce from '../Utilities/warnOnce';
19+
1120
export type HighResTimeStamp = number;
12-
// TODO: Extend once new types (such as event) are supported
13-
export type PerformanceEntryType = empty;
21+
// TODO: Extend once new types (such as event) are supported.
22+
// TODO: Get rid of the "undefined" once there is at least one type supported.
23+
export type PerformanceEntryType = 'undefined';
1424

1525
export class PerformanceEntry {
1626
name: string;
1727
entryType: PerformanceEntryType;
1828
startTime: HighResTimeStamp;
1929
duration: number;
2030

31+
constructor(init: {
32+
name: string,
33+
entryType: PerformanceEntryType,
34+
startTime: HighResTimeStamp,
35+
duration: number,
36+
}) {
37+
this.name = init.name;
38+
this.entryType = init.entryType;
39+
this.startTime = init.startTime;
40+
this.duration = init.duration;
41+
}
42+
2143
// $FlowIgnore: Flow(unclear-type)
2244
toJSON(): Object {
2345
return {
@@ -29,6 +51,21 @@ export class PerformanceEntry {
2951
}
3052
}
3153

54+
function rawToPerformanceEntryType(
55+
type: RawPerformanceEntryType,
56+
): PerformanceEntryType {
57+
return 'undefined';
58+
}
59+
60+
function rawToPerformanceEntry(entry: RawPerformanceEntry): PerformanceEntry {
61+
return new PerformanceEntry({
62+
name: entry.name,
63+
entryType: rawToPerformanceEntryType(entry.entryType),
64+
startTime: entry.startTime,
65+
duration: entry.duration,
66+
});
67+
}
68+
3269
export type PerformanceEntryList = $ReadOnlyArray<PerformanceEntry>;
3370

3471
export class PerformanceObserverEntryList {
@@ -73,6 +110,19 @@ export type PerformanceObserverInit =
73110
type: PerformanceEntryType,
74111
};
75112

113+
let _observedEntryTypeRefCount: Map<PerformanceEntryType, number> = new Map();
114+
115+
let _observers: Set<PerformanceObserver> = new Set();
116+
117+
let _onPerformanceEntryCallbackIsSet: boolean = false;
118+
119+
function warnNoNativePerformanceObserver() {
120+
warnOnce(
121+
'missing-native-performance-observer',
122+
'Missing native implementation of PerformanceObserver',
123+
);
124+
}
125+
76126
/**
77127
* Implementation of the PerformanceObserver interface for RN,
78128
* corresponding to the standard in https://www.w3.org/TR/performance-timeline/
@@ -95,24 +145,81 @@ export type PerformanceObserverInit =
95145
*/
96146
export default class PerformanceObserver {
97147
_callback: PerformanceObserverCallback;
148+
_entryTypes: $ReadOnlySet<PerformanceEntryType>;
98149

99150
constructor(callback: PerformanceObserverCallback) {
100151
this._callback = callback;
101152
}
102153

103154
observe(options: PerformanceObserverInit) {
104-
console.log('PerformanceObserver: started observing');
155+
if (!NativePerformanceObserver) {
156+
warnNoNativePerformanceObserver();
157+
return;
158+
}
159+
if (!_onPerformanceEntryCallbackIsSet) {
160+
NativePerformanceObserver.setOnPerformanceEntryCallback(
161+
onPerformanceEntry,
162+
);
163+
_onPerformanceEntryCallbackIsSet = true;
164+
}
165+
if (options.entryTypes) {
166+
this._entryTypes = new Set(options.entryTypes);
167+
} else {
168+
this._entryTypes = new Set([options.type]);
169+
}
170+
this._entryTypes.forEach(type => {
171+
if (!_observedEntryTypeRefCount.has(type)) {
172+
NativePerformanceObserver.startReporting(type);
173+
}
174+
_observedEntryTypeRefCount.set(
175+
type,
176+
(_observedEntryTypeRefCount.get(type) ?? 0) + 1,
177+
);
178+
});
179+
_observers.add(this);
105180
}
106181

107182
disconnect(): void {
108-
console.log('PerformanceObserver: stopped observing');
109-
}
110-
111-
takeRecords(): PerformanceEntryList {
112-
return [];
183+
if (!NativePerformanceObserver) {
184+
warnNoNativePerformanceObserver();
185+
return;
186+
}
187+
this._entryTypes.forEach(type => {
188+
const entryTypeRefCount = _observedEntryTypeRefCount.get(type) ?? 0;
189+
if (entryTypeRefCount === 1) {
190+
_observedEntryTypeRefCount.delete(type);
191+
NativePerformanceObserver.stopReporting(type);
192+
} else if (entryTypeRefCount !== 0) {
193+
_observedEntryTypeRefCount.set(type, entryTypeRefCount - 1);
194+
}
195+
});
196+
_observers.delete(this);
197+
if (_observers.size === 0) {
198+
NativePerformanceObserver.setOnPerformanceEntryCallback();
199+
_onPerformanceEntryCallbackIsSet = false;
200+
}
113201
}
114202

115203
static supportedEntryTypes: $ReadOnlyArray<PerformanceEntryType> =
116204
// TODO: add types once they are fully supported
117205
Object.freeze([]);
118206
}
207+
208+
// This is a callback that gets scheduled and periodically called from the native side
209+
function onPerformanceEntry() {
210+
if (!NativePerformanceObserver) {
211+
return;
212+
}
213+
const rawEntries: RawPerformanceEntryList =
214+
NativePerformanceObserver.getPendingEntries();
215+
const entries = rawEntries.map(rawToPerformanceEntry);
216+
_observers.forEach(observer => {
217+
const entriesForObserver: PerformanceEntryList = entries.filter(entry =>
218+
observer._entryTypes.has(entry.entryType),
219+
);
220+
observer._callback(
221+
new PerformanceObserverEntryList(entriesForObserver),
222+
observer,
223+
);
224+
});
225+
}

0 commit comments

Comments
 (0)