diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm index 9d7b67f07d4..8b5b3d1b22a 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm @@ -14,6 +14,7 @@ #import #import #import +#import #import #import @@ -97,8 +98,18 @@ using namespace facebook::react; - (void)_setStateAndResubscribeImageResponseObserver:(ImageShadowNode::ConcreteState::Shared const &)state { if (_state) { - auto &observerCoordinator = _state->getData().getImageRequest().getObserverCoordinator(); + auto const &imageRequest = _state->getData().getImageRequest(); + auto &observerCoordinator = imageRequest.getObserverCoordinator(); observerCoordinator.removeObserver(_imageResponseObserverProxy); + if (CoreFeatures::cancelImageDownloadsOnRecycle) { + // Cancelling image request because we are no longer observing it. + // This is not 100% correct place to do this because we may want to + // re-create RCTImageComponentView with the same image and if it + // was cancelled before downloaded, download is not resumed. + // This will only become issue if we decouple life cycle of a + // ShadowNode from ComponentView, which is not something we do now. + imageRequest.cancel(); + } } _state = state; diff --git a/packages/react-native/React/Fabric/RCTSurfacePresenter.mm b/packages/react-native/React/Fabric/RCTSurfacePresenter.mm index d52cbdfe763..d86ce029407 100644 --- a/packages/react-native/React/Fabric/RCTSurfacePresenter.mm +++ b/packages/react-native/React/Fabric/RCTSurfacePresenter.mm @@ -285,6 +285,10 @@ static BackgroundExecutor RCTGetBackgroundExecutor() CoreFeatures::cacheNSTextStorage = true; } + if (reactNativeConfig && reactNativeConfig->getBool("react_fabric:cancel_image_downloads_on_recycle")) { + CoreFeatures::cancelImageDownloadsOnRecycle = true; + } + auto componentRegistryFactory = [factory = wrapManagedObject(_mountingManager.componentViewRegistry.componentViewFactory)]( EventDispatcher::Weak const &eventDispatcher, ContextContainer::Shared const &contextContainer) { diff --git a/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.cpp b/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.cpp index f095e117ec8..5f0abf2f4fc 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.cpp @@ -15,5 +15,6 @@ bool CoreFeatures::blockPaintForUseLayoutEffect = false; bool CoreFeatures::useNativeState = false; bool CoreFeatures::cacheNSTextStorage = false; bool CoreFeatures::cacheLastTextMeasurement = false; +bool CoreFeatures::cancelImageDownloadsOnRecycle = false; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.h b/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.h index 69b7f87d3b5..36d31cd18ef 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.h +++ b/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.h @@ -43,6 +43,10 @@ class CoreFeatures { // This flag enables a caching mechanism to avoid subsequents measurements // of the same Text with the same constrainst. static bool cacheLastTextMeasurement; + + // Fabric was not cancelling image downloads when was removed + // from view hierarchy. This feature flag enables this feature. + static bool cancelImageDownloadsOnRecycle; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageRequest.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageRequest.h index 5da3e018aac..78c1b4c8112 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageRequest.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageRequest.h @@ -46,6 +46,12 @@ class ImageRequest final { */ void setCancelationFunction(std::function cancelationFunction); + /* + * Calls cancel function if one is defined. Should be when downloading + * image isn't needed anymore. E.g. was removed. + */ + void cancel() const; + /* * Returns the Image Source associated with the request. */ diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageRequest.cpp b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageRequest.cpp index ed60cdeb544..cca7918e2d6 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageRequest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageRequest.cpp @@ -21,6 +21,12 @@ void ImageRequest::setCancelationFunction( cancelRequest_ = cancelationFunction; } +void ImageRequest::cancel() const { + if (cancelRequest_) { + cancelRequest_(); + } +} + const ImageSource &ImageRequest::getImageSource() const { return imageSource_; }