mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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:
committed by
Lorenzo Sciandra
parent
d02b51332e
commit
ea0cd5b7ca
@@ -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);
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user