diff --git a/packages/react-native/Libraries/Core/setUpReactDevTools.js b/packages/react-native/Libraries/Core/setUpReactDevTools.js index e25f0a44c98073..7bbc209a7e2653 100644 --- a/packages/react-native/Libraries/Core/setUpReactDevTools.js +++ b/packages/react-native/Libraries/Core/setUpReactDevTools.js @@ -1,261 +1,261 @@ -/** - * 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 - * @format - */ +// /** +// * 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 +// * @format +// */ -'use strict'; +// 'use strict'; -import type {Domain} from '../../src/private/devsupport/rndevtools/setUpFuseboxReactDevToolsDispatcher'; -import type {Spec as NativeReactDevToolsRuntimeSettingsModuleSpec} from '../../src/private/devsupport/rndevtools/specs/NativeReactDevToolsRuntimeSettingsModule'; +// import type {Domain} from '../../src/private/devsupport/rndevtools/setUpFuseboxReactDevToolsDispatcher'; +// import type {Spec as NativeReactDevToolsRuntimeSettingsModuleSpec} from '../../src/private/devsupport/rndevtools/specs/NativeReactDevToolsRuntimeSettingsModule'; -if (__DEV__) { - if (typeof global.queueMicrotask !== 'function') { - console.error( - 'queueMicrotask should exist before setting up React DevTools.', - ); - } +// if (__DEV__) { +// if (typeof global.queueMicrotask !== 'function') { +// console.error( +// 'queueMicrotask should exist before setting up React DevTools.', +// ); +// } - // Keep in sync with ExceptionsManager/installConsoleErrorReporter - // $FlowExpectedError[prop-missing] - if (console._errorOriginal != null) { - console.error( - 'ExceptionsManager should be set up after React DevTools to avoid console.error arguments mutation', - ); - } -} +// // Keep in sync with ExceptionsManager/installConsoleErrorReporter +// // $FlowExpectedError[prop-missing] +// if (console._errorOriginal != null) { +// console.error( +// 'ExceptionsManager should be set up after React DevTools to avoid console.error arguments mutation', +// ); +// } +// } -if (__DEV__) { - // Register dispatcher on global, which can be used later by Chrome DevTools frontend - require('../../src/private/devsupport/rndevtools/setUpFuseboxReactDevToolsDispatcher'); - const { - initialize, - connectToDevTools, - connectWithCustomMessagingProtocol, - } = require('react-devtools-core'); +// if (__DEV__) { +// // Register dispatcher on global, which can be used later by Chrome DevTools frontend +// require('../../src/private/devsupport/rndevtools/setUpFuseboxReactDevToolsDispatcher'); +// const { +// initialize, +// connectToDevTools, +// connectWithCustomMessagingProtocol, +// } = require('react-devtools-core'); - const reactDevToolsSettingsManager = require('../../src/private/devsupport/rndevtools/ReactDevToolsSettingsManager'); - const serializedHookSettings = - reactDevToolsSettingsManager.getGlobalHookSettings(); - const maybeReactDevToolsRuntimeSettingsModuleModule = - require('../../src/private/devsupport/rndevtools/specs/NativeReactDevToolsRuntimeSettingsModule').default; +// const reactDevToolsSettingsManager = require('../../src/private/devsupport/rndevtools/ReactDevToolsSettingsManager'); +// const serializedHookSettings = +// reactDevToolsSettingsManager.getGlobalHookSettings(); +// const maybeReactDevToolsRuntimeSettingsModuleModule = +// require('../../src/private/devsupport/rndevtools/specs/NativeReactDevToolsRuntimeSettingsModule').default; - let hookSettings = null; - if (serializedHookSettings != null) { - try { - const parsedSettings = JSON.parse(serializedHookSettings); - hookSettings = parsedSettings; - } catch { - console.error( - 'Failed to parse persisted React DevTools hook settings. React DevTools will be initialized with default settings.', - ); - } - } +// let hookSettings = null; +// if (serializedHookSettings != null) { +// try { +// const parsedSettings = JSON.parse(serializedHookSettings); +// hookSettings = parsedSettings; +// } catch { +// console.error( +// 'Failed to parse persisted React DevTools hook settings. React DevTools will be initialized with default settings.', +// ); +// } +// } - const { - isProfiling: shouldStartProfilingNow, - profilingSettings: initialProfilingSettings, - } = readReloadAndProfileConfig(maybeReactDevToolsRuntimeSettingsModuleModule); +// const { +// isProfiling: shouldStartProfilingNow, +// profilingSettings: initialProfilingSettings, +// } = readReloadAndProfileConfig(maybeReactDevToolsRuntimeSettingsModuleModule); - // Install hook before React is loaded. - initialize(hookSettings, shouldStartProfilingNow, initialProfilingSettings); +// // Install hook before React is loaded. +// initialize(hookSettings, shouldStartProfilingNow, initialProfilingSettings); - // This should be defined in DEV, otherwise error is expected. - const fuseboxReactDevToolsDispatcher = - global.__FUSEBOX_REACT_DEVTOOLS_DISPATCHER__; - const reactDevToolsFuseboxGlobalBindingName = - fuseboxReactDevToolsDispatcher.BINDING_NAME; +// // This should be defined in DEV, otherwise error is expected. +// const fuseboxReactDevToolsDispatcher = +// global.__FUSEBOX_REACT_DEVTOOLS_DISPATCHER__; +// const reactDevToolsFuseboxGlobalBindingName = +// fuseboxReactDevToolsDispatcher.BINDING_NAME; - const ReactNativeStyleAttributes = - require('../Components/View/ReactNativeStyleAttributes').default; - const resolveRNStyle = require('../StyleSheet/flattenStyle').default; +// const ReactNativeStyleAttributes = +// require('../Components/View/ReactNativeStyleAttributes').default; +// const resolveRNStyle = require('../StyleSheet/flattenStyle').default; - function handleReactDevToolsSettingsUpdate(settings: Object) { - reactDevToolsSettingsManager.setGlobalHookSettings( - JSON.stringify(settings), - ); - } +// function handleReactDevToolsSettingsUpdate(settings: Object) { +// reactDevToolsSettingsManager.setGlobalHookSettings( +// JSON.stringify(settings), +// ); +// } - let disconnect = null; - function disconnectBackendFromReactDevToolsInFuseboxIfNeeded() { - if (disconnect != null) { - disconnect(); - disconnect = null; - } - } +// let disconnect = null; +// function disconnectBackendFromReactDevToolsInFuseboxIfNeeded() { +// if (disconnect != null) { +// disconnect(); +// disconnect = null; +// } +// } - function connectToReactDevToolsInFusebox(domain: Domain) { - const { - isReloadAndProfileSupported, - isProfiling, - onReloadAndProfile, - onReloadAndProfileFlagsReset, - } = readReloadAndProfileConfig( - maybeReactDevToolsRuntimeSettingsModuleModule, - ); - disconnect = connectWithCustomMessagingProtocol({ - onSubscribe: listener => { - domain.onMessage.addEventListener(listener); - }, - onUnsubscribe: listener => { - domain.onMessage.removeEventListener(listener); - }, - onMessage: (event, payload) => { - domain.sendMessage({event, payload}); - }, - nativeStyleEditorValidAttributes: Object.keys(ReactNativeStyleAttributes), - resolveRNStyle, - onSettingsUpdated: handleReactDevToolsSettingsUpdate, - isReloadAndProfileSupported, - isProfiling, - onReloadAndProfile, - onReloadAndProfileFlagsReset, - }); - } +// function connectToReactDevToolsInFusebox(domain: Domain) { +// const { +// isReloadAndProfileSupported, +// isProfiling, +// onReloadAndProfile, +// onReloadAndProfileFlagsReset, +// } = readReloadAndProfileConfig( +// maybeReactDevToolsRuntimeSettingsModuleModule, +// ); +// disconnect = connectWithCustomMessagingProtocol({ +// onSubscribe: listener => { +// domain.onMessage.addEventListener(listener); +// }, +// onUnsubscribe: listener => { +// domain.onMessage.removeEventListener(listener); +// }, +// onMessage: (event, payload) => { +// domain.sendMessage({event, payload}); +// }, +// nativeStyleEditorValidAttributes: Object.keys(ReactNativeStyleAttributes), +// resolveRNStyle, +// onSettingsUpdated: handleReactDevToolsSettingsUpdate, +// isReloadAndProfileSupported, +// isProfiling, +// onReloadAndProfile, +// onReloadAndProfileFlagsReset, +// }); +// } - let isWebSocketOpen = false; - let ws = null; - function connectToWSBasedReactDevToolsFrontend() { - if (ws !== null && isWebSocketOpen) { - // If the DevTools backend is already connected, don't recreate the WebSocket. - // This would break the connection. - // If there isn't an active connection, a backend may be waiting to connect, - // in which case it's okay to make a new one. - return; - } +// let isWebSocketOpen = false; +// let ws = null; +// function connectToWSBasedReactDevToolsFrontend() { +// if (ws !== null && isWebSocketOpen) { +// // If the DevTools backend is already connected, don't recreate the WebSocket. +// // This would break the connection. +// // If there isn't an active connection, a backend may be waiting to connect, +// // in which case it's okay to make a new one. +// return; +// } - // not when debugging in chrome - // TODO(t12832058) This check is broken - if (!window.document) { - const AppState = require('../AppState/AppState').default; - const getDevServer = require('./Devtools/getDevServer').default; +// // not when debugging in chrome +// // TODO(t12832058) This check is broken +// if (!window.document) { +// const AppState = require('../AppState/AppState').default; +// const getDevServer = require('./Devtools/getDevServer').default; - // Don't steal the DevTools from currently active app. - // Note: if you add any AppState subscriptions to this file, - // you will also need to guard against `AppState.isAvailable`, - // or the code will throw for bundles that don't have it. - const isAppActive = () => AppState.currentState !== 'background'; +// // Don't steal the DevTools from currently active app. +// // Note: if you add any AppState subscriptions to this file, +// // you will also need to guard against `AppState.isAvailable`, +// // or the code will throw for bundles that don't have it. +// const isAppActive = () => AppState.currentState !== 'background'; - // Get hostname from development server (packager) - const devServer = getDevServer(); - const host = devServer.bundleLoadedFromServer - ? devServer.url - .replace(/https?:\/\//, '') - .replace(/\/$/, '') - .split(':')[0] - : 'localhost'; +// // Get hostname from development server (packager) +// const devServer = getDevServer(); +// const host = devServer.bundleLoadedFromServer +// ? devServer.url +// .replace(/https?:\/\//, '') +// .replace(/\/$/, '') +// .split(':')[0] +// : 'localhost'; - // Read the optional global variable for backward compatibility. - // It was added in https://github.com/facebook/react-native/commit/bf2b435322e89d0aeee8792b1c6e04656c2719a0. - const port = - // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-use] - window.__REACT_DEVTOOLS_PORT__ != null - ? window.__REACT_DEVTOOLS_PORT__ - : 8097; +// // Read the optional global variable for backward compatibility. +// // It was added in https://github.com/facebook/react-native/commit/bf2b435322e89d0aeee8792b1c6e04656c2719a0. +// const port = +// // $FlowFixMe[prop-missing] +// // $FlowFixMe[incompatible-use] +// window.__REACT_DEVTOOLS_PORT__ != null +// ? window.__REACT_DEVTOOLS_PORT__ +// : 8097; - const WebSocket = require('../WebSocket/WebSocket').default; - ws = new WebSocket('ws://' + host + ':' + port); - ws.addEventListener('close', event => { - isWebSocketOpen = false; - }); - ws.addEventListener('open', event => { - isWebSocketOpen = true; - }); +// const WebSocket = require('../WebSocket/WebSocket').default; +// ws = new WebSocket('ws://' + host + ':' + port); +// ws.addEventListener('close', event => { +// isWebSocketOpen = false; +// }); +// ws.addEventListener('open', event => { +// isWebSocketOpen = true; +// }); - const { - isReloadAndProfileSupported, - isProfiling, - onReloadAndProfile, - onReloadAndProfileFlagsReset, - } = readReloadAndProfileConfig( - maybeReactDevToolsRuntimeSettingsModuleModule, - ); - connectToDevTools({ - isAppActive, - resolveRNStyle, - nativeStyleEditorValidAttributes: Object.keys( - ReactNativeStyleAttributes, - ), - websocket: ws, - onSettingsUpdated: handleReactDevToolsSettingsUpdate, - isReloadAndProfileSupported, - isProfiling, - onReloadAndProfile, - onReloadAndProfileFlagsReset, - }); - } - } +// const { +// isReloadAndProfileSupported, +// isProfiling, +// onReloadAndProfile, +// onReloadAndProfileFlagsReset, +// } = readReloadAndProfileConfig( +// maybeReactDevToolsRuntimeSettingsModuleModule, +// ); +// connectToDevTools({ +// isAppActive, +// resolveRNStyle, +// nativeStyleEditorValidAttributes: Object.keys( +// ReactNativeStyleAttributes, +// ), +// websocket: ws, +// onSettingsUpdated: handleReactDevToolsSettingsUpdate, +// isReloadAndProfileSupported, +// isProfiling, +// onReloadAndProfile, +// onReloadAndProfileFlagsReset, +// }); +// } +// } - // 1. If React DevTools has already been opened and initialized in Fusebox, bindings survive reloads - if (global[reactDevToolsFuseboxGlobalBindingName] != null) { - disconnectBackendFromReactDevToolsInFuseboxIfNeeded(); - const domain = - fuseboxReactDevToolsDispatcher.initializeDomain('react-devtools'); - connectToReactDevToolsInFusebox(domain); - } +// // 1. If React DevTools has already been opened and initialized in Fusebox, bindings survive reloads +// if (global[reactDevToolsFuseboxGlobalBindingName] != null) { +// disconnectBackendFromReactDevToolsInFuseboxIfNeeded(); +// const domain = +// fuseboxReactDevToolsDispatcher.initializeDomain('react-devtools'); +// connectToReactDevToolsInFusebox(domain); +// } - // 2. If React DevTools panel in Fusebox was opened for the first time after the runtime has been created - // 2. OR if React DevTools frontend was re-initialized: Chrome DevTools was closed and then re-opened - global.__FUSEBOX_REACT_DEVTOOLS_DISPATCHER__.onDomainInitialization.addEventListener( - (domain: Domain) => { - if (domain.name === 'react-devtools') { - disconnectBackendFromReactDevToolsInFuseboxIfNeeded(); - connectToReactDevToolsInFusebox(domain); - } - }, - ); +// // 2. If React DevTools panel in Fusebox was opened for the first time after the runtime has been created +// // 2. OR if React DevTools frontend was re-initialized: Chrome DevTools was closed and then re-opened +// global.__FUSEBOX_REACT_DEVTOOLS_DISPATCHER__.onDomainInitialization.addEventListener( +// (domain: Domain) => { +// if (domain.name === 'react-devtools') { +// disconnectBackendFromReactDevToolsInFuseboxIfNeeded(); +// connectToReactDevToolsInFusebox(domain); +// } +// }, +// ); - // 3. Fallback to attempting to connect WS-based RDT frontend - const RCTNativeAppEventEmitter = - require('../EventEmitter/RCTNativeAppEventEmitter').default; - RCTNativeAppEventEmitter.addListener( - 'RCTDevMenuShown', - connectToWSBasedReactDevToolsFrontend, - ); - connectToWSBasedReactDevToolsFrontend(); // Try connecting once on load -} +// // 3. Fallback to attempting to connect WS-based RDT frontend +// const RCTNativeAppEventEmitter = +// require('../EventEmitter/RCTNativeAppEventEmitter').default; +// RCTNativeAppEventEmitter.addListener( +// 'RCTDevMenuShown', +// connectToWSBasedReactDevToolsFrontend, +// ); +// connectToWSBasedReactDevToolsFrontend(); // Try connecting once on load +// } -function readReloadAndProfileConfig( - maybeModule: ?NativeReactDevToolsRuntimeSettingsModuleSpec, -) { - const isReloadAndProfileSupported = maybeModule != null; - const config = maybeModule?.getReloadAndProfileConfig(); - const isProfiling = config?.shouldReloadAndProfile === true; - const profilingSettings = { - recordChangeDescriptions: config?.recordChangeDescriptions === true, - recordTimeline: false, - }; - const onReloadAndProfile = (recordChangeDescriptions: boolean) => { - if (maybeModule == null) { - return; - } +// function readReloadAndProfileConfig( +// maybeModule: ?NativeReactDevToolsRuntimeSettingsModuleSpec, +// ) { +// const isReloadAndProfileSupported = maybeModule != null; +// const config = maybeModule?.getReloadAndProfileConfig(); +// const isProfiling = config?.shouldReloadAndProfile === true; +// const profilingSettings = { +// recordChangeDescriptions: config?.recordChangeDescriptions === true, +// recordTimeline: false, +// }; +// const onReloadAndProfile = (recordChangeDescriptions: boolean) => { +// if (maybeModule == null) { +// return; +// } - maybeModule.setReloadAndProfileConfig({ - shouldReloadAndProfile: true, - recordChangeDescriptions, - }); - }; - const onReloadAndProfileFlagsReset = () => { - if (maybeModule == null) { - return; - } +// maybeModule.setReloadAndProfileConfig({ +// shouldReloadAndProfile: true, +// recordChangeDescriptions, +// }); +// }; +// const onReloadAndProfileFlagsReset = () => { +// if (maybeModule == null) { +// return; +// } - maybeModule.setReloadAndProfileConfig({ - shouldReloadAndProfile: false, - recordChangeDescriptions: false, - }); - }; +// maybeModule.setReloadAndProfileConfig({ +// shouldReloadAndProfile: false, +// recordChangeDescriptions: false, +// }); +// }; - return { - isReloadAndProfileSupported, - isProfiling, - profilingSettings, - onReloadAndProfile, - onReloadAndProfileFlagsReset, - }; -} +// return { +// isReloadAndProfileSupported, +// isProfiling, +// profilingSettings, +// onReloadAndProfile, +// onReloadAndProfileFlagsReset, +// }; +// } diff --git a/packages/react-native/Libraries/ReactNative/FabricUIManager.js b/packages/react-native/Libraries/ReactNative/FabricUIManager.js index 13b203f0abd953..4494f07a61252b 100644 --- a/packages/react-native/Libraries/ReactNative/FabricUIManager.js +++ b/packages/react-native/Libraries/ReactNative/FabricUIManager.js @@ -27,6 +27,7 @@ import defineLazyObjectProperty from '../Utilities/defineLazyObjectProperty'; export type NodeSet = Array; export type NodeProps = {...}; +export opaque type ShadowNodeFamilyReference = mixed; export interface Spec { +createNode: ( reactTag: number, @@ -40,6 +41,7 @@ export interface Spec { +cloneNodeWithNewProps: (node: Node, newProps: NodeProps) => Node; +cloneNodeWithNewChildrenAndProps: (node: Node, newProps: NodeProps) => Node; +createChildSet: (rootTag: RootTag) => NodeSet; + +createShadowNodeFamilyReference: (node: Node) => ShadowNodeFamilyReference; +appendChild: (parentNode: Node, child: Node) => Node; +appendChildToSet: (childSet: NodeSet, child: Node) => void; +completeRoot: (rootTag: RootTag, childSet: NodeSet) => void; @@ -111,6 +113,7 @@ const CACHED_PROPERTIES = [ 'cloneNodeWithNewProps', 'cloneNodeWithNewChildrenAndProps', 'createChildSet', + 'createShadowNodeFamilyReference', 'appendChild', 'appendChildToSet', 'completeRoot', diff --git a/packages/react-native/React/Base/RCTBundleURLProvider.mm b/packages/react-native/React/Base/RCTBundleURLProvider.mm index fb1d601c075d8d..9feb42e2d4e7c1 100644 --- a/packages/react-native/React/Base/RCTBundleURLProvider.mm +++ b/packages/react-native/React/Base/RCTBundleURLProvider.mm @@ -388,7 +388,7 @@ - (void)updateValue:(id)object forKey:(NSString *)key - (BOOL)enableDev { - return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTEnableDevKey]; + return NO; } - (BOOL)enableMinification diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp index 3ec219424f3eb2..5c6055aec03cff 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp @@ -1,3 +1,4 @@ + /* * Copyright (c) Meta Platforms, Inc. and affiliates. * @@ -128,6 +129,26 @@ void TurboModuleBinding::install( return; } + defineReadOnlyGlobal( + runtime, + "RN$clearLongLivedObjectCollection", + jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii( + runtime, "RN$clearLongLivedObjectCollection"), + 0, + [longLivedObjectCollection]( + jsi::Runtime& rt, + const jsi::Value& /*thisVal*/, + const jsi::Value* /*args*/, + size_t /*count*/) { + LongLivedObjectCollection::get(rt).clear(); + if (longLivedObjectCollection) { + longLivedObjectCollection->clear(); + } + return jsi::Value::undefined(); + })); + defineReadOnlyGlobal(runtime, "RN$UnifiedNativeModuleProxy", true); defineReadOnlyGlobal( runtime, diff --git a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp index 531d9ae416e34b..91b441e4b5ed4a 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp @@ -37,7 +37,7 @@ void NativeIntersectionObserver::observe( intersectionObserverManager_.observe( intersectionObserverId, - shadowNode, + shadowNode->getFamilyShared(), thresholds, rootThresholds, uiManager); @@ -48,7 +48,8 @@ void NativeIntersectionObserver::unobserve( IntersectionObserverObserverId intersectionObserverId, jsi::Object targetShadowNode) { auto shadowNode = shadowNodeFromValue(runtime, std::move(targetShadowNode)); - intersectionObserverManager_.unobserve(intersectionObserverId, *shadowNode); + intersectionObserverManager_.unobserve( + intersectionObserverId, shadowNode->getFamilyShared()); } void NativeIntersectionObserver::connect( @@ -101,7 +102,7 @@ NativeIntersectionObserver::convertToNativeModuleEntry( NativeIntersectionObserverEntry nativeModuleEntry = { entry.intersectionObserverId, - (*entry.shadowNode).getInstanceHandle(runtime), + (*entry.shadowNodeFamily).getInstanceHandle(runtime), targetRect, rootRect, intersectionRect, diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp index 3af382c1dd6403..8bda5b733d0a72 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp @@ -191,12 +191,7 @@ const SharedEventEmitter& ShadowNode::getEventEmitter() const { } jsi::Value ShadowNode::getInstanceHandle(jsi::Runtime& runtime) const { - auto instanceHandle = family_->instanceHandle_; - if (instanceHandle == nullptr) { - return jsi::Value::null(); - } - - return instanceHandle->getInstanceHandle(runtime); + return family_->getInstanceHandle(runtime); } Tag ShadowNode::getTag() const { @@ -341,6 +336,10 @@ const ShadowNodeFamily& ShadowNode::getFamily() const { return *family_; } +ShadowNodeFamily::Shared ShadowNode::getFamilyShared() const { + return family_; +} + ShadowNode::Unshared ShadowNode::cloneTree( const ShadowNodeFamily& shadowNodeFamily, const std::function& diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h index ae648988875ade..63c21a99617b75 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h @@ -159,6 +159,8 @@ class ShadowNode : public Sealable, const ShadowNodeFamily& getFamily() const; + ShadowNodeFamily::Shared getFamilyShared() const; + #pragma mark - Mutating Methods virtual void appendChild(const Shared& child); diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp index 543ce2abe847f5..bf486802ccabc4 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp @@ -76,6 +76,14 @@ Tag ShadowNodeFamily::getTag() const { return tag_; } +jsi::Value ShadowNodeFamily::getInstanceHandle(jsi::Runtime& runtime) const { + if (instanceHandle_ == nullptr) { + return jsi::Value::null(); + } + + return instanceHandle_->getInstanceHandle(runtime); +} + InstanceHandle::Shared ShadowNodeFamily::getInstanceHandle() const { return instanceHandle_; } diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h index 203f895ff7cc16..83856089528755 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h @@ -39,7 +39,7 @@ struct ShadowNodeFamilyFragment { * Represents all things that shadow nodes from the same family have in common. * To be used inside `ShadowNode` class *only*. */ -class ShadowNodeFamily final { +class ShadowNodeFamily final : public jsi::NativeState { public: using Shared = std::shared_ptr; using Weak = std::weak_ptr; @@ -122,6 +122,7 @@ class ShadowNodeFamily final { */ Tag getTag() const; + jsi::Value getInstanceHandle(jsi::Runtime& runtime) const; InstanceHandle::Shared getInstanceHandle() const; void setInstanceHandle(InstanceHandle::Shared& instanceHandle) const; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp index 2589ee749abb1b..bc8d0c875a5e3e 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp @@ -16,11 +16,11 @@ namespace facebook::react { IntersectionObserver::IntersectionObserver( IntersectionObserverObserverId intersectionObserverId, - ShadowNode::Shared targetShadowNode, + ShadowNodeFamily::Shared targetShadowNodeFamily, std::vector thresholds, std::optional> rootThresholds) : intersectionObserverId_(intersectionObserverId), - targetShadowNode_(std::move(targetShadowNode)), + targetShadowNodeFamily_(std::move(targetShadowNodeFamily)), thresholds_(std::move(thresholds)), rootThresholds_(std::move(rootThresholds)) {} @@ -112,8 +112,7 @@ IntersectionObserver::updateIntersectionObservation( layoutableRootShadowNode != nullptr && "RootShadowNode instances must always inherit from LayoutableShadowNode."); - auto targetAncestors = - targetShadowNode_->getFamily().getAncestors(rootShadowNode); + auto targetAncestors = targetShadowNodeFamily_->getAncestors(rootShadowNode); // Absolute coordinates of the root auto rootBoundingRect = getRootBoundingRect(*layoutableRootShadowNode); @@ -189,7 +188,7 @@ IntersectionObserver::setIntersectingState( state_ = newState; IntersectionObserverEntry entry{ intersectionObserverId_, - targetShadowNode_, + targetShadowNodeFamily_, targetBoundingRect, rootBoundingRect, intersectionRect, @@ -212,7 +211,7 @@ IntersectionObserver::setNotIntersectingState( state_ = IntersectionObserverState::NotIntersecting(); IntersectionObserverEntry entry{ intersectionObserverId_, - targetShadowNode_, + targetShadowNodeFamily_, targetBoundingRect, rootBoundingRect, intersectionRect, diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h index ba6a4449b366a0..89b45d83b653b7 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h @@ -8,7 +8,7 @@ #pragma once #include -#include +#include #include #include #include @@ -20,7 +20,7 @@ using IntersectionObserverObserverId = int32_t; struct IntersectionObserverEntry { IntersectionObserverObserverId intersectionObserverId; - ShadowNode::Shared shadowNode; + ShadowNodeFamily::Shared shadowNodeFamily; Rect targetRect; Rect rootRect; Rect intersectionRect; @@ -28,13 +28,19 @@ struct IntersectionObserverEntry { // TODO(T156529385) Define `DOMHighResTimeStamp` as an alias for `double` and // use it here. double time; + + bool sameShadowNodeFamily( + const ShadowNodeFamily& otherShadowNodeFamily) const { + return std::addressof(*shadowNodeFamily) == + std::addressof(otherShadowNodeFamily); + } }; class IntersectionObserver { public: IntersectionObserver( IntersectionObserverObserverId intersectionObserverId, - ShadowNode::Shared targetShadowNode, + ShadowNodeFamily::Shared targetShadowNodeFamily, std::vector thresholds, std::optional> rootThresholds = std::nullopt); @@ -51,8 +57,10 @@ class IntersectionObserver { return intersectionObserverId_; } - const ShadowNode& getTargetShadowNode() const { - return *targetShadowNode_; + bool isTargetShadowNodeFamily( + const ShadowNodeFamily& shadowNodeFamily) const { + return std::addressof(*targetShadowNodeFamily_) == + std::addressof(shadowNodeFamily); } std::vector getThresholds() const { @@ -75,7 +83,7 @@ class IntersectionObserver { double time); IntersectionObserverObserverId intersectionObserverId_; - ShadowNode::Shared targetShadowNode_; + ShadowNodeFamily::Shared targetShadowNodeFamily_; std::vector thresholds_; std::optional> rootThresholds_; mutable IntersectionObserverState state_ = diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp index 7fa55a5a470f18..e2ac61ae1d2de8 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp @@ -17,13 +17,13 @@ IntersectionObserverManager::IntersectionObserverManager() = default; void IntersectionObserverManager::observe( IntersectionObserverObserverId intersectionObserverId, - const ShadowNode::Shared& shadowNode, + const ShadowNodeFamily::Shared& shadowNodeFamily, std::vector thresholds, std::optional> rootThresholds, const UIManager& uiManager) { TraceSection s("IntersectionObserverManager::observe"); - auto surfaceId = shadowNode->getSurfaceId(); + auto surfaceId = shadowNodeFamily->getSurfaceId(); // The actual observer lives in the array, so we need to create it there and // then get a reference. Otherwise we only update its state in a copy. @@ -36,7 +36,7 @@ void IntersectionObserverManager::observe( auto& observers = observersBySurfaceId_[surfaceId]; observers.emplace_back(IntersectionObserver{ intersectionObserverId, - shadowNode, + shadowNodeFamily, std::move(thresholds), std::move(rootThresholds)}); observer = &observers.back(); @@ -77,13 +77,13 @@ void IntersectionObserverManager::observe( void IntersectionObserverManager::unobserve( IntersectionObserverObserverId intersectionObserverId, - const ShadowNode& shadowNode) { + const ShadowNodeFamily::Shared& shadowNodeFamily) { TraceSection s("IntersectionObserverManager::unobserve"); { std::unique_lock lock(observersMutex_); - auto surfaceId = shadowNode.getSurfaceId(); + auto surfaceId = shadowNodeFamily->getSurfaceId(); auto observersIt = observersBySurfaceId_.find(surfaceId); if (observersIt == observersBySurfaceId_.end()) { @@ -96,11 +96,10 @@ void IntersectionObserverManager::unobserve( std::remove_if( observers.begin(), observers.end(), - [intersectionObserverId, &shadowNode](const auto& observer) { + [intersectionObserverId, &shadowNodeFamily](const auto& observer) { return observer.getIntersectionObserverId() == intersectionObserverId && - ShadowNode::sameFamily( - observer.getTargetShadowNode(), shadowNode); + observer.isTargetShadowNodeFamily(*shadowNodeFamily); }), observers.end()); @@ -116,9 +115,9 @@ void IntersectionObserverManager::unobserve( std::remove_if( pendingEntries_.begin(), pendingEntries_.end(), - [intersectionObserverId, &shadowNode](const auto& entry) { + [intersectionObserverId, &shadowNodeFamily](const auto& entry) { return entry.intersectionObserverId == intersectionObserverId && - ShadowNode::sameFamily(*entry.shadowNode, shadowNode); + entry.sameShadowNodeFamily(*shadowNodeFamily); }), pendingEntries_.end()); } diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h index 93ae10dd1b2ab0..af65a599203420 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h @@ -22,14 +22,14 @@ class IntersectionObserverManager final : public UIManagerMountHook { void observe( IntersectionObserverObserverId intersectionObserverId, - const ShadowNode::Shared& shadowNode, + const ShadowNodeFamily::Shared& shadowNode, std::vector thresholds, std::optional> rootThresholds, const UIManager& uiManager); void unobserve( IntersectionObserverObserverId intersectionObserverId, - const ShadowNode& shadowNode); + const ShadowNodeFamily::Shared& shadowNode); void connect( UIManager& uiManager, diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index c5baaf76cfe440..8fed466491bea5 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -235,6 +235,24 @@ jsi::Value UIManagerBinding::get( }); } + if (methodName == "createShadowNodeFamilyReference") { + auto paramCount = 1; + return jsi::Function::createFromHostFunction( + runtime, + name, + paramCount, + [methodName, paramCount]( + jsi::Runtime& runtime, + const jsi::Value& /*thisValue*/, + const jsi::Value* arguments, + size_t count) { + validateArgumentCount(runtime, methodName, paramCount, count); + + auto node = shadowNodeFromValue(runtime, arguments[0]); + return valueFromShadowNodeFamily(runtime, node->getFamilyShared()); + }); + } + if (methodName == "setIsJSResponder") { auto paramCount = 3; return jsi::Function::createFromHostFunction( diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h b/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h index ec9ff8cf7efb2c..ee37f4ec49ec01 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h @@ -42,6 +42,16 @@ inline static ShadowNode::Shared shadowNodeFromValue( ->shadowNode; } +inline static ShadowNodeFamily::Shared shadowNodeFamilyFromValue( + jsi::Runtime& runtime, + const jsi::Value& value) { + if (value.isNull()) { + return nullptr; + } + + return value.getObject(runtime).getNativeState(runtime); +} + inline static jsi::Value valueFromShadowNode( jsi::Runtime& runtime, ShadowNode::Shared shadowNode, @@ -60,6 +70,17 @@ inline static jsi::Value valueFromShadowNode( return obj; } +inline static jsi::Value valueFromShadowNodeFamily( + jsi::Runtime& runtime, + ShadowNodeFamily::Shared shadowNodeFamily) { + jsi::Object obj(runtime); + // Need to const_cast since JSI only allows non-const pointees + obj.setNativeState( + runtime, + std::const_pointer_cast(std::move(shadowNodeFamily))); + return obj; +} + // TODO: once we no longer need to mutate the return value (appendChildToSet) // make this a SharedListOfShared inline static ShadowNode::UnsharedListOfShared shadowNodeListFromValue( diff --git a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp index db92e3e75ca49e..b8a0288e4aa354 100644 --- a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp +++ b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp @@ -586,6 +586,23 @@ void ReactInstance::initializeRuntime( args[1].getObject(runtime).getFunction(runtime)); return jsi::Value::undefined(); })); + + defineReadOnlyGlobal( + runtime, + "RN$trimMemory", + jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii( + runtime, "RN$trimMemory"), + 0, + [this]( + jsi::Runtime& rt, + const jsi::Value& /*thisVal*/, + const jsi::Value* /*args*/, + size_t /*count*/) { + this->handleMemoryPressureJs(15); + return jsi::Value::undefined(); + })); timerManager_->attachGlobals(runtime); diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm index e76e1d18df9946..9b99180ba51ebf 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm @@ -131,7 +131,7 @@ - (instancetype)initWithDelegate:(id)delegate } _launchOptions = launchOptions; - if (ReactNativeFeatureFlags::enableJSRuntimeGCOnMemoryPressureOnIOS()) { + if (true) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification @@ -145,7 +145,7 @@ - (instancetype)initWithDelegate:(id)delegate - (void)dealloc { - if (ReactNativeFeatureFlags::enableJSRuntimeGCOnMemoryPressureOnIOS()) { + if (true) { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; 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 742ea105bafb38..c6ddb1d712e2e1 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 @@ -15,6 +15,7 @@ import type {HostInstance} from 'react-native'; import type IntersectionObserverType from 'react-native/src/private/webapis/intersectionobserver/IntersectionObserver'; import ensureInstance from '../../../__tests__/utilities/ensureInstance'; +import {createShadowNodeReferenceCountingRef} from '../../../__tests__/utilities/ShadowNodeReferenceCounter'; import * as Fantom from '@react-native/fantom'; import * as React from 'react'; import {ScrollView, View} from 'react-native'; @@ -843,6 +844,49 @@ describe('IntersectionObserver', () => { }); }); + // TODO (T223234714): Fix memory leak and enable this test. + it.skip('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); + }; + }; + + function Observe({children}: $ReadOnly<{children?: React.Node}>) { + return {children}; + } + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect(getReferenceCount()).toBeGreaterThan(0); + + Fantom.runTask(() => { + root.render(); + }); + + // TODO (T223254666): Delete this and figure out why test fails. + Fantom.runTask(() => { + root.render(); + }); + + expect(getReferenceCount()).toBe(0); + }); + describe('rootThreshold', () => { it('should report partial intersecting initial state correctly', () => { const nodeRef = React.createRef();