mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Reference implementation (mock) for NativePerformanceObserver (#36116)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36116 [Changelog][Internal] Add a minimal/reference JavaScript implementation for NativePerformanceObserver - the purpose is both unit testing (JS and native sides separately) and potentially shimming the part of functionality that is not dependent on native side. This is both a setup for adding general unit tests for the Performance* APIs, but also to be able to do non-trivial changes on JS side for WebPerformance (such as in (D43154319). Reviewed By: rubennorte Differential Revision: D43167392 fbshipit-source-id: 213d9534d810dece1dd464f910e92e08dbf39508
This commit is contained in:
committed by
Facebook GitHub Bot
parent
96fb708d3e
commit
f76d4dee6f
@@ -45,4 +45,10 @@ void NativePerformanceObserver::setOnPerformanceEntryCallback(
|
||||
PerformanceEntryReporter::getInstance().setReportingCallback(callback);
|
||||
}
|
||||
|
||||
void NativePerformanceObserver::logRawEntry(
|
||||
jsi::Runtime &rt,
|
||||
RawPerformanceEntry entry) {
|
||||
PerformanceEntryReporter::getInstance().logEntry(entry);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -69,6 +69,8 @@ class NativePerformanceObserver
|
||||
jsi::Runtime &rt,
|
||||
std::optional<AsyncCallback<>> callback);
|
||||
|
||||
void logRawEntry(jsi::Runtime &rt, RawPerformanceEntry entry);
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
@@ -12,13 +12,6 @@ import type {TurboModule} from '../TurboModule/RCTExport';
|
||||
|
||||
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
|
||||
|
||||
export const RawPerformanceEntryTypeValues = {
|
||||
UNDEFINED: 0,
|
||||
MARK: 1,
|
||||
MEASURE: 2,
|
||||
EVENT: 3,
|
||||
};
|
||||
|
||||
export type RawPerformanceEntryType = number;
|
||||
|
||||
export type RawPerformanceEntry = {|
|
||||
|
||||
@@ -8,74 +8,15 @@
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import type {
|
||||
RawPerformanceEntry,
|
||||
RawPerformanceEntryType,
|
||||
} from './NativePerformanceObserver';
|
||||
import type {PerformanceEntryType} from './PerformanceEntry';
|
||||
|
||||
import warnOnce from '../Utilities/warnOnce';
|
||||
import NativePerformanceObserver, {
|
||||
RawPerformanceEntryTypeValues,
|
||||
} from './NativePerformanceObserver';
|
||||
import NativePerformanceObserver from './NativePerformanceObserver';
|
||||
import {PerformanceEntry} from './PerformanceEntry';
|
||||
import {PerformanceEventTiming} from './PerformanceEventTiming';
|
||||
|
||||
function rawToPerformanceEntryType(
|
||||
type: RawPerformanceEntryType,
|
||||
): PerformanceEntryType {
|
||||
switch (type) {
|
||||
case RawPerformanceEntryTypeValues.MARK:
|
||||
return 'mark';
|
||||
case RawPerformanceEntryTypeValues.MEASURE:
|
||||
return 'measure';
|
||||
case RawPerformanceEntryTypeValues.EVENT:
|
||||
return 'event';
|
||||
default:
|
||||
throw new TypeError(
|
||||
`rawToPerformanceEntryType: unexpected performance entry type received: ${type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function performanceEntryTypeToRaw(
|
||||
type: PerformanceEntryType,
|
||||
): RawPerformanceEntryType {
|
||||
switch (type) {
|
||||
case 'mark':
|
||||
return RawPerformanceEntryTypeValues.MARK;
|
||||
case 'measure':
|
||||
return RawPerformanceEntryTypeValues.MEASURE;
|
||||
case 'event':
|
||||
return RawPerformanceEntryTypeValues.EVENT;
|
||||
default:
|
||||
// Verify exhaustive check with Flow
|
||||
(type: empty);
|
||||
throw new TypeError(
|
||||
`performanceEntryTypeToRaw: unexpected performance entry type received: ${type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function rawToPerformanceEntry(entry: RawPerformanceEntry): PerformanceEntry {
|
||||
if (entry.entryType === RawPerformanceEntryTypeValues.EVENT) {
|
||||
return new PerformanceEventTiming({
|
||||
name: entry.name,
|
||||
startTime: entry.startTime,
|
||||
duration: entry.duration,
|
||||
processingStart: entry.processingStart,
|
||||
processingEnd: entry.processingEnd,
|
||||
interactionId: entry.interactionId,
|
||||
});
|
||||
} else {
|
||||
return new PerformanceEntry({
|
||||
name: entry.name,
|
||||
entryType: rawToPerformanceEntryType(entry.entryType),
|
||||
startTime: entry.startTime,
|
||||
duration: entry.duration,
|
||||
});
|
||||
}
|
||||
}
|
||||
import {
|
||||
performanceEntryTypeToRaw,
|
||||
rawToPerformanceEntry,
|
||||
} from './RawPerformanceEntry';
|
||||
|
||||
export type PerformanceEntryList = $ReadOnlyArray<PerformanceEntry>;
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import type {
|
||||
RawPerformanceEntry,
|
||||
RawPerformanceEntryType,
|
||||
} from './NativePerformanceObserver';
|
||||
import type {PerformanceEntryType} from './PerformanceEntry';
|
||||
|
||||
import {PerformanceEntry} from './PerformanceEntry';
|
||||
import {PerformanceEventTiming} from './PerformanceEventTiming';
|
||||
|
||||
export const RawPerformanceEntryTypeValues = {
|
||||
UNDEFINED: 0,
|
||||
MARK: 1,
|
||||
MEASURE: 2,
|
||||
EVENT: 3,
|
||||
};
|
||||
|
||||
export function rawToPerformanceEntry(
|
||||
entry: RawPerformanceEntry,
|
||||
): PerformanceEntry {
|
||||
if (entry.entryType === RawPerformanceEntryTypeValues.EVENT) {
|
||||
return new PerformanceEventTiming({
|
||||
name: entry.name,
|
||||
startTime: entry.startTime,
|
||||
duration: entry.duration,
|
||||
processingStart: entry.processingStart,
|
||||
processingEnd: entry.processingEnd,
|
||||
interactionId: entry.interactionId,
|
||||
});
|
||||
} else {
|
||||
return new PerformanceEntry({
|
||||
name: entry.name,
|
||||
entryType: rawToPerformanceEntryType(entry.entryType),
|
||||
startTime: entry.startTime,
|
||||
duration: entry.duration,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function rawToPerformanceEntryType(
|
||||
type: RawPerformanceEntryType,
|
||||
): PerformanceEntryType {
|
||||
switch (type) {
|
||||
case RawPerformanceEntryTypeValues.MARK:
|
||||
return 'mark';
|
||||
case RawPerformanceEntryTypeValues.MEASURE:
|
||||
return 'measure';
|
||||
case RawPerformanceEntryTypeValues.EVENT:
|
||||
return 'event';
|
||||
case RawPerformanceEntryTypeValues.UNDEFINED:
|
||||
throw new TypeError(
|
||||
"rawToPerformanceEntryType: UNDEFINED can't be cast to PerformanceEntryType",
|
||||
);
|
||||
default:
|
||||
throw new TypeError(
|
||||
`rawToPerformanceEntryType: unexpected performance entry type received: ${type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function performanceEntryTypeToRaw(
|
||||
type: PerformanceEntryType,
|
||||
): RawPerformanceEntryType {
|
||||
switch (type) {
|
||||
case 'mark':
|
||||
return RawPerformanceEntryTypeValues.MARK;
|
||||
case 'measure':
|
||||
return RawPerformanceEntryTypeValues.MEASURE;
|
||||
case 'event':
|
||||
return RawPerformanceEntryTypeValues.EVENT;
|
||||
default:
|
||||
// Verify exhaustive check with Flow
|
||||
(type: empty);
|
||||
throw new TypeError(
|
||||
`performanceEntryTypeToRaw: unexpected performance entry type received: ${type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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 {
|
||||
GetPendingEntriesResult,
|
||||
RawPerformanceEntry,
|
||||
RawPerformanceEntryType,
|
||||
Spec as NativePerformanceObserver,
|
||||
} from '../NativePerformanceObserver';
|
||||
|
||||
const reportingType: Set<RawPerformanceEntryType> = new Set();
|
||||
let entries: Array<RawPerformanceEntry> = [];
|
||||
let onPerformanceEntryCallback: ?() => void;
|
||||
|
||||
const NativePerformanceObserverMock: NativePerformanceObserver = {
|
||||
startReporting: (entryType: RawPerformanceEntryType) => {
|
||||
reportingType.add(entryType);
|
||||
},
|
||||
|
||||
stopReporting: (entryType: RawPerformanceEntryType) => {
|
||||
reportingType.delete(entryType);
|
||||
},
|
||||
|
||||
popPendingEntries: (): GetPendingEntriesResult => {
|
||||
const res = entries;
|
||||
entries = [];
|
||||
return {
|
||||
droppedEntriesCount: 0,
|
||||
entries: res,
|
||||
};
|
||||
},
|
||||
|
||||
setOnPerformanceEntryCallback: (callback?: () => void) => {
|
||||
onPerformanceEntryCallback = callback;
|
||||
},
|
||||
|
||||
logRawEntry: (entry: RawPerformanceEntry) => {
|
||||
if (reportingType.has(entry.entryType)) {
|
||||
entries.push(entry);
|
||||
// $FlowFixMe[incompatible-call]
|
||||
global.queueMicrotask(() => {
|
||||
// We want to emulate the way it's done in native (i.e. async/batched)
|
||||
onPerformanceEntryCallback?.();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default NativePerformanceObserverMock;
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import NativePerformanceObserverMock from '../__mocks__/NativePerformanceObserver';
|
||||
import {RawPerformanceEntryTypeValues} from '../RawPerformanceEntry';
|
||||
|
||||
describe('NativePerformanceObserver', () => {
|
||||
it('correctly starts and stops listening to entries in a nominal scenario', async () => {
|
||||
NativePerformanceObserverMock.startReporting(
|
||||
RawPerformanceEntryTypeValues.MARK,
|
||||
);
|
||||
|
||||
NativePerformanceObserverMock.logRawEntry({
|
||||
name: 'mark1',
|
||||
entryType: RawPerformanceEntryTypeValues.MARK,
|
||||
startTime: 0,
|
||||
duration: 10,
|
||||
});
|
||||
|
||||
NativePerformanceObserverMock.logRawEntry({
|
||||
name: 'mark2',
|
||||
entryType: RawPerformanceEntryTypeValues.MARK,
|
||||
startTime: 0,
|
||||
duration: 20,
|
||||
});
|
||||
|
||||
NativePerformanceObserverMock.logRawEntry({
|
||||
name: 'event1',
|
||||
entryType: RawPerformanceEntryTypeValues.EVENT,
|
||||
startTime: 0,
|
||||
duration: 20,
|
||||
});
|
||||
|
||||
const entriesResult = NativePerformanceObserverMock.popPendingEntries();
|
||||
expect(entriesResult).not.toBe(undefined);
|
||||
const entries = entriesResult.entries;
|
||||
|
||||
expect(entries.length).toBe(2);
|
||||
expect(entries[0].name).toBe('mark1');
|
||||
expect(entries[1].name).toBe('mark2');
|
||||
|
||||
const entriesResult1 = NativePerformanceObserverMock.popPendingEntries();
|
||||
expect(entriesResult1).not.toBe(undefined);
|
||||
const entries1 = entriesResult1.entries;
|
||||
expect(entries1.length).toBe(0);
|
||||
|
||||
NativePerformanceObserverMock.stopReporting('mark');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import {RawPerformanceEntryTypeValues} from '../RawPerformanceEntry';
|
||||
|
||||
// NOTE: Jest mocks of transitive dependencies don't appear to work with
|
||||
// ES6 module imports, therefore forced to use commonjs style imports here.
|
||||
const NativePerformanceObserver = require('../NativePerformanceObserver');
|
||||
const PerformanceObserver = require('../PerformanceObserver').default;
|
||||
|
||||
jest.mock(
|
||||
'../NativePerformanceObserver',
|
||||
() => require('../__mocks__/NativePerformanceObserver').default,
|
||||
);
|
||||
|
||||
describe('PerformanceObserver', () => {
|
||||
it('can be mocked by a reference NativePerformanceObserver implementation', async () => {
|
||||
expect(NativePerformanceObserver).not.toBe(undefined);
|
||||
|
||||
let totalEntries = 0;
|
||||
const observer = new PerformanceObserver((list, _observer) => {
|
||||
const entries = list.getEntries();
|
||||
expect(entries).toHaveLength(1);
|
||||
const entry = entries[0];
|
||||
expect(entry.name).toBe('mark1');
|
||||
expect(entry.entryType).toBe('mark');
|
||||
totalEntries += entries.length;
|
||||
});
|
||||
expect(() => observer.observe({entryTypes: ['mark']})).not.toThrow();
|
||||
|
||||
NativePerformanceObserver.logRawEntry({
|
||||
name: 'mark1',
|
||||
entryType: RawPerformanceEntryTypeValues.MARK,
|
||||
startTime: 0,
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
await jest.runAllTicks();
|
||||
expect(totalEntries).toBe(1);
|
||||
observer.disconnect();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user