diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 25650af04b4fa2..2663eb951cb353 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -3392,16 +3392,19 @@ public class com/facebook/react/modules/dialog/DialogModule : com/facebook/fbrea } public class com/facebook/react/modules/fresco/FrescoModule : com/facebook/react/bridge/ReactContextBaseJavaModule, com/facebook/react/bridge/LifecycleEventListener, com/facebook/react/modules/common/ModuleDataCleaner$Cleanable, com/facebook/react/turbomodule/core/interfaces/TurboModule { - public static final field NAME Ljava/lang/String; + public static final field Companion Lcom/facebook/react/modules/fresco/FrescoModule$Companion; public fun (Lcom/facebook/react/bridge/ReactApplicationContext;)V + public fun (Lcom/facebook/react/bridge/ReactApplicationContext;Lcom/facebook/imagepipeline/core/ImagePipeline;)V public fun (Lcom/facebook/react/bridge/ReactApplicationContext;Lcom/facebook/imagepipeline/core/ImagePipeline;Z)V public fun (Lcom/facebook/react/bridge/ReactApplicationContext;Lcom/facebook/imagepipeline/core/ImagePipeline;ZZ)V + public synthetic fun (Lcom/facebook/react/bridge/ReactApplicationContext;Lcom/facebook/imagepipeline/core/ImagePipeline;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lcom/facebook/react/bridge/ReactApplicationContext;Z)V public fun (Lcom/facebook/react/bridge/ReactApplicationContext;ZLcom/facebook/imagepipeline/core/ImagePipelineConfig;)V + public synthetic fun (Lcom/facebook/react/bridge/ReactApplicationContext;ZLcom/facebook/imagepipeline/core/ImagePipelineConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun clearSensitiveData ()V - public static fun getDefaultConfigBuilder (Lcom/facebook/react/bridge/ReactContext;)Lcom/facebook/imagepipeline/core/ImagePipelineConfig$Builder; + public static final fun getDefaultConfigBuilder (Lcom/facebook/react/bridge/ReactContext;)Lcom/facebook/imagepipeline/core/ImagePipelineConfig$Builder; public fun getName ()Ljava/lang/String; - public static fun hasBeenInitialized ()Z + public static final fun hasBeenInitialized ()Z public fun initialize ()V public fun invalidate ()V public fun onHostDestroy ()V @@ -3409,13 +3412,22 @@ public class com/facebook/react/modules/fresco/FrescoModule : com/facebook/react public fun onHostResume ()V } -public class com/facebook/react/modules/fresco/ReactNetworkImageRequest : com/facebook/imagepipeline/request/ImageRequest { - protected fun (Lcom/facebook/imagepipeline/request/ImageRequestBuilder;Lcom/facebook/react/bridge/ReadableMap;)V - public static fun fromBuilderWithHeaders (Lcom/facebook/imagepipeline/request/ImageRequestBuilder;Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/modules/fresco/ReactNetworkImageRequest; - public fun getHeaders ()Lcom/facebook/react/bridge/ReadableMap; +public final class com/facebook/react/modules/fresco/FrescoModule$Companion { + public final fun getDefaultConfigBuilder (Lcom/facebook/react/bridge/ReactContext;)Lcom/facebook/imagepipeline/core/ImagePipelineConfig$Builder; + public final fun hasBeenInitialized ()Z +} + +public final class com/facebook/react/modules/fresco/ReactNetworkImageRequest : com/facebook/imagepipeline/request/ImageRequest { + public static final field Companion Lcom/facebook/react/modules/fresco/ReactNetworkImageRequest$Companion; + public synthetic fun (Lcom/facebook/imagepipeline/request/ImageRequestBuilder;Lcom/facebook/react/bridge/ReadableMap;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public static final fun fromBuilderWithHeaders (Lcom/facebook/imagepipeline/request/ImageRequestBuilder;Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/modules/fresco/ReactNetworkImageRequest; +} + +public final class com/facebook/react/modules/fresco/ReactNetworkImageRequest$Companion { + public final fun fromBuilderWithHeaders (Lcom/facebook/imagepipeline/request/ImageRequestBuilder;Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/modules/fresco/ReactNetworkImageRequest; } -public class com/facebook/react/modules/fresco/SystraceRequestListener : com/facebook/imagepipeline/listener/BaseRequestListener { +public final class com/facebook/react/modules/fresco/SystraceRequestListener : com/facebook/imagepipeline/listener/BaseRequestListener { public fun ()V public fun onProducerEvent (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public fun onProducerFinishWithCancellation (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V @@ -3558,6 +3570,12 @@ public class com/facebook/react/modules/network/OkHttpClientProvider { public static fun setOkHttpClientFactory (Lcom/facebook/react/modules/network/OkHttpClientFactory;)V } +public class com/facebook/react/modules/network/OkHttpCompat { + public fun ()V + public static fun getCookieJarContainer (Lokhttp3/OkHttpClient;)Lcom/facebook/react/modules/network/CookieJarContainer; + public static fun getHeadersFromMap (Ljava/util/Map;)Lokhttp3/Headers; +} + public abstract interface class com/facebook/react/modules/network/ProgressListener { public abstract fun onProgress (JJZ)V } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java deleted file mode 100644 index 3ac33b5d0e58bf..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * 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. - */ - -package com.facebook.react.modules.fresco; - -import androidx.annotation.Nullable; -import com.facebook.common.logging.FLog; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory; -import com.facebook.imagepipeline.core.ImagePipeline; -import com.facebook.imagepipeline.core.ImagePipelineConfig; -import com.facebook.imagepipeline.listener.RequestListener; -import com.facebook.react.bridge.LifecycleEventListener; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.common.ReactConstants; -import com.facebook.react.module.annotations.ReactModule; -import com.facebook.react.modules.common.ModuleDataCleaner; -import com.facebook.react.modules.network.CookieJarContainer; -import com.facebook.react.modules.network.ForwardingCookieHandler; -import com.facebook.react.modules.network.OkHttpClientProvider; -import com.facebook.react.turbomodule.core.interfaces.TurboModule; -import java.util.HashSet; -import okhttp3.JavaNetCookieJar; -import okhttp3.OkHttpClient; - -/** - * Module to initialize the Fresco library. - * - *

Does not expose any methods to JavaScript code. For initialization and cleanup only. - */ -@ReactModule(name = FrescoModule.NAME, needsEagerInit = true) -public class FrescoModule extends ReactContextBaseJavaModule - implements ModuleDataCleaner.Cleanable, LifecycleEventListener, TurboModule { - - public static final String NAME = "FrescoModule"; - private final boolean mClearOnDestroy; - private @Nullable ImagePipelineConfig mConfig; - private @Nullable ImagePipeline mImagePipeline; - - private static boolean sHasBeenInitialized = false; - - /** - * Create a new Fresco module with a default configuration (or the previously given configuration - * via {@link #FrescoModule(ReactApplicationContext, boolean, ImagePipelineConfig)}. - * - * @param reactContext the context to use - */ - public FrescoModule(ReactApplicationContext reactContext) { - this(reactContext, true, null); - } - - /** - * Create a new Fresco module with a default configuration (or the previously given configuration - * via {@link #FrescoModule(ReactApplicationContext, boolean, ImagePipelineConfig)}. - * - * @param clearOnDestroy whether to clear the memory cache in onHostDestroy: this should be {@code - * true} for pure RN apps and {@code false} for apps that use Fresco outside of RN as well - * @param reactContext the context to use - */ - public FrescoModule(ReactApplicationContext reactContext, boolean clearOnDestroy) { - this(reactContext, clearOnDestroy, null); - } - - /** - * Create a new Fresco module with a default configuration (or the previously given configuration - * via {@link #FrescoModule(ReactApplicationContext, boolean, ImagePipelineConfig)}. - * - * @param clearOnDestroy whether to clear the memory cache in onHostDestroy: this should be {@code - * true} for pure RN apps and {@code false} for apps that use Fresco outside of RN as well - * @param reactContext the context to use - */ - public FrescoModule( - ReactApplicationContext reactContext, - @Nullable ImagePipeline imagePipeline, - boolean clearOnDestroy) { - this(reactContext, imagePipeline, clearOnDestroy, false); - } - - /** - * Create a new Fresco module with a default configuration (or the previously given configuration - * via {@link #FrescoModule(ReactApplicationContext, boolean, ImagePipelineConfig)}. - * - * @param clearOnDestroy whether to clear the memory cache in onHostDestroy: this should be {@code - * true} for pure RN apps and {@code false} for apps that use Fresco outside of RN as well - * @param reactContext the context to use - * @param hasBeenInitializedExternally whether Fresco has already been initialized - */ - public FrescoModule( - ReactApplicationContext reactContext, - @Nullable ImagePipeline imagePipeline, - boolean clearOnDestroy, - boolean hasBeenInitializedExternally) { - this(reactContext, clearOnDestroy); - mImagePipeline = imagePipeline; - if (hasBeenInitializedExternally) { - sHasBeenInitialized = true; - } - } - - /** - * Create a new Fresco module with a given ImagePipelineConfig. This should only be called when - * the module has not been initialized yet. You can use {@link #hasBeenInitialized()} to check - * this and call {@link #FrescoModule(ReactApplicationContext)} if it is already initialized. - * Otherwise, the given Fresco configuration will be ignored. - * - * @param reactContext the context to use - * @param clearOnDestroy whether to clear the memory cache in onHostDestroy: this should be {@code - * true} for pure RN apps and {@code false} for apps that use Fresco outside of RN as well - * @param config the Fresco configuration, which will only be used for the first initialization - */ - public FrescoModule( - ReactApplicationContext reactContext, - boolean clearOnDestroy, - @Nullable ImagePipelineConfig config) { - super(reactContext); - mClearOnDestroy = clearOnDestroy; - mConfig = config; - } - - @Override - public void initialize() { - super.initialize(); - - ReactApplicationContext reactContext = getReactApplicationContext(); - reactContext.addLifecycleEventListener(this); - if (!hasBeenInitialized()) { - if (mConfig == null) { - mConfig = getDefaultConfig(reactContext); - } - Fresco.initialize(reactContext.getApplicationContext(), mConfig); - sHasBeenInitialized = true; - } else if (mConfig != null) { - FLog.w( - ReactConstants.TAG, - "Fresco has already been initialized with a different config. " - + "The new Fresco configuration will be ignored!"); - } - mConfig = null; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public void clearSensitiveData() { - // Clear image cache. - getImagePipeline().clearCaches(); - } - - /** - * Check whether the FrescoModule has already been initialized. If this is the case, Calls to - * {@link #FrescoModule(ReactApplicationContext, ImagePipelineConfig)} will ignore the given - * configuration. - * - * @return true if this module has already been initialized - */ - public static boolean hasBeenInitialized() { - return sHasBeenInitialized; - } - - private static ImagePipelineConfig getDefaultConfig(ReactContext context) { - return getDefaultConfigBuilder(context).build(); - } - - /** - * Get the default Fresco configuration builder. Allows adding of configuration options in - * addition to the default values. - * - * @return {@link ImagePipelineConfig.Builder} that has been initialized with default values - */ - public static ImagePipelineConfig.Builder getDefaultConfigBuilder(ReactContext context) { - HashSet requestListeners = new HashSet<>(); - requestListeners.add(new SystraceRequestListener()); - - OkHttpClient client = OkHttpClientProvider.createClient(); - - // make sure to forward cookies for any requests via the okHttpClient - // so that image requests to endpoints that use cookies still work - CookieJarContainer container = (CookieJarContainer) client.cookieJar(); - ForwardingCookieHandler handler = new ForwardingCookieHandler(context); - container.setCookieJar(new JavaNetCookieJar(handler)); - - return OkHttpImagePipelineConfigFactory.newBuilder(context.getApplicationContext(), client) - .setNetworkFetcher(new ReactOkHttpNetworkFetcher(client)) - .setDownsampleEnabled(false) - .setRequestListeners(requestListeners); - } - - @Override - public void onHostResume() {} - - @Override - public void onHostPause() {} - - @Override - public void onHostDestroy() { - // According to the javadoc for LifecycleEventListener#onHostDestroy, this is only called when - // the 'last' ReactActivity is being destroyed, which effectively means the app is being - // backgrounded. - if (hasBeenInitialized() && mClearOnDestroy) { - getImagePipeline().clearMemoryCaches(); - } - } - - private ImagePipeline getImagePipeline() { - if (mImagePipeline == null) { - mImagePipeline = Fresco.getImagePipeline(); - } - return mImagePipeline; - } - - @Override - public void invalidate() { - getReactApplicationContext().removeLifecycleEventListener(this); - super.invalidate(); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.kt new file mode 100644 index 00000000000000..43d08b45231a9a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.kt @@ -0,0 +1,163 @@ +/* + * 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. + */ + +package com.facebook.react.modules.fresco + +import com.facebook.common.logging.FLog +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory.newBuilder +import com.facebook.imagepipeline.core.DownsampleMode +import com.facebook.imagepipeline.core.ImagePipeline +import com.facebook.imagepipeline.core.ImagePipelineConfig +import com.facebook.imagepipeline.listener.RequestListener +import com.facebook.react.bridge.LifecycleEventListener +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.common.ReactConstants +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.modules.common.ModuleDataCleaner +import com.facebook.react.modules.network.ForwardingCookieHandler +import com.facebook.react.modules.network.OkHttpClientProvider +import com.facebook.react.modules.network.OkHttpCompat +import com.facebook.react.turbomodule.core.interfaces.TurboModule +import okhttp3.JavaNetCookieJar + +/** + * Module to initialize the Fresco library. + * + * Does not expose any methods to JavaScript code. For initialization and cleanup only. + */ +@ReactModule(name = FrescoModule.NAME, needsEagerInit = true) +public open class FrescoModule +@JvmOverloads +constructor( + reactContext: ReactApplicationContext?, + private val clearOnDestroy: Boolean = true, + imagePipelineConfig: ImagePipelineConfig? = null +) : + ReactContextBaseJavaModule(reactContext), + ModuleDataCleaner.Cleanable, + LifecycleEventListener, + TurboModule { + private var config: ImagePipelineConfig? = imagePipelineConfig + private var pipeline: ImagePipeline? = null + + /** + * Create a new Fresco module with a default configuration (or the previously given configuration + * via [.FrescoModule]. + * + * @param reactContext the context to use + * @param imagePipeline the Fresco image pipeline to use + * @param clearOnDestroy whether to clear the memory cache in onHostDestroy: this should be `true` + * for pure RN apps and `false` for apps that use Fresco outside of RN as well + * @param hasBeenInitializedExternally whether Fresco has already been initialized + */ + @JvmOverloads + public constructor( + reactContext: ReactApplicationContext?, + imagePipeline: ImagePipeline?, + clearOnDestroy: Boolean = true, + hasBeenInitializedExternally: Boolean = false + ) : this(reactContext, clearOnDestroy) { + pipeline = imagePipeline + if (hasBeenInitializedExternally) { + hasBeenInitialized = true + } + } + + override fun initialize() { + super.initialize() + val reactContext = reactApplicationContext + reactContext.addLifecycleEventListener(this) + if (!hasBeenInitialized()) { + if (config == null) { + config = getDefaultConfig(reactContext) + } + Fresco.initialize(reactContext.applicationContext, config) + hasBeenInitialized = true + } else if (config != null) { + FLog.w( + ReactConstants.TAG, + "Fresco has already been initialized with a different config. " + + "The new Fresco configuration will be ignored!") + } + config = null + } + + override fun getName(): String = NAME + + override fun clearSensitiveData() { + // Clear image cache. + imagePipeline?.clearCaches() + } + + override fun onHostResume(): Unit = Unit + + override fun onHostPause(): Unit = Unit + + override fun onHostDestroy() { + // According to the javadoc for LifecycleEventListener#onHostDestroy, this is only called when + // the 'last' ReactActivity is being destroyed, which effectively means the app is being + // backgrounded. + if (hasBeenInitialized() && clearOnDestroy) { + imagePipeline!!.clearMemoryCaches() + } + } + + private val imagePipeline: ImagePipeline? + get() { + if (pipeline == null) { + pipeline = Fresco.getImagePipeline() + } + return pipeline + } + + override fun invalidate() { + reactApplicationContext.removeLifecycleEventListener(this) + super.invalidate() + } + + public companion object { + internal const val NAME = "FrescoModule" + private var hasBeenInitialized = false + + /** + * Check whether the FrescoModule has already been initialized. If this is the case, Calls to + * [.FrescoModule] will ignore the given configuration. + * + * @return true if this module has already been initialized + */ + @JvmStatic public fun hasBeenInitialized(): Boolean = hasBeenInitialized + + private fun getDefaultConfig(context: ReactContext): ImagePipelineConfig = + getDefaultConfigBuilder(context).build() + + /** + * Get the default Fresco configuration builder. Allows adding of configuration options in + * addition to the default values. + * + * @return [ImagePipelineConfig.Builder] that has been initialized with default values + */ + @JvmStatic + public fun getDefaultConfigBuilder(context: ReactContext): ImagePipelineConfig.Builder { + val requestListeners = HashSet() + requestListeners.add(SystraceRequestListener()) + val client = OkHttpClientProvider.createClient() + + // make sure to forward cookies for any requests via the okHttpClient + // so that image requests to endpoints that use cookies still work + val container = OkHttpCompat.getCookieJarContainer(client) + val handler = ForwardingCookieHandler(context) + container.setCookieJar(JavaNetCookieJar(handler)) + return newBuilder(context.applicationContext, client) + .setNetworkFetcher(ReactOkHttpNetworkFetcher(client)) + .setDownsampleMode(DownsampleMode.AUTO) + .setRequestListeners(requestListeners) + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactNetworkImageRequest.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactNetworkImageRequest.java deleted file mode 100644 index 997d0165e24eb5..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactNetworkImageRequest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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. - */ - -package com.facebook.react.modules.fresco; - -import androidx.annotation.Nullable; -import com.facebook.imagepipeline.request.ImageRequest; -import com.facebook.imagepipeline.request.ImageRequestBuilder; -import com.facebook.react.bridge.ReadableMap; - -/** Extended ImageRequest with request headers */ -public class ReactNetworkImageRequest extends ImageRequest { - - /** Headers for the request */ - @Nullable private final ReadableMap mHeaders; - - public static ReactNetworkImageRequest fromBuilderWithHeaders( - ImageRequestBuilder builder, @Nullable ReadableMap headers) { - return new ReactNetworkImageRequest(builder, headers); - } - - protected ReactNetworkImageRequest(ImageRequestBuilder builder, @Nullable ReadableMap headers) { - super(builder); - mHeaders = headers; - } - - @Nullable - public ReadableMap getHeaders() { - return mHeaders; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactNetworkImageRequest.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactNetworkImageRequest.kt new file mode 100644 index 00000000000000..44d2b0436855ab --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactNetworkImageRequest.kt @@ -0,0 +1,31 @@ +/* + * 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. + */ + +package com.facebook.react.modules.fresco + +import com.facebook.imagepipeline.request.ImageRequest +import com.facebook.imagepipeline.request.ImageRequestBuilder +import com.facebook.react.bridge.ReadableMap + +/** Extended ImageRequest with request headers */ +public class ReactNetworkImageRequest +private constructor( + builder: ImageRequestBuilder, + /** Headers for the request */ + internal val headers: ReadableMap? +) : ImageRequest(builder) { + + public companion object { + @JvmStatic + public fun fromBuilderWithHeaders( + builder: ImageRequestBuilder, + headers: ReadableMap? + ): ReactNetworkImageRequest { + return ReactNetworkImageRequest(builder, headers) + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.java deleted file mode 100644 index 557c6ca9ec30f3..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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. - */ - -package com.facebook.react.modules.fresco; - -import android.net.Uri; -import android.os.SystemClock; -import com.facebook.imagepipeline.backends.okhttp3.OkHttpNetworkFetcher; -import com.facebook.imagepipeline.producers.NetworkFetcher; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableMapKeySetIterator; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Executor; -import okhttp3.CacheControl; -import okhttp3.Headers; -import okhttp3.OkHttpClient; -import okhttp3.Request; - -class ReactOkHttpNetworkFetcher extends OkHttpNetworkFetcher { - - private static final String TAG = "ReactOkHttpNetworkFetcher"; - - private final OkHttpClient mOkHttpClient; - private final Executor mCancellationExecutor; - - /** - * @param okHttpClient client to use - */ - public ReactOkHttpNetworkFetcher(OkHttpClient okHttpClient) { - super(okHttpClient); - mOkHttpClient = okHttpClient; - mCancellationExecutor = okHttpClient.dispatcher().executorService(); - } - - private Map getHeaders(ReadableMap readableMap) { - if (readableMap == null) { - return null; - } - ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); - Map map = new HashMap<>(); - while (iterator.hasNextKey()) { - String key = iterator.nextKey(); - String value = readableMap.getString(key); - map.put(key, value); - } - return map; - } - - @Override - public void fetch( - final OkHttpNetworkFetcher.OkHttpNetworkFetchState fetchState, - final NetworkFetcher.Callback callback) { - fetchState.submitTime = SystemClock.elapsedRealtime(); - final Uri uri = fetchState.getUri(); - Map requestHeaders = null; - if (fetchState.getContext().getImageRequest() instanceof ReactNetworkImageRequest) { - ReactNetworkImageRequest networkImageRequest = - (ReactNetworkImageRequest) fetchState.getContext().getImageRequest(); - requestHeaders = getHeaders(networkImageRequest.getHeaders()); - } - if (requestHeaders == null) { - requestHeaders = Collections.emptyMap(); - } - final Request request = - new Request.Builder() - .cacheControl(new CacheControl.Builder().noStore().build()) - .url(uri.toString()) - .headers(Headers.of(requestHeaders)) - .get() - .build(); - - fetchWithRequest(fetchState, callback, request); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.kt new file mode 100644 index 00000000000000..54f387e814d080 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.kt @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package com.facebook.react.modules.fresco + +import android.os.SystemClock +import com.facebook.imagepipeline.backends.okhttp3.OkHttpNetworkFetcher +import com.facebook.imagepipeline.producers.NetworkFetcher +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.modules.network.OkHttpCompat +import okhttp3.CacheControl +import okhttp3.OkHttpClient +import okhttp3.Request + +internal class ReactOkHttpNetworkFetcher(private val okHttpClient: OkHttpClient) : + OkHttpNetworkFetcher(okHttpClient) { + private fun getHeaders(readableMap: ReadableMap?): Map? { + if (readableMap == null) { + return null + } + val iterator = readableMap.keySetIterator() + val map: MutableMap = HashMap() + while (iterator.hasNextKey()) { + val key = iterator.nextKey() + readableMap.getString(key)?.let { map[key] = it } + } + return map + } + + override fun fetch(fetchState: OkHttpNetworkFetchState, callback: NetworkFetcher.Callback) { + fetchState.submitTime = SystemClock.elapsedRealtime() + val uri = fetchState.uri + var requestHeaders: Map? = null + if (fetchState.context.imageRequest is ReactNetworkImageRequest) { + val networkImageRequest = fetchState.context.imageRequest as ReactNetworkImageRequest + requestHeaders = getHeaders(networkImageRequest.headers) + } + val headers = OkHttpCompat.getHeadersFromMap(requestHeaders) + val request = + Request.Builder() + .cacheControl(CacheControl.Builder().noStore().build()) + .url(uri.toString()) + .headers(headers) + .get() + .build() + fetchWithRequest(fetchState, callback, request) + } + + private companion object { + private const val TAG = "ReactOkHttpNetworkFetcher" + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/SystraceRequestListener.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/SystraceRequestListener.java deleted file mode 100644 index 12e5b5c3d86dfb..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/SystraceRequestListener.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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. - */ - -package com.facebook.react.modules.fresco; - -import android.util.Pair; -import com.facebook.imagepipeline.listener.BaseRequestListener; -import com.facebook.imagepipeline.request.ImageRequest; -import com.facebook.systrace.Systrace; -import java.util.HashMap; -import java.util.Map; - -/** Logs requests to Systrace */ -public class SystraceRequestListener extends BaseRequestListener { - - int mCurrentID = 0; - Map> mProducerID = new HashMap<>(); - Map> mRequestsID = new HashMap<>(); - - @Override - public void onProducerStart(String requestId, String producerName) { - if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { - return; - } - - StringBuilder entryName = new StringBuilder(); - entryName.append("FRESCO_PRODUCER_"); - entryName.append(producerName.replace(':', '_')); - - Pair requestPair = Pair.create(mCurrentID, entryName.toString()); - Systrace.beginAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, requestPair.second, mCurrentID); - mProducerID.put(requestId, requestPair); - mCurrentID++; - } - - @Override - public void onProducerFinishWithSuccess( - String requestId, String producerName, Map extraMap) { - if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { - return; - } - - if (mProducerID.containsKey(requestId)) { - Pair entry = mProducerID.get(requestId); - Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first); - mProducerID.remove(requestId); - } - } - - @Override - public void onProducerFinishWithFailure( - String requestId, String producerName, Throwable throwable, Map extraMap) { - if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { - return; - } - - if (mProducerID.containsKey(requestId)) { - Pair entry = mProducerID.get(requestId); - Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first); - mProducerID.remove(requestId); - } - } - - @Override - public void onProducerFinishWithCancellation( - String requestId, String producerName, Map extraMap) { - if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { - return; - } - - if (mProducerID.containsKey(requestId)) { - Pair entry = mProducerID.get(requestId); - Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first); - mProducerID.remove(requestId); - } - } - - @Override - public void onProducerEvent(String requestId, String producerName, String producerEventName) { - if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { - return; - } - - StringBuilder entryName = new StringBuilder(); - entryName.append("FRESCO_PRODUCER_EVENT_"); - entryName.append(requestId.replace(':', '_')); - entryName.append("_"); - entryName.append(producerName.replace(':', '_')); - entryName.append("_"); - entryName.append(producerEventName.replace(':', '_')); - Systrace.traceInstant( - Systrace.TRACE_TAG_REACT_FRESCO, entryName.toString(), Systrace.EventScope.THREAD); - } - - @Override - public void onRequestStart( - ImageRequest request, Object callerContext, String requestId, boolean isPrefetch) { - if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { - return; - } - - StringBuilder entryName = new StringBuilder(); - entryName.append("FRESCO_REQUEST_"); - entryName.append(request.getSourceUri().toString().replace(':', '_')); - - Pair requestPair = Pair.create(mCurrentID, entryName.toString()); - Systrace.beginAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, requestPair.second, mCurrentID); - mRequestsID.put(requestId, requestPair); - mCurrentID++; - } - - @Override - public void onRequestSuccess(ImageRequest request, String requestId, boolean isPrefetch) { - if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { - return; - } - - if (mRequestsID.containsKey(requestId)) { - Pair entry = mRequestsID.get(requestId); - Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first); - mRequestsID.remove(requestId); - } - } - - @Override - public void onRequestFailure( - ImageRequest request, String requestId, Throwable throwable, boolean isPrefetch) { - if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { - return; - } - - if (mRequestsID.containsKey(requestId)) { - Pair entry = mRequestsID.get(requestId); - Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first); - mRequestsID.remove(requestId); - } - } - - @Override - public void onRequestCancellation(String requestId) { - if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { - return; - } - - if (mRequestsID.containsKey(requestId)) { - Pair entry = mRequestsID.get(requestId); - Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first); - mRequestsID.remove(requestId); - } - } - - @Override - public boolean requiresExtraMap(String id) { - return false; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/SystraceRequestListener.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/SystraceRequestListener.kt new file mode 100644 index 00000000000000..89fb49f9ce468f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/SystraceRequestListener.kt @@ -0,0 +1,152 @@ +/* + * 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. + */ + +package com.facebook.react.modules.fresco + +import android.util.Pair +import com.facebook.imagepipeline.listener.BaseRequestListener +import com.facebook.imagepipeline.request.ImageRequest +import com.facebook.systrace.Systrace + +/** Logs requests to Systrace */ +public class SystraceRequestListener : BaseRequestListener() { + private var currentId: Int = 0 + private var producerId: MutableMap> = mutableMapOf() + private var requestsId: MutableMap> = mutableMapOf() + + override fun onProducerStart(requestId: String, producerName: String) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return + } + val entryName = StringBuilder() + entryName.append("FRESCO_PRODUCER_") + entryName.append(producerName.replace(':', '_')) + val requestPair = Pair.create(currentId, entryName.toString()) + Systrace.beginAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, requestPair.second, currentId) + producerId[requestId] = requestPair + currentId++ + } + + override fun onProducerFinishWithSuccess( + requestId: String, + producerName: String, + extraMap: Map? + ) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return + } + if (producerId.containsKey(requestId)) { + val entry = producerId[requestId]!! + Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first) + producerId.remove(requestId) + } + } + + override fun onProducerFinishWithFailure( + requestId: String, + producerName: String, + t: Throwable, + extraMap: Map? + ) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return + } + if (producerId.containsKey(requestId)) { + val entry = producerId[requestId]!! + Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first) + producerId.remove(requestId) + } + } + + override fun onProducerFinishWithCancellation( + requestId: String, + producerName: String, + extraMap: Map? + ) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return + } + if (producerId.containsKey(requestId)) { + val entry = producerId[requestId]!! + Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first) + producerId.remove(requestId) + } + } + + override fun onProducerEvent(requestId: String, producerName: String, eventName: String) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return + } + val entryName = StringBuilder() + entryName.append("FRESCO_PRODUCER_EVENT_") + entryName.append(requestId.replace(':', '_')) + entryName.append("_") + entryName.append(producerName.replace(':', '_')) + entryName.append("_") + entryName.append(eventName.replace(':', '_')) + Systrace.traceInstant( + Systrace.TRACE_TAG_REACT_FRESCO, entryName.toString(), Systrace.EventScope.THREAD) + } + + override fun onRequestStart( + request: ImageRequest, + callerContext: Any, + requestId: String, + isPrefetch: Boolean + ) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return + } + val entryName = StringBuilder() + entryName.append("FRESCO_REQUEST_") + entryName.append(request.sourceUri.toString().replace(':', '_')) + val requestPair = Pair.create(currentId, entryName.toString()) + Systrace.beginAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, requestPair.second, currentId) + requestsId[requestId] = requestPair + currentId++ + } + + override fun onRequestSuccess(request: ImageRequest, requestId: String, isPrefetch: Boolean) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return + } + if (requestsId.containsKey(requestId)) { + val entry = requestsId[requestId]!! + Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first) + requestsId.remove(requestId) + } + } + + override fun onRequestFailure( + request: ImageRequest, + requestId: String, + throwable: Throwable, + isPrefetch: Boolean + ) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return + } + if (requestsId.containsKey(requestId)) { + val entry = requestsId[requestId]!! + Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first) + requestsId.remove(requestId) + } + } + + override fun onRequestCancellation(requestId: String) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return + } + if (requestsId.containsKey(requestId)) { + val entry = requestsId[requestId]!! + Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_FRESCO, entry.second, entry.first) + requestsId.remove(requestId) + } + } + + override fun requiresExtraMap(requestId: String): Boolean = false +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpCompat.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpCompat.java new file mode 100644 index 00000000000000..42e123efc98b94 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpCompat.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +package com.facebook.react.modules.network; + +import java.util.Collections; +import java.util.Map; +import okhttp3.Headers; +import okhttp3.OkHttpClient; + +/** + * Helper class that provides wrappers for compatibility between different OkHttp versions. + * + *

This is required for Kotlin code compatibility, in particular, therefore if you are going to + * migrate this file to Kotlin, please first ensure that there is no OkHttp API discrepancy between + * different RN platform environments, and then consider getting rid of this compat layer + * altogether. + */ +public class OkHttpCompat { + public static CookieJarContainer getCookieJarContainer(OkHttpClient client) { + return (CookieJarContainer) client.cookieJar(); + } + + public static Headers getHeadersFromMap(Map headers) { + if (headers == null) { + return Headers.of(Collections.emptyMap()); + } else { + return Headers.of(headers); + } + } +}