diff --git a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp index fe8e26efabbb68..725a46c3bdbd4d 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp @@ -49,21 +49,6 @@ NativeIntersectionObserver::NativeIntersectionObserver( std::shared_ptr jsInvoker) : NativeIntersectionObserverCxxSpec(std::move(jsInvoker)) {} -void NativeIntersectionObserver::observe( - jsi::Runtime& runtime, - NativeIntersectionObserverObserveOptions options) { - observeV2(runtime, std::move(options)); -} - -void NativeIntersectionObserver::unobserve( - jsi::Runtime& runtime, - IntersectionObserverObserverId intersectionObserverId, - std::shared_ptr targetShadowNode) { - auto token = - tokenFromShadowNodeFamily(runtime, targetShadowNode->getFamilyShared()); - unobserveV2(runtime, intersectionObserverId, std::move(token)); -} - jsi::Object NativeIntersectionObserver::observeV2( jsi::Runtime& runtime, NativeIntersectionObserverObserveOptions options) { diff --git a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h index 08cddb59adf4f9..87fae93125b5c6 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h @@ -64,19 +64,6 @@ class NativeIntersectionObserver public: NativeIntersectionObserver(std::shared_ptr jsInvoker); - // TODO(T223605846): Remove legacy observe method - [[deprecated("Please use observeV2")]] - void observe( - jsi::Runtime& runtime, - NativeIntersectionObserverObserveOptions options); - - // TODO(T223605846): Remove legacy unobserve method - [[deprecated("Please use unobserveV2")]] - void unobserve( - jsi::Runtime& runtime, - IntersectionObserverObserverId intersectionObserverId, - std::shared_ptr targetShadowNode); - jsi::Object observeV2( jsi::Runtime& runtime, NativeIntersectionObserverObserveOptions options); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 03fa8cb884e90b..897b2f9cc8938e 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -856,16 +856,6 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, - utilizeTokensInIntersectionObserver: { - defaultValue: true, - metadata: { - dateAdded: '2025-05-06', - description: 'Use tokens in IntersectionObserver vs ShadowNode.', - expectedReleaseValue: true, - purpose: 'experimentation', - }, - ossReleaseStage: 'none', - }, }, }; diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 7e652e4a318512..49aecd1f22ecd7 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -43,7 +43,6 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{ shouldUseAnimatedObjectForTransform: Getter, shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter, shouldUseSetNativePropsInFabric: Getter, - utilizeTokensInIntersectionObserver: Getter, }>; export type ReactNativeFeatureFlagsJsOnlyOverrides = OverridesFor; @@ -190,11 +189,6 @@ export const shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter = cre */ export const shouldUseSetNativePropsInFabric: Getter = createJavaScriptFlagGetter('shouldUseSetNativePropsInFabric', true); -/** - * Use tokens in IntersectionObserver vs ShadowNode. - */ -export const utilizeTokensInIntersectionObserver: Getter = createJavaScriptFlagGetter('utilizeTokensInIntersectionObserver', true); - /** * Common flag for testing. Do NOT modify. */ diff --git a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js b/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js index e70a705ee6550f..0341ff5c5d0a73 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js +++ b/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @fantom_flags utilizeTokensInIntersectionObserver:* * @flow strict-local * @format */ @@ -20,7 +19,6 @@ import * as Fantom from '@react-native/fantom'; import * as React from 'react'; import {createRef, useState} from 'react'; import {ScrollView, View} from 'react-native'; -import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags'; import setUpIntersectionObserver from 'react-native/src/private/setup/setUpIntersectionObserver'; import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; import DOMRectReadOnly from 'react-native/src/private/webapis/geometry/DOMRectReadOnly'; @@ -1494,44 +1492,42 @@ describe('IntersectionObserver', () => { }); }); - if (ReactNativeFeatureFlags.utilizeTokensInIntersectionObserver()) { - it('should not retain initial children of observed targets', () => { - const root = Fantom.createRoot(); - observer = new IntersectionObserver(() => {}); - - const [getReferenceCount, ref] = createShadowNodeReferenceCountingRef(); - - const observeRef: React.RefSetter< - React.ElementRef, - > = instance => { - const element = ensureReactNativeElement(instance); - observer.observe(element); - return () => { - observer.unobserve(element); - }; - }; + it('should not retain initial children of observed targets', () => { + const root = Fantom.createRoot(); + observer = new IntersectionObserver(() => {}); - function Observe({children}: $ReadOnly<{children?: React.Node}>) { - return {children}; - } + const [getReferenceCount, ref] = createShadowNodeReferenceCountingRef(); - Fantom.runTask(() => { - root.render( - - - , - ); - }); + const observeRef: React.RefSetter< + React.ElementRef, + > = instance => { + const element = ensureReactNativeElement(instance); + observer.observe(element); + return () => { + observer.unobserve(element); + }; + }; - expect(getReferenceCount()).toBeGreaterThan(0); + function Observe({children}: $ReadOnly<{children?: React.Node}>) { + return {children}; + } - Fantom.runTask(() => { - root.render(); - }); + Fantom.runTask(() => { + root.render( + + + , + ); + }); - expect(getReferenceCount()).toBe(0); + expect(getReferenceCount()).toBeGreaterThan(0); + + Fantom.runTask(() => { + root.render(); }); - } + + expect(getReferenceCount()).toBe(0); + }); it('should NOT report multiple entries when observing a target that exists and we modify it later in the same tick', () => { const root = Fantom.createRoot({ diff --git a/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js b/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js index 6cbd285d110a67..db9e17db8bf642 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js +++ b/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js @@ -27,13 +27,13 @@ import type {NativeIntersectionObserverToken} from '../specs/NativeIntersectionO import * as Systrace from '../../../../../Libraries/Performance/Systrace'; import warnOnce from '../../../../../Libraries/Utilities/warnOnce'; -import * as ReactNativeFeatureFlags from '../../../featureflags/ReactNativeFeatureFlags'; import { getInstanceHandle, getNativeNodeReference, } from '../../dom/nodes/internals/NodeInternals'; import {createIntersectionObserverEntry} from '../IntersectionObserverEntry'; import NativeIntersectionObserver from '../specs/NativeIntersectionObserver'; +import nullthrows from 'nullthrows'; export type IntersectionObserverId = number; @@ -69,36 +69,11 @@ function setTargetForInstanceHandle( instanceHandleToTargetMap.set(key, target); } -// The mapping between ReactNativeElement and their corresponding shadow node -// also needs to be kept here because React removes the link when unmounting. -const targetToShadowNodeMap: WeakMap< - ReactNativeElement, - ReturnType, -> = new WeakMap(); - const targetToTokenMap: WeakMap< ReactNativeElement, NativeIntersectionObserverToken, > = new WeakMap(); -let modernNativeIntersectionObserver = - NativeIntersectionObserver == null - ? null - : NativeIntersectionObserver.observeV2 == null || - NativeIntersectionObserver.unobserveV2 == null - ? null - : { - observe: NativeIntersectionObserver.observeV2, - unobserve: NativeIntersectionObserver.unobserveV2, - }; - -if ( - modernNativeIntersectionObserver && - !ReactNativeFeatureFlags.utilizeTokensInIntersectionObserver() -) { - modernNativeIntersectionObserver = null; -} - /** * Registers the given intersection observer and returns a unique ID for it, * which is required to start observing targets. @@ -189,34 +164,19 @@ export function observe({ // access it even after the instance handle has been unmounted. setTargetForInstanceHandle(instanceHandle, target); - if (modernNativeIntersectionObserver == null) { - // Same for the mapping between the target and its shadow node. - targetToShadowNodeMap.set(target, targetNativeNodeReference); - } - if (!isConnected) { NativeIntersectionObserver.connect(notifyIntersectionObservers); isConnected = true; } - if (modernNativeIntersectionObserver == null) { - NativeIntersectionObserver.observe({ - intersectionObserverId, - rootShadowNode: rootNativeNodeReference, - targetShadowNode: targetNativeNodeReference, - thresholds: registeredObserver.observer.thresholds, - rootThresholds: registeredObserver.observer.rnRootThresholds, - }); - } else { - const token = modernNativeIntersectionObserver.observe({ - intersectionObserverId, - rootShadowNode: rootNativeNodeReference, - targetShadowNode: targetNativeNodeReference, - thresholds: registeredObserver.observer.thresholds, - rootThresholds: registeredObserver.observer.rnRootThresholds, - }); - targetToTokenMap.set(target, token); - } + const token = nullthrows(NativeIntersectionObserver.observeV2)({ + intersectionObserverId, + rootShadowNode: rootNativeNodeReference, + targetShadowNode: targetNativeNodeReference, + thresholds: registeredObserver.observer.thresholds, + rootThresholds: registeredObserver.observer.rnRootThresholds, + }); + targetToTokenMap.set(target, token); return true; } @@ -240,33 +200,18 @@ export function unobserve( return; } - if (modernNativeIntersectionObserver == null) { - const targetNativeNodeReference = targetToShadowNodeMap.get(target); - if (targetNativeNodeReference == null) { - console.error( - 'IntersectionObserverManager: could not find registration data for target', - ); - return; - } - - NativeIntersectionObserver.unobserve( - intersectionObserverId, - targetNativeNodeReference, - ); - } else { - const targetToken = targetToTokenMap.get(target); - if (targetToken == null) { - console.error( - 'IntersectionObserverManager: could not find registration data for target', - ); - return; - } - - modernNativeIntersectionObserver.unobserve( - intersectionObserverId, - targetToken, + const targetToken = targetToTokenMap.get(target); + if (targetToken == null) { + console.error( + 'IntersectionObserverManager: could not find registration data for target', ); + return; } + + nullthrows(NativeIntersectionObserver.unobserveV2)( + intersectionObserverId, + targetToken, + ); } /** diff --git a/packages/react-native/src/private/webapis/intersectionobserver/specs/NativeIntersectionObserver.js b/packages/react-native/src/private/webapis/intersectionobserver/specs/NativeIntersectionObserver.js index af1cac035ab061..f5e36098787bf2 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/specs/NativeIntersectionObserver.js +++ b/packages/react-native/src/private/webapis/intersectionobserver/specs/NativeIntersectionObserver.js @@ -34,10 +34,6 @@ export type NativeIntersectionObserverObserveOptions = { export opaque type NativeIntersectionObserverToken = mixed; export interface Spec extends TurboModule { - // TODO(T223605846): Remove legacy observe method - +observe: (options: NativeIntersectionObserverObserveOptions) => void; - // TODO(T223605846): Remove legacy unobserve method - +unobserve: (intersectionObserverId: number, targetShadowNode: mixed) => void; +observeV2?: ( options: NativeIntersectionObserverObserveOptions, ) => NativeIntersectionObserverToken;