first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_nativemodule_webperformance_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_webperformance OBJECT ${react_nativemodule_webperformance_SRC})
target_include_directories(react_nativemodule_webperformance PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_webperformance
react_performance_timeline
react_codegen_rncore
react_cxxreact
)
target_compile_reactnative_options(react_nativemodule_webperformance PRIVATE)
target_compile_options(react_nativemodule_webperformance PRIVATE -Wpedantic -Wno-deprecated-declarations)

View File

@@ -0,0 +1,405 @@
/*
* 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.
*/
#include "NativePerformance.h"
#include <memory>
#include <unordered_map>
#include <variant>
#include <cxxreact/JSExecutor.h>
#include <cxxreact/ReactMarker.h>
#include <jsi/JSIDynamic.h>
#include <jsi/instrumentation.h>
#include <react/performance/timeline/PerformanceEntryReporter.h>
#include <react/performance/timeline/PerformanceObserver.h>
#include "NativePerformance.h"
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
#include "Plugins.h"
#endif
std::shared_ptr<facebook::react::TurboModule> NativePerformanceModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativePerformance>(
std::move(jsInvoker));
}
namespace facebook::react {
namespace {
class PerformanceObserverWrapper : public jsi::NativeState {
public:
explicit PerformanceObserverWrapper(
const std::shared_ptr<PerformanceObserver> observer)
: observer(observer) {}
const std::shared_ptr<PerformanceObserver> observer;
};
void sortEntries(std::vector<PerformanceEntry>& entries) {
return std::stable_sort(
entries.begin(), entries.end(), PerformanceEntrySorter{});
}
NativePerformanceEntry toNativePerformanceEntry(const PerformanceEntry& entry) {
auto nativeEntry = std::visit(
[](const auto& entryData) -> NativePerformanceEntry {
return {
.name = entryData.name,
.entryType = entryData.entryType,
.startTime = entryData.startTime,
.duration = entryData.duration,
};
},
entry);
if (std::holds_alternative<PerformanceEventTiming>(entry)) {
auto eventEntry = std::get<PerformanceEventTiming>(entry);
nativeEntry.processingStart = eventEntry.processingStart;
nativeEntry.processingEnd = eventEntry.processingEnd;
nativeEntry.interactionId = eventEntry.interactionId;
}
if (std::holds_alternative<PerformanceResourceTiming>(entry)) {
auto resourceEntry = std::get<PerformanceResourceTiming>(entry);
nativeEntry.fetchStart = resourceEntry.fetchStart;
nativeEntry.requestStart = resourceEntry.requestStart;
nativeEntry.connectStart = resourceEntry.connectStart;
nativeEntry.connectEnd = resourceEntry.connectEnd;
nativeEntry.responseStart = resourceEntry.responseStart;
nativeEntry.responseEnd = resourceEntry.responseEnd;
nativeEntry.responseStatus = resourceEntry.responseStatus;
nativeEntry.contentType = resourceEntry.contentType;
nativeEntry.encodedBodySize = resourceEntry.encodedBodySize;
nativeEntry.decodedBodySize = resourceEntry.decodedBodySize;
}
return nativeEntry;
}
std::vector<NativePerformanceEntry> toNativePerformanceEntries(
std::vector<PerformanceEntry>& entries) {
std::vector<NativePerformanceEntry> result;
result.reserve(entries.size());
for (auto& entry : entries) {
result.emplace_back(toNativePerformanceEntry(entry));
}
return result;
}
const std::array<PerformanceEntryType, 2> ENTRY_TYPES_AVAILABLE_FROM_TIMELINE{
{PerformanceEntryType::MARK, PerformanceEntryType::MEASURE}};
bool isAvailableFromTimeline(PerformanceEntryType entryType) {
return entryType == PerformanceEntryType::MARK ||
entryType == PerformanceEntryType::MEASURE;
}
std::shared_ptr<PerformanceObserver> tryGetObserver(
jsi::Runtime& rt,
jsi::Object& observerObj) {
if (!observerObj.hasNativeState(rt)) {
return nullptr;
}
auto observerWrapper = std::dynamic_pointer_cast<PerformanceObserverWrapper>(
observerObj.getNativeState(rt));
return observerWrapper ? observerWrapper->observer : nullptr;
}
PerformanceEntryReporter::UserTimingDetailProvider getDetailProviderFromEntry(
jsi::Runtime& rt,
jsi::Value& entry) {
return [&rt, &entry]() -> folly::dynamic {
try {
auto detail = entry.asObject(rt).getProperty(rt, "detail");
return jsi::dynamicFromValue(rt, detail);
} catch (jsi::JSIException&) {
return nullptr;
}
};
}
} // namespace
NativePerformance::NativePerformance(std::shared_ptr<CallInvoker> jsInvoker)
: NativePerformanceCxxSpec(std::move(jsInvoker)) {}
HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) {
// This is not spec-compliant, as this is the duration from system boot to
// now, instead of from app startup to now.
// This should be carefully changed eventually.
return HighResTimeStamp::now();
}
HighResDuration NativePerformance::timeOrigin(jsi::Runtime& /*rt*/) {
// This is not spec-compliant, as this is an approximation from Unix epoch to
// system boot, instead of a precise duration from Unix epoch to app startup.
return HighResTimeStamp::unsafeOriginFromUnixTimeStamp();
}
void NativePerformance::reportMark(
jsi::Runtime& rt,
const std::string& name,
HighResTimeStamp startTime,
jsi::Value entry) {
PerformanceEntryReporter::getInstance()->reportMark(
name, startTime, getDetailProviderFromEntry(rt, entry));
}
void NativePerformance::reportMeasure(
jsi::Runtime& rt,
const std::string& name,
HighResTimeStamp startTime,
HighResDuration duration,
jsi::Value entry) {
PerformanceEntryReporter::getInstance()->reportMeasure(
name, startTime, duration, getDetailProviderFromEntry(rt, entry));
}
std::optional<double> NativePerformance::getMarkTime(
jsi::Runtime& /*rt*/,
const std::string& name) {
auto markTime = PerformanceEntryReporter::getInstance()->getMarkTime(name);
return markTime ? std::optional{(*markTime).toDOMHighResTimeStamp()}
: std::nullopt;
}
void NativePerformance::clearMarks(
jsi::Runtime& /*rt*/,
std::optional<std::string> entryName) {
if (entryName) {
PerformanceEntryReporter::getInstance()->clearEntries(
PerformanceEntryType::MARK, *entryName);
} else {
PerformanceEntryReporter::getInstance()->clearEntries(
PerformanceEntryType::MARK);
}
}
void NativePerformance::clearMeasures(
jsi::Runtime& /*rt*/,
std::optional<std::string> entryName) {
if (entryName) {
PerformanceEntryReporter::getInstance()->clearEntries(
PerformanceEntryType::MEASURE, *entryName);
} else {
PerformanceEntryReporter::getInstance()->clearEntries(
PerformanceEntryType::MEASURE);
}
}
std::vector<NativePerformanceEntry> NativePerformance::getEntries(
jsi::Runtime& /*rt*/) {
std::vector<PerformanceEntry> entries;
for (auto entryType : ENTRY_TYPES_AVAILABLE_FROM_TIMELINE) {
PerformanceEntryReporter::getInstance()->getEntries(entries, entryType);
}
sortEntries(entries);
return toNativePerformanceEntries(entries);
}
std::vector<NativePerformanceEntry> NativePerformance::getEntriesByName(
jsi::Runtime& /*rt*/,
const std::string& entryName,
std::optional<PerformanceEntryType> entryType) {
std::vector<PerformanceEntry> entries;
if (entryType) {
if (isAvailableFromTimeline(*entryType)) {
PerformanceEntryReporter::getInstance()->getEntries(
entries, *entryType, entryName);
}
} else {
for (auto type : ENTRY_TYPES_AVAILABLE_FROM_TIMELINE) {
PerformanceEntryReporter::getInstance()->getEntries(
entries, type, entryName);
}
}
sortEntries(entries);
return toNativePerformanceEntries(entries);
}
std::vector<NativePerformanceEntry> NativePerformance::getEntriesByType(
jsi::Runtime& /*rt*/,
PerformanceEntryType entryType) {
std::vector<PerformanceEntry> entries;
if (isAvailableFromTimeline(entryType)) {
PerformanceEntryReporter::getInstance()->getEntries(entries, entryType);
}
sortEntries(entries);
return toNativePerformanceEntries(entries);
}
std::vector<std::pair<std::string, uint32_t>> NativePerformance::getEventCounts(
jsi::Runtime& /*rt*/) {
const auto& eventCounts =
PerformanceEntryReporter::getInstance()->getEventCounts();
return {eventCounts.begin(), eventCounts.end()};
}
std::unordered_map<std::string, double> NativePerformance::getSimpleMemoryInfo(
jsi::Runtime& rt) {
auto heapInfo = rt.instrumentation().getHeapInfo(false);
std::unordered_map<std::string, double> heapInfoToJs;
for (auto& entry : heapInfo) {
heapInfoToJs[entry.first] = static_cast<double>(entry.second);
}
return heapInfoToJs;
}
std::unordered_map<std::string, double>
NativePerformance::getReactNativeStartupTiming(jsi::Runtime& /*rt*/) {
std::unordered_map<std::string, double> result;
ReactMarker::StartupLogger& startupLogger =
ReactMarker::StartupLogger::getInstance();
if (!std::isnan(startupLogger.getAppStartupStartTime())) {
result["startTime"] = startupLogger.getAppStartupStartTime();
} else if (!std::isnan(startupLogger.getInitReactRuntimeStartTime())) {
result["startTime"] = startupLogger.getInitReactRuntimeStartTime();
}
if (!std::isnan(startupLogger.getInitReactRuntimeStartTime())) {
result["initializeRuntimeStart"] =
startupLogger.getInitReactRuntimeStartTime();
}
if (!std::isnan(startupLogger.getRunJSBundleStartTime())) {
result["executeJavaScriptBundleEntryPointStart"] =
startupLogger.getRunJSBundleStartTime();
}
if (!std::isnan(startupLogger.getAppStartupEndTime())) {
result["endTime"] = startupLogger.getAppStartupEndTime();
}
return result;
}
jsi::Object NativePerformance::createObserver(
jsi::Runtime& rt,
NativePerformancePerformanceObserverCallback callback) {
// The way we dispatch performance observer callbacks is a bit different from
// the spec. The specification requires us to queue a single task that
// dispatches observer callbacks. Instead, we are queuing all callbacks as
// separate tasks in the scheduler.
PerformanceObserverCallback cb = [callback = std::move(callback)]() {
callback.callWithPriority(SchedulerPriority::IdlePriority);
};
auto& registry =
PerformanceEntryReporter::getInstance()->getObserverRegistry();
auto observer = PerformanceObserver::create(registry, std::move(cb));
auto observerWrapper = std::make_shared<PerformanceObserverWrapper>(observer);
jsi::Object observerObj{rt};
observerObj.setNativeState(rt, observerWrapper);
return observerObj;
}
double NativePerformance::getDroppedEntriesCount(
jsi::Runtime& rt,
jsi::Object observerObj) {
auto observer = tryGetObserver(rt, observerObj);
if (!observer) {
return 0;
}
return observer->getDroppedEntriesCount();
}
void NativePerformance::observe(
jsi::Runtime& rt,
jsi::Object observerObj,
NativePerformancePerformanceObserverObserveOptions options) {
auto observer = tryGetObserver(rt, observerObj);
if (!observer) {
return;
}
auto durationThreshold =
options.durationThreshold.value_or(HighResDuration::zero());
// observer of type multiple
if (options.entryTypes.has_value()) {
std::unordered_set<PerformanceEntryType> entryTypes;
auto rawTypes = options.entryTypes.value();
for (auto rawType : rawTypes) {
entryTypes.insert(Bridging<PerformanceEntryType>::fromJs(rt, rawType));
}
observer->observe(entryTypes);
} else { // single
auto buffered = options.buffered.value_or(false);
if (options.type.has_value()) {
observer->observe(
static_cast<PerformanceEntryType>(options.type.value()),
{.buffered = buffered, .durationThreshold = durationThreshold});
}
}
}
void NativePerformance::disconnect(jsi::Runtime& rt, jsi::Object observerObj) {
auto observerWrapper = std::dynamic_pointer_cast<PerformanceObserverWrapper>(
observerObj.getNativeState(rt));
if (!observerWrapper) {
return;
}
auto observer = observerWrapper->observer;
observer->disconnect();
}
std::vector<NativePerformanceEntry> NativePerformance::takeRecords(
jsi::Runtime& rt,
jsi::Object observerObj,
bool sort) {
auto observerWrapper = std::dynamic_pointer_cast<PerformanceObserverWrapper>(
observerObj.getNativeState(rt));
if (!observerWrapper) {
return {};
}
auto observer = observerWrapper->observer;
auto records = observer->takeRecords();
if (sort) {
sortEntries(records);
}
return toNativePerformanceEntries(records);
}
std::vector<PerformanceEntryType>
NativePerformance::getSupportedPerformanceEntryTypes(jsi::Runtime& /*rt*/) {
auto supportedEntryTypes = PerformanceEntryReporter::getSupportedEntryTypes();
return {supportedEntryTypes.begin(), supportedEntryTypes.end()};
}
#pragma mark - Testing
void NativePerformance::clearEventCountsForTesting(jsi::Runtime& /*rt*/) {
PerformanceEntryReporter::getInstance()->clearEventCounts();
}
} // namespace facebook::react

View File

@@ -0,0 +1,177 @@
/*
* 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.
*/
#pragma once
#if __has_include("rncoreJSI.h") // Cmake headers on Android
#include "rncoreJSI.h"
#elif __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
#include "FBReactNativeSpecJSI.h"
#else
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
#include <react/performance/timeline/PerformanceEntry.h>
#include <memory>
#include <optional>
#include <string>
namespace facebook::react {
using NativePerformancePerformanceObserverCallback = AsyncCallback<>;
using NativePerformancePerformanceObserverObserveOptions = NativePerformancePerformanceObserverInit<
// entryTypes
std::optional<std::vector<int>>,
// type
std::optional<int>,
// buffered
std::optional<bool>,
// durationThreshold
std::optional<HighResDuration>>;
template <>
struct Bridging<PerformanceEntryType> {
static PerformanceEntryType fromJs(jsi::Runtime & /*rt*/, const jsi::Value &value)
{
return static_cast<PerformanceEntryType>(value.asNumber());
}
static int toJs(jsi::Runtime & /*rt*/, const PerformanceEntryType &value)
{
return static_cast<int>(value);
}
};
// Our Native Module codegen does not support JS type unions, so we use a
// flattened object here as an intermediate format.
struct NativePerformanceEntry {
std::string name;
PerformanceEntryType entryType;
HighResTimeStamp startTime;
HighResDuration duration;
// For PerformanceEventTiming only
std::optional<HighResTimeStamp> processingStart;
std::optional<HighResTimeStamp> processingEnd;
std::optional<PerformanceEntryInteractionId> interactionId;
// For PerformanceResourceTiming only
std::optional<HighResTimeStamp> fetchStart;
std::optional<HighResTimeStamp> requestStart;
std::optional<HighResTimeStamp> connectStart;
std::optional<HighResTimeStamp> connectEnd;
std::optional<HighResTimeStamp> responseStart;
std::optional<HighResTimeStamp> responseEnd;
std::optional<int> responseStatus;
std::optional<std::string> contentType;
std::optional<int> encodedBodySize;
std::optional<int> decodedBodySize;
};
template <>
struct Bridging<NativePerformanceEntry> : NativePerformanceRawPerformanceEntryBridging<NativePerformanceEntry> {};
template <>
struct Bridging<NativePerformancePerformanceObserverObserveOptions>
: NativePerformancePerformanceObserverInitBridging<NativePerformancePerformanceObserverObserveOptions> {};
class NativePerformance : public NativePerformanceCxxSpec<NativePerformance> {
public:
NativePerformance(std::shared_ptr<CallInvoker> jsInvoker);
#pragma mark - DOM Performance (High Resolution Time) (https://www.w3.org/TR/hr-time-3/#dom-performance)
// https://www.w3.org/TR/hr-time-3/#now-method
HighResTimeStamp now(jsi::Runtime &rt);
// https://www.w3.org/TR/hr-time-3/#timeorigin-attribute
HighResDuration timeOrigin(jsi::Runtime &rt);
#pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/)
void reportMark(jsi::Runtime &rt, const std::string &name, HighResTimeStamp time, jsi::Value entry);
void reportMeasure(
jsi::Runtime &rt,
const std::string &name,
HighResTimeStamp startTime,
HighResDuration duration,
jsi::Value entry);
std::optional<double> getMarkTime(jsi::Runtime &rt, const std::string &name);
// https://w3c.github.io/user-timing/#clearmarks-method
void clearMarks(jsi::Runtime &rt, std::optional<std::string> entryName = std::nullopt);
// https://w3c.github.io/user-timing/#clearmeasures-method
void clearMeasures(jsi::Runtime &rt, std::optional<std::string> entryName = std::nullopt);
#pragma mark - Performance Timeline (https://w3c.github.io/performance-timeline/#performance-timeline)
// https://www.w3.org/TR/performance-timeline/#getentries-method
std::vector<NativePerformanceEntry> getEntries(jsi::Runtime &rt);
// https://www.w3.org/TR/performance-timeline/#getentriesbytype-method
std::vector<NativePerformanceEntry> getEntriesByType(jsi::Runtime &rt, PerformanceEntryType entryType);
// https://www.w3.org/TR/performance-timeline/#getentriesbyname-method
std::vector<NativePerformanceEntry> getEntriesByName(
jsi::Runtime &rt,
const std::string &entryName,
std::optional<PerformanceEntryType> entryType = std::nullopt);
#pragma mark - Performance Observer (https://w3c.github.io/performance-timeline/#the-performanceobserver-interface)
jsi::Object createObserver(jsi::Runtime &rt, NativePerformancePerformanceObserverCallback callback);
// https://www.w3.org/TR/performance-timeline/#dom-performanceobservercallbackoptions-droppedentriescount
double getDroppedEntriesCount(jsi::Runtime &rt, jsi::Object observerObj);
void observe(jsi::Runtime &rt, jsi::Object observer, NativePerformancePerformanceObserverObserveOptions options);
void disconnect(jsi::Runtime &rt, jsi::Object observer);
std::vector<NativePerformanceEntry> takeRecords(
jsi::Runtime &rt,
jsi::Object observerObj,
// When called via `observer.takeRecords` it should be in insertion order.
// When called via the observer callback, it should be in chronological
// order with respect to `startTime`.
bool sort);
std::vector<PerformanceEntryType> getSupportedPerformanceEntryTypes(jsi::Runtime &rt);
#pragma mark - Event Timing API functions (https://www.w3.org/TR/event-timing/)
// https://www.w3.org/TR/event-timing/#dom-performance-eventcounts
std::vector<std::pair<std::string, uint32_t>> getEventCounts(jsi::Runtime &rt);
#pragma mark - Non-standard memory functions
// To align with web API, we will make sure to return three properties
// (jsHeapSizeLimit, totalJSHeapSize, usedJSHeapSize) + anything needed from
// the VM side.
// `jsHeapSizeLimit`: The maximum size of the heap, in bytes, that
// is available to the context.
// `totalJSHeapSize`: The total allocated heap size, in bytes.
// `usedJSHeapSize`: The currently active segment of JS heap, in
// bytes.
//
// Note that we use int64_t here and it's ok to lose precision in JS doubles
// for heap size information, as double's 2^53 sig bytes is large enough.
std::unordered_map<std::string, double> getSimpleMemoryInfo(jsi::Runtime &rt);
#pragma mark - RN-specific startup timing
// Collect and return the RN app startup timing information for performance
// tracking.
std::unordered_map<std::string, double> getReactNativeStartupTiming(jsi::Runtime &rt);
#pragma mark - Testing
void clearEventCountsForTesting(jsi::Runtime &rt);
};
} // namespace facebook::react

View File

@@ -0,0 +1,60 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
header_search_paths = []
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the module access its own files
end
Pod::Spec.new do |s|
s.name = "React-webperformancenativemodule"
s.version = version
s.summary = "React Native idle callbacks native module"
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("*.{cpp,h}", "*.h")
s.header_dir = "react/nativemodule/webperformance"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"OTHER_CFLAGS" => "$(inherited)",
"DEFINES_MODULE" => "YES" }
if ENV['USE_FRAMEWORKS']
s.module_name = "webperformancenativemodule"
s.header_mappings_dir = "../.."
end
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
s.dependency "React-cxxreact"
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.dependency "ReactCommon/turbomodule/core"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-performancetimeline")
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
end