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 bda8adb91a6..ae056768664 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.cpp @@ -16,6 +16,7 @@ bool CoreFeatures::blockPaintForUseLayoutEffect = false; bool CoreFeatures::useNativeState = false; bool CoreFeatures::cacheNSTextStorage = false; bool CoreFeatures::cacheLastTextMeasurement = false; +bool CoreFeatures::cancelImageDownloadsOnRecycle = false; } // namespace react } // namespace facebook diff --git a/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.h b/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.h index 45defe2afd1..65b10a96abb 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.h +++ b/packages/react-native/ReactCommon/react/renderer/core/CoreFeatures.h @@ -44,6 +44,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 react diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageRequest.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageRequest.h index d3dbee8bdeb..ff6d4d5bef2 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageRequest.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageRequest.h @@ -47,6 +47,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 01e65d6353c..ec42014e39e 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 @@ -22,6 +22,12 @@ void ImageRequest::setCancelationFunction( cancelRequest_ = cancelationFunction; } +void ImageRequest::cancel() const { + if (cancelRequest_) { + cancelRequest_(); + } +} + const ImageSource &ImageRequest::getImageSource() const { return imageSource_; }