Compare commits

...
Author SHA1 Message Date
Tim Yung 49f47cd7bd Test Change to React Native
Summary: This is a test commit.

Differential Revision: D76916165
2025-06-18 11:32:41 -07:00
Peter Abbondanzo 94cbf206d6 Remove focus change listener and restore original when dropping view instance (#52093)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52093

This change updates the `BaseViewManager` implementation to drop and restore the original focus listener when a view instance has its `onDropViewInstance` method called. This is necessary to support view recycling, since the `addEventEmitters` method is called each time a recycled view is popped out of the stack. This would result in N+1 `onFocus`/`onBlur` calls for each time the view is recycled.

Changelog: [Android][Fixed] - Remove focus change listener when dropping/recycling view instances

Reviewed By: NickGerleman

Differential Revision: D76852137

fbshipit-source-id: 9e980e7a1850a952baf04724bc251ff32186c6fa
2025-06-18 11:21:32 -07:00
Rubén Norte 6bfc1187a8 Improve formatting for benchmark output (#52106)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52106

Changelog: [internal]

This slightly improves the formatting of the output produced by benchmarks, so we can just copy&paste the result to share it as valid Markdown.

Reviewed By: christophpurrer

Differential Revision: D76898244

fbshipit-source-id: dc1040ee3787c7f0dcb747c9fba8eb14086a0087
2025-06-18 09:36:36 -07:00
Rubén Norte 29704b1f02 Add support for Static Hermes staging in Fantom (#52105)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52105

Changelog: [internal]

I just learnt there's a Hermes variant that we don't support (staging) so this adds support for it.

Reviewed By: christophpurrer

Differential Revision: D76897715

fbshipit-source-id: 3113edde3c785d71ad4a57dd435f16e13ab46976
2025-06-18 09:36:36 -07:00
10 changed files with 124 additions and 49 deletions
@@ -10,6 +10,8 @@
'use strict';
// This is a test change. @nocommit
const passthroughSyntaxPlugins = require('../passthrough-syntax-plugins');
const lazyImports = require('./lazy-imports');
@@ -3315,6 +3315,7 @@ public abstract class com/facebook/react/uimanager/BaseViewManager : com/faceboo
public fun getExportedCustomBubblingEventTypeConstants ()Ljava/util/Map;
public fun getExportedCustomDirectEventTypeConstants ()Ljava/util/Map;
protected fun onAfterUpdateTransaction (Landroid/view/View;)V
public fun onDropViewInstance (Landroid/view/View;)V
public fun onLayoutChange (Landroid/view/View;IIIIIIII)V
protected fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View;
public fun setAccessibilityActions (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;)V
@@ -173,30 +173,19 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
protected void addEventEmitters(@NonNull ThemedReactContext reactContext, @NonNull T view) {
super.addEventEmitters(reactContext, view);
@Nullable OnFocusChangeListener originalFocusChangeListener = view.getOnFocusChangeListener();
view.setOnFocusChangeListener(
(v, hasFocus) -> {
if (originalFocusChangeListener != null) {
originalFocusChangeListener.onFocusChange(v, hasFocus);
}
int surfaceId = UIManagerHelper.getSurfaceId(v.getContext());
if (surfaceId == View.NO_ID) {
return;
}
if (view.getContext() instanceof ThemedReactContext) {
ThemedReactContext themedReactContext = (ThemedReactContext) v.getContext();
@Nullable
EventDispatcher eventDispatcher =
UIManagerHelper.getEventDispatcherForReactTag(themedReactContext, view.getId());
if (eventDispatcher != null) {
if (hasFocus) {
eventDispatcher.dispatchEvent(new FocusEvent(surfaceId, view.getId()));
} else {
eventDispatcher.dispatchEvent(new BlurEvent(surfaceId, view.getId()));
}
}
}
});
BaseVMFocusChangeListener focusChangeListener =
new BaseVMFocusChangeListener(view.getOnFocusChangeListener());
focusChangeListener.attach(view);
}
@Override
public void onDropViewInstance(@NonNull T view) {
super.onDropViewInstance(view);
@Nullable OnFocusChangeListener focusChangeListener = view.getOnFocusChangeListener();
if (focusChangeListener instanceof BaseVMFocusChangeListener) {
((BaseVMFocusChangeListener) focusChangeListener).detach(view);
}
}
// Currently, layout listener is only attached when transform or transformOrigin is set.
@@ -1052,4 +1041,49 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
}
// Please add new props to BaseViewManagerDelegate as well!
/**
* A helper class to keep track of the original focus change listener if one is set. This is
* especially helpful for views that are recycled so we can retain and restore the original
* listener upon recycling (onDropViewInstance).
*/
private class BaseVMFocusChangeListener<V extends View> implements OnFocusChangeListener {
private @Nullable OnFocusChangeListener mOriginalFocusChangeListener;
public BaseVMFocusChangeListener(@Nullable OnFocusChangeListener originalFocusChangeListener) {
mOriginalFocusChangeListener = originalFocusChangeListener;
}
public void attach(T view) {
view.setOnFocusChangeListener(this);
}
public void detach(T view) {
view.setOnFocusChangeListener(mOriginalFocusChangeListener);
}
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (mOriginalFocusChangeListener != null) {
mOriginalFocusChangeListener.onFocusChange(view, hasFocus);
}
int surfaceId = UIManagerHelper.getSurfaceId(view.getContext());
if (surfaceId == View.NO_ID) {
return;
}
if (view.getContext() instanceof ThemedReactContext) {
ThemedReactContext themedReactContext = (ThemedReactContext) view.getContext();
@Nullable
EventDispatcher eventDispatcher =
UIManagerHelper.getEventDispatcherForReactTag(themedReactContext, view.getId());
if (eventDispatcher != null) {
if (hasFocus) {
eventDispatcher.dispatchEvent(new FocusEvent(surfaceId, view.getId()));
} else {
eventDispatcher.dispatchEvent(new BlurEvent(surfaceId, view.getId()));
}
}
}
}
}
}
@@ -93,4 +93,21 @@ class BaseViewManagerTest {
view.onFocusChangeListener.onFocusChange(view, true)
verify(originalFocusListener, times(1)).onFocusChange(view, true)
}
@Test
fun testDroppingViewInstanceRestoresFocusChangeListener() {
val originalFocusListener = mock<OnFocusChangeListener>()
view.onFocusChangeListener = originalFocusListener
viewManager.addEventEmitters(themedReactContext, view)
Assertions.assertThat(view.onFocusChangeListener).isNotEqualTo(originalFocusListener)
view.onFocusChangeListener.onFocusChange(view, true)
verify(originalFocusListener, times(1)).onFocusChange(view, true)
Assertions.assertThat(originalFocusListener).isNotEqualTo(view.onFocusChangeListener)
viewManager.onDropViewInstance(view)
view.onFocusChangeListener.onFocusChange(view, true)
verify(originalFocusListener, times(2)).onFocusChange(view, true)
Assertions.assertThat(originalFocusListener).isEqualTo(view.onFocusChangeListener)
}
}
+5 -3
View File
@@ -35,10 +35,12 @@ function formatFantomHermesVariant(hermesVariant: HermesVariant): string {
switch (hermesVariant) {
case FantomTestConfigHermesVariant.Hermes:
return 'hermes';
case FantomTestConfigHermesVariant.StaticHermes:
return 'hermes 🆕';
case FantomTestConfigHermesVariant.StaticHermesStable:
return 'shermes 🆕';
case FantomTestConfigHermesVariant.StaticHermesStaging:
return 'shermes ⏭️';
case FantomTestConfigHermesVariant.StaticHermesExperimental:
return 'hermes 🧪';
return 'shermes 🧪';
}
}
+10 -6
View File
@@ -90,7 +90,7 @@ const MAX_FANTOM_CONFIGURATION_VARIATIONS = 12;
* /**
* * @flow strict-local
* * @fantom_mode opt
* * @fantom_hermes_variant static_hermes
* * @fantom_hermes_variant static_hermes_stable
* * @fantom_flags commonTestFlag:true
* * @fantom_flags jsOnlyTestFlag:true
* * @fantom_react_fb_flags reactInternalFlag:true
@@ -101,8 +101,8 @@ const MAX_FANTOM_CONFIGURATION_VARIATIONS = 12;
* - `fantom_mode`: specifies the level of optimization to compile the test
* with. Valid values are `dev`, `dev-bytecode` and `opt`.
* - `fantom_hermes_variant`: specifies the Hermes variant to use to run the
* test. Valid values are `hermes`, `static_hermes` and
* `static_hermes_experimental`.
* test. Valid values are `hermes`, `static_hermes_stable`,
* `static_hermes_staging` and `static_hermes_experimental`.
* - `fantom_flags`: specifies the configuration for common and JS-only feature
* flags. They can be specified in the same pragma or in different ones, and
* the format is `<flag_name>:<value>`.
@@ -187,8 +187,11 @@ export default function getFantomTestConfigs(
case 'hermes':
config.hermesVariant = HermesVariant.Hermes;
break;
case 'static_hermes':
config.hermesVariant = HermesVariant.StaticHermes;
case 'static_hermes_stable':
config.hermesVariant = HermesVariant.StaticHermesStable;
break;
case 'static_hermes_staging':
config.hermesVariant = HermesVariant.StaticHermesStaging;
break;
case 'static_hermes_experimental':
config.hermesVariant = HermesVariant.StaticHermesExperimental;
@@ -196,7 +199,8 @@ export default function getFantomTestConfigs(
case '*':
configVariations.push([
{hermesVariant: HermesVariant.Hermes},
{hermesVariant: HermesVariant.StaticHermes},
{hermesVariant: HermesVariant.StaticHermesStable},
{hermesVariant: HermesVariant.StaticHermesStaging},
{hermesVariant: HermesVariant.StaticHermesExperimental},
]);
break;
+8 -3
View File
@@ -20,7 +20,8 @@ const BUCK_ISOLATION_DIR = 'react-native-fantom-buck-out';
export enum HermesVariant {
Hermes,
StaticHermes, // Static Hermes Stable
StaticHermesStable, // Static Hermes Stable
StaticHermesStaging, // Static Hermes Staging
StaticHermesExperimental, // Static Hermes Trunk
}
@@ -30,8 +31,10 @@ export function getBuckOptionsForHermes(
switch (variant) {
case HermesVariant.Hermes:
return [];
case HermesVariant.StaticHermes:
case HermesVariant.StaticHermesStable:
return ['-c hermes.static_hermes=stable'];
case HermesVariant.StaticHermesStaging:
return ['-c hermes.static_hermes=staging'];
case HermesVariant.StaticHermesExperimental:
return ['-c hermes.static_hermes=trunk'];
}
@@ -41,8 +44,10 @@ export function getHermesCompilerTarget(variant: HermesVariant): string {
switch (variant) {
case HermesVariant.Hermes:
return '//xplat/hermes/tools/hermesc:hermesc';
case HermesVariant.StaticHermes:
case HermesVariant.StaticHermesStable:
return '//xplat/shermes/stable:hermesc';
case HermesVariant.StaticHermesStaging:
return '//xplat/shermes/staging:hermesc';
case HermesVariant.StaticHermesExperimental:
return '//xplat/static_h:hermesc';
}
+3 -11
View File
@@ -172,20 +172,12 @@ export function suite(
function printBenchmarkResults(bench: Bench) {
const {fantomConfigSummary} = getConstants();
const longestTaskNameLength = bench.tasks.reduce(
(maxLength, task) => Math.max(maxLength, task.name.length),
0,
);
const separatorWidth = 137 + longestTaskNameLength - 'Task name'.length;
const benchmarkName =
(bench.name ?? 'Benchmark') +
(fantomConfigSummary ? ` (${fantomConfigSummary})` : '');
console.log('-'.repeat(separatorWidth));
console.log(
`| ${benchmarkName}${' '.repeat(separatorWidth - (4 + benchmarkName.length))} |`,
);
console.log('-'.repeat(separatorWidth));
console.log('');
console.log(`### ${benchmarkName} ###`);
console.table(nullthrows(bench.table()));
console.log('-'.repeat(separatorWidth) + '\n');
console.log('');
}
@@ -6,12 +6,12 @@
*
* @flow strict-local
* @format
* @fantom_hermes_variant static_hermes
* @fantom_hermes_variant static_hermes_stable
*/
declare var HermesInternal: $HermesInternalType;
describe('"@fantom_hermes_variant static_hermes" in docblock', () => {
describe('"@fantom_hermes_variant static_hermes_stable" in docblock', () => {
it('should use Static Hermes', () => {
expect(HermesInternal.getRuntimeProperties?.()['Static Hermes']).toBe(true);
});
@@ -0,0 +1,18 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @fantom_hermes_variant static_hermes_staging
*/
declare var HermesInternal: $HermesInternalType;
describe('"@fantom_hermes_variant static_hermes_staging" in docblock', () => {
it('should use Static Hermes', () => {
expect(HermesInternal.getRuntimeProperties?.()['Static Hermes']).toBe(true);
});
});