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
This commit is contained in:
Ruslan Shestopalyuk
2022-11-22 11:13:38 +00:00
committed by Lorenzo Sciandra
parent d02b51332e
commit ea0cd5b7ca
2 changed files with 159 additions and 8 deletions
@@ -0,0 +1,44 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
import type {TurboModule} from '../../TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry';
export type RawTimeStamp = number;
export const RawPerformanceEntryTypeValues = {
UNDEFINED: 0,
};
export type RawPerformanceEntryType = number;
export type RawPerformanceEntry = $ReadOnly<{
name: string,
entryType: RawPerformanceEntryType,
startTime: RawTimeStamp,
duration: number,
// For "event" entries only:
processingStart?: RawTimeStamp,
processingEnd?: RawTimeStamp,
interactionId?: RawTimeStamp,
}>;
export type RawPerformanceEntryList = $ReadOnlyArray<RawPerformanceEntry>;
export interface Spec extends TurboModule {
+startReporting: (entryType: string) => void;
+stopReporting: (entryType: string) => void;
+getPendingEntries: () => RawPerformanceEntryList;
+setOnPerformanceEntryCallback: (callback?: () => void) => void;
}
export default (TurboModuleRegistry.get<Spec>('PerformanceObserver'): ?Spec);
+115 -8
View File
@@ -8,9 +8,19 @@
* @flow strict
*/
import type {
RawPerformanceEntry,
RawPerformanceEntryList,
RawPerformanceEntryType,
} from '../NativeModules/specs/NativePerformanceObserverCxx';
import NativePerformanceObserver from '../NativeModules/specs/NativePerformanceObserverCxx';
import warnOnce from '../Utilities/warnOnce';
export type HighResTimeStamp = number;
// TODO: Extend once new types (such as event) are supported
export type PerformanceEntryType = empty;
// TODO: Extend once new types (such as event) are supported.
// TODO: Get rid of the "undefined" once there is at least one type supported.
export type PerformanceEntryType = 'undefined';
export class PerformanceEntry {
name: string;
@@ -18,6 +28,18 @@ export class PerformanceEntry {
startTime: HighResTimeStamp;
duration: number;
constructor(init: {
name: string,
entryType: PerformanceEntryType,
startTime: HighResTimeStamp,
duration: number,
}) {
this.name = init.name;
this.entryType = init.entryType;
this.startTime = init.startTime;
this.duration = init.duration;
}
// $FlowIgnore: Flow(unclear-type)
toJSON(): Object {
return {
@@ -29,6 +51,21 @@ export class PerformanceEntry {
}
}
function rawToPerformanceEntryType(
type: RawPerformanceEntryType,
): PerformanceEntryType {
return 'undefined';
}
function rawToPerformanceEntry(entry: RawPerformanceEntry): PerformanceEntry {
return new PerformanceEntry({
name: entry.name,
entryType: rawToPerformanceEntryType(entry.entryType),
startTime: entry.startTime,
duration: entry.duration,
});
}
export type PerformanceEntryList = $ReadOnlyArray<PerformanceEntry>;
export class PerformanceObserverEntryList {
@@ -73,6 +110,19 @@ export type PerformanceObserverInit =
type: PerformanceEntryType,
};
let _observedEntryTypeRefCount: Map<PerformanceEntryType, number> = new Map();
let _observers: Set<PerformanceObserver> = new Set();
let _onPerformanceEntryCallbackIsSet: boolean = false;
function warnNoNativePerformanceObserver() {
warnOnce(
'missing-native-performance-observer',
'Missing native implementation of PerformanceObserver',
);
}
/**
* Implementation of the PerformanceObserver interface for RN,
* corresponding to the standard in https://www.w3.org/TR/performance-timeline/
@@ -95,24 +145,81 @@ export type PerformanceObserverInit =
*/
export default class PerformanceObserver {
_callback: PerformanceObserverCallback;
_entryTypes: $ReadOnlySet<PerformanceEntryType>;
constructor(callback: PerformanceObserverCallback) {
this._callback = callback;
}
observe(options: PerformanceObserverInit) {
console.log('PerformanceObserver: started observing');
if (!NativePerformanceObserver) {
warnNoNativePerformanceObserver();
return;
}
if (!_onPerformanceEntryCallbackIsSet) {
NativePerformanceObserver.setOnPerformanceEntryCallback(
onPerformanceEntry,
);
_onPerformanceEntryCallbackIsSet = true;
}
if (options.entryTypes) {
this._entryTypes = new Set(options.entryTypes);
} else {
this._entryTypes = new Set([options.type]);
}
this._entryTypes.forEach(type => {
if (!_observedEntryTypeRefCount.has(type)) {
NativePerformanceObserver.startReporting(type);
}
_observedEntryTypeRefCount.set(
type,
(_observedEntryTypeRefCount.get(type) ?? 0) + 1,
);
});
_observers.add(this);
}
disconnect(): void {
console.log('PerformanceObserver: stopped observing');
}
takeRecords(): PerformanceEntryList {
return [];
if (!NativePerformanceObserver) {
warnNoNativePerformanceObserver();
return;
}
this._entryTypes.forEach(type => {
const entryTypeRefCount = _observedEntryTypeRefCount.get(type) ?? 0;
if (entryTypeRefCount === 1) {
_observedEntryTypeRefCount.delete(type);
NativePerformanceObserver.stopReporting(type);
} else if (entryTypeRefCount !== 0) {
_observedEntryTypeRefCount.set(type, entryTypeRefCount - 1);
}
});
_observers.delete(this);
if (_observers.size === 0) {
NativePerformanceObserver.setOnPerformanceEntryCallback();
_onPerformanceEntryCallbackIsSet = false;
}
}
static supportedEntryTypes: $ReadOnlyArray<PerformanceEntryType> =
// TODO: add types once they are fully supported
Object.freeze([]);
}
// This is a callback that gets scheduled and periodically called from the native side
function onPerformanceEntry() {
if (!NativePerformanceObserver) {
return;
}
const rawEntries: RawPerformanceEntryList =
NativePerformanceObserver.getPendingEntries();
const entries = rawEntries.map(rawToPerformanceEntry);
_observers.forEach(observer => {
const entriesForObserver: PerformanceEntryList = entries.filter(entry =>
observer._entryTypes.has(entry.entryType),
);
observer._callback(
new PerformanceObserverEntryList(entriesForObserver),
observer,
);
});
}