Compare commits

..
86 changed files with 2203 additions and 2600 deletions
+4 -17
View File
@@ -23,8 +23,8 @@ jobs:
id: restore-ios-slice
uses: actions/cache/restore@v4
with:
key: v3-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
path: packages/react-native/
path: packages/react-native/third-party/
key: v2-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
- name: Setup node.js
if: steps.restore-ios-slice.outputs.cache-hit != 'true'
uses: ./.github/actions/setup-node
@@ -101,12 +101,6 @@ jobs:
# This is going to be replaced by a CLI script
cd packages/react-native
node scripts/ios-prebuild -b -f "${{ matrix.flavor }}" -p "${{ matrix.slice }}"
- name: Upload headers
uses: actions/upload-artifact@v4
with:
name: prebuild-ios-core-headers-${{ matrix.flavor }}-${{ matrix.slice }}
path:
packages/react-native/.build/headers
- name: Upload artifacts
uses: actions/upload-artifact@v4.3.4
with:
@@ -117,10 +111,10 @@ jobs:
uses: actions/cache/save@v4
if: ${{ github.ref == 'refs/heads/main' }} # To avoid that the cache explode
with:
key: v3-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
key: v2-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
enableCrossOsArchive: true
path: |
packages/react-native/.build/output/spm/${{ matrix.flavor }}/Build/Products
packages/react-native/.build/headers
compose-xcframework:
runs-on: macos-14
@@ -159,13 +153,6 @@ jobs:
pattern: prebuild-ios-core-slice-${{ matrix.flavor }}-*
path: packages/react-native/.build/output/spm/${{ matrix.flavor }}/Build/Products
merge-multiple: true
- name: Download headers
if: steps.restore-ios-xcframework.outputs.cache-hit != 'true'
uses: actions/download-artifact@v4
with:
pattern: prebuild-ios-core-headers-${{ matrix.flavor }}-*
path: packages/react-native/.build/headers
merge-multiple: true
- name: Setup Keychain
if: ${{ steps.restore-ios-xcframework.outputs.cache-hit != 'true' && env.REACT_ORG_CODE_SIGNING_P12_CERT != '' }}
uses: apple-actions/import-codesign-certs@v3 # https://github.com/marketplace/actions/import-code-signing-certificates
-2
View File
@@ -55,8 +55,6 @@ nexusPublishing {
sonatype {
username.set(sonatypeUsername)
password.set(sonatypePassword)
nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
}
}
}
+1 -1
View File
@@ -74,6 +74,7 @@
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-native": "^4.0.0",
"eslint-plugin-redundant-undefined": "^0.4.0",
"eslint-plugin-relay": "^1.8.3",
@@ -110,7 +111,6 @@
"ws": "^6.2.3"
},
"resolutions": {
"eslint-plugin-react-hooks": "6.1.0-canary-12bc60f5-20250613",
"react-is": "19.1.0"
}
}
@@ -37,7 +37,7 @@ internal object DependencyUtils {
}
}
// We add the snapshot for users on nightlies.
mavenRepoFromUrl("https://central.sonatype.com/repository/maven-snapshots/") { repo ->
mavenRepoFromUrl("https://oss.sonatype.org/content/repositories/snapshots/") { repo ->
repo.content { it.excludeGroup("org.webkit") }
}
repositories.mavenCentral { repo ->
@@ -45,7 +45,7 @@ class DependencyUtilsTest {
@Test
fun configureRepositories_containsSnapshotRepo() {
val repositoryURI = URI.create("https://central.sonatype.com/repository/maven-snapshots/")
val repositoryURI = URI.create("https://oss.sonatype.org/content/repositories/snapshots/")
val project = createProject()
configureRepositories(project)
@@ -176,7 +176,7 @@ class DependencyUtilsTest {
@Test
fun configureRepositories_snapshotRepoHasHigherPriorityThanMavenCentral() {
val repositoryURI = URI.create("https://central.sonatype.com/repository/maven-snapshots/")
val repositoryURI = URI.create("https://oss.sonatype.org/content/repositories/snapshots/")
val mavenCentralURI = URI.create("https://repo.maven.apache.org/maven2/")
val project = createProject()
-1
View File
@@ -13,7 +13,6 @@
export type PlatformType = 'iOS' | 'android';
export type SchemaType = $ReadOnly<{
libraryName?: string,
modules: $ReadOnly<{
[hasteModuleName: string]: ComponentSchema | NativeModuleSchema,
}>,
@@ -28,11 +28,6 @@ const argv = yargs
alias: 'exclude',
default: null,
})
.option('l', {
describe: 'Library name to use for schema generation',
alias: 'libraryName',
default: null,
})
.parseSync();
const [outfile, ...fileList] = argv._;
@@ -40,12 +35,10 @@ const platform: ?string = argv.platform;
const exclude: string = argv.exclude;
const excludeRegExp: ?RegExp =
exclude != null && exclude !== '' ? new RegExp(exclude) : null;
const libraryName: ?string = argv.libraryName;
combineSchemasInFileListAndWriteToFile(
fileList,
platform != null ? platform.toLowerCase() : platform,
outfile,
excludeRegExp,
libraryName,
);
@@ -21,11 +21,8 @@ const path = require('path');
const flowParser = new FlowParser();
const typescriptParser = new TypeScriptParser();
function combineSchemas(
files: Array<string>,
libraryName: ?string,
): SchemaType {
const combined = files.reduce(
function combineSchemas(files: Array<string>): SchemaType {
return files.reduce(
(merged, filename) => {
const contents = fs.readFileSync(filename, 'utf8');
@@ -49,11 +46,6 @@ function combineSchemas(
},
{modules: {}},
);
return {
libraryName: libraryName || '',
modules: combined.modules,
};
}
function expandDirectoriesIntoFiles(
@@ -82,14 +74,13 @@ function combineSchemasInFileList(
fileList: Array<string>,
platform: ?string,
exclude: ?RegExp,
libraryName: ?string,
): SchemaType {
const expandedFileList = expandDirectoriesIntoFiles(
fileList,
platform,
exclude,
);
const combined = combineSchemas(expandedFileList, libraryName);
const combined = combineSchemas(expandedFileList);
if (Object.keys(combined.modules).length === 0) {
console.error(
'No modules to process in combine-js-to-schema-cli. If this is unexpected, please check if you set up your NativeComponent correctly. See combine-js-to-schema.js for how codegen finds modules.',
@@ -103,14 +94,8 @@ function combineSchemasInFileListAndWriteToFile(
platform: ?string,
outfile: string,
exclude: ?RegExp,
libraryName: ?string,
): void {
const combined = combineSchemasInFileList(
fileList,
platform,
exclude,
libraryName,
);
const combined = combineSchemasInFileList(fileList, platform, exclude);
const formattedSchema = JSON.stringify(combined);
fs.writeFileSync(outfile, formattedSchema);
}
@@ -99,15 +99,8 @@ for (const file of schemaFiles) {
}
}
if (
module.type === 'Component' &&
schema.libraryName === 'FBReactNativeSpec'
) {
continue;
} else {
modules[specName] = module;
specNameToFile[specName] = file;
}
modules[specName] = module;
specNameToFile[specName] = file;
}
}
}
+4 -18
View File
@@ -73,20 +73,6 @@ const ALL_GENERATORS = {
generateViewConfigJs: generateViewConfigJs.generate,
};
type FilesOutput = Map<string, string>;
type GenerateFunction = (
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean,
headerPrefix?: string,
) => FilesOutput;
type LibraryGeneratorsFunctions = $ReadOnly<{
[string]: Array<GenerateFunction>,
}>;
type LibraryOptions = $ReadOnly<{
libraryName: string,
schema: SchemaType,
@@ -94,7 +80,6 @@ type LibraryOptions = $ReadOnly<{
packageName?: string, // Some platforms have a notion of package, which should be configurable.
assumeNonnull: boolean,
useLocalIncludePaths?: boolean,
libraryGenerators?: LibraryGeneratorsFunctions,
}>;
type SchemasOptions = $ReadOnly<{
@@ -128,7 +113,7 @@ type SchemasConfig = $ReadOnly<{
test?: boolean,
}>;
const LIBRARY_GENERATORS: LibraryGeneratorsFunctions = {
const LIBRARY_GENERATORS = {
descriptors: [
generateComponentDescriptorCpp.generate,
generateComponentDescriptorH.generate,
@@ -246,6 +231,8 @@ function checkOrWriteFiles(
module.exports = {
allGenerators: ALL_GENERATORS,
libraryGenerators: LIBRARY_GENERATORS,
schemaGenerators: SCHEMAS_GENERATORS,
generate(
{
@@ -255,7 +242,6 @@ module.exports = {
packageName,
assumeNonnull,
useLocalIncludePaths,
libraryGenerators = LIBRARY_GENERATORS,
}: LibraryOptions,
{generators, test}: LibraryConfig,
): boolean {
@@ -292,7 +278,7 @@ module.exports = {
const generatedFiles: Array<CodeGenFile> = [];
for (const name of generators) {
for (const generator of libraryGenerators[name]) {
for (const generator of LIBRARY_GENERATORS[name]) {
generator(
libraryName,
schema,
@@ -13,13 +13,15 @@
#import <memory>
#if USE_THIRD_PARTY_JSC != 1
#if USE_HERMES
#if __has_include(<jsireact/HermesExecutorFactory.h>)
#import <jsireact/HermesExecutorFactory.h>
#elif __has_include(<reacthermes/HermesExecutorFactory.h>)
#import <reacthermes/HermesExecutorFactory.h>
#endif
#endif
#elif USE_THIRD_PARTY_JSC != 1
#import <React/JSCExecutorFactory.h>
#endif // USE_HERMES
#import <ReactCommon/RCTTurboModuleManager.h>
#import <jsireact/JSIExecutor.h>
@@ -145,10 +145,16 @@ std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupDefaultJsExecutor
}
[turboModuleManager installJSBindings:runtime];
};
#if USE_THIRD_PARTY_JSC != 1
#if USE_HERMES
return std::make_unique<facebook::react::HermesExecutorFactory>(
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
#endif
#elif USE_THIRD_PARTY_JSC != 1
return std::make_unique<facebook::react::JSCExecutorFactory>(
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
#else
throw std::runtime_error("No JSExecutorFactory specified.");
return nullptr;
#endif // USE_HERMES
}
std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupJsExecutorFactoryForOldArch(
@@ -163,8 +169,14 @@ std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupJsExecutorFactory
facebook::react::RuntimeSchedulerBinding::createAndInstallIfNeeded(runtime, runtimeScheduler);
}
};
#if USE_THIRD_PARTY_JSC != 1
#if USE_HERMES
return std::make_unique<facebook::react::HermesExecutorFactory>(
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
#endif
#elif USE_THIRD_PARTY_JSC != 1
return std::make_unique<facebook::react::JSCExecutorFactory>(
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
#else
throw std::runtime_error("No JSExecutorFactory specified.");
return nullptr;
#endif // USE_HERMES
}
@@ -9,8 +9,10 @@
#import <ReactCommon/RCTHost.h>
#import "RCTAppSetupUtils.h"
#import "RCTDependencyProvider.h"
#if USE_THIRD_PARTY_JSC != 1
#if USE_HERMES
#import <React/RCTHermesInstanceFactory.h>
#elif USE_THIRD_PARTY_JSC != 1
#import <React/RCTJscInstanceFactory.h>
#endif
#import <react/nativemodule/defaults/DefaultTurboModules.h>
@@ -43,8 +45,12 @@
- (JSRuntimeFactoryRef)createJSRuntimeFactory
{
#if USE_THIRD_PARTY_JSC != 1
#if USE_HERMES
return jsrt_create_hermes_factory();
#elif USE_THIRD_PARTY_JSC != 1
return jsrt_create_jsc_factory();
#else
return nullptr;
#endif
}
@@ -17,8 +17,12 @@ else
end
is_new_arch_enabled = ENV["RCT_NEW_ARCH_ENABLED"] != "0"
use_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == '1'
new_arch_enabled_flag = (is_new_arch_enabled ? " -DRCT_NEW_ARCH_ENABLED=1" : "")
other_cflags = "$(inherited) " + new_arch_enabled_flag + js_engine_flags()
hermes_flag = (use_hermes ? " -DUSE_HERMES=1" : "")
use_third_party_jsc_flag = ENV['USE_THIRD_PARTY_JSC'] == '1' ? " -DUSE_THIRD_PARTY_JSC=1" : ""
other_cflags = "$(inherited) " + new_arch_enabled_flag + hermes_flag + use_third_party_jsc_flag
header_search_paths = [
"$(PODS_TARGET_SRCROOT)/../../ReactCommon",
@@ -27,7 +31,7 @@ header_search_paths = [
"$(PODS_ROOT)/Headers/Public/ReactCommon",
"$(PODS_ROOT)/Headers/Public/React-RCTFabric",
"$(PODS_ROOT)/Headers/Private/Yoga",
].concat(use_hermes() ? [
].concat(use_hermes ? [
"$(PODS_ROOT)/Headers/Public/React-hermes",
"$(PODS_ROOT)/Headers/Public/hermes-engine"
] : [])
@@ -62,7 +66,7 @@ Pod::Spec.new do |s|
s.dependency "React-CoreModules"
s.dependency "React-RCTFBReactNativeSpec"
s.dependency "React-defaultsnativemodule"
if use_hermes()
if use_hermes
s.dependency 'React-hermes'
end
@@ -50,7 +50,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
if use_hermes()
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency "hermes-engine"
end
@@ -72,8 +72,3 @@ export interface ImageURISource {
}
export type ImageRequireSource = number;
export type ImageSource =
| ImageRequireSource
| ImageURISource
| ReadonlyArray<ImageURISource>;
+1 -4
View File
@@ -336,10 +336,7 @@ let reactRuntime = RNTarget(
name: .reactRuntime,
path: "ReactCommon/react/runtime",
excludedPaths: ["tests", "iostests", "platform"],
dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactCxxReact, .reactJsErrorHandler, .reactPerformanceTimeline, .reactUtils, .reactFeatureFlags, .reactJsInspector, .reactJsiTooling, .reactHermes, .reactRuntimeScheduler, .hermesPrebuilt],
defines: [
CXXSetting.define("HERMES_ENABLE_DEBUGGER", to: "1", .when(configuration: BuildConfiguration.debug))
]
dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactCxxReact, .reactJsErrorHandler, .reactPerformanceTimeline, .reactUtils, .reactFeatureFlags, .reactJsInspector, .reactJsiTooling, .reactHermes, .reactRuntimeScheduler, .hermesPrebuilt]
)
/// React-runtimeApple.podspec
+15 -6
View File
@@ -16,6 +16,10 @@ else
source[:tag] = "v#{version}"
end
use_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == '1'
use_hermes_flag = use_hermes ? "-DUSE_HERMES=1" : ""
use_third_party_jsc_flag = ENV['USE_THIRD_PARTY_JSC'] == '1' ? "-DUSE_THIRD_PARTY_JSC=1" : ""
header_subspecs = {
'CoreModulesHeaders' => 'React/CoreModules/**/*.h',
'RCTActionSheetHeaders' => 'Libraries/ActionSheetIOS/*.h',
@@ -31,7 +35,7 @@ header_subspecs = {
}
frameworks_search_paths = []
frameworks_search_paths << "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-hermes\"" if use_hermes()
frameworks_search_paths << "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-hermes\"" if use_hermes
header_search_paths = [
"$(PODS_TARGET_SRCROOT)/ReactCommon",
@@ -52,7 +56,7 @@ Pod::Spec.new do |s|
s.platforms = min_supported_versions
s.source = source
s.resource_bundle = { "RCTI18nStrings" => ["React/I18n/strings/*.lproj"]}
s.compiler_flags = js_engine_flags()
s.compiler_flags = use_hermes_flag + ' ' + use_third_party_jsc_flag
s.header_dir = "React"
s.weak_framework = "JavaScriptCore"
s.pod_target_xcconfig = {
@@ -76,9 +80,13 @@ Pod::Spec.new do |s|
"React/Inspector/**/*",
"React/Runtime/**/*",
]
# The default is use hermes, we don't have jsc installed
exclude_files = exclude_files.append("React/CxxBridge/JSCExecutorFactory.{h,mm}")
# If we are using Hermes (the default is use hermes, so USE_HERMES can be nil), we don't have jsc installed
# So we have to exclude the JSCExecutorFactory
if use_hermes
exclude_files = exclude_files.append("React/CxxBridge/JSCExecutorFactory.{h,mm}")
elsif ENV['USE_THIRD_PARTY_JSC'] == '1'
exclude_files = exclude_files.append("React/CxxBridge/JSCExecutorFactory.{h,mm}")
end
ss.exclude_files = exclude_files
ss.private_header_files = "React/Cxx*/*.h"
@@ -115,7 +123,7 @@ Pod::Spec.new do |s|
s.dependency "React-runtimescheduler"
s.dependency "Yoga"
if use_hermes()
if use_hermes
s.dependency "React-hermes"
end
@@ -128,6 +136,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
add_dependency(s, "RCTDeprecation")
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
end
+2 -15
View File
@@ -19,12 +19,9 @@
#import <CommonCrypto/CommonCrypto.h>
#import <React/RCTUtilsUIOverride.h>
#import <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
#import "RCTAssert.h"
#import "RCTLog.h"
using namespace facebook::react;
NSString *const RCTErrorUnspecified = @"EUNSPECIFIED";
// Returns the Path of Home directory
@@ -317,12 +314,7 @@ void RCTUnsafeExecuteOnMainQueueSyncWithError(dispatch_block_t block, NSString *
return;
}
if (ReactNativeFeatureFlags::enableMainQueueCoordinatorOnIOS()) {
unsafeExecuteOnMainThreadSync(block);
return;
}
if (ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
if (facebook::react::ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
RCTLogError(@"RCTUnsafeExecuteOnMainQueueSync: %@", context);
}
@@ -349,12 +341,7 @@ static void RCTUnsafeExecuteOnMainQueueOnceSync(dispatch_once_t *onceToken, disp
return;
}
if (ReactNativeFeatureFlags::enableMainQueueCoordinatorOnIOS()) {
unsafeExecuteOnMainThreadSync(block);
return;
}
if (ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
if (facebook::react::ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
RCTLogError(@"RCTUnsafeExecuteOnMainQueueOnceSync: Sync dispatches to the main queue can deadlock React Native.");
}
@@ -45,8 +45,10 @@
#import <react/utils/FollyConvert.h>
#import <reactperflogger/BridgeNativeModulePerfLogger.h>
#if !defined(USE_HERMES) || USE_HERMES == 1
#if USE_HERMES
#import <reacthermes/HermesExecutorFactory.h>
#elif USE_THIRD_PARTY_JSC != 1
#import "JSCExecutorFactory.h"
#endif
#import "RCTJSIExecutorRuntimeInstaller.h"
@@ -469,8 +471,12 @@ struct RCTInstanceCallback : public InstanceCallback {
}
if (!executorFactory) {
auto installBindings = RCTJSIExecutorRuntimeInstaller(nullptr);
#if !defined(USE_HERMES) || USE_HERMES == 1
#if USE_HERMES
executorFactory = std::make_shared<HermesExecutorFactory>(installBindings);
#elif USE_THIRD_PARTY_JSC != 1
executorFactory = std::make_shared<JSCExecutorFactory>(installBindings);
#else
throw std::runtime_error("No JSExecutorFactory specified.");
#endif
}
} else {
@@ -1138,9 +1144,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithBundleURL
/**
* Prevent super from calling setUp (that'd create another batchedBridge)
*/
- (void)setUp
{
}
- (void)setUp {}
- (Class)executorClass
{
@@ -58,14 +58,15 @@ Pod::Spec.new do |s|
add_dependency(s, "React-RuntimeCore")
add_dependency(s, "React-RuntimeApple")
if use_third_party_jsc()
s.exclude_files = ["RCTHermesInstanceFactory.{mm,h}", "RCTJscInstanceFactory.{mm,h}"]
else
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency "hermes-engine"
add_dependency(s, "React-RuntimeHermes")
s.exclude_files = "RCTJscInstanceFactory.{h,mm}"
elsif ENV['USE_THIRD_PARTY_JSC'] == '1'
s.exclude_files = ["RCTHermesInstanceFactory.{mm,h}", "RCTJscInstanceFactory.{mm,h}"]
else
s.exclude_files = ["RCTHermesInstanceFactory.{mm,h}"]
end
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
end
@@ -35,11 +35,6 @@ typedef struct {
UIColor *right;
} RCTBorderColors;
/**
* Determine the largest border inset value.
*/
RCT_EXTERN CGFloat RCTMaxBorderInset(UIEdgeInsets borderInsets);
/**
* Determine if the border widths, colors and radii are all equal.
*/
@@ -10,11 +10,6 @@
static const CGFloat RCTViewBorderThreshold = 0.001;
CGFloat RCTMaxBorderInset(UIEdgeInsets borderInsets)
{
return MAX(MAX(borderInsets.top, borderInsets.left), MAX(borderInsets.bottom, borderInsets.right));
}
BOOL RCTBorderInsetsAreEqual(UIEdgeInsets borderInsets)
{
return ABS(borderInsets.left - borderInsets.right) < RCTViewBorderThreshold &&
@@ -420,8 +415,8 @@ static UIImage *RCTGetSolidBorderImage(
return image;
}
// Currently, the dashed / dotted implementation only supports a single colour,
// as that's currently required and supported on Android.
// Currently, the dashed / dotted implementation only supports a single colour +
// single width, as that's currently required and supported on Android.
//
// Supporting individual widths + colours on each side is possible by modifying
// the current implementation. The idea is that we will draw four different lines
@@ -491,12 +486,12 @@ static UIImage *RCTGetDashedOrDottedBorderImage(
{
NSCParameterAssert(borderStyle == RCTBorderStyleDashed || borderStyle == RCTBorderStyleDotted);
if (!RCTBorderColorsAreEqual(borderColors)) {
if (!RCTBorderColorsAreEqual(borderColors) || !RCTBorderInsetsAreEqual(borderInsets)) {
RCTLogWarn(@"Unsupported dashed / dotted border style");
return nil;
}
const CGFloat lineWidth = RCTMaxBorderInset(borderInsets);
const CGFloat lineWidth = borderInsets.top;
if (lineWidth <= 0.0) {
return nil;
}
@@ -524,34 +519,6 @@ static UIImage *RCTGetDashedOrDottedBorderImage(
CGPathRef path =
RCTPathCreateWithRoundedRect(pathRect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL, NO);
if (!RCTBorderInsetsAreEqual(borderInsets)) {
CGContextSaveGState(context);
{
// Create a path representing the full rect
CGMutablePathRef outerPath = CGPathCreateMutable();
CGPathAddRect(outerPath, NULL, rect);
CGRect insetRect = CGRectMake(
rect.origin.x + borderInsets.left,
rect.origin.y + borderInsets.top,
rect.size.width - borderInsets.left - borderInsets.right,
rect.size.height - borderInsets.top - borderInsets.bottom);
// The padding edge (inner border) radius is the outer border radius minus the corresponding border thickness
CGPathRef innerRoundedRect =
RCTPathCreateWithRoundedRect(insetRect, RCTGetCornerInsets(cornerRadii, borderInsets), NULL, NO);
// Add both paths to outerPath
CGPathAddPath(outerPath, NULL, innerRoundedRect);
// Clip using even-odd
CGContextAddPath(context, outerPath);
CGContextEOClip(context);
CGPathRelease(outerPath);
CGPathRelease(innerRoundedRect);
}
}
CGFloat dashLengths[2];
dashLengths[0] = dashLengths[1] = (borderStyle == RCTBorderStyleDashed ? 3 : 1) * lineWidth;
@@ -950,16 +950,16 @@ public abstract interface class com/facebook/react/bridge/MemoryPressureListener
public abstract fun handleMemoryPressure (I)V
}
public final class com/facebook/react/bridge/ModuleHolder {
public class com/facebook/react/bridge/ModuleHolder {
public fun <init> (Lcom/facebook/react/bridge/NativeModule;)V
public fun <init> (Lcom/facebook/react/module/model/ReactModuleInfo;Ljavax/inject/Provider;)V
public final fun destroy ()V
public final fun getCanOverrideExistingModule ()Z
public final fun getClassName ()Ljava/lang/String;
public final fun getModule ()Lcom/facebook/react/bridge/NativeModule;
public final fun getName ()Ljava/lang/String;
public final fun isCxxModule ()Z
public final fun isTurboModule ()Z
public fun destroy ()V
public fun getCanOverrideExistingModule ()Z
public fun getClassName ()Ljava/lang/String;
public fun getModule ()Lcom/facebook/react/bridge/NativeModule;
public fun getName ()Ljava/lang/String;
public fun isCxxModule ()Z
public fun isTurboModule ()Z
}
public final class com/facebook/react/bridge/ModuleSpec {
@@ -2747,6 +2747,36 @@ public final class com/facebook/react/modules/debug/DevSettingsModule : com/face
public final class com/facebook/react/modules/debug/DevSettingsModule$Companion {
}
public final class com/facebook/react/modules/debug/FpsDebugFrameCallback : android/view/Choreographer$FrameCallback {
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
public fun doFrame (J)V
public final fun get4PlusFrameStutters ()I
public final fun getExpectedNumFrames ()I
public final fun getFps ()D
public final fun getFpsInfo (J)Lcom/facebook/react/modules/debug/FpsDebugFrameCallback$FpsInfo;
public final fun getJsFPS ()D
public final fun getNumFrames ()I
public final fun getNumJSFrames ()I
public final fun getTotalTimeMS ()I
public final fun reset ()V
public final fun start ()V
public final fun start (D)V
public static synthetic fun start$default (Lcom/facebook/react/modules/debug/FpsDebugFrameCallback;DILjava/lang/Object;)V
public final fun startAndRecordFpsAtEachFrame ()V
public final fun stop ()V
}
public final class com/facebook/react/modules/debug/FpsDebugFrameCallback$FpsInfo {
public fun <init> (IIIIDDI)V
public final fun getFps ()D
public final fun getJsFps ()D
public final fun getTotal4PlusFrameStutters ()I
public final fun getTotalExpectedFrames ()I
public final fun getTotalFrames ()I
public final fun getTotalJsFrames ()I
public final fun getTotalTimeMs ()I
}
public final class com/facebook/react/modules/debug/SourceCodeModule : com/facebook/fbreact/specs/NativeSourceCodeSpec {
public static final field Companion Lcom/facebook/react/modules/debug/SourceCodeModule$Companion;
public static final field NAME Ljava/lang/String;
@@ -6417,6 +6447,37 @@ public final class com/facebook/react/views/text/TextAttributes {
public fun toString ()Ljava/lang/String;
}
public class com/facebook/react/views/text/TextLayoutManager {
public static final field AS_KEY_BASE_ATTRIBUTES S
public static final field AS_KEY_CACHE_ID S
public static final field AS_KEY_FRAGMENTS S
public static final field AS_KEY_HASH S
public static final field AS_KEY_STRING S
public static final field FR_KEY_HEIGHT S
public static final field FR_KEY_IS_ATTACHMENT S
public static final field FR_KEY_REACT_TAG S
public static final field FR_KEY_STRING S
public static final field FR_KEY_TEXT_ATTRIBUTES S
public static final field FR_KEY_WIDTH S
public static final field PA_KEY_ADJUST_FONT_SIZE_TO_FIT S
public static final field PA_KEY_ELLIPSIZE_MODE S
public static final field PA_KEY_HYPHENATION_FREQUENCY S
public static final field PA_KEY_INCLUDE_FONT_PADDING S
public static final field PA_KEY_MAXIMUM_FONT_SIZE S
public static final field PA_KEY_MAX_NUMBER_OF_LINES S
public static final field PA_KEY_MINIMUM_FONT_SIZE S
public static final field PA_KEY_TEXT_ALIGN_VERTICAL S
public static final field PA_KEY_TEXT_BREAK_STRATEGY S
public fun <init> ()V
public static fun deleteCachedSpannableForTag (I)V
public static fun getOrCreateSpannableForText (Landroid/content/Context;Lcom/facebook/react/common/mapbuffer/MapBuffer;Lcom/facebook/react/views/text/ReactTextViewManagerCallback;)Landroid/text/Spannable;
public static fun getTextGravity (Lcom/facebook/react/common/mapbuffer/MapBuffer;Landroid/text/Spannable;I)I
public static fun isRTL (Lcom/facebook/react/common/mapbuffer/MapBuffer;)Z
public static fun measureLines (Landroid/content/Context;Lcom/facebook/react/common/mapbuffer/MapBuffer;Lcom/facebook/react/common/mapbuffer/MapBuffer;FF)Lcom/facebook/react/bridge/WritableArray;
public static fun measureText (Landroid/content/Context;Lcom/facebook/react/common/mapbuffer/MapBuffer;Lcom/facebook/react/common/mapbuffer/MapBuffer;FLcom/facebook/yoga/YogaMeasureMode;FLcom/facebook/yoga/YogaMeasureMode;Lcom/facebook/react/views/text/ReactTextViewManagerCallback;[F)J
public static fun setCachedSpannableForTag (ILandroid/text/Spannable;)V
}
public abstract interface class com/facebook/react/views/textinput/ContentSizeWatcher {
public abstract fun onLayout ()V
}
@@ -111,7 +111,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext?) :
private var valueMap: Array<BatchExecutionOpCodes>? = null
@JvmStatic
fun fromId(id: Int): BatchExecutionOpCodes {
public fun fromId(id: Int): BatchExecutionOpCodes {
val valueMapNonnull: Array<BatchExecutionOpCodes> =
valueMap ?: BatchExecutionOpCodes.values()
if (valueMap == null) {
@@ -0,0 +1,413 @@
/*
* 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.
*/
package com.facebook.react.bridge;
import static com.facebook.infer.annotation.Assertions.assertNotNull;
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT;
import androidx.annotation.Nullable;
import com.facebook.debug.holder.PrinterHolder;
import com.facebook.debug.tags.ReactDebugOverlayTags;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.common.annotations.internal.LegacyArchitecture;
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel;
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger;
import com.facebook.systrace.SystraceMessage;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
class JavaMethodWrapper implements JavaModuleWrapper.NativeMethod {
static {
LegacyArchitectureLogger.assertLegacyArchitecture(
"JavaMethodWrapper", LegacyArchitectureLogLevel.ERROR);
}
private abstract static class ArgumentExtractor<T> {
public int getJSArgumentsNeeded() {
return 1;
}
public abstract @Nullable T extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex);
}
private static final ArgumentExtractor<Boolean> ARGUMENT_EXTRACTOR_BOOLEAN =
new ArgumentExtractor<Boolean>() {
@Override
public Boolean extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return jsArguments.getBoolean(atIndex);
}
};
private static final ArgumentExtractor<Double> ARGUMENT_EXTRACTOR_DOUBLE =
new ArgumentExtractor<Double>() {
@Override
public Double extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return jsArguments.getDouble(atIndex);
}
};
private static final ArgumentExtractor<Float> ARGUMENT_EXTRACTOR_FLOAT =
new ArgumentExtractor<Float>() {
@Override
public Float extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return (float) jsArguments.getDouble(atIndex);
}
};
private static final ArgumentExtractor<Integer> ARGUMENT_EXTRACTOR_INTEGER =
new ArgumentExtractor<Integer>() {
@Override
public Integer extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return (int) jsArguments.getDouble(atIndex);
}
};
private static final ArgumentExtractor<String> ARGUMENT_EXTRACTOR_STRING =
new ArgumentExtractor<String>() {
@Override
public String extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return jsArguments.getString(atIndex);
}
};
private static final ArgumentExtractor<ReadableArray> ARGUMENT_EXTRACTOR_ARRAY =
new ArgumentExtractor<ReadableArray>() {
@Override
public ReadableArray extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return jsArguments.getArray(atIndex);
}
};
private static final ArgumentExtractor<Dynamic> ARGUMENT_EXTRACTOR_DYNAMIC =
new ArgumentExtractor<Dynamic>() {
@Override
public Dynamic extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return DynamicFromArray.create(jsArguments, atIndex);
}
};
private static final ArgumentExtractor<ReadableMap> ARGUMENT_EXTRACTOR_MAP =
new ArgumentExtractor<ReadableMap>() {
@Override
public ReadableMap extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
return jsArguments.getMap(atIndex);
}
};
private static final ArgumentExtractor<Callback> ARGUMENT_EXTRACTOR_CALLBACK =
new ArgumentExtractor<Callback>() {
@Override
public @Nullable Callback extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
if (jsArguments.isNull(atIndex)) {
return null;
} else {
int id = (int) jsArguments.getDouble(atIndex);
return new com.facebook.react.bridge.CallbackImpl(jsInstance, id);
}
}
};
private static final ArgumentExtractor<Promise> ARGUMENT_EXTRACTOR_PROMISE =
new ArgumentExtractor<Promise>() {
@Override
public int getJSArgumentsNeeded() {
return 2;
}
@Override
public Promise extractArgument(
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
Callback resolve =
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex);
Callback reject =
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex + 1);
return new PromiseImpl(resolve, reject);
}
};
private static final boolean DEBUG =
PrinterHolder.getPrinter().shouldDisplayLogMessage(ReactDebugOverlayTags.BRIDGE_CALLS);
private static char paramTypeToChar(Class paramClass) {
char tryCommon = commonTypeToChar(paramClass);
if (tryCommon != '\0') {
return tryCommon;
}
if (paramClass == Callback.class) {
return 'X';
} else if (paramClass == Promise.class) {
return 'P';
} else if (paramClass == ReadableMap.class) {
return 'M';
} else if (paramClass == ReadableArray.class) {
return 'A';
} else if (paramClass == Dynamic.class) {
return 'Y';
} else {
throw new RuntimeException("Got unknown param class: " + paramClass.getSimpleName());
}
}
private static char returnTypeToChar(Class returnClass) {
// Keep this in sync with MethodInvoker
char tryCommon = commonTypeToChar(returnClass);
if (tryCommon != '\0') {
return tryCommon;
}
if (returnClass == void.class) {
return 'v';
} else if (returnClass == WritableMap.class) {
return 'M';
} else if (returnClass == WritableArray.class) {
return 'A';
} else {
throw new RuntimeException("Got unknown return class: " + returnClass.getSimpleName());
}
}
private static char commonTypeToChar(Class typeClass) {
if (typeClass == boolean.class) {
return 'z';
} else if (typeClass == Boolean.class) {
return 'Z';
} else if (typeClass == int.class) {
return 'i';
} else if (typeClass == Integer.class) {
return 'I';
} else if (typeClass == double.class) {
return 'd';
} else if (typeClass == Double.class) {
return 'D';
} else if (typeClass == float.class) {
return 'f';
} else if (typeClass == Float.class) {
return 'F';
} else if (typeClass == String.class) {
return 'S';
} else {
return '\0';
}
}
private final Method mMethod;
private final Class[] mParameterTypes;
private final int mParamLength;
private final JavaModuleWrapper mModuleWrapper;
private String mType = BaseJavaModule.METHOD_TYPE_ASYNC;
private boolean mArgumentsProcessed = false;
private @Nullable ArgumentExtractor[] mArgumentExtractors;
private @Nullable String mSignature;
private @Nullable Object[] mArguments;
private @Nullable int mJSArgumentsNeeded;
public JavaMethodWrapper(JavaModuleWrapper module, Method method, boolean isSync) {
mModuleWrapper = module;
mMethod = method;
mMethod.setAccessible(true);
mParameterTypes = mMethod.getParameterTypes();
mParamLength = mParameterTypes.length;
if (isSync) {
mType = BaseJavaModule.METHOD_TYPE_SYNC;
} else if (mParamLength > 0 && (mParameterTypes[mParamLength - 1] == Promise.class)) {
mType = BaseJavaModule.METHOD_TYPE_PROMISE;
}
}
private void processArguments() {
if (mArgumentsProcessed) {
return;
}
SystraceMessage.beginSection(TRACE_TAG_REACT, "processArguments")
.arg("method", mModuleWrapper.getName() + "." + mMethod.getName())
.flush();
try {
mArgumentsProcessed = true;
mArgumentExtractors = buildArgumentExtractors(mParameterTypes);
mSignature =
buildSignature(mMethod, mParameterTypes, (mType.equals(BaseJavaModule.METHOD_TYPE_SYNC)));
// Since native methods are invoked from a message queue executed on a single thread, it is
// safe to allocate only one arguments object per method that can be reused across calls
mArguments = new Object[mParameterTypes.length];
mJSArgumentsNeeded = calculateJSArgumentsNeeded();
} finally {
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
}
}
public Method getMethod() {
return mMethod;
}
public String getSignature() {
if (!mArgumentsProcessed) {
processArguments();
}
return assertNotNull(mSignature);
}
private String buildSignature(Method method, Class[] paramTypes, boolean isSync) {
StringBuilder builder = new StringBuilder(paramTypes.length + 2);
if (isSync) {
builder.append(returnTypeToChar(method.getReturnType()));
builder.append('.');
} else {
builder.append("v.");
}
for (int i = 0; i < paramTypes.length; i++) {
Class paramClass = paramTypes[i];
if (paramClass == Promise.class) {
Assertions.assertCondition(
i == paramTypes.length - 1, "Promise must be used as last parameter only");
}
builder.append(paramTypeToChar(paramClass));
}
return builder.toString();
}
private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) {
ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length];
for (int i = 0; i < paramTypes.length; i += argumentExtractors[i].getJSArgumentsNeeded()) {
Class argumentClass = paramTypes[i];
if (argumentClass == Boolean.class || argumentClass == boolean.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_BOOLEAN;
} else if (argumentClass == Integer.class || argumentClass == int.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_INTEGER;
} else if (argumentClass == Double.class || argumentClass == double.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_DOUBLE;
} else if (argumentClass == Float.class || argumentClass == float.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_FLOAT;
} else if (argumentClass == String.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_STRING;
} else if (argumentClass == Callback.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_CALLBACK;
} else if (argumentClass == Promise.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_PROMISE;
Assertions.assertCondition(
i == paramTypes.length - 1, "Promise must be used as last parameter only");
} else if (argumentClass == ReadableMap.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_MAP;
} else if (argumentClass == ReadableArray.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_ARRAY;
} else if (argumentClass == Dynamic.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_DYNAMIC;
} else {
throw new RuntimeException("Got unknown argument class: " + argumentClass.getSimpleName());
}
}
return argumentExtractors;
}
private int calculateJSArgumentsNeeded() {
int n = 0;
for (ArgumentExtractor extractor : assertNotNull(mArgumentExtractors)) {
n += extractor.getJSArgumentsNeeded();
}
return n;
}
private String getAffectedRange(int startIndex, int jsArgumentsNeeded) {
return jsArgumentsNeeded > 1
? "" + startIndex + "-" + (startIndex + jsArgumentsNeeded - 1)
: "" + startIndex;
}
@Override
public void invoke(JSInstance jsInstance, ReadableArray parameters) {
String traceName = mModuleWrapper.getName() + "." + mMethod.getName();
SystraceMessage.beginSection(TRACE_TAG_REACT, "callJavaModuleMethod")
.arg("method", traceName)
.flush();
if (DEBUG) {
PrinterHolder.getPrinter()
.logMessage(
ReactDebugOverlayTags.BRIDGE_CALLS,
"JS->Java: %s.%s()",
mModuleWrapper.getName(),
mMethod.getName());
}
try {
if (!mArgumentsProcessed) {
processArguments();
}
if (mArguments == null || mArgumentExtractors == null) {
throw new Error("processArguments failed");
}
if (mJSArgumentsNeeded != parameters.size()) {
throw new NativeArgumentsParseException(
traceName + " got " + parameters.size() + " arguments, expected " + mJSArgumentsNeeded);
}
int i = 0, jsArgumentsConsumed = 0;
try {
for (; i < mArgumentExtractors.length; i++) {
mArguments[i] =
mArgumentExtractors[i].extractArgument(jsInstance, parameters, jsArgumentsConsumed);
jsArgumentsConsumed += mArgumentExtractors[i].getJSArgumentsNeeded();
}
} catch (UnexpectedNativeTypeException | NullPointerException e) {
throw new NativeArgumentsParseException(
e.getMessage()
+ " (constructing arguments for "
+ traceName
+ " at argument index "
+ getAffectedRange(
jsArgumentsConsumed, mArgumentExtractors[i].getJSArgumentsNeeded())
+ ")",
e);
}
try {
mMethod.invoke(mModuleWrapper.getModule(), mArguments);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(createInvokeExceptionMessage(traceName), e);
} catch (InvocationTargetException ite) {
// Exceptions thrown from native module calls end up wrapped in InvocationTargetException
// which just make traces harder to read and bump out useful information
if (ite.getCause() instanceof RuntimeException) {
throw (RuntimeException) ite.getCause();
}
throw new RuntimeException(createInvokeExceptionMessage(traceName), ite);
}
} finally {
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
}
}
/**
* Makes it easier to determine the cause of an error invoking a native method from Javascript
* code by adding the function name.
*/
private static String createInvokeExceptionMessage(String traceName) {
return "Could not invoke " + traceName;
}
/**
* Determines how the method is exported in JavaScript: METHOD_TYPE_ASYNC for regular methods
* METHOD_TYPE_PROMISE for methods that return a promise object to the caller. METHOD_TYPE_SYNC
* for sync methods
*/
@Override
public String getType() {
return mType;
}
}
@@ -1,399 +0,0 @@
/*
* 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.
*/
package com.facebook.react.bridge
import com.facebook.debug.holder.PrinterHolder
import com.facebook.debug.tags.ReactDebugOverlayTags
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
import com.facebook.systrace.Systrace.TRACE_TAG_REACT
import com.facebook.systrace.SystraceMessage
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
internal class JavaMethodWrapper(
private val moduleWrapper: JavaModuleWrapper,
val method: Method,
isSync: Boolean
) : JavaModuleWrapper.NativeMethod {
private abstract class ArgumentExtractor<T> {
open fun getJSArgumentsNeeded(): Int = 1
abstract fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): T?
}
private val parameterTypes: Array<Class<*>>
private val paramLength: Int
/**
* Determines how the method is exported in JavaScript: METHOD_TYPE_ASYNC for regular methods
* METHOD_TYPE_PROMISE for methods that return a promise object to the caller. METHOD_TYPE_SYNC
* for sync methods
*/
override var type: String = BaseJavaModule.METHOD_TYPE_ASYNC
private var argumentsProcessed = false
private var argumentExtractors: Array<ArgumentExtractor<*>>? = null
private var internalSignature: String? = null
private var arguments: Array<Any?>? = null
private var jsArgumentsNeeded = 0
init {
method.isAccessible = true
parameterTypes = method.parameterTypes
paramLength = parameterTypes.size
if (isSync) {
type = BaseJavaModule.METHOD_TYPE_SYNC
} else if (paramLength > 0 && (parameterTypes[paramLength - 1] == Promise::class.java)) {
type = BaseJavaModule.METHOD_TYPE_PROMISE
}
}
private fun processArguments() {
if (argumentsProcessed) {
return
}
SystraceMessage.beginSection(TRACE_TAG_REACT, "processArguments")
.arg("method", moduleWrapper.name + "." + method.name)
.flush()
try {
argumentsProcessed = true
argumentExtractors = buildArgumentExtractors(parameterTypes)
internalSignature =
buildSignature(method, parameterTypes, (type == BaseJavaModule.METHOD_TYPE_SYNC))
// Since native methods are invoked from a message queue executed on a single thread, it is
// safe to allocate only one arguments object per method that can be reused across calls
arguments = arrayOfNulls(parameterTypes.size)
jsArgumentsNeeded = calculateJSArgumentsNeeded()
} finally {
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
}
}
val signature: String?
get() {
if (!argumentsProcessed) {
processArguments()
}
return checkNotNull(internalSignature)
}
private fun buildSignature(method: Method, paramTypes: Array<Class<*>>, isSync: Boolean): String =
buildString(paramTypes.size + 2) {
if (isSync) {
append(returnTypeToChar(method.returnType))
append('.')
} else {
append("v.")
}
for (i in paramTypes.indices) {
val paramClass = paramTypes[i]
if (paramClass == Promise::class.java) {
check(i == paramTypes.size - 1) { "Promise must be used as last parameter only" }
}
append(paramTypeToChar(paramClass))
}
}
private fun buildArgumentExtractors(paramTypes: Array<Class<*>>): Array<ArgumentExtractor<*>> {
val argumentExtractors = arrayOfNulls<ArgumentExtractor<*>>(paramTypes.size)
var i = 0
while (i < paramTypes.size) {
val argumentClass = paramTypes[i]
val extractor: ArgumentExtractor<*> =
when (argumentClass) {
Boolean::class.javaObjectType,
Boolean::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_BOOLEAN
Int::class.javaObjectType,
Int::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_INTEGER
Double::class.javaObjectType,
Double::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_DOUBLE
Float::class.javaObjectType,
Float::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_FLOAT
String::class.java -> ARGUMENT_EXTRACTOR_STRING
Callback::class.java -> ARGUMENT_EXTRACTOR_CALLBACK
Promise::class.java -> {
check(i == paramTypes.size - 1) { "Promise must be used as last parameter only" }
ARGUMENT_EXTRACTOR_PROMISE
}
ReadableMap::class.java -> ARGUMENT_EXTRACTOR_MAP
ReadableArray::class.java -> ARGUMENT_EXTRACTOR_ARRAY
Dynamic::class.java -> ARGUMENT_EXTRACTOR_DYNAMIC
else ->
throw RuntimeException("Got unknown argument class: ${argumentClass.simpleName}")
}
argumentExtractors[i] = extractor
i += extractor.getJSArgumentsNeeded()
}
return argumentExtractors.requireNoNulls()
}
private fun calculateJSArgumentsNeeded(): Int {
var n = 0
for (extractor in checkNotNull(argumentExtractors)) {
n += extractor.getJSArgumentsNeeded()
}
return n
}
private fun getAffectedRange(startIndex: Int, jsArgumentsNeeded: Int): String =
if (jsArgumentsNeeded > 1) {
"$startIndex-${startIndex + jsArgumentsNeeded - 1}"
} else {
"$startIndex"
}
override fun invoke(jsInstance: JSInstance, parameters: ReadableArray) {
val traceName = moduleWrapper.name + "." + method.name
SystraceMessage.beginSection(TRACE_TAG_REACT, "callJavaModuleMethod")
.arg("method", traceName)
.flush()
if (DEBUG) {
PrinterHolder.printer.logMessage(
ReactDebugOverlayTags.BRIDGE_CALLS, "JS->Java: %s.%s()", moduleWrapper.name, method.name)
}
try {
if (!argumentsProcessed) {
processArguments()
}
val validatedArguments =
requireNotNull(arguments) { "processArguments failed: 'arguments' is null." }
val validatedArgumentExtractors =
requireNotNull(argumentExtractors) {
"processArguments failed: 'argumentExtractors' is null."
}
if (jsArgumentsNeeded != parameters.size()) {
throw NativeArgumentsParseException(
"$traceName got ${parameters.size()} arguments, expected $jsArgumentsNeeded")
}
var i = 0
var jsArgumentsConsumed = 0
try {
while (i < validatedArgumentExtractors.size) {
validatedArguments[i] =
validatedArgumentExtractors[i].extractArgument(
jsInstance, parameters, jsArgumentsConsumed)
jsArgumentsConsumed += validatedArgumentExtractors[i].getJSArgumentsNeeded()
i++
}
} catch (e: UnexpectedNativeTypeException) {
throw NativeArgumentsParseException(
"${e.message} (constructing arguments for $traceName at argument index ${
getAffectedRange(
jsArgumentsConsumed,
validatedArgumentExtractors[i].getJSArgumentsNeeded()
)
})",
e)
} catch (e: NullPointerException) {
throw NativeArgumentsParseException(
"${e.message} (constructing arguments for $traceName at argument index ${
getAffectedRange(
jsArgumentsConsumed,
validatedArgumentExtractors[i].getJSArgumentsNeeded()
)
})",
e)
}
try {
method.invoke(moduleWrapper.module, *validatedArguments)
} catch (e: IllegalArgumentException) {
throw RuntimeException(createInvokeExceptionMessage(traceName), e)
} catch (e: IllegalAccessException) {
throw RuntimeException(createInvokeExceptionMessage(traceName), e)
} catch (e: InvocationTargetException) {
// Exceptions thrown from native module calls end up wrapped in InvocationTargetException
// which just make traces harder to read and bump out useful information
if (e.cause is RuntimeException) {
throw (e.cause as RuntimeException)
}
throw RuntimeException(createInvokeExceptionMessage(traceName), e)
}
} finally {
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
}
}
companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"JavaMethodWrapper", LegacyArchitectureLogLevel.ERROR)
}
private val ARGUMENT_EXTRACTOR_BOOLEAN: ArgumentExtractor<Boolean> =
object : ArgumentExtractor<Boolean>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Boolean = jsArguments.getBoolean(atIndex)
}
private val ARGUMENT_EXTRACTOR_DOUBLE: ArgumentExtractor<Double> =
object : ArgumentExtractor<Double>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Double = jsArguments.getDouble(atIndex)
}
private val ARGUMENT_EXTRACTOR_FLOAT: ArgumentExtractor<Float> =
object : ArgumentExtractor<Float>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Float = jsArguments.getDouble(atIndex).toFloat()
}
private val ARGUMENT_EXTRACTOR_INTEGER: ArgumentExtractor<Int> =
object : ArgumentExtractor<Int>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Int = jsArguments.getDouble(atIndex).toInt()
}
private val ARGUMENT_EXTRACTOR_STRING: ArgumentExtractor<String> =
object : ArgumentExtractor<String>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): String? = jsArguments.getString(atIndex)
}
private val ARGUMENT_EXTRACTOR_ARRAY: ArgumentExtractor<ReadableArray> =
object : ArgumentExtractor<ReadableArray>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): ReadableArray? = jsArguments.getArray(atIndex)
}
private val ARGUMENT_EXTRACTOR_DYNAMIC: ArgumentExtractor<Dynamic> =
object : ArgumentExtractor<Dynamic>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Dynamic = DynamicFromArray.create(jsArguments, atIndex)
}
private val ARGUMENT_EXTRACTOR_MAP: ArgumentExtractor<ReadableMap> =
object : ArgumentExtractor<ReadableMap>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): ReadableMap? = jsArguments.getMap(atIndex)
}
private val ARGUMENT_EXTRACTOR_CALLBACK: ArgumentExtractor<Callback> =
object : ArgumentExtractor<Callback>() {
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Callback? =
if (jsArguments.isNull(atIndex)) {
null
} else {
val id = jsArguments.getDouble(atIndex).toInt()
CallbackImpl(jsInstance, id)
}
}
private val ARGUMENT_EXTRACTOR_PROMISE: ArgumentExtractor<Promise> =
object : ArgumentExtractor<Promise>() {
override fun getJSArgumentsNeeded(): Int = 2
override fun extractArgument(
jsInstance: JSInstance,
jsArguments: ReadableArray,
atIndex: Int
): Promise {
val resolve =
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex)
val reject =
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex + 1)
return PromiseImpl(resolve, reject)
}
}
private val DEBUG =
PrinterHolder.printer.shouldDisplayLogMessage(ReactDebugOverlayTags.BRIDGE_CALLS)
private fun paramTypeToChar(paramClass: Class<*>): Char {
val tryCommon = commonTypeToChar(paramClass)
if (tryCommon != '\u0000') {
return tryCommon
}
return when (paramClass) {
Callback::class.java -> 'X'
Promise::class.java -> 'P'
ReadableMap::class.java -> 'M'
ReadableArray::class.java -> 'A'
Dynamic::class.java -> 'Y'
else -> throw RuntimeException("Got unknown param class: ${paramClass.simpleName}")
}
}
private fun returnTypeToChar(returnClass: Class<*>): Char {
// Keep this in sync with MethodInvoker
val tryCommon = commonTypeToChar(returnClass)
if (tryCommon != '\u0000') {
return tryCommon
}
return when (returnClass) {
Void.TYPE -> 'v'
WritableMap::class.java -> 'M'
WritableArray::class.java -> 'A'
else -> throw RuntimeException("Got unknown return class: ${returnClass.simpleName}")
}
}
private fun commonTypeToChar(typeClass: Class<*>): Char {
return when (typeClass) {
Boolean::class.javaPrimitiveType -> 'z'
Boolean::class.javaObjectType -> 'Z'
Int::class.javaPrimitiveType -> 'i'
Int::class.javaObjectType -> 'I'
Double::class.javaPrimitiveType -> 'd'
Double::class.javaObjectType -> 'D'
Float::class.javaPrimitiveType -> 'f'
Float::class.javaObjectType -> 'F'
String::class.java -> 'S'
else -> '\u0000'
}
}
/**
* Makes it easier to determine the cause of an error invoking a native method from Javascript
* code by adding the function name.
*/
private fun createInvokeExceptionMessage(traceName: String): String =
"Could not invoke $traceName"
}
}
@@ -67,7 +67,7 @@ public class JavaScriptModuleRegistry {
return name ?: getJSModuleName(moduleInterface).also { name = it }
}
override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
public override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
val jsArgs = if (args != null) Arguments.fromJavaArgs(args) else WritableNativeArray()
catalystInstance.callFunction(getJSModuleName(), method.name, jsArgs)
return null
@@ -0,0 +1,247 @@
/*
* 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.
*/
package com.facebook.react.bridge;
import static com.facebook.infer.annotation.Assertions.assertNotNull;
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_MODULE_END;
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_MODULE_START;
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.debug.holder.PrinterHolder;
import com.facebook.debug.tags.ReactDebugOverlayTags;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.systrace.SystraceMessage;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Provider;
/**
* Holder to enable us to lazy create native modules.
*
* <p>This works by taking a provider instead of an instance, when it is first required we'll create
* and initialize it. Initialization currently always happens on the UI thread but this is due to
* change for performance reasons.
*
* <p>Lifecycle events via a {@link LifecycleEventListener} will still always happen on the UI
* thread.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
@DoNotStrip
public class ModuleHolder {
private static final AtomicInteger sInstanceKeyCounter = new AtomicInteger(1);
private final int mInstanceKey = sInstanceKeyCounter.getAndIncrement();
private final String mName;
private final ReactModuleInfo mReactModuleInfo;
private @Nullable Provider<? extends NativeModule> mProvider;
// Outside of the constructor, these should only be checked or set when synchronized on this
private @Nullable @GuardedBy("this") NativeModule mModule;
// These are used to communicate phases of creation and initialization across threads
private @GuardedBy("this") boolean mInitializable;
private @GuardedBy("this") boolean mIsCreating;
private @GuardedBy("this") boolean mIsInitializing;
public ModuleHolder(ReactModuleInfo moduleInfo, Provider<? extends NativeModule> provider) {
mName = moduleInfo.name();
mProvider = provider;
mReactModuleInfo = moduleInfo;
if (moduleInfo.needsEagerInit()) {
mModule = create();
}
}
public ModuleHolder(NativeModule nativeModule) {
mName = nativeModule.getName();
mReactModuleInfo =
new ReactModuleInfo(
nativeModule.getName(),
nativeModule.getClass().getSimpleName(),
nativeModule.canOverrideExistingModule(),
true,
CxxModuleWrapper.class.isAssignableFrom(nativeModule.getClass()),
ReactModuleInfo.classIsTurboModule(nativeModule.getClass()));
mModule = nativeModule;
PrinterHolder.getPrinter()
.logMessage(ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", mName);
}
/*
* Checks if mModule has been created, and if so tries to initialize the module unless another
* thread is already doing the initialization.
* If mModule has not been created, records that initialization is needed
*/
/* package */ void markInitializable() {
boolean shouldInitializeNow = false;
NativeModule module = null;
synchronized (this) {
mInitializable = true;
if (mModule != null) {
Assertions.assertCondition(!mIsInitializing);
shouldInitializeNow = true;
module = mModule;
}
}
if (shouldInitializeNow) {
Assertions.assertNotNull(module);
doInitialize(module);
}
}
/* package */ synchronized boolean hasInstance() {
return mModule != null;
}
public synchronized void destroy() {
if (mModule != null) {
mModule.invalidate();
}
}
@DoNotStrip
public String getName() {
return mName;
}
public boolean getCanOverrideExistingModule() {
return mReactModuleInfo.canOverrideExistingModule();
}
public boolean isTurboModule() {
return mReactModuleInfo.isTurboModule();
}
public boolean isCxxModule() {
return mReactModuleInfo.isCxxModule();
}
public String getClassName() {
return mReactModuleInfo.className();
}
@DoNotStrip
public NativeModule getModule() {
NativeModule module;
boolean shouldCreate = false;
synchronized (this) {
if (mModule != null) {
return mModule;
// if mModule has not been set, and no one is creating it. Then this thread should call
// create
} else if (!mIsCreating) {
shouldCreate = true;
mIsCreating = true;
} else {
// Wait for mModule to be created by another thread
}
}
if (shouldCreate) {
module = create();
// Once module is built (and initialized if markInitializable has been called), modify mModule
// And signal any waiting threads that it is acceptable to read the field now
synchronized (this) {
mIsCreating = false;
this.notifyAll();
}
return module;
} else {
synchronized (this) {
// Block waiting for another thread to build mModule instance
// Since mIsCreating is true until after creation and instantiation (if needed), we wait
// until the module is ready to use.
while (mModule == null && mIsCreating) {
try {
this.wait();
} catch (InterruptedException e) {
continue;
}
}
return Assertions.assertNotNull(mModule);
}
}
}
private NativeModule create() {
SoftAssertions.assertCondition(mModule == null, "Creating an already created module.");
ReactMarker.logMarker(CREATE_MODULE_START, mName, mInstanceKey);
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.createModule")
.arg("name", mName)
.flush();
PrinterHolder.getPrinter()
.logMessage(ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", mName);
NativeModule module;
try {
module = assertNotNull(mProvider).get();
mProvider = null;
boolean shouldInitializeNow = false;
synchronized (this) {
mModule = module;
if (mInitializable && !mIsInitializing) {
shouldInitializeNow = true;
}
}
if (shouldInitializeNow) {
doInitialize(module);
}
} catch (Throwable ex) {
/**
* When NativeModules are created from JavaScript, any exception that occurs in the creation
* process will have its stack trace swallowed before we display a RedBox to the user. Really,
* we should have our HostObjects on Android understand JniExceptions and log the stack trace
* to logcat. For now, logging to Logcat directly when creation fails is sufficient.
*
* @todo(T53311351)
*/
FLog.e(ReactConstants.TAG, ex, "Failed to create NativeModule '%s'", mName);
throw ex;
} finally {
ReactMarker.logMarker(CREATE_MODULE_END, mName, mInstanceKey);
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
}
return module;
}
private void doInitialize(NativeModule module) {
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.initialize")
.arg("name", mName)
.flush();
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_START, mName, mInstanceKey);
try {
boolean shouldInitialize = false;
// Check to see if another thread is initializing the object, if not claim the responsibility
synchronized (this) {
if (mInitializable && !mIsInitializing) {
shouldInitialize = true;
mIsInitializing = true;
}
}
if (shouldInitialize) {
module.initialize();
// Once finished, set flags accordingly, but we don't expect anyone to wait for this to
// finish
// So no need to notify other threads
synchronized (this) {
mIsInitializing = false;
}
}
} finally {
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_END, mName, mInstanceKey);
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
}
}
}
@@ -1,226 +0,0 @@
/*
* 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.
*/
package com.facebook.react.bridge
import androidx.annotation.GuardedBy
import com.facebook.common.logging.FLog
import com.facebook.debug.holder.PrinterHolder
import com.facebook.debug.tags.ReactDebugOverlayTags
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.common.ReactConstants
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.systrace.Systrace.TRACE_TAG_REACT
import com.facebook.systrace.SystraceMessage
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Provider
/**
* Holder to enable us to lazy create native modules.
*
* This works by taking a provider instead of an instance, when it is first required we'll create
* and initialize it. Initialization currently always happens on the UI thread but this is due to
* change for performance reasons.
*
* Lifecycle events via a [LifecycleEventListener] will still always happen on the UI thread.
*/
@DoNotStrip
public class ModuleHolder {
private val instanceKey = instanceKeyCounter.getAndIncrement()
@get:DoNotStrip public val name: String
private val reactModuleInfo: ReactModuleInfo
private var provider: Provider<out NativeModule>? = null
// Outside of the constructor, this should only be checked or set when synchronized on this
@GuardedBy("this") private var internalModule: NativeModule? = null
// This is used to communicate phases of creation and initialization across threads
@GuardedBy("this") private var initializable = false
@GuardedBy("this") private var isCreating = false
@GuardedBy("this") private var isInitializing = false
public constructor(moduleInfo: ReactModuleInfo, provider: Provider<out NativeModule?>) {
name = moduleInfo.name
this.provider = provider
reactModuleInfo = moduleInfo
if (moduleInfo.needsEagerInit) {
internalModule = create()
}
}
public constructor(nativeModule: NativeModule) {
name = nativeModule.name
reactModuleInfo =
ReactModuleInfo(
nativeModule.name,
nativeModule.javaClass.simpleName,
nativeModule.canOverrideExistingModule(),
true,
CxxModuleWrapper::class.java.isAssignableFrom(nativeModule.javaClass),
ReactModuleInfo.classIsTurboModule(nativeModule.javaClass))
internalModule = nativeModule
PrinterHolder.printer.logMessage(
ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", name)
}
/*
* Checks if [internalModule] has been created, and if so tries to initialize the module unless another
* thread is already doing the initialization.
* If [internalModule] has not been created, records that initialization is needed.
*/
internal fun markInitializable() {
var shouldInitializeNow = false
var module: NativeModule? = null
synchronized(this) {
initializable = true
if (internalModule != null) {
check(!isInitializing)
shouldInitializeNow = true
module = internalModule
}
}
if (shouldInitializeNow) {
checkNotNull(module)
doInitialize(module)
}
}
@Synchronized internal fun hasInstance(): Boolean = internalModule != null
@Synchronized
public fun destroy() {
internalModule?.invalidate()
}
public val canOverrideExistingModule: Boolean
get() = reactModuleInfo.canOverrideExistingModule
public val isTurboModule: Boolean
get() = reactModuleInfo.isTurboModule
public val isCxxModule: Boolean
get() = reactModuleInfo.isCxxModule
public val className: String
get() = reactModuleInfo.className
@get:DoNotStrip
public val module: NativeModule
get() {
val module: NativeModule
var shouldCreate = false
synchronized(this) {
val safeModule = internalModule
if (safeModule != null) {
return safeModule
// if `internalModule` has not been set, and no one is creating it. Then this thread
// should call
// create
} else if (!isCreating) {
shouldCreate = true
isCreating = true
} else {
// Wait for `internalModule` to be created by another thread
}
}
if (shouldCreate) {
module = create()
// Once module is built (and initialized if markInitializable has been called), modify
// `internalModule`
// And signal any waiting threads that it is acceptable to read the field now
synchronized(this) {
isCreating = false
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (this as Object).notifyAll()
}
return module
} else {
synchronized(this) {
// Block waiting for another thread to build `internalModule` instance
// Since isCreating is true until after creation and instantiation (if needed), we wait
// until the module is ready to use.
while (internalModule == null && isCreating) {
try {
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (this as Object).wait()
} catch (e: InterruptedException) {
continue
}
}
return checkNotNull(internalModule)
}
}
}
private fun create(): NativeModule {
SoftAssertions.assertCondition(internalModule == null, "Creating an already created module.")
ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_START, name, instanceKey)
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.createModule")
.arg("name", name)
.flush()
PrinterHolder.printer.logMessage(
ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", name)
val module: NativeModule
try {
module = checkNotNull(provider).get()
provider = null
var shouldInitializeNow = false
synchronized(this) {
internalModule = module
if (initializable && !isInitializing) {
shouldInitializeNow = true
}
}
if (shouldInitializeNow) {
doInitialize(module)
}
} catch (e: Throwable) {
/**
* When NativeModules are created from JavaScript, any exception that occurs in the creation
* process will have its stack trace swallowed before we display a RedBox to the user. Really,
* we should have our HostObjects on Android understand JniExceptions and log the stack trace
* to logcat. For now, logging to Logcat directly when creation fails is sufficient.
*
* @todo(T53311351)
*/
FLog.e(ReactConstants.TAG, e, "Failed to create NativeModule '%s'", name)
throw e
} finally {
ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_END, name, instanceKey)
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
}
return module
}
private fun doInitialize(module: NativeModule?) {
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.initialize")
.arg("name", name)
.flush()
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_START, name, instanceKey)
try {
var shouldInitialize = false
// Check to see if another thread is initializing the object, if not claim the responsibility
synchronized(this) {
if (initializable && !isInitializing) {
shouldInitialize = true
isInitializing = true
}
}
if (shouldInitialize) {
module?.initialize()
// Once finished, set flags accordingly, but we don't expect anyone to wait for this to
// finish, so no need to notify other threads.
synchronized(this) { isInitializing = false }
}
} finally {
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_END, name, instanceKey)
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
}
}
private companion object {
private val instanceKeyCounter = AtomicInteger(1)
}
}
@@ -33,7 +33,7 @@ internal class FpsView(reactContext: ReactContext?) : FrameLayout(reactContext!!
textView = findViewById<View>(R.id.fps_text) as TextView
frameCallback = FpsDebugFrameCallback(reactContext!!)
fpsMonitorRunnable = FPSMonitorRunnable()
setCurrentFPS(0.0, 0.0, 0, 0, frameCallback.isRunningOnFabric)
setCurrentFPS(0.0, 0.0, 0, 0)
}
override fun onAttachedToWindow() {
@@ -53,21 +53,16 @@ internal class FpsView(reactContext: ReactContext?) : FrameLayout(reactContext!!
currentFPS: Double,
currentJSFPS: Double,
droppedUIFrames: Int,
total4PlusFrameStutters: Int,
runningOnFabric: Boolean
total4PlusFrameStutters: Int
) {
var fpsString =
val fpsString =
String.format(
Locale.US,
"UI: %.1f fps\n%d dropped so far\n%d stutters (4+) so far",
"UI: %.1f fps\n%d dropped so far\n%d stutters (4+) so far\nJS: %.1f fps",
currentFPS,
droppedUIFrames,
total4PlusFrameStutters)
if (!runningOnFabric) {
// The JS FPS is only relevant for the legacy architecture, as Fabric we don't use
// BridgeIdleDebugListener to track JS frame drops.
fpsString += String.format(Locale.US, "\nJS: %.1f fps", currentJSFPS)
}
total4PlusFrameStutters,
currentJSFPS)
textView.text = fpsString
FLog.d(ReactConstants.TAG, fpsString)
}
@@ -85,11 +80,7 @@ internal class FpsView(reactContext: ReactContext?) : FrameLayout(reactContext!!
totalFramesDropped += frameCallback.expectedNumFrames - frameCallback.numFrames
total4PlusFrameStutters += frameCallback.get4PlusFrameStutters()
setCurrentFPS(
frameCallback.fps,
frameCallback.jsFPS,
totalFramesDropped,
total4PlusFrameStutters,
frameCallback.isRunningOnFabric)
frameCallback.fps, frameCallback.jsFPS, totalFramesDropped, total4PlusFrameStutters)
frameCallback.reset()
postDelayed(this, UPDATE_INTERVAL_MS.toLong())
}
@@ -40,7 +40,7 @@ internal class DevMenuModule(
devSupportManager.setHotModuleReplacementEnabled(enabled)
}
companion object {
const val NAME: String = NativeDevMenuSpec.NAME
public companion object {
public const val NAME: String = NativeDevMenuSpec.NAME
}
}
@@ -8,10 +8,12 @@
package com.facebook.react.modules.debug
import android.view.Choreographer
import com.facebook.infer.annotation.Assertions
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.uimanager.UIManagerModule
import java.util.TreeMap
/**
* Each time a frame is drawn, records whether it should have expected any more callbacks since the
@@ -23,8 +25,17 @@ import com.facebook.react.uimanager.UIManagerModule
* idle and not trying to update the UI. This is different from the FPS above since JS rendering is
* async.
*/
internal class FpsDebugFrameCallback(private val reactContext: ReactContext) :
public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
Choreographer.FrameCallback {
public class FpsInfo(
public val totalFrames: Int,
public val totalJsFrames: Int,
public val totalExpectedFrames: Int,
public val total4PlusFrameStutters: Int,
public val fps: Double,
public val jsFps: Double,
public val totalTimeMs: Int
)
private var choreographer: Choreographer? = null
private val didJSUpdateUiDuringFrameDetector: DidJSUpdateUiDuringFrameDetector =
@@ -35,7 +46,9 @@ internal class FpsDebugFrameCallback(private val reactContext: ReactContext) :
private var expectedNumFramesPrev = 0
private var fourPlusFrameStutters = 0
private var numFrameCallbacksWithBatchDispatches = 0
private var isRecordingFpsInfoAtEachFrame = false
private var targetFps = DEFAULT_FPS
private var timeToFps: TreeMap<Long, FpsInfo>? = null
override fun doFrame(l: Long) {
if (firstFrameTime == -1L) {
@@ -52,12 +65,25 @@ internal class FpsDebugFrameCallback(private val reactContext: ReactContext) :
if (framesDropped >= 4) {
fourPlusFrameStutters++
}
if (isRecordingFpsInfoAtEachFrame) {
Assertions.assertNotNull(timeToFps)
val info =
FpsInfo(
numFrames,
numJSFrames,
expectedNumFrames,
fourPlusFrameStutters,
fps,
jsFPS,
totalTimeMS)
timeToFps?.put(System.currentTimeMillis(), info)
}
expectedNumFramesPrev = expectedNumFrames
choreographer?.postFrameCallback(this)
}
@JvmOverloads
fun start(targetFps: Double = this.targetFps) {
public fun start(targetFps: Double = this.targetFps) {
// T172641976: re-think if we need to implement addBridgeIdleDebugListener and
// removeBridgeIdleDebugListener for Bridgeless
@Suppress("DEPRECATION")
@@ -65,11 +91,6 @@ internal class FpsDebugFrameCallback(private val reactContext: ReactContext) :
val uiManagerModule = reactContext.getNativeModule(UIManagerModule::class.java)
if (!reactContext.isBridgeless) {
reactContext.catalystInstance.addBridgeIdleDebugListener(didJSUpdateUiDuringFrameDetector)
isRunningOnFabric = false
} else {
// T172641976 Consider either implementing a mechanism similar to addBridgeIdleDebugListener
// for Fabric or point users to use RNDT.
isRunningOnFabric = true
}
uiManagerModule?.setViewHierarchyUpdateDebugListener(didJSUpdateUiDuringFrameDetector)
}
@@ -80,7 +101,13 @@ internal class FpsDebugFrameCallback(private val reactContext: ReactContext) :
}
}
fun stop() {
public fun startAndRecordFpsAtEachFrame() {
timeToFps = TreeMap()
isRecordingFpsInfoAtEachFrame = true
start()
}
public fun stop() {
@Suppress("DEPRECATION")
if (!ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE) {
val uiManagerModule = reactContext.getNativeModule(UIManagerModule::class.java)
@@ -96,48 +123,53 @@ internal class FpsDebugFrameCallback(private val reactContext: ReactContext) :
}
}
val fps: Double
public val fps: Double
get() =
if (lastFrameTime == firstFrameTime) {
0.0
} else numFrames.toDouble() * 1e9 / (lastFrameTime - firstFrameTime)
/**
* Please note that this value is not relevant if running on Fabric. That's because we don't
* implement addBridgeIdleDebugListener on Fabric.
*/
val jsFPS: Double
public val jsFPS: Double
get() =
if (lastFrameTime == firstFrameTime) {
0.0
} else numJSFrames.toDouble() * 1e9 / (lastFrameTime - firstFrameTime)
val numFrames: Int
public val numFrames: Int
get() = numFrameCallbacks - 1
private val numJSFrames: Int
public val numJSFrames: Int
get() = numFrameCallbacksWithBatchDispatches - 1
val expectedNumFrames: Int
public val expectedNumFrames: Int
get() {
val totalTimeMS = totalTimeMS.toDouble()
return (targetFps * totalTimeMS / 1000 + 1).toInt()
}
var isRunningOnFabric = true
private set
public fun get4PlusFrameStutters(): Int = fourPlusFrameStutters
fun get4PlusFrameStutters(): Int = fourPlusFrameStutters
private val totalTimeMS: Int
public val totalTimeMS: Int
get() = ((lastFrameTime.toDouble() - firstFrameTime) / 1000000.0).toInt()
fun reset() {
/**
* Returns the FpsInfo as if stop had been called at the given upToTimeMs. Only valid if
* monitoring was started with [startAndRecordFpsAtEachFrame].
*/
public fun getFpsInfo(upToTimeMs: Long): FpsInfo? {
Assertions.assertNotNull(timeToFps, "FPS was not recorded at each frame!")
val (_, value) = timeToFps?.floorEntry(upToTimeMs) ?: return null
return value
}
public fun reset() {
firstFrameTime = -1
lastFrameTime = -1
numFrameCallbacks = 0
fourPlusFrameStutters = 0
numFrameCallbacksWithBatchDispatches = 0
isRecordingFpsInfoAtEachFrame = false
timeToFps = null
}
private companion object {
@@ -74,7 +74,7 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
reactApplicationContext.removeLifecycleEventListener(this)
}
companion object {
const val NAME: String = NativeDeviceInfoSpec.NAME
public companion object {
public const val NAME: String = NativeDeviceInfoSpec.NAME
}
}
@@ -84,7 +84,7 @@ internal class BridgelessCatalystInstance(private val reactHost: ReactHostImpl)
throw UnsupportedOperationException("Unimplemented method 'destroy'")
}
override val isDestroyed: Boolean
public override val isDestroyed: Boolean
get() = throw UnsupportedOperationException("Unimplemented method 'isDestroyed'")
@VisibleForTesting
@@ -96,16 +96,16 @@ internal class BridgelessCatalystInstance(private val reactHost: ReactHostImpl)
reactHost.currentReactContext?.getJSModule(jsInterface)
@get:Deprecated("Deprecated in Java")
override val javaScriptContextHolder: JavaScriptContextHolder
public override val javaScriptContextHolder: JavaScriptContextHolder
get() = reactHost.javaScriptContextHolder!!
@Suppress("INAPPLICABLE_JVM_NAME")
@get:Deprecated("Deprecated in Java")
@get:JvmName("getJSCallInvokerHolder") // This is needed to keep backward compatibility
override val jsCallInvokerHolder: CallInvokerHolder
public override val jsCallInvokerHolder: CallInvokerHolder
get() = reactHost.jsCallInvokerHolder!!
override val nativeMethodCallInvokerHolder: NativeMethodCallInvokerHolder
public override val nativeMethodCallInvokerHolder: NativeMethodCallInvokerHolder
get() =
throw UnsupportedOperationException(
"Unimplemented method 'getNativeMethodCallInvokerHolder'")
@@ -119,23 +119,23 @@ internal class BridgelessCatalystInstance(private val reactHost: ReactHostImpl)
override fun getNativeModule(moduleName: String): NativeModule? =
reactHost.getNativeModule(moduleName)
override val nativeModules: Collection<NativeModule>
public override val nativeModules: Collection<NativeModule>
get() = reactHost.nativeModules
override val reactQueueConfiguration: ReactQueueConfiguration
public override val reactQueueConfiguration: ReactQueueConfiguration
get() = reactHost.reactQueueConfiguration!!
override val runtimeExecutor: RuntimeExecutor?
public override val runtimeExecutor: RuntimeExecutor?
get() = reactHost.runtimeExecutor
override val runtimeScheduler: RuntimeScheduler
public override val runtimeScheduler: RuntimeScheduler
get() = throw UnsupportedOperationException("Unimplemented method 'getRuntimeScheduler'")
override fun extendNativeModules(modules: NativeModuleRegistry) {
public override fun extendNativeModules(modules: NativeModuleRegistry) {
throw UnsupportedOperationException("Unimplemented method 'extendNativeModules'")
}
override val sourceURL: String
public override val sourceURL: String
get() = throw UnsupportedOperationException("Unimplemented method 'getSourceURL'")
override fun addBridgeIdleDebugListener(listener: NotThreadSafeBridgeIdleDebugListener) {
@@ -57,7 +57,7 @@ internal class BridgelessReactContext(context: Context, private val reactHost: R
override fun getSourceURL(): String? = sourceURLRef.get()
fun setSourceURL(sourceURL: String?) {
public fun setSourceURL(sourceURL: String?) {
sourceURLRef.set(sourceURL)
}
@@ -59,7 +59,7 @@ internal class FabricEventDispatcher(
eventEmitter.registerFabricEventEmitter(fabricEventEmitter)
}
override fun dispatchEvent(event: Event<*>) {
public override fun dispatchEvent(event: Event<*>) {
for (listener in listeners) {
listener.onEventDispatch(event)
}
@@ -100,7 +100,7 @@ internal class FabricEventDispatcher(
}
}
override fun dispatchAllEvents() {
public override fun dispatchAllEvents() {
scheduleDispatchOfBatchedEvents()
}
@@ -116,46 +116,46 @@ internal class FabricEventDispatcher(
}
/** Add a listener to this EventDispatcher. */
override fun addListener(listener: EventDispatcherListener) {
public override fun addListener(listener: EventDispatcherListener) {
listeners.add(listener)
}
/** Remove a listener from this EventDispatcher. */
override fun removeListener(listener: EventDispatcherListener) {
public override fun removeListener(listener: EventDispatcherListener) {
listeners.remove(listener)
}
override fun addBatchEventDispatchedListener(listener: BatchEventDispatchedListener) {
public override fun addBatchEventDispatchedListener(listener: BatchEventDispatchedListener) {
postEventDispatchListeners.add(listener)
}
override fun removeBatchEventDispatchedListener(listener: BatchEventDispatchedListener) {
public override fun removeBatchEventDispatchedListener(listener: BatchEventDispatchedListener) {
postEventDispatchListeners.remove(listener)
}
override fun onHostResume() {
public override fun onHostResume() {
scheduleDispatchOfBatchedEvents()
if (!ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) {
currentFrameCallback.resume()
}
}
override fun onHostPause() {
public override fun onHostPause() {
cancelDispatchOfBatchedEvents()
}
override fun onHostDestroy() {
public override fun onHostDestroy() {
cancelDispatchOfBatchedEvents()
}
fun invalidate() {
public fun invalidate() {
eventEmitter.registerFabricEventEmitter(null)
UiThreadUtil.runOnUiThread { cancelDispatchOfBatchedEvents() }
}
@Deprecated("Private API, should only be used when the concrete implementation is known.")
override fun onCatalystInstanceDestroyed() {
public override fun onCatalystInstanceDestroyed() {
invalidate()
}
@@ -21,9 +21,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
internal class LayoutUpdateAnimation : AbstractLayoutAnimation() {
override fun isValid(): Boolean = durationMs > 0
internal override fun isValid(): Boolean = durationMs > 0
override fun createAnimationImpl(
internal override fun createAnimationImpl(
view: View,
x: Int,
y: Int,
@@ -26,7 +26,7 @@ internal class ColorStop(var color: Int? = null, val position: LengthPercentage?
internal class ProcessedColorStop(var color: Int? = null, val position: Float? = null)
internal object ColorStopUtils {
fun getFixedColorStops(
public fun getFixedColorStops(
colorStops: List<ColorStop>,
gradientLineLength: Float
): List<ProcessedColorStop> {
@@ -10,5 +10,5 @@ package com.facebook.react.uimanager.style
import android.graphics.Shader
internal interface Gradient {
fun getShader(width: Float, height: Float): Shader
public fun getShader(width: Float, height: Float): Shader
}
@@ -34,7 +34,7 @@ internal class MaintainVisibleScrollPositionHelper<ScrollViewT>(
private val horizontal: Boolean
) : UIManagerListener where ScrollViewT : HasSmoothScroll?, ScrollViewT : ViewGroup? {
var config: Config? = null
public var config: Config? = null
private var firstVisibleViewRef: WeakReference<View>? = null
private var prevFirstVisibleFrame: Rect? = null
private var isListening = false
@@ -16,7 +16,7 @@ import com.facebook.proguard.annotations.DoNotStrip
*/
@DoNotStrip
internal class PreparedLayout(
val layout: Layout,
val maximumNumberOfLines: Int,
val verticalOffset: Float
public val layout: Layout,
public val maximumNumberOfLines: Int,
public val verticalOffset: Float
)
@@ -41,7 +41,7 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
private var clickableSpans: List<ClickableSpan> = emptyList()
private var selection: TextSelection? = null
var preparedLayout: PreparedLayout? = null
public var preparedLayout: PreparedLayout? = null
set(value) {
if (field != value) {
val lastSelection = selection
@@ -63,7 +63,7 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
// T221698007: This is closest to existing behavior, but does not align with web. We may want to
// change in the future if not too breaking.
var overflow: Overflow = Overflow.HIDDEN
public var overflow: Overflow = Overflow.HIDDEN
set(value) {
if (field != value) {
field = value
@@ -71,9 +71,9 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
}
}
@ColorInt var selectionColor: Int? = null
public @ColorInt var selectionColor: Int? = null
val text: CharSequence?
public val text: CharSequence?
get() = preparedLayout?.layout?.text
init {
@@ -88,7 +88,7 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
preparedLayout = null
}
fun recycleView(): Unit {
public fun recycleView(): Unit {
initView()
BackgroundStyleApplicator.reset(this)
overflow = Overflow.HIDDEN
@@ -122,7 +122,7 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
// No-op
}
fun setSelection(start: Int, end: Int) {
public fun setSelection(start: Int, end: Int) {
val layout = checkNotNull(preparedLayout).layout
if (start < 0 || end > layout.text.length || start >= end) {
throw IllegalArgumentException(
@@ -143,7 +143,7 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
invalidate()
}
fun clearSelection() {
public fun clearSelection() {
selection = null
invalidate()
}
@@ -98,23 +98,23 @@ internal class PreparedLayoutTextViewManager :
}
@ReactProp(name = "overflow")
fun setOverflow(view: PreparedLayoutTextView, overflow: String?): Unit {
public fun setOverflow(view: PreparedLayoutTextView, overflow: String?): Unit {
view.overflow = overflow?.let { Overflow.fromString(it) } ?: Overflow.HIDDEN
}
@ReactProp(name = "accessible")
fun setAccessible(view: PreparedLayoutTextView, accessible: Boolean): Unit {
public fun setAccessible(view: PreparedLayoutTextView, accessible: Boolean): Unit {
view.isFocusable = accessible
}
@ReactProp(name = "selectable", defaultBoolean = false)
fun setSelectable(view: PreparedLayoutTextView, isSelectable: Boolean): Unit {
public fun setSelectable(view: PreparedLayoutTextView, isSelectable: Boolean): Unit {
// T222052152: Implement fine-grained text selection for PreparedLayoutTextView
// view.setTextIsSelectable(isSelectable);
}
@ReactProp(name = "selectionColor", customType = "Color")
fun setSelectionColor(view: PreparedLayoutTextView, color: Int?): Unit {
public fun setSelectionColor(view: PreparedLayoutTextView, color: Int?): Unit {
if (color == null) {
view.selectionColor = DefaultStyleValuesUtil.getDefaultTextColorHighlight(view.context)
} else {
@@ -131,7 +131,7 @@ internal class PreparedLayoutTextViewManager :
ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
ViewProps.BORDER_BOTTOM_LEFT_RADIUS],
defaultFloat = Float.NaN)
fun setBorderRadius(view: PreparedLayoutTextView, index: Int, borderRadius: Float): Unit {
public fun setBorderRadius(view: PreparedLayoutTextView, index: Int, borderRadius: Float): Unit {
val radius =
if (borderRadius.isNaN()) null
else LengthPercentage(borderRadius, LengthPercentageType.POINT)
@@ -139,7 +139,7 @@ internal class PreparedLayoutTextViewManager :
}
@ReactProp(name = "borderStyle")
fun setBorderStyle(view: PreparedLayoutTextView, borderStyle: String?): Unit {
public fun setBorderStyle(view: PreparedLayoutTextView, borderStyle: String?): Unit {
val parsedBorderStyle = if (borderStyle == null) null else BorderStyle.fromString(borderStyle)
BackgroundStyleApplicator.setBorderStyle(view, parsedBorderStyle)
}
@@ -155,7 +155,7 @@ internal class PreparedLayoutTextViewManager :
ViewProps.BORDER_START_WIDTH,
ViewProps.BORDER_END_WIDTH],
defaultFloat = Float.NaN)
fun setBorderWidth(view: PreparedLayoutTextView, index: Int, width: Float): Unit {
public fun setBorderWidth(view: PreparedLayoutTextView, index: Int, width: Float): Unit {
BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.values()[index], width)
}
@@ -174,12 +174,12 @@ internal class PreparedLayoutTextViewManager :
ViewProps.BORDER_BLOCK_START_COLOR,
],
customType = "Color")
fun setBorderColor(view: PreparedLayoutTextView, index: Int, color: Int?): Unit {
public fun setBorderColor(view: PreparedLayoutTextView, index: Int, color: Int?): Unit {
BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.values()[index], color)
}
@ReactProp(name = "disabled", defaultBoolean = false)
fun setDisabled(view: PreparedLayoutTextView, disabled: Boolean): Unit {
public fun setDisabled(view: PreparedLayoutTextView, disabled: Boolean): Unit {
view.setEnabled(!disabled)
}
@@ -214,7 +214,7 @@ internal class PreparedLayoutTextViewManager :
reactTextViewManagerCallback?.onPostProcessSpannable(text)
}
companion object {
const val REACT_CLASS: String = "RCTText"
public companion object {
public const val REACT_CLASS: String = "RCTText"
}
}
@@ -110,7 +110,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
if (child instanceof ReactRawTextShadowNode) {
String childText = ((ReactRawTextShadowNode) child).getText();
if (childText != null) {
sb.append(TextTransform.apply(childText, textAttributes.textTransform));
sb.append(TextTransform.applyNonNull(childText, textAttributes.textTransform));
}
} else if (child instanceof ReactBaseTextShadowNode) {
buildSpannedFromShadowNode(
@@ -265,7 +265,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
if (text != null) {
// Handle text that is provided via a prop (e.g. the `value` and `defaultValue` props on
// TextInput).
sb.append(TextTransform.apply(text, textShadowNode.mTextAttributes.textTransform));
sb.append(TextTransform.applyNonNull(text, textShadowNode.mTextAttributes.textTransform));
}
buildSpannedFromShadowNode(textShadowNode, sb, ops, null, supportsInlineViews, inlineViews, 0);
@@ -134,12 +134,12 @@ public constructor(
view.setSpanned(spanned)
val minimumFontSize: Float =
paragraphAttributes.getDouble(TextLayoutManager.PA_KEY_MINIMUM_FONT_SIZE).toFloat()
paragraphAttributes.getDouble(TextLayoutManager.PA_KEY_MINIMUM_FONT_SIZE.toInt()).toFloat()
view.setMinimumFontSize(minimumFontSize)
val textBreakStrategy =
TextAttributeProps.getTextBreakStrategy(
paragraphAttributes.getString(TextLayoutManager.PA_KEY_TEXT_BREAK_STRATEGY))
paragraphAttributes.getString(TextLayoutManager.PA_KEY_TEXT_BREAK_STRATEGY.toInt()))
val currentJustificationMode =
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) 0 else view.getJustificationMode()
@@ -147,7 +147,7 @@ public constructor(
spanned,
-1, // UNUSED FOR TEXT
false, // TODO add this into local Data
TextLayoutManager.getTextGravity(attributedString, spanned),
TextLayoutManager.getTextGravity(attributedString, spanned, view.gravityHorizontal),
textBreakStrategy,
TextAttributeProps.getJustificationMode(props, currentJustificationMode))
}
@@ -21,7 +21,11 @@ internal enum class TextTransform {
internal companion object {
@JvmStatic
fun apply(text: String, textTransform: TextTransform?): String =
fun apply(text: String?, textTransform: TextTransform?): String? =
text?.applyTextTransform(textTransform)
@JvmStatic
fun applyNonNull(text: String, textTransform: TextTransform?): String =
text.applyTextTransform(textTransform)
}
}
@@ -43,7 +43,7 @@ internal class FrescoBasedReactTextInlineImageShadowNode(
private var tintColor = 0
@ReactProp(name = "src")
fun setSource(sources: ReadableArray?) {
public fun setSource(sources: ReadableArray?) {
val source =
if (sources == null || sources.size() == 0 || sources.getType(0) != ReadableType.Map) null
else checkNotNull(sources.getMap(0)).getString("uri")
@@ -69,12 +69,12 @@ internal class FrescoBasedReactTextInlineImageShadowNode(
}
@ReactProp(name = "headers")
fun setHeaders(newHeaders: ReadableMap?) {
public fun setHeaders(newHeaders: ReadableMap?) {
headers = newHeaders
}
@ReactProp(name = "tintColor", customType = "Color")
fun setTintColor(newTintColor: Int) {
public fun setTintColor(newTintColor: Int) {
tintColor = newTintColor
}
@@ -98,13 +98,13 @@ internal class FrescoBasedReactTextInlineImageShadowNode(
}
@ReactProp(name = ViewProps.RESIZE_MODE)
fun setResizeMode(newResizeMode: String?) {
public fun setResizeMode(newResizeMode: String?) {
resizeMode = newResizeMode
}
fun getUri(): Uri? = uri
public fun getUri(): Uri? = uri
fun getHeaders(): ReadableMap? = headers
public fun getHeaders(): ReadableMap? = headers
override fun isVirtual(): Boolean = true
@@ -124,13 +124,13 @@ internal class FrescoBasedReactTextInlineImageShadowNode(
resizeMode)
}
fun getDraweeControllerBuilder() = draweeControllerBuilder
public fun getDraweeControllerBuilder() = draweeControllerBuilder
fun getCallerContext(): Any? = callerContext
public fun getCallerContext(): Any? = callerContext
// TODO: t9053573 is tracking that this code should be shared
companion object {
fun getResourceDrawableUri(context: Context, name: String?): Uri? {
public fun getResourceDrawableUri(context: Context, name: String?): Uri? {
if (name == null || name.isEmpty()) {
return null
}
@@ -73,23 +73,23 @@ internal class FrescoBasedReactTextInlineImageSpan(
* The ReactTextView that holds this ImageSpan is responsible for passing these methods on so that
* we can do proper lifetime management for Fresco
*/
override fun onDetachedFromWindow() {
public override fun onDetachedFromWindow() {
draweeHolder.onDetach()
}
override fun onStartTemporaryDetach() {
public override fun onStartTemporaryDetach() {
draweeHolder.onDetach()
}
override fun onAttachedToWindow() {
public override fun onAttachedToWindow() {
draweeHolder.onAttach()
}
override fun onFinishTemporaryDetach() {
public override fun onFinishTemporaryDetach() {
draweeHolder.onAttach()
}
override fun getSize(
public override fun getSize(
paint: Paint,
text: CharSequence,
start: Int,
@@ -110,11 +110,11 @@ internal class FrescoBasedReactTextInlineImageSpan(
return _width
}
override fun setTextView(textView: TextView?) {
public override fun setTextView(textView: TextView?) {
this.textView = textView
}
override fun draw(
public override fun draw(
canvas: Canvas,
text: CharSequence,
start: Int,
@@ -16,7 +16,8 @@ import android.text.style.MetricAffectingSpan
* The letter spacing is specified in pixels, which are converted to ems at paint time; this span
* must therefore be applied after any spans affecting font size.
*/
internal class CustomLetterSpacingSpan(val spacing: Float) : MetricAffectingSpan(), ReactSpan {
internal class CustomLetterSpacingSpan(public val spacing: Float) :
MetricAffectingSpan(), ReactSpan {
override fun updateDrawState(paint: TextPaint) {
apply(paint)
}
@@ -71,6 +71,8 @@ import com.facebook.react.views.text.ReactTextViewManagerCallback
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontVariant
import com.facebook.react.views.text.TextAttributeProps
import com.facebook.react.views.text.TextLayoutManager
import com.facebook.react.views.text.TextTransform
import com.facebook.react.views.text.TextTransform.Companion.apply
import com.facebook.react.views.text.internal.span.TextInlineImageSpan.Companion.possiblyUpdateInlineImageSpans
import java.util.LinkedList
@@ -180,7 +182,7 @@ public open class ReactTextInputManager public constructor() :
private fun getReactTextUpdate(text: String?, mostRecentEventCount: Int): ReactTextUpdate {
val sb = SpannableStringBuilder()
sb.append(text)
sb.append(apply(text, TextTransform.UNSET))
return ReactTextUpdate(
sb, mostRecentEventCount, false, 0f, 0f, 0f, 0f, Gravity.NO_GRAVITY, 0, 0)
}
@@ -50,9 +50,9 @@ import org.mockito.stubbing.Answer
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
object MockCompat {
public object MockCompat {
// Same as Mockito's 'eq()', but works for non-nullable types
fun <T : Any> eq(value: T): T = ArgumentMatchers.eq(value) ?: value
public fun <T : Any> eq(value: T): T = ArgumentMatchers.eq(value) ?: value
// Same as Mockito's 'any()', but works for non-nullable types
fun <T> any(): T {
@@ -16,6 +16,8 @@ else
source[:tag] = "v#{version}"
end
using_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
Pod::Spec.new do |s|
s.name = "ReactCommon"
s.module_name = "ReactCommon"
@@ -46,7 +48,7 @@ Pod::Spec.new do |s|
ss.dependency "React-cxxreact", version
ss.dependency "React-jsi", version
ss.dependency "React-logger", version
if use_hermes()
if using_hermes
ss.dependency "hermes-engine"
end
@@ -56,7 +58,7 @@ Pod::Spec.new do |s|
sss.exclude_files = "react/bridging/tests"
sss.header_dir = "react/bridging"
sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\"" }
if use_hermes()
if using_hermes
sss.dependency "hermes-engine"
end
end
@@ -48,7 +48,7 @@ Pod::Spec.new do |s|
s.resource_bundles = {'React-cxxreact_privacy' => 'PrivacyInfo.xcprivacy'}
if use_hermes()
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
s.dependency 'hermes-engine'
end
@@ -44,7 +44,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-featureflags")
add_dependency(s, "React-debug")
if use_hermes()
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
s.dependency 'hermes-engine'
end
@@ -5,6 +5,10 @@
require "json"
js_engine = ENV['USE_HERMES'] == "0" ?
:jsc :
:hermes
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
@@ -38,7 +42,7 @@ Pod::Spec.new do |s|
"jsi/jsilib-windows.cpp",
"**/test/*"
]
if use_hermes()
if js_engine == :hermes
# JSI is a part of hermes-engine. Including them also in react-native will violate the One Definition Rulle.
files_to_exclude += [ "jsi/jsi.cpp" ]
s.dependency "hermes-engine"
@@ -36,7 +36,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
if use_hermes()
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
s.dependency 'hermes-engine'
end
@@ -55,7 +55,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
s.dependency "React-perflogger", version
if use_hermes()
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency "hermes-engine"
end
@@ -16,7 +16,6 @@
#include <jsinspector-modern/InspectorInterfaces.h>
#include <jsinspector-modern/InspectorPackagerConnection.h>
#include <format>
#include <memory>
#include "FollyDynamicMatchers.h"
@@ -26,7 +25,7 @@
using namespace ::testing;
using namespace std::literals::chrono_literals;
using namespace std::literals::string_literals;
using folly::dynamic, folly::toJson;
using folly::dynamic, folly::toJson, folly::sformat;
namespace facebook::react::jsinspector_modern {
@@ -282,7 +281,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendReceiveEvents) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -322,7 +321,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendReceiveEvents) {
AtJsonPtr("/params", ElementsAre("arg1", "arg2"))))))
.RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -374,7 +373,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendReceiveEventsToMultiplePages) {
for (int i = 0; i < kNumPages; ++i) {
// Connect to the i-th page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -416,7 +415,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendReceiveEventsToMultiplePages) {
*localConnections_[i],
sendMessage(JsonParsed(AtJsonPtr("/method", Eq(method)))))
.RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -446,7 +445,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendEventToAllConnections) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -487,7 +486,7 @@ TEST_F(InspectorPackagerConnectionTest, TestConnectThenDisconnect) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -499,7 +498,7 @@ TEST_F(InspectorPackagerConnectionTest, TestConnectThenDisconnect) {
// Disconnect from the page.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "disconnect",
"payload": {{
@@ -522,7 +521,7 @@ TEST_F(InspectorPackagerConnectionTest, TestConnectThenCloseSocket) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -550,7 +549,7 @@ TEST_F(InspectorPackagerConnectionTest, TestConnectThenSocketFailure) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -580,7 +579,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -626,7 +625,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -638,7 +637,7 @@ TEST_F(
// Try connecting to the same page again. This results in a disconnection.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -661,7 +660,7 @@ TEST_F(InspectorPackagerConnectionTest, TestMultipleDisconnect) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -673,7 +672,7 @@ TEST_F(InspectorPackagerConnectionTest, TestMultipleDisconnect) {
// Disconnect from the page.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "disconnect",
"payload": {{
@@ -684,7 +683,7 @@ TEST_F(InspectorPackagerConnectionTest, TestMultipleDisconnect) {
EXPECT_FALSE(localConnections_[0]);
// Disconnect again. This is a noop.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "disconnect",
"payload": {{
@@ -707,7 +706,7 @@ TEST_F(InspectorPackagerConnectionTest, TestDisconnectThenSendEvent) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -719,7 +718,7 @@ TEST_F(InspectorPackagerConnectionTest, TestDisconnectThenSendEvent) {
// Disconnect from the page.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "disconnect",
"payload": {{
@@ -731,7 +730,7 @@ TEST_F(InspectorPackagerConnectionTest, TestDisconnectThenSendEvent) {
// Send an event from the frontend (remote) to the backend (local). This
// is a noop.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -755,7 +754,7 @@ TEST_F(InspectorPackagerConnectionTest, TestSendEventToUnknownPage) {
// Send an event from the frontend (remote) to the backend (local). This
// is a noop (except for logging).
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -922,7 +921,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
retainedWebSocketDelegate->didReceiveMessage(std::format(
retainedWebSocketDelegate->didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -942,7 +941,7 @@ TEST_F(
AtJsonPtr("/params", ElementsAre("arg1", "arg2"))))))
.RetiresOnSaturation();
retainedWebSocketDelegate->didReceiveMessage(std::format(
retainedWebSocketDelegate->didReceiveMessage(sformat(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -976,7 +975,7 @@ TEST_F(InspectorPackagerConnectionTest, TestDestroyConnectionOnPageRemoved) {
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -1006,7 +1005,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -1046,7 +1045,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -1065,7 +1064,7 @@ TEST_F(
// Disconnect from the page.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "disconnect",
"payload": {{
@@ -1076,7 +1075,7 @@ TEST_F(
EXPECT_FALSE(localConnections_[0]);
// Connect to the same page again.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -1128,7 +1127,7 @@ TEST_F(
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
// Connect to the page.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -1152,7 +1151,7 @@ TEST_F(
// Disconnect from the page.
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "disconnect",
"payload": {{
@@ -1163,7 +1162,7 @@ TEST_F(
EXPECT_FALSE(localConnections_[0]);
// Connect to the same page again.
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -1257,7 +1256,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageId)))))))
.RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -1266,7 +1265,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
}})",
toJson(std::to_string(pageId))));
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -1292,7 +1291,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageId)))))))
.RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -1301,7 +1300,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
}})",
toJson(std::to_string(pageId))));
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -1320,7 +1319,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
// page.
mockNextConnectionBehavior = Accept;
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "connect",
"payload": {{
@@ -1337,7 +1336,7 @@ TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
AtJsonPtr("/params", ElementsAre("arg1", "arg2"))))))
.RetiresOnSaturation();
webSockets_[0]->getDelegate().didReceiveMessage(std::format(
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
R"({{
"event": "wrappedEvent",
"payload": {{
@@ -8,13 +8,13 @@
#include <folly/Format.h>
#include <folly/executors/ManualExecutor.h>
#include <folly/executors/QueuedImmediateExecutor.h>
#include <format>
#include "JsiIntegrationTest.h"
#include "engines/JsiIntegrationTestGenericEngineAdapter.h"
#include "engines/JsiIntegrationTestHermesEngineAdapter.h"
using namespace ::testing;
using folly::sformat;
namespace facebook::react::jsinspector_modern {
@@ -486,7 +486,7 @@ TYPED_TEST(JsiIntegrationHermesTest, EvaluateExpressionInExecutionContext) {
}
}
})"));
this->toPage_->sendMessage(std::format(
this->toPage_->sendMessage(sformat(
R"({{
"id": 1,
"method": "Runtime.evaluate",
@@ -508,7 +508,7 @@ TYPED_TEST(JsiIntegrationHermesTest, EvaluateExpressionInExecutionContext) {
// Now the old execution context is stale.
this->expectMessageFromPage(
JsonParsed(AllOf(AtJsonPtr("/id", 3), AtJsonPtr("/error/code", -32600))));
this->toPage_->sendMessage(std::format(
this->toPage_->sendMessage(sformat(
R"({{
"id": 3,
"method": "Runtime.evaluate",
@@ -731,7 +731,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObject) {
// Ensure we can get the properties of the object.
this->expectMessageFromPage(JsonParsed(
AllOf(AtJsonPtr("/id", 2), AtJsonPtr("/result/result", SizeIs(Gt(0))))));
this->toPage_->sendMessage(std::format(
this->toPage_->sendMessage(sformat(
R"({{
"id": 2,
"method": "Runtime.getProperties",
@@ -744,7 +744,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObject) {
"id": 3,
"result": {}
})"));
this->toPage_->sendMessage(std::format(
this->toPage_->sendMessage(sformat(
R"({{
"id": 3,
"method": "Runtime.releaseObject",
@@ -755,7 +755,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObject) {
// Getting properties for a released object results in an error.
this->expectMessageFromPage(
JsonParsed(AllOf(AtJsonPtr("/id", 4), AtJsonPtr("/error/code", -32000))));
this->toPage_->sendMessage(std::format(
this->toPage_->sendMessage(sformat(
R"({{
"id": 4,
"method": "Runtime.getProperties",
@@ -766,7 +766,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObject) {
// Releasing an already released object is an error.
this->expectMessageFromPage(
JsonParsed(AllOf(AtJsonPtr("/id", 5), AtJsonPtr("/error/code", -32000))));
this->toPage_->sendMessage(std::format(
this->toPage_->sendMessage(sformat(
R"({{
"id": 5,
"method": "Runtime.releaseObject",
@@ -797,7 +797,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObjectGroup) {
// Ensure we can get the properties of the object.
this->expectMessageFromPage(JsonParsed(
AllOf(AtJsonPtr("/id", 2), AtJsonPtr("/result/result", SizeIs(Gt(0))))));
this->toPage_->sendMessage(std::format(
this->toPage_->sendMessage(sformat(
R"({{
"id": 2,
"method": "Runtime.getProperties",
@@ -819,7 +819,7 @@ TYPED_TEST(JsiIntegrationHermesTest, ReleaseRemoteObjectGroup) {
// Getting properties for a released object results in an error.
this->expectMessageFromPage(
JsonParsed(AllOf(AtJsonPtr("/id", 4), AtJsonPtr("/error/code", -32000))));
this->toPage_->sendMessage(std::format(
this->toPage_->sendMessage(sformat(
R"({{
"id": 4,
"method": "Runtime.getProperties",
@@ -62,12 +62,15 @@ Pod::Spec.new do |s|
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
if use_third_party_jsc()
s.exclude_files = ["ReactCommon/RCTHermesInstance.{mm,h}", "ReactCommon/RCTJscInstance.{mm,h}"]
else
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency "hermes-engine"
add_dependency(s, "React-RuntimeHermes")
s.exclude_files = "ReactCommon/RCTJscInstance.{mm,h}"
elsif ENV['USE_THIRD_PARTY_JSC'] == '1'
s.exclude_files = ["ReactCommon/RCTHermesInstance.{mm,h}", "ReactCommon/RCTJscInstance.{mm,h}"]
else
s.dependency "React-jsc"
s.exclude_files = "ReactCommon/RCTHermesInstance.{mm,h}"
end
add_rn_third_party_dependencies(s)
@@ -44,7 +44,4 @@ Pod::Spec.new do |s|
"DEFINES_MODULE" => "YES" }
s.dependency "React-jsi", version
s.dependency "React-featureflags", version
add_dependency(s, "React-debug")
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
end
@@ -24,7 +24,7 @@ void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
template <typename DataT>
inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
std::function<DataT(jsi::Runtime&)>&& runtimeWork) {
std::function<DataT(jsi::Runtime& runtime)>&& runtimeWork) {
DataT data;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
@@ -24,7 +24,7 @@ void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
template <typename DataT>
inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
std::function<DataT(jsi::Runtime&)>&& runtimeWork) {
std::function<DataT(jsi::Runtime& runtime)>&& runtimeWork) {
DataT data;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
@@ -33,7 +33,4 @@ inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK(
return data;
}
void unsafeExecuteOnMainThreadSync(std::function<void()> work);
} // namespace facebook::react
@@ -5,160 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
#import <react/debug/react_native_assert.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <react/utils/OnScopeExit.h>
#import <algorithm>
#import <functional>
#import <future>
#import <mutex>
#import <optional>
#import <thread>
#include <future>
#include <thread>
namespace facebook::react {
namespace {
class UITask {
std::promise<void> _isDone;
std::function<void()> _uiWork;
public:
UITask(UITask &&other) = default;
UITask &operator=(UITask &&other) = default;
UITask(const UITask &) = delete;
UITask &operator=(const UITask &) = delete;
~UITask() = default;
UITask(std::function<void()> &&uiWork) : _uiWork(std::move(uiWork)) {}
void operator()()
{
if (!_uiWork) {
return;
}
OnScopeExit onScopeExit(^{
_uiWork = nullptr;
_isDone.set_value();
});
_uiWork();
}
std::future<void> future()
{
return _isDone.get_future();
}
};
// Protects access to g_uiTask
std::mutex &g_mutex()
{
static std::mutex mutex;
return mutex;
}
std::condition_variable &g_cv()
{
static std::condition_variable cv;
return cv;
}
std::mutex &g_ticket()
{
static std::mutex ticket;
return ticket;
}
std::optional<UITask> &g_uiTask()
{
static std::optional<UITask> uiTaskQueue;
return uiTaskQueue;
}
// Must be called holding g_mutex();
bool hasUITask()
{
return g_uiTask().has_value();
}
// Must be called holding g_mutex();
UITask takeUITask()
{
react_native_assert(hasUITask());
auto uiTask = std::move(*g_uiTask());
g_uiTask() = std::nullopt;
return uiTask;
}
// Must be called holding g_mutex();
UITask &postUITask(std::function<void()> &&uiWork)
{
react_native_assert(!hasUITask());
g_uiTask() = UITask(std::move(uiWork));
g_cv().notify_one();
return *g_uiTask();
}
bool g_isRunningUITask = false;
void runUITask(UITask &uiTask)
{
react_native_assert([[NSThread currentThread] isMainThread]);
g_isRunningUITask = true;
OnScopeExit onScopeExit([]() { g_isRunningUITask = false; });
uiTask();
}
/**
* This method is resilient to multiple javascript threads.
* This can happen when multiple react instances interleave.
*
* The extension from 1 js thread to n: All js threads race to
* get a ticket to post a ui task. The first one to get the ticket
* will post the ui task, and go to sleep. The cooridnator or
* main queue will execute that ui task, waking up the js thread
* and releasing that ticket. Another js thread will get the ticket.
*
* For simplicity, we will just use this algorithm for all bg threads.
* Not just the js thread.
*/
void saferExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor &runtimeExecutor,
std::function<void(jsi::Runtime &runtime)> &&runtimeWork)
{
react_native_assert([[NSThread currentThread] isMainThread] && !g_isRunningUITask);
jsi::Runtime *runtime = nullptr;
std::promise<void> runtimeWorkDone;
runtimeExecutor([&runtime, runtimeWorkDoneFuture = runtimeWorkDone.get_future().share()](jsi::Runtime &rt) {
{
std::lock_guard<std::mutex> lock(g_mutex());
runtime = &rt;
g_cv().notify_one();
}
runtimeWorkDoneFuture.wait();
});
while (true) {
std::unique_lock<std::mutex> lock(g_mutex());
g_cv().wait(lock, [&] { return runtime != nullptr || hasUITask(); });
if (runtime != nullptr) {
break;
}
auto uiTask = takeUITask();
lock.unlock();
runUITask(uiTask);
}
OnScopeExit onScopeExit([&]() { runtimeWorkDone.set_value(); });
// Calls into runtime scheduler, which takes care of error handling
runtimeWork(*runtime);
}
/*
* Schedules `runtimeWork` to be executed on the same thread using the
* `RuntimeExecutor`, and blocks on its completion.
@@ -175,7 +26,7 @@ void saferExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(
* - [JS thread] Signal runtime capture block is finished:
* resolve(runtimeCaptureBlockDone);
*/
void legacyExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(
void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor &runtimeExecutor,
std::function<void(jsi::Runtime &)> &&runtimeWork)
{
@@ -207,54 +58,4 @@ void legacyExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeCaptureBlockDone.get_future().wait();
}
} // namespace
void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor &runtimeExecutor,
std::function<void(jsi::Runtime &)> &&runtimeWork)
{
if (ReactNativeFeatureFlags::enableMainQueueCoordinatorOnIOS()) {
saferExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(runtimeExecutor, std::move(runtimeWork));
} else {
legacyExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(runtimeExecutor, std::move(runtimeWork));
}
}
/**
* This method is resilient to multiple javascript threads.
* This can happen when multiple react instances interleave.
*
* The extension from 1 js thread to n: All js threads race to
* get a ticket to post a ui task. The first one to get the ticket
* will post the ui task, and go to sleep. The cooridnator or
* main queue will execute that ui task, waking up the js thread
* and releasing that ticket. Another js thread will get the ticket.
*
* For simplicity, we will just use this method for all bg threads.
* Not just the js thread.
*/
void unsafeExecuteOnMainThreadSync(std::function<void()> work)
{
std::lock_guard<std::mutex> ticket(g_ticket());
std::future<void> isDone;
{
std::lock_guard<std::mutex> lock(g_mutex());
isDone = postUITask(std::move(work)).future();
}
dispatch_async(dispatch_get_main_queue(), ^{
std::unique_lock<std::mutex> lock(g_mutex());
if (!hasUITask()) {
return;
}
auto uiTask = takeUITask();
lock.unlock();
runUITask(uiTask);
});
isDone.wait();
}
} // namespace facebook::react
-1
View File
@@ -81,7 +81,6 @@
"gradle.properties",
"gradle/libs.versions.toml",
"index.js",
"index.flow.js",
"interface.js",
"jest-preset.js",
"jest",
@@ -26,9 +26,53 @@ class JSEngineTests < Test::Unit::TestCase
Pod::Config.reset()
Pod::UI.reset()
podSpy_cleanUp()
ENV['USE_HERMES'] = '1'
ENV['CI'] = nil
end
# =============== #
# TEST - setupJsc #
# =============== #
def test_setupJsc_installsPods
# Arrange
fabric_enabled = false
# Act
setup_jsc!(:react_native_path => @react_native_path, :fabric_enabled => fabric_enabled)
# Assert
assert_equal($podInvocationCount, 2)
assert_equal($podInvocation["React-jsi"][:path], "../../ReactCommon/jsi")
assert_equal($podInvocation["React-jsc"][:path], "../../ReactCommon/jsc")
end
def test_setupJsc_installsPods_installsFabricSubspecWhenFabricEnabled
# Arrange
fabric_enabled = true
# Act
setup_jsc!(:react_native_path => @react_native_path, :fabric_enabled => fabric_enabled)
# Assert
assert_equal($podInvocationCount, 3)
assert_equal($podInvocation["React-jsi"][:path], "../../ReactCommon/jsi")
assert_equal($podInvocation["React-jsc"][:path], "../../ReactCommon/jsc")
assert_equal($podInvocation["React-jsc/Fabric"][:path], "../../ReactCommon/jsc")
end
def test_setupJsc_installsPodsWithThirdPartyJSC
# Arrange
ENV['USE_THIRD_PARTY_JSC'] = '1'
fabric_enabled = false
# Act
setup_jsc!(:react_native_path => @react_native_path, :fabric_enabled => fabric_enabled)
# Assert
assert_equal($podInvocationCount, 1)
assert_equal($podInvocation["React-jsi"][:path], "../../ReactCommon/jsi")
end
# ================== #
# TEST - setupHermes #
# ================== #
@@ -34,6 +34,7 @@ class UtilsTests < Test::Unit::TestCase
Xcodeproj::Plist.reset()
XcodebuildMock.reset()
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
ENV['USE_HERMES'] = '1'
ENV['USE_FRAMEWORKS'] = nil
system_reset_commands
$RN_PLATFORMS = nil
@@ -105,6 +106,21 @@ class UtilsTests < Test::Unit::TestCase
})
end
def test_getDefaultFlag_whenOldArchitectureButHermesDisabled()
# Arrange
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
ENV['USE_HERMES'] = '0'
# Act
flags = ReactNativePodsUtils.get_default_flags()
# Assert
assert_equal(flags, {
:fabric_enabled => false,
:hermes_enabled => false,
})
end
def test_getDefaultFlag_whenNewArchitecture()
# Arrange
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
@@ -119,6 +135,21 @@ class UtilsTests < Test::Unit::TestCase
})
end
def test_getDefaultFlag_whenNewArchitectureButHermesDisabled()
# Arrange
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
ENV['USE_HERMES'] = '0'
# Act
flags = ReactNativePodsUtils.get_default_flags()
# Assert
assert_equal(flags, {
:fabric_enabled => true,
:hermes_enabled => false,
})
end
# ============== #
# TEST - has_pod #
# ============== #
@@ -5,6 +5,19 @@
require_relative './utils.rb'
# It sets up the JavaScriptCore.
#
# @parameter react_native_path: relative path to react-native
# @parameter fabric_enabled: whether Fabirc is enabled
def setup_jsc!(react_native_path: "../node_modules/react-native", fabric_enabled: false)
if ENV['USE_THIRD_PARTY_JSC'] != '1'
pod 'React-jsc', :path => "#{react_native_path}/ReactCommon/jsc"
if fabric_enabled
pod 'React-jsc/Fabric', :path => "#{react_native_path}/ReactCommon/jsc"
end
end
end
# It sets up the Hermes.
#
# @parameter react_native_path: relative path to react-native
@@ -19,37 +32,11 @@ def setup_hermes!(react_native_path: "../node_modules/react-native")
pod 'React-hermes', :path => "#{react_native_path}/ReactCommon/hermes"
end
def use_third_party_jsc
return ENV['USE_THIRD_PARTY_JSC'] == '1'
end
# use Hermes is the default. The only other option is the third-party JSC
# if the 3rd party JSC is not true, we always want to use Hermes.
def use_hermes
return !use_third_party_jsc()
end
def use_hermes_flags
return "-DUSE_HERMES=1"
end
def use_third_party_jsc_flags
return "-DUSE_THIRD_PARTY_JSC=1"
end
def js_engine_flags()
if use_hermes()
return use_hermes_flags()
else
return use_third_party_jsc_flags()
end
end
# Utility function to depend on JS engine based on the environment variable.
def depend_on_js_engine(s)
if use_hermes()
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency 'hermes-engine'
elsif use_third_party_jsc()
elsif ENV['USE_THIRD_PARTY_JSC'] != '1'
s.dependency 'React-jsc'
end
end
@@ -142,17 +142,8 @@ class ReactNativeDependenciesUtils
end
def self.nightly_tarball_url(version)
artefact_coordinate = "react-native-artifacts"
artefact_name = "reactnative-dependencies-debug.tar.gz"
xml_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/maven-metadata.xml"
xml = REXML::Document.new(Net::HTTP.get(URI(xml_url)))
timestamp = xml.elements['metadata/versioning/snapshot/timestamp'].text
build_number = xml.elements['metadata/versioning/snapshot/buildNumber'].text
full_version = "#{version}-#{timestamp}-#{build_number}"
final_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/#{artefact_coordinate}-#{full_version}-#{artefact_name}"
return final_url
params = "r=snapshots\&g=com.facebook.react\&a=react-native-artifacts\&c=reactnative-dependencies-debug\&e=tar.gz\&v=#{version}-SNAPSHOT"
return resolve_url_redirects("http://oss.sonatype.org/service/local/artifact/maven/redirect\?#{params}")
end
def self.download_stable_rndeps(react_native_path, version, configuration)
@@ -6,7 +6,6 @@
require 'shellwords'
require_relative "./helpers.rb"
require_relative "./jsengine.rb"
# Utilities class for React Native Cocoapods
class ReactNativePodsUtils
@@ -33,7 +32,7 @@ class ReactNativePodsUtils
flags[:hermes_enabled] = true
end
if !use_hermes()
if ENV['USE_HERMES'] == '0'
flags[:hermes_enabled] = false
end
@@ -64,14 +64,12 @@ def use_react_native! (
fabric_enabled: false,
new_arch_enabled: NewArchitectureHelper.new_arch_enabled,
production: false, # deprecated
hermes_enabled: true, # deprecated. Hermes is the default engine and JSC has been moved to community support
hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true,
app_path: '..',
config_file_dir: '',
privacy_file_aggregation_enabled: true
)
error_if_try_to_use_jsc_from_core()
hermes_enabled= true
# Set the app_path as env variable so the podspecs can access it.
ENV['APP_PATH'] = app_path
ENV['REACT_NATIVE_PATH'] = path
@@ -95,6 +93,7 @@ def use_react_native! (
fabric_enabled = fabric_enabled || NewArchitectureHelper.new_arch_enabled
ENV['RCT_FABRIC_ENABLED'] = fabric_enabled ? "1" : "0"
ENV['USE_HERMES'] = hermes_enabled ? "1" : "0"
ENV['RCT_AGGREGATE_PRIVACY_FILES'] = privacy_file_aggregation_enabled ? "1" : "0"
ENV["RCT_NEW_ARCH_ENABLED"] = new_arch_enabled ? "1" : "0"
@@ -143,6 +142,8 @@ def use_react_native! (
if hermes_enabled
setup_hermes!(:react_native_path => prefix)
else
setup_jsc!(:react_native_path => prefix, :fabric_enabled => fabric_enabled)
end
pod 'React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor"
@@ -384,7 +385,8 @@ end
def print_jsc_removal_message()
puts ''
puts '=============== JavaScriptCore is being moved ==============='.yellow
puts 'JavaScriptCore has been removed from React Native. It can now be'.yellow
puts 'JavaScriptCore has been extracted from react-native core'.yellow
puts 'and will be removed in a future release. It can now be'.yellow
puts 'installed from `@react-native-community/javascriptcore`'.yellow
puts 'See: https://github.com/react-native-community/javascriptcore'.yellow
puts '============================================================='.yellow
@@ -410,19 +412,6 @@ def print_cocoapods_deprecation_message()
end
def error_if_try_to_use_jsc_from_core()
explicitly_not_use_hermes = ENV['USE_HERMES'] != nil && ENV['USE_HERMES'] == '0'
not_use_3rd_party_jsc = ENV['USE_THIRD_PARTY_JSC'] == nil || ENV['USE_THIRD_PARTY_JSC'] == '0'
if (explicitly_not_use_hermes && not_use_3rd_party_jsc)
message = "Hermes is the default engine and JSC has been moved to community support.\n" +
"Please remove the USE_HERMES=0, as it is not supported anymore.\n" +
"If you want to use JSC, you can install it from `@react-native-community/javascriptcore`.\n" +
"See: https://github.com/react-native-community/javascriptcore"
puts message.red
exit()
end
end
# Function that executes after React Native has been installed to configure some flags and build settings.
#
# Parameters
@@ -440,16 +429,17 @@ def react_native_post_install(
ReactNativePodsUtils.apply_mac_catalyst_patches(installer) if mac_catalyst_enabled
hermes_enabled = ENV['USE_HERMES'] == '1'
privacy_file_aggregation_enabled = ENV['RCT_AGGREGATE_PRIVACY_FILES'] == '1'
if use_hermes()
if hermes_enabled
ReactNativePodsUtils.set_gcc_preprocessor_definition_for_React_hermes(installer)
end
ReactNativePodsUtils.set_gcc_preprocessor_definition_for_debugger(installer)
ReactNativePodsUtils.fix_library_search_paths(installer)
ReactNativePodsUtils.update_search_paths(installer)
ReactNativePodsUtils.set_build_setting(installer, build_setting: "USE_HERMES", value: use_hermes())
ReactNativePodsUtils.set_build_setting(installer, build_setting: "USE_HERMES", value: hermes_enabled)
ReactNativePodsUtils.set_build_setting(installer, build_setting: "REACT_NATIVE_PATH", value: File.join("${PODS_ROOT}", "..", react_native_path))
ReactNativePodsUtils.set_build_setting(installer, build_setting: "SWIFT_ACTIVE_COMPILATION_CONDITIONS", value: ['$(inherited)', 'DEBUG'], config_name: "Debug")
@@ -469,7 +459,7 @@ def react_native_post_install(
NewArchitectureHelper.modify_flags_for_new_architecture(installer, NewArchitectureHelper.new_arch_enabled)
NewArchitectureHelper.set_RCTNewArchEnabled_in_info_plist(installer, NewArchitectureHelper.new_arch_enabled)
if !use_hermes() && !use_third_party_jsc()
if ENV['USE_HERMES'] == '0' && ENV['USE_THIRD_PARTY_JSC'] != '1'
print_jsc_removal_message()
end
@@ -230,17 +230,8 @@ def download_hermes_tarball(react_native_path, tarball_url, version, configurati
end
def nightly_tarball_url(version)
artefact_coordinate = "react-native-artifacts"
artefact_name = "hermes-ios-debug.tar.gz"
xml_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/maven-metadata.xml"
xml = REXML::Document.new(Net::HTTP.get(URI(xml_url)))
timestamp = xml.elements['metadata/versioning/snapshot/timestamp'].text
build_number = xml.elements['metadata/versioning/snapshot/buildNumber'].text
full_version = "#{version}-#{timestamp}-#{build_number}"
final_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/#{artefact_coordinate}-#{full_version}-#{artefact_name}"
return final_url
params = "r=snapshots\&g=com.facebook.react\&a=react-native-artifacts\&c=hermes-ios-debug\&e=tar.gz\&v=#{version}-SNAPSHOT"
return resolve_url_redirects("http://oss.sonatype.org/service/local/artifact/maven/redirect\?#{params}")
end
def resolve_url_redirects(url)
@@ -10,17 +10,9 @@ hermesc_dir_path="$1"; shift
jsi_path="$1"
# This script is supposed to be executed from Xcode "run script" phase.
# Xcode sets up its build environment based on the build target (iphone, iphonesimulator, macosx).
# Xcode sets up its build environment based on the build target (iphone, iphonesimulator, macodsx).
# We want to make sure that hermesc is built for mac.
# So we clean the environment with env -i, and explicitly set SDKROOT to macosx
SDKROOT=$(xcode-select -p)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
env -i \
PATH="$PATH" \
SDKROOT="$SDKROOT" \
"$CMAKE_BINARY" -S "${PODS_ROOT}/hermes-engine" -B "$hermesc_dir_path" -DJSI_DIR="$jsi_path"
env -i \
PATH="$PATH" \
SDKROOT="$SDKROOT" \
"$CMAKE_BINARY" --build "$hermesc_dir_path" --target hermesc -j "$(sysctl -n hw.ncpu)"
env -i SDKROOT="$SDKROOT" "$CMAKE_BINARY" -S "${PODS_ROOT}/hermes-engine" -B "$hermesc_dir_path" -DJSI_DIR="$jsi_path"
env -i SDKROOT="$SDKROOT" "$CMAKE_BINARY" --build "$hermesc_dir_path" --target hermesc -j "$(sysctl -n hw.ncpu)"
+6
View File
@@ -37,9 +37,15 @@ def pods(target_name, options = {})
fabric_enabled = true
# Hermes is now enabled by default.
# The following line will only disable Hermes if the USE_HERMES envvar is SET to a value other than 1 (e.g. USE_HERMES=0).
hermes_enabled = !ENV.has_key?('USE_HERMES') || ENV['USE_HERMES'] == '1'
puts "Configuring #{target_name} with Fabric #{fabric_enabled ? "enabled" : "disabled"}.#{hermes_enabled ? " Using Hermes engine." : ""}"
use_react_native!(
path: @prefix_path,
fabric_enabled: fabric_enabled,
hermes_enabled: hermes_enabled,
app_path: "#{Dir.pwd}",
config_file_dir: "#{Dir.pwd}/node_modules",
production: false, #deprecated
+1 -1
View File
@@ -31,7 +31,7 @@ If you are still having a problem after doing the clean up (which can happen if
Both macOS and Xcode are required.
1. `cd packages/rn-tester`
2. Install [Bundler](https://bundler.io/): `gem install bundler`. We use bundler to install the right version of [CocoaPods](https://cocoapods.org/) locally.
3. Install Bundler and CocoaPods dependencies: `bundle install && bundle exec pod install` or `yarn prepare-ios`.
3. Install Bundler and CocoaPods dependencies: `bundle install && bundle exec pod install` or `yarn prepare-ios`. In order to use JSC instead of Hermes engine, run: `USE_HERMES=0 bundle exec pod install` or `yarn prepare-ios --arch old --jsvm jsc` instead.
4. Open the generated `RNTesterPods.xcworkspace`. This is not checked in, as it is generated by CocoaPods. Do not open `RNTesterPods.xcodeproj` directly.
#### Note for Apple Silicon users
@@ -8,8 +8,6 @@
* @format
*/
import type {ReportFullyDrawnViewType} from './ReportFullyDrawnViewNativeComponent';
import {View} from 'react-native';
export default View as ReportFullyDrawnViewType;
export default View;
@@ -7,12 +7,11 @@
package com.facebook.react.uiapp
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.View
import android.widget.FrameLayout
import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.facebook.react.FBRNTesterEndToEndHelper
@@ -44,28 +43,13 @@ internal class RNTesterActivity : ReactActivity() {
if (this::initialProps.isInitialized) initialProps else Bundle()
}
// set background color so it will show below transparent system bars on forced edge-to-edge
private fun maybeUpdateBackgroundColor() {
val isDarkMode =
resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
Configuration.UI_MODE_NIGHT_YES
val color =
if (isDarkMode) {
Color.rgb(11, 6, 0)
} else {
Color.rgb(243, 248, 255)
}
window?.setBackgroundDrawable(color.toDrawable())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fullyDrawnReporter.addReporter()
maybeUpdateBackgroundColor()
// set background color so it will show below transparent system bars on forced edge-to-edge
this.window?.setBackgroundDrawable(ColorDrawable(Color.BLACK))
// register insets listener to update margins on the ReactRootView to avoid overlap w/ system
// bars
getReactDelegate()?.getReactRootView()?.let { rootView ->
@@ -85,13 +69,6 @@ internal class RNTesterActivity : ReactActivity() {
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// update background color on UI mode change
maybeUpdateBackgroundColor()
}
override fun createReactActivityDelegate() = RNTesterActivityDelegate(this, mainComponentName)
override fun getMainComponentName() = "RNTesterApp"
@@ -55,7 +55,6 @@ const config /*: InputConfigT */ = {
// We need to wrap the default transformer so we can run it from source
// using babel-register.
babelTransformerPath: path.resolve(__dirname, 'metro-babel-transformer.js'),
hermesParser: true,
},
serializer: {
// Force an empty list so Metro doesn't inject InitializeCore in tests.
@@ -37,7 +37,6 @@ add_react_third_party_ndk_subdir(folly)
# Common targets
add_react_common_subdir(yoga)
add_react_common_subdir(react/featureflags)
file(GLOB SOURCES "src/*.cpp" "src/*.h")
add_executable(fantom_tester ${SOURCES})
@@ -49,7 +48,6 @@ target_link_libraries(fantom_tester
double-conversion
fast_float
folly_runtime
react_featureflags
yogacore)
target_compile_options(fantom_tester
@@ -15,10 +15,3 @@ cmake -S "$SCRIPT_DIR" -B "$BUILD_DIR" \
-DREACT_COMMON_DIR="${REACT_NATIVE_ROOT_DIR}/ReactCommon"
cmake --build "$BUILD_DIR" --target fantom_tester
while getopts ":r" opt; do
case $opt in
r) "$BUILD_DIR/fantom_tester" ;;
\?) echo "Invalid option: -$OPTARG"; exit 1;;
esac
done
@@ -5,40 +5,19 @@
* LICENSE file in the root directory of this source tree.
*/
#include <fmt/format.h>
#include <glog/logging.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h>
#include <yoga/YGEnums.h>
#include <yoga/YGValue.h>
#include <format>
#include <iostream>
#include <memory>
using namespace facebook::react;
static void setUpLogging() {
google::InitGoogleLogging("react-native-fantom");
FLAGS_logtostderr = true;
}
static void setUpFeatureFlags() {
folly::dynamic dynamicFeatureFlags = folly::dynamic::object();
dynamicFeatureFlags["enableBridgelessArchitecture"] = true;
dynamicFeatureFlags["cxxNativeAnimatedEnabled"] = true;
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsDynamicProvider>(
dynamicFeatureFlags));
}
int main() {
setUpLogging();
setUpFeatureFlags();
google::InitGoogleLogging("fantom_tester");
FLAGS_logtostderr = true;
LOG(INFO) << "Hello, I am fantom_tester using glog!";
LOG(INFO) << std::format(
LOG(INFO) << fmt::format(
"[Yoga] undefined == zero: {}", YGValueZero == YGValueUndefined);
return 0;
@@ -14,6 +14,7 @@ def use_react_native! (
fabric_enabled: false,
new_arch_enabled: NewArchitectureHelper.new_arch_enabled,
production: false, # deprecated
hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true,
flipper_configuration: FlipperConfiguration.disabled,
app_path: '..',
config_file_dir: '',
@@ -26,6 +27,7 @@ def use_react_native! (
path: "../node_modules/react-native",
fabric_enabled: false,
production: false, # deprecated
hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true,
flipper_configuration: FlipperConfiguration.disabled,
app_path: '..',
config_file_dir: '',
@@ -37,6 +39,7 @@ const expectedReactNativePodsFile = `
def use_react_native! (
path: "../node_modules/react-native",
production: false, # deprecated
hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true,
flipper_configuration: FlipperConfiguration.disabled,
app_path: '..',
config_file_dir: '',
+5 -221
View File
@@ -19,25 +19,11 @@
js-tokens "^4.0.0"
picocolors "^1.0.0"
"@babel/code-frame@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be"
integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
dependencies:
"@babel/helper-validator-identifier" "^7.27.1"
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.5", "@babel/compat-data@^7.26.8":
version "7.26.8"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367"
integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==
"@babel/compat-data@^7.27.2":
version "7.27.5"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.5.tgz#7d0658ec1a8420fc866d1df1b03bea0e79934c82"
integrity sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==
"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.25.2":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.9.tgz#71838542a4b1e49dfed353d7acbc6eb89f4a76f2"
@@ -59,27 +45,6 @@
json5 "^2.2.3"
semver "^6.3.1"
"@babel/core@^7.24.4":
version "7.27.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.4.tgz#cc1fc55d0ce140a1828d1dd2a2eba285adbfb3ce"
integrity sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.27.1"
"@babel/generator" "^7.27.3"
"@babel/helper-compilation-targets" "^7.27.2"
"@babel/helper-module-transforms" "^7.27.3"
"@babel/helpers" "^7.27.4"
"@babel/parser" "^7.27.4"
"@babel/template" "^7.27.2"
"@babel/traverse" "^7.27.4"
"@babel/types" "^7.27.3"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/eslint-parser@^7.25.1":
version "7.26.8"
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.26.8.tgz#55c4f4aae4970ae127f7a12369182ed6250e6f09"
@@ -100,17 +65,6 @@
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^3.0.2"
"@babel/generator@^7.27.3":
version "7.27.5"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.5.tgz#3eb01866b345ba261b04911020cbe22dd4be8c8c"
integrity sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==
dependencies:
"@babel/parser" "^7.27.5"
"@babel/types" "^7.27.3"
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4"
@@ -118,13 +72,6 @@
dependencies:
"@babel/types" "^7.25.9"
"@babel/helper-annotate-as-pure@^7.27.1":
version "7.27.3"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5"
integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==
dependencies:
"@babel/types" "^7.27.3"
"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9", "@babel/helper-compilation-targets@^7.26.5":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8"
@@ -136,17 +83,6 @@
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-compilation-targets@^7.27.2":
version "7.27.2"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d"
integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==
dependencies:
"@babel/compat-data" "^7.27.2"
"@babel/helper-validator-option" "^7.27.1"
browserslist "^4.24.0"
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-create-class-features-plugin@^7.25.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71"
@@ -160,19 +96,6 @@
"@babel/traverse" "^7.26.9"
semver "^6.3.1"
"@babel/helper-create-class-features-plugin@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz#5bee4262a6ea5ddc852d0806199eb17ca3de9281"
integrity sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==
dependencies:
"@babel/helper-annotate-as-pure" "^7.27.1"
"@babel/helper-member-expression-to-functions" "^7.27.1"
"@babel/helper-optimise-call-expression" "^7.27.1"
"@babel/helper-replace-supers" "^7.27.1"
"@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
"@babel/traverse" "^7.27.1"
semver "^6.3.1"
"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.25.9":
version "7.26.3"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz#5169756ecbe1d95f7866b90bb555b022595302a0"
@@ -201,14 +124,6 @@
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/helper-member-expression-to-functions@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44"
integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==
dependencies:
"@babel/traverse" "^7.27.1"
"@babel/types" "^7.27.1"
"@babel/helper-module-imports@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715"
@@ -217,14 +132,6 @@
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/helper-module-imports@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204"
integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==
dependencies:
"@babel/traverse" "^7.27.1"
"@babel/types" "^7.27.1"
"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae"
@@ -234,15 +141,6 @@
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/traverse" "^7.25.9"
"@babel/helper-module-transforms@^7.27.3":
version "7.27.3"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz#db0bbcfba5802f9ef7870705a7ef8788508ede02"
integrity sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==
dependencies:
"@babel/helper-module-imports" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@babel/traverse" "^7.27.3"
"@babel/helper-optimise-call-expression@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e"
@@ -250,23 +148,11 @@
dependencies:
"@babel/types" "^7.25.9"
"@babel/helper-optimise-call-expression@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200"
integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==
dependencies:
"@babel/types" "^7.27.1"
"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35"
integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==
"@babel/helper-plugin-utils@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c"
integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==
"@babel/helper-remap-async-to-generator@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92"
@@ -285,15 +171,6 @@
"@babel/helper-optimise-call-expression" "^7.25.9"
"@babel/traverse" "^7.26.5"
"@babel/helper-replace-supers@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0"
integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==
dependencies:
"@babel/helper-member-expression-to-functions" "^7.27.1"
"@babel/helper-optimise-call-expression" "^7.27.1"
"@babel/traverse" "^7.27.1"
"@babel/helper-skip-transparent-expression-wrappers@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9"
@@ -302,44 +179,21 @@
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/helper-skip-transparent-expression-wrappers@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56"
integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==
dependencies:
"@babel/traverse" "^7.27.1"
"@babel/types" "^7.27.1"
"@babel/helper-string-parser@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c"
integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
"@babel/helper-string-parser@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
"@babel/helper-validator-identifier@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
"@babel/helper-validator-identifier@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8"
integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
"@babel/helper-validator-option@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72"
integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
"@babel/helper-validator-option@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f"
integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==
"@babel/helper-wrap-function@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0"
@@ -357,14 +211,6 @@
"@babel/template" "^7.26.9"
"@babel/types" "^7.26.9"
"@babel/helpers@^7.27.4":
version "7.27.6"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.6.tgz#6456fed15b2cb669d2d1fabe84b66b34991d812c"
integrity sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==
dependencies:
"@babel/template" "^7.27.2"
"@babel/types" "^7.27.6"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.3", "@babel/parser@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.9.tgz#d9e78bee6dc80f9efd8f2349dcfbbcdace280fd5"
@@ -372,13 +218,6 @@
dependencies:
"@babel/types" "^7.26.9"
"@babel/parser@^7.24.4", "@babel/parser@^7.27.2", "@babel/parser@^7.27.4", "@babel/parser@^7.27.5":
version "7.27.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.5.tgz#ed22f871f110aa285a6fd934a0efed621d118826"
integrity sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==
dependencies:
"@babel/types" "^7.27.3"
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe"
@@ -881,14 +720,6 @@
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-transform-private-methods@^7.24.4":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af"
integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==
dependencies:
"@babel/helper-create-class-features-plugin" "^7.27.1"
"@babel/helper-plugin-utils" "^7.27.1"
"@babel/plugin-transform-private-methods@^7.24.7", "@babel/plugin-transform-private-methods@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57"
@@ -1189,15 +1020,6 @@
"@babel/parser" "^7.26.9"
"@babel/types" "^7.26.9"
"@babel/template@^7.27.2":
version "7.27.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d"
integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==
dependencies:
"@babel/code-frame" "^7.27.1"
"@babel/parser" "^7.27.2"
"@babel/types" "^7.27.1"
"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.8", "@babel/traverse@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.9.tgz#4398f2394ba66d05d988b2ad13c219a2c857461a"
@@ -1211,19 +1033,6 @@
debug "^4.3.1"
globals "^11.1.0"
"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.27.4":
version "7.27.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.4.tgz#b0045ac7023c8472c3d35effd7cc9ebd638da6ea"
integrity sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==
dependencies:
"@babel/code-frame" "^7.27.1"
"@babel/generator" "^7.27.3"
"@babel/parser" "^7.27.4"
"@babel/template" "^7.27.2"
"@babel/types" "^7.27.3"
debug "^4.3.1"
globals "^11.1.0"
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.2", "@babel/types@^7.25.9", "@babel/types@^7.26.9", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce"
@@ -1232,14 +1041,6 @@
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.27.6":
version "7.27.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.6.tgz#a434ca7add514d4e646c80f7375c0aa2befc5535"
integrity sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==
dependencies:
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -4226,17 +4027,10 @@ eslint-plugin-jsx-a11y@^6.6.0:
safe-regex-test "^1.0.3"
string.prototype.includes "^2.0.0"
eslint-plugin-react-hooks@6.1.0-canary-12bc60f5-20250613, eslint-plugin-react-hooks@^5.2.0:
version "6.1.0-canary-12bc60f5-20250613"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-6.1.0-canary-12bc60f5-20250613.tgz#fac24a3cf8c2b7397b06cc39e016b6b4acad1078"
integrity sha512-K+594rswc9TF1sxVO/Mp5QxxgD6tDsFT/FiwJ+O0/z9+Id6IKUyLn5S7TP24sIij6caUE6aRSEaOqkeIqmzK9A==
dependencies:
"@babel/core" "^7.24.4"
"@babel/parser" "^7.24.4"
"@babel/plugin-transform-private-methods" "^7.24.4"
hermes-parser "^0.25.1"
zod "^3.22.4"
zod-validation-error "^3.0.3"
eslint-plugin-react-hooks@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3"
integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==
eslint-plugin-react-native-globals@^0.1.1:
version "0.1.2"
@@ -5002,7 +4796,7 @@ hermes-estree@0.28.1:
resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.28.1.tgz#631e6db146b06e62fc1c630939acf4a3c77d1b24"
integrity sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ==
hermes-parser@0.25.1, hermes-parser@^0.25.1:
hermes-parser@0.25.1:
version "0.25.1"
resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1"
integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==
@@ -9236,13 +9030,3 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zod-validation-error@^3.0.3:
version "3.5.0"
resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.5.0.tgz#13d31d04abbc72f3cc33c5466985f861f54ff96c"
integrity sha512-IWK6O51sRkq0YsnYD2oLDuK2BNsIjYUlR0+1YSd4JyBzm6/892IWroUnLc7oW4FU+b0f6948BHi6H8MDcqpOGw==
zod@^3.22.4:
version "3.25.64"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.64.tgz#57b5c7e76dd64e447f7e710285fcdb396b32f803"
integrity sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==