1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#import <mach/mach_time.h>
9#import <objc/runtime.h>
10#import <atomic>
11
12#import <ImageIO/ImageIO.h>
13
14#import <ABI48_0_0FBReactNativeSpec/ABI48_0_0FBReactNativeSpec.h>
15#import <ABI48_0_0React/ABI48_0_0RCTConvert.h>
16#import <ABI48_0_0React/ABI48_0_0RCTDefines.h>
17#import <ABI48_0_0React/ABI48_0_0RCTImageCache.h>
18#import <ABI48_0_0React/ABI48_0_0RCTImageLoader.h>
19#import <ABI48_0_0React/ABI48_0_0RCTImageLoaderWithAttributionProtocol.h>
20#import <ABI48_0_0React/ABI48_0_0RCTImageUtils.h>
21#import <ABI48_0_0React/ABI48_0_0RCTLog.h>
22#import <ABI48_0_0React/ABI48_0_0RCTNetworking.h>
23#import <ABI48_0_0React/ABI48_0_0RCTUtils.h>
24
25#import "ABI48_0_0RCTImagePlugins.h"
26
27using namespace ABI48_0_0facebook::ABI48_0_0React;
28
29static BOOL imagePerfInstrumentationEnabled = NO;
30
31BOOL ABI48_0_0RCTImageLoadingPerfInstrumentationEnabled(void)
32{
33  return imagePerfInstrumentationEnabled;
34}
35
36void ABI48_0_0RCTEnableImageLoadingPerfInstrumentation(BOOL enabled)
37{
38  imagePerfInstrumentationEnabled = enabled;
39}
40
41static NSInteger ABI48_0_0RCTImageBytesForImage(UIImage *image)
42{
43  NSInteger singleImageBytes = (NSInteger)(image.size.width * image.size.height * image.scale * image.scale * 4);
44  return image.images ? image.images.count * singleImageBytes : singleImageBytes;
45}
46
47static uint64_t monotonicTimeGetCurrentNanoseconds(void)
48{
49  static struct mach_timebase_info tb_info = {0};
50  static dispatch_once_t onceToken;
51  dispatch_once(&onceToken, ^{
52    __unused int ret = mach_timebase_info(&tb_info);
53    assert(0 == ret);
54  });
55
56  return (mach_absolute_time() * tb_info.numer) / tb_info.denom;
57}
58
59static NSError *addResponseHeadersToError(NSError *originalError, NSHTTPURLResponse *response)
60{
61  NSMutableDictionary<NSString *, id> *_userInfo =
62      (NSMutableDictionary<NSString *, id> *)originalError.userInfo.mutableCopy;
63  _userInfo[@"httpStatusCode"] = [NSNumber numberWithInt:response.statusCode];
64  _userInfo[@"httpResponseHeaders"] = response.allHeaderFields;
65  NSError *error = [NSError errorWithDomain:originalError.domain code:originalError.code userInfo:_userInfo];
66
67  return error;
68}
69
70@interface ABI48_0_0RCTImageLoader () <ABI48_0_0NativeImageLoaderIOSSpec, ABI48_0_0RCTImageLoaderWithAttributionProtocol>
71
72@end
73
74@implementation UIImage (ABI48_0_0React)
75
76- (NSInteger)ABI48_0_0ReactDecodedImageBytes
77{
78  NSNumber *imageBytes = objc_getAssociatedObject(self, _cmd);
79  if (!imageBytes) {
80    imageBytes = @(ABI48_0_0RCTImageBytesForImage(self));
81  }
82  return [imageBytes integerValue];
83}
84
85- (void)setABI48_0_0ReactDecodedImageBytes:(NSInteger)bytes
86{
87  objc_setAssociatedObject(self, @selector(ABI48_0_0ReactDecodedImageBytes), @(bytes), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
88}
89
90@end
91
92@implementation ABI48_0_0RCTImageLoader {
93  NSArray<id<ABI48_0_0RCTImageURLLoader>> * (^_loadersProvider)(ABI48_0_0RCTModuleRegistry *);
94  NSArray<id<ABI48_0_0RCTImageDataDecoder>> * (^_decodersProvider)(ABI48_0_0RCTModuleRegistry *);
95  NSArray<id<ABI48_0_0RCTImageURLLoader>> *_loaders;
96  NSArray<id<ABI48_0_0RCTImageDataDecoder>> *_decoders;
97  NSOperationQueue *_imageDecodeQueue;
98  dispatch_queue_t _URLRequestQueue;
99  id<ABI48_0_0RCTImageCache> _imageCache;
100  NSMutableArray *_pendingTasks;
101  NSInteger _activeTasks;
102  NSMutableArray *_pendingDecodes;
103  NSInteger _scheduledDecodes;
104  NSUInteger _activeBytes;
105  std::mutex _loadersMutex;
106  __weak id<ABI48_0_0RCTImageRedirectProtocol> _redirectDelegate;
107}
108
109@synthesize bridge = _bridge;
110@synthesize moduleRegistry = _moduleRegistry;
111@synthesize maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks;
112@synthesize maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks;
113@synthesize maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes;
114
115ABI48_0_0RCT_EXPORT_MODULE()
116
117- (instancetype)init
118{
119  return [self initWithRedirectDelegate:nil];
120}
121
122+ (BOOL)requiresMainQueueSetup
123{
124  return NO;
125}
126
127- (instancetype)initWithRedirectDelegate:(id<ABI48_0_0RCTImageRedirectProtocol>)redirectDelegate
128{
129  if (self = [super init]) {
130    _redirectDelegate = redirectDelegate;
131  }
132  return self;
133}
134
135- (instancetype)initWithRedirectDelegate:(id<ABI48_0_0RCTImageRedirectProtocol>)redirectDelegate
136                         loadersProvider:(NSArray<id<ABI48_0_0RCTImageURLLoader>> * (^)(ABI48_0_0RCTModuleRegistry *))getLoaders
137                        decodersProvider:(NSArray<id<ABI48_0_0RCTImageDataDecoder>> * (^)(ABI48_0_0RCTModuleRegistry *))getHandlers
138{
139  if (self = [self initWithRedirectDelegate:redirectDelegate]) {
140    _loadersProvider = getLoaders;
141    _decodersProvider = getHandlers;
142  }
143  return self;
144}
145
146- (void)setUp
147{
148  // Set defaults
149  _maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks ?: 4;
150  _maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2;
151  _maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 * 1024; // 30MB
152
153  _URLRequestQueue = dispatch_queue_create("com.facebook.ABI48_0_0React.ImageLoaderURLRequestQueue", DISPATCH_QUEUE_SERIAL);
154}
155
156- (float)handlerPriority
157{
158  return 2;
159}
160#pragma mark - ABI48_0_0RCTImageLoaderProtocol 1/3
161
162- (id<ABI48_0_0RCTImageCache>)imageCache
163{
164  if (!_imageCache) {
165    // set up with default cache
166    _imageCache = [ABI48_0_0RCTImageCache new];
167  }
168  return _imageCache;
169}
170
171- (void)setImageCache:(id<ABI48_0_0RCTImageCache>)cache
172{
173  if (_imageCache) {
174    ABI48_0_0RCTLogWarn(@"ABI48_0_0RCTImageCache was already set and has now been overridden.");
175  }
176  _imageCache = cache;
177}
178
179- (id<ABI48_0_0RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
180{
181  if (!_maxConcurrentLoadingTasks) {
182    [self setUp];
183  }
184
185  if (!_loaders) {
186    std::unique_lock<std::mutex> guard(_loadersMutex);
187    if (!_loaders) {
188      // Get loaders, sorted in reverse priority order (highest priority first)
189      if (_loadersProvider) {
190        _loaders = _loadersProvider(self.moduleRegistry);
191      } else {
192        ABI48_0_0RCTAssert(_bridge, @"Trying to find ABI48_0_0RCTImageURLLoaders and bridge not set.");
193        _loaders = [_bridge modulesConformingToProtocol:@protocol(ABI48_0_0RCTImageURLLoader)];
194      }
195
196      _loaders =
197          [_loaders sortedArrayUsingComparator:^NSComparisonResult(id<ABI48_0_0RCTImageURLLoader> a, id<ABI48_0_0RCTImageURLLoader> b) {
198            float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
199            float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
200            if (priorityA > priorityB) {
201              return NSOrderedAscending;
202            } else if (priorityA < priorityB) {
203              return NSOrderedDescending;
204            } else {
205              return NSOrderedSame;
206            }
207          }];
208    }
209  }
210
211  if (ABI48_0_0RCT_DEBUG) {
212    // Check for handler conflicts
213    float previousPriority = 0;
214    id<ABI48_0_0RCTImageURLLoader> previousLoader = nil;
215    for (id<ABI48_0_0RCTImageURLLoader> loader in _loaders) {
216      float priority = [loader respondsToSelector:@selector(loaderPriority)] ? [loader loaderPriority] : 0;
217      if (previousLoader && priority < previousPriority) {
218        return previousLoader;
219      }
220      if ([loader canLoadImageURL:URL]) {
221        if (previousLoader) {
222          if (priority == previousPriority) {
223            ABI48_0_0RCTLogError(
224                @"The ABI48_0_0RCTImageURLLoaders %@ and %@ both reported that"
225                 " they can load the URL %@, and have equal priority"
226                 " (%g). This could result in non-deterministic behavior.",
227                loader,
228                previousLoader,
229                URL,
230                priority);
231          }
232        } else {
233          previousLoader = loader;
234          previousPriority = priority;
235        }
236      }
237    }
238    return previousLoader;
239  }
240
241  // Normal code path
242  for (id<ABI48_0_0RCTImageURLLoader> loader in _loaders) {
243    if ([loader canLoadImageURL:URL]) {
244      return loader;
245    }
246  }
247  return nil;
248}
249
250#pragma mark - Private Image Decoding & Resizing
251
252- (id<ABI48_0_0RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
253{
254  if (!_maxConcurrentLoadingTasks) {
255    [self setUp];
256  }
257
258  if (!_decoders) {
259    // Get decoders, sorted in reverse priority order (highest priority first)
260
261    if (_decodersProvider) {
262      _decoders = _decodersProvider(self.moduleRegistry);
263    } else {
264      ABI48_0_0RCTAssert(_bridge, @"Trying to find ABI48_0_0RCTImageDataDecoders and bridge not set.");
265      _decoders = [_bridge modulesConformingToProtocol:@protocol(ABI48_0_0RCTImageDataDecoder)];
266    }
267
268    _decoders = [_decoders
269        sortedArrayUsingComparator:^NSComparisonResult(id<ABI48_0_0RCTImageDataDecoder> a, id<ABI48_0_0RCTImageDataDecoder> b) {
270          float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
271          float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
272          if (priorityA > priorityB) {
273            return NSOrderedAscending;
274          } else if (priorityA < priorityB) {
275            return NSOrderedDescending;
276          } else {
277            return NSOrderedSame;
278          }
279        }];
280  }
281
282  if (ABI48_0_0RCT_DEBUG) {
283    // Check for handler conflicts
284    float previousPriority = 0;
285    id<ABI48_0_0RCTImageDataDecoder> previousDecoder = nil;
286    for (id<ABI48_0_0RCTImageDataDecoder> decoder in _decoders) {
287      float priority = [decoder respondsToSelector:@selector(decoderPriority)] ? [decoder decoderPriority] : 0;
288      if (previousDecoder && priority < previousPriority) {
289        return previousDecoder;
290      }
291      if ([decoder canDecodeImageData:data]) {
292        if (previousDecoder) {
293          if (priority == previousPriority) {
294            ABI48_0_0RCTLogError(
295                @"The ABI48_0_0RCTImageDataDecoders %@ and %@ both reported that"
296                 " they can decode the data <NSData %p; %tu bytes>, and"
297                 " have equal priority (%g). This could result in"
298                 " non-deterministic behavior.",
299                decoder,
300                previousDecoder,
301                data,
302                data.length,
303                priority);
304          }
305        } else {
306          previousDecoder = decoder;
307          previousPriority = priority;
308        }
309      }
310    }
311    return previousDecoder;
312  }
313
314  // Normal code path
315  for (id<ABI48_0_0RCTImageDataDecoder> decoder in _decoders) {
316    if ([decoder canDecodeImageData:data]) {
317      return decoder;
318    }
319  }
320  return nil;
321}
322
323static UIImage *ABI48_0_0RCTResizeImageIfNeeded(UIImage *image, CGSize size, CGFloat scale, ABI48_0_0RCTResizeMode resizeMode)
324{
325  if (CGSizeEqualToSize(size, CGSizeZero) || CGSizeEqualToSize(image.size, CGSizeZero) ||
326      CGSizeEqualToSize(image.size, size)) {
327    return image;
328  }
329  CGRect targetSize = ABI48_0_0RCTTargetRect(image.size, size, scale, resizeMode);
330  CGAffineTransform transform = ABI48_0_0RCTTransformFromTargetRect(image.size, targetSize);
331  image = ABI48_0_0RCTTransformImage(image, size, scale, transform);
332  return image;
333}
334
335#pragma mark - ABI48_0_0RCTImageLoaderProtocol 2/3
336
337- (nullable ABI48_0_0RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
338                                                           callback:(ABI48_0_0RCTImageLoaderCompletionBlock)callback
339{
340  return [self loadImageWithURLRequest:imageURLRequest priority:ABI48_0_0RCTImageLoaderPriorityImmediate callback:callback];
341}
342
343- (nullable ABI48_0_0RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
344                                                           priority:(ABI48_0_0RCTImageLoaderPriority)priority
345                                                           callback:(ABI48_0_0RCTImageLoaderCompletionBlock)callback
346{
347  return [self loadImageWithURLRequest:imageURLRequest
348                                  size:CGSizeZero
349                                 scale:1
350                               clipped:YES
351                            resizeMode:ABI48_0_0RCTResizeModeStretch
352                              priority:priority
353                         progressBlock:nil
354                      partialLoadBlock:nil
355                       completionBlock:callback];
356}
357
358- (nullable ABI48_0_0RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
359                                                               size:(CGSize)size
360                                                              scale:(CGFloat)scale
361                                                            clipped:(BOOL)clipped
362                                                         resizeMode:(ABI48_0_0RCTResizeMode)resizeMode
363                                                      progressBlock:(ABI48_0_0RCTImageLoaderProgressBlock)progressBlock
364                                                   partialLoadBlock:(ABI48_0_0RCTImageLoaderPartialLoadBlock)partialLoadBlock
365                                                    completionBlock:(ABI48_0_0RCTImageLoaderCompletionBlock)completionBlock
366{
367  return [self loadImageWithURLRequest:imageURLRequest
368                                  size:size
369                                 scale:scale
370                               clipped:clipped
371                            resizeMode:resizeMode
372                              priority:ABI48_0_0RCTImageLoaderPriorityImmediate
373                         progressBlock:progressBlock
374                      partialLoadBlock:partialLoadBlock
375                       completionBlock:completionBlock];
376}
377
378- (nullable ABI48_0_0RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
379                                                               size:(CGSize)size
380                                                              scale:(CGFloat)scale
381                                                            clipped:(BOOL)clipped
382                                                         resizeMode:(ABI48_0_0RCTResizeMode)resizeMode
383                                                           priority:(ABI48_0_0RCTImageLoaderPriority)priority
384                                                      progressBlock:(ABI48_0_0RCTImageLoaderProgressBlock)progressBlock
385                                                   partialLoadBlock:(ABI48_0_0RCTImageLoaderPartialLoadBlock)partialLoadBlock
386                                                    completionBlock:(ABI48_0_0RCTImageLoaderCompletionBlock)completionBlock
387{
388  ABI48_0_0RCTImageURLLoaderRequest *request = [self loadImageWithURLRequest:imageURLRequest
389      size:size
390      scale:scale
391      clipped:clipped
392      resizeMode:resizeMode
393      priority:priority
394      attribution:{}
395      progressBlock:progressBlock
396      partialLoadBlock:partialLoadBlock
397      completionBlock:^(NSError *error, UIImage *image, id metadata) {
398        completionBlock(error, image);
399      }];
400  return ^{
401    [request cancel];
402  };
403}
404
405#pragma mark - Private Downloader Methods
406
407- (void)dequeueTasks
408{
409  dispatch_async(_URLRequestQueue, ^{
410    // Remove completed tasks
411    NSMutableArray *tasksToRemove = nil;
412    for (ABI48_0_0RCTNetworkTask *task in self->_pendingTasks.reverseObjectEnumerator) {
413      switch (task.status) {
414        case ABI48_0_0RCTNetworkTaskFinished:
415          if (!tasksToRemove) {
416            tasksToRemove = [NSMutableArray new];
417          }
418          [tasksToRemove addObject:task];
419          self->_activeTasks--;
420          break;
421        case ABI48_0_0RCTNetworkTaskPending:
422          break;
423        case ABI48_0_0RCTNetworkTaskInProgress:
424          // Check task isn't "stuck"
425          if (task.requestToken == nil) {
426            ABI48_0_0RCTLogWarn(@"Task orphaned for request %@", task.request);
427            if (!tasksToRemove) {
428              tasksToRemove = [NSMutableArray new];
429            }
430            [tasksToRemove addObject:task];
431            self->_activeTasks--;
432            [task cancel];
433          }
434          break;
435      }
436    }
437
438    if (tasksToRemove) {
439      [self->_pendingTasks removeObjectsInArray:tasksToRemove];
440    }
441
442    // Start queued decode
443    NSInteger activeDecodes = self->_scheduledDecodes - self->_pendingDecodes.count;
444    while (activeDecodes == 0 ||
445           (self->_activeBytes <= self->_maxConcurrentDecodingBytes &&
446            activeDecodes <= self->_maxConcurrentDecodingTasks)) {
447      dispatch_block_t decodeBlock = self->_pendingDecodes.firstObject;
448      if (decodeBlock) {
449        [self->_pendingDecodes removeObjectAtIndex:0];
450        decodeBlock();
451      } else {
452        break;
453      }
454    }
455
456    // Start queued tasks
457    for (ABI48_0_0RCTNetworkTask *task in self->_pendingTasks) {
458      if (MAX(self->_activeTasks, self->_scheduledDecodes) >= self->_maxConcurrentLoadingTasks) {
459        break;
460      }
461      if (task.status == ABI48_0_0RCTNetworkTaskPending) {
462        [task start];
463        self->_activeTasks++;
464      }
465    }
466  });
467}
468
469/**
470 * This returns either an image, or raw image data, depending on the loading
471 * path taken. This is useful if you want to skip decoding, e.g. when preloading
472 * the image, or retrieving metadata.
473 */
474- (ABI48_0_0RCTImageURLLoaderRequest *)_loadImageOrDataWithURLRequest:(NSURLRequest *)request
475                                                        size:(CGSize)size
476                                                       scale:(CGFloat)scale
477                                                  resizeMode:(ABI48_0_0RCTResizeMode)resizeMode
478                                                    priority:(ABI48_0_0RCTImageLoaderPriority)priority
479                                                 attribution:(const ImageURLLoaderAttribution &)attribution
480                                               progressBlock:(ABI48_0_0RCTImageLoaderProgressBlock)progressHandler
481                                            partialLoadBlock:(ABI48_0_0RCTImageLoaderPartialLoadBlock)partialLoadHandler
482                                             completionBlock:(void (^)(
483                                                                 NSError *error,
484                                                                 id imageOrData,
485                                                                 id imageMetadata,
486                                                                 BOOL cacheResult,
487                                                                 NSURLResponse *response))completionBlock
488{
489  {
490    NSMutableURLRequest *mutableRequest = [request mutableCopy];
491    [NSURLProtocol setProperty:@"ABI48_0_0RCTImageLoader" forKey:@"trackingName" inRequest:mutableRequest];
492
493    // Add missing png extension
494    if (request.URL.fileURL && request.URL.pathExtension.length == 0) {
495      mutableRequest.URL = [request.URL URLByAppendingPathExtension:@"png"];
496    }
497    if (_redirectDelegate != nil) {
498      mutableRequest.URL = [_redirectDelegate redirectAssetsURL:mutableRequest.URL];
499    }
500    request = mutableRequest;
501  }
502
503  // Create a copy here so the value is retained when accessed in the blocks below.
504  ImageURLLoaderAttribution attributionCopy(attribution);
505
506  // Find suitable image URL loader
507  id<ABI48_0_0RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:request.URL];
508  BOOL requiresScheduling =
509      [loadHandler respondsToSelector:@selector(requiresScheduling)] ? [loadHandler requiresScheduling] : YES;
510
511  BOOL cacheResult =
512      [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ? [loadHandler shouldCacheLoadedImages] : YES;
513
514  if (request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
515    cacheResult = NO;
516  }
517
518  if (cacheResult && partialLoadHandler) {
519    UIImage *image = [[self imageCache] imageForUrl:request.URL.absoluteString
520                                               size:size
521                                              scale:scale
522                                         resizeMode:resizeMode];
523    if (image) {
524      partialLoadHandler(image);
525    }
526  }
527
528  auto cancelled = std::make_shared<std::atomic<int>>(0);
529  __block dispatch_block_t cancelLoad = nil;
530  __block NSLock *cancelLoadLock = [NSLock new];
531  NSString *requestId =
532      [NSString stringWithFormat:@"%@-%llu", [[NSUUID UUID] UUIDString], monotonicTimeGetCurrentNanoseconds()];
533
534  void (^completionHandler)(NSError *, id, id, NSURLResponse *) =
535      ^(NSError *error, id imageOrData, id imageMetadata, NSURLResponse *response) {
536        [cancelLoadLock lock];
537        cancelLoad = nil;
538        [cancelLoadLock unlock];
539
540        // If we've received an image, we should try to set it synchronously,
541        // if it's data, do decoding on a background thread.
542        if (ABI48_0_0RCTIsMainQueue() && ![imageOrData isKindOfClass:[UIImage class]]) {
543          // Most loaders do not return on the main thread, so caller is probably not
544          // expecting it, and may do expensive post-processing in the callback
545          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
546            if (!std::atomic_load(cancelled.get())) {
547              completionBlock(error, imageOrData, imageMetadata, cacheResult, response);
548            }
549          });
550        } else if (!std::atomic_load(cancelled.get())) {
551          if (response && error && [response isKindOfClass:[NSHTTPURLResponse class]]) {
552            NSHTTPURLResponse *_httpResp = (NSHTTPURLResponse *)response;
553            error = addResponseHeadersToError(error, _httpResp);
554          }
555          completionBlock(error, imageOrData, imageMetadata, cacheResult, response);
556        }
557      };
558
559  // If the loader doesn't require scheduling we call it directly on
560  // the main queue.
561  if (loadHandler && !requiresScheduling) {
562    if ([loadHandler conformsToProtocol:@protocol(ABI48_0_0RCTImageURLLoaderWithAttribution)]) {
563      return [(id<ABI48_0_0RCTImageURLLoaderWithAttribution>)loadHandler
564             loadImageForURL:request.URL
565                        size:size
566                       scale:scale
567                  resizeMode:resizeMode
568                   requestId:requestId
569                    priority:priority
570                 attribution:attributionCopy
571             progressHandler:progressHandler
572          partialLoadHandler:partialLoadHandler
573           completionHandler:^(NSError *error, UIImage *image, id metadata) {
574             completionHandler(error, image, metadata, nil);
575           }];
576    }
577    ABI48_0_0RCTImageLoaderCancellationBlock cb = [loadHandler loadImageForURL:request.URL
578                                                                 size:size
579                                                                scale:scale
580                                                           resizeMode:resizeMode
581                                                      progressHandler:progressHandler
582                                                   partialLoadHandler:partialLoadHandler
583                                                    completionHandler:^(NSError *error, UIImage *image) {
584                                                      completionHandler(error, image, nil, nil);
585                                                    }];
586    return [[ABI48_0_0RCTImageURLLoaderRequest alloc] initWithRequestId:nil imageURL:request.URL cancellationBlock:cb];
587  }
588
589  // All access to URL cache must be serialized
590  if (!_URLRequestQueue) {
591    [self setUp];
592  }
593
594  __weak ABI48_0_0RCTImageLoader *weakSelf = self;
595  dispatch_async(_URLRequestQueue, ^{
596    __typeof(self) strongSelf = weakSelf;
597    if (atomic_load(cancelled.get()) || !strongSelf) {
598      return;
599    }
600
601    if (loadHandler) {
602      dispatch_block_t cancelLoadLocal;
603      if ([loadHandler conformsToProtocol:@protocol(ABI48_0_0RCTImageURLLoaderWithAttribution)]) {
604        ABI48_0_0RCTImageURLLoaderRequest *loaderRequest = [(id<ABI48_0_0RCTImageURLLoaderWithAttribution>)loadHandler
605               loadImageForURL:request.URL
606                          size:size
607                         scale:scale
608                    resizeMode:resizeMode
609                     requestId:requestId
610                      priority:priority
611                   attribution:attributionCopy
612               progressHandler:progressHandler
613            partialLoadHandler:partialLoadHandler
614             completionHandler:^(NSError *error, UIImage *image, id metadata) {
615               completionHandler(error, image, metadata, nil);
616             }];
617        cancelLoadLocal = loaderRequest.cancellationBlock;
618      } else {
619        cancelLoadLocal = [loadHandler loadImageForURL:request.URL
620                                                  size:size
621                                                 scale:scale
622                                            resizeMode:resizeMode
623                                       progressHandler:progressHandler
624                                    partialLoadHandler:partialLoadHandler
625                                     completionHandler:^(NSError *error, UIImage *image) {
626                                       completionHandler(error, image, nil, nil);
627                                     }];
628      }
629      [cancelLoadLock lock];
630      cancelLoad = cancelLoadLocal;
631      [cancelLoadLock unlock];
632    } else {
633      UIImage *image;
634      if (cacheResult) {
635        image = [[strongSelf imageCache] imageForUrl:request.URL.absoluteString
636                                                size:size
637                                               scale:scale
638                                          resizeMode:resizeMode];
639      }
640
641      if (image) {
642        completionHandler(nil, image, nil, nil);
643      } else {
644        // Use networking module to load image
645        dispatch_block_t cancelLoadLocal =
646            [strongSelf _loadURLRequest:request
647                          progressBlock:progressHandler
648                        completionBlock:^(NSError *error, id imageOrData, NSURLResponse *response) {
649                          completionHandler(error, imageOrData, nil, response);
650                        }];
651        [cancelLoadLock lock];
652        cancelLoad = cancelLoadLocal;
653        [cancelLoadLock unlock];
654      }
655    }
656  });
657
658  return [[ABI48_0_0RCTImageURLLoaderRequest alloc] initWithRequestId:requestId
659                                                    imageURL:request.URL
660                                           cancellationBlock:^{
661                                             BOOL alreadyCancelled = atomic_fetch_or(cancelled.get(), 1) ? YES : NO;
662                                             if (alreadyCancelled) {
663                                               return;
664                                             }
665                                             [cancelLoadLock lock];
666                                             dispatch_block_t cancelLoadLocal = cancelLoad;
667                                             cancelLoad = nil;
668                                             [cancelLoadLock unlock];
669                                             if (cancelLoadLocal) {
670                                               cancelLoadLocal();
671                                             }
672                                           }];
673}
674
675- (ABI48_0_0RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request
676                                     progressBlock:(ABI48_0_0RCTImageLoaderProgressBlock)progressHandler
677                                   completionBlock:(void (^)(NSError *error, id imageOrData, NSURLResponse *response))
678                                                       completionHandler
679{
680  ABI48_0_0RCTNetworking *networking = [_moduleRegistry moduleForName:"Networking"];
681  if (ABI48_0_0RCT_DEBUG && !networking) {
682    ABI48_0_0RCTLogError(
683        @"No suitable image URL loader found for %@. You may need to "
684         " import the ABI48_0_0RCTNetwork library in order to load images.",
685        request.URL.absoluteString);
686    return NULL;
687  }
688
689  // Check if networking module can load image
690  if (ABI48_0_0RCT_DEBUG && ![networking canHandleRequest:request]) {
691    ABI48_0_0RCTLogError(@"No suitable image URL loader found for %@", request.URL.absoluteString);
692    return NULL;
693  }
694
695  // Use networking module to load image
696  ABI48_0_0RCTURLRequestCompletionBlock processResponse = ^(NSURLResponse *response, NSData *data, NSError *error) {
697    // Check for system errors
698    if (error) {
699      completionHandler(error, nil, response);
700      return;
701    } else if (!response) {
702      completionHandler(ABI48_0_0RCTErrorWithMessage(@"Response metadata error"), nil, response);
703      return;
704    } else if (!data) {
705      completionHandler(ABI48_0_0RCTErrorWithMessage(@"Unknown image download error"), nil, response);
706      return;
707    }
708
709    // Check for http errors
710    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
711      NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
712      if (statusCode != 200) {
713        NSString *errorMessage = [NSString stringWithFormat:@"Failed to load %@", response.URL];
714        NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errorMessage};
715        completionHandler(
716            [[NSError alloc] initWithDomain:NSURLErrorDomain code:statusCode userInfo:userInfo], nil, response);
717        return;
718      }
719    }
720
721    // Call handler
722    completionHandler(nil, data, response);
723  };
724
725  // Download image
726  __weak __typeof(self) weakSelf = self;
727  __block ABI48_0_0RCTNetworkTask *task =
728      [networking networkTaskWithRequest:request
729                         completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
730                           __typeof(self) strongSelf = weakSelf;
731                           if (!strongSelf) {
732                             return;
733                           }
734
735                           if (error || !response || !data) {
736                             NSError *someError = nil;
737                             if (error) {
738                               someError = error;
739                             } else if (!response) {
740                               someError = ABI48_0_0RCTErrorWithMessage(@"Response metadata error");
741                             } else {
742                               someError = ABI48_0_0RCTErrorWithMessage(@"Unknown image download error");
743                             }
744                             completionHandler(someError, nil, response);
745                             [strongSelf dequeueTasks];
746                             return;
747                           }
748
749                           dispatch_async(strongSelf->_URLRequestQueue, ^{
750                             // Process image data
751                             processResponse(response, data, nil);
752
753                             // Prepare for next task
754                             [strongSelf dequeueTasks];
755                           });
756                         }];
757
758  task.downloadProgressBlock = ^(int64_t progress, int64_t total) {
759    if (progressHandler) {
760      progressHandler(progress, total);
761    }
762  };
763
764  if (task) {
765    if (!_pendingTasks) {
766      _pendingTasks = [NSMutableArray new];
767    }
768    [_pendingTasks addObject:task];
769    [self dequeueTasks];
770  }
771
772  return ^{
773    __typeof(self) strongSelf = weakSelf;
774    if (!strongSelf || !task) {
775      return;
776    }
777    dispatch_async(strongSelf->_URLRequestQueue, ^{
778      [task cancel];
779      task = nil;
780    });
781    [strongSelf dequeueTasks];
782  };
783}
784
785#pragma mark - ABI48_0_0RCTImageLoaderWithAttributionProtocol
786
787- (ABI48_0_0RCTImageURLLoaderRequest *)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
788                                                 size:(CGSize)size
789                                                scale:(CGFloat)scale
790                                              clipped:(BOOL)clipped
791                                           resizeMode:(ABI48_0_0RCTResizeMode)resizeMode
792                                             priority:(ABI48_0_0RCTImageLoaderPriority)priority
793                                          attribution:(const ImageURLLoaderAttribution &)attribution
794                                        progressBlock:(ABI48_0_0RCTImageLoaderProgressBlock)progressBlock
795                                     partialLoadBlock:(ABI48_0_0RCTImageLoaderPartialLoadBlock)partialLoadBlock
796                                      completionBlock:(ABI48_0_0RCTImageLoaderCompletionBlockWithMetadata)completionBlock
797{
798  auto cancelled = std::make_shared<std::atomic<int>>(0);
799  __block dispatch_block_t cancelLoad = nil;
800  __block NSLock *cancelLoadLock = [NSLock new];
801  dispatch_block_t cancellationBlock = ^{
802    BOOL alreadyCancelled = atomic_fetch_or(cancelled.get(), 1) ? YES : NO;
803    if (alreadyCancelled) {
804      return;
805    }
806    [cancelLoadLock lock];
807    dispatch_block_t cancelLoadLocal = cancelLoad;
808    cancelLoad = nil;
809    [cancelLoadLock unlock];
810    if (cancelLoadLocal) {
811      cancelLoadLocal();
812    }
813  };
814
815  __weak ABI48_0_0RCTImageLoader *weakSelf = self;
816  void (^completionHandler)(NSError *, id, id, BOOL, NSURLResponse *) =
817      ^(NSError *error, id imageOrData, id imageMetadata, BOOL cacheResult, NSURLResponse *response) {
818        __typeof(self) strongSelf = weakSelf;
819        if (std::atomic_load(cancelled.get()) || !strongSelf) {
820          return;
821        }
822
823        if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {
824          [cancelLoadLock lock];
825          cancelLoad = nil;
826          [cancelLoadLock unlock];
827          completionBlock(error, imageOrData, imageMetadata);
828          return;
829        }
830
831        ABI48_0_0RCTImageLoaderCompletionBlock decodeCompletionHandler = ^(NSError *error_, UIImage *image) {
832          if (cacheResult && image) {
833            // Store decoded image in cache
834            [[strongSelf imageCache] addImageToCache:image
835                                                 URL:imageURLRequest.URL.absoluteString
836                                                size:size
837                                               scale:scale
838                                          resizeMode:resizeMode
839                                            response:response];
840          }
841          [cancelLoadLock lock];
842          cancelLoad = nil;
843          [cancelLoadLock unlock];
844          completionBlock(error_, image, nil);
845        };
846        dispatch_block_t cancelLoadLocal = [strongSelf decodeImageData:imageOrData
847                                                                  size:size
848                                                                 scale:scale
849                                                               clipped:clipped
850                                                            resizeMode:resizeMode
851                                                       completionBlock:decodeCompletionHandler];
852        [cancelLoadLock lock];
853        cancelLoad = cancelLoadLocal;
854        [cancelLoadLock unlock];
855      };
856
857  ABI48_0_0RCTImageURLLoaderRequest *loaderRequest = [self _loadImageOrDataWithURLRequest:imageURLRequest
858                                                                            size:size
859                                                                           scale:scale
860                                                                      resizeMode:resizeMode
861                                                                        priority:priority
862                                                                     attribution:attribution
863                                                                   progressBlock:progressBlock
864                                                                partialLoadBlock:partialLoadBlock
865                                                                 completionBlock:completionHandler];
866  cancelLoad = loaderRequest.cancellationBlock;
867  return [[ABI48_0_0RCTImageURLLoaderRequest alloc] initWithRequestId:loaderRequest.requestId
868                                                    imageURL:imageURLRequest.URL
869                                           cancellationBlock:cancellationBlock];
870}
871
872- (BOOL)shouldEnablePerfLoggingForRequestUrl:(NSURL *)url
873{
874  id<ABI48_0_0RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:url];
875  if ([loadHandler respondsToSelector:@selector(shouldEnablePerfLogging)]) {
876    return [(id<ABI48_0_0RCTImageURLLoaderWithAttribution>)loadHandler shouldEnablePerfLogging];
877  }
878  return NO;
879}
880
881- (void)trackURLImageVisibilityForRequest:(ABI48_0_0RCTImageURLLoaderRequest *)loaderRequest imageView:(UIView *)imageView
882{
883  if (!loaderRequest || !imageView) {
884    return;
885  }
886
887  id<ABI48_0_0RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL];
888  if ([loadHandler respondsToSelector:@selector(trackURLImageVisibilityForRequest:imageView:)]) {
889    [(id<ABI48_0_0RCTImageURLLoaderWithAttribution>)loadHandler trackURLImageVisibilityForRequest:loaderRequest
890                                                                               imageView:imageView];
891  }
892}
893
894- (void)trackURLImageRequestDidDestroy:(ABI48_0_0RCTImageURLLoaderRequest *)loaderRequest
895{
896  if (!loaderRequest) {
897    return;
898  }
899
900  id<ABI48_0_0RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL];
901  if ([loadHandler respondsToSelector:@selector(trackURLImageRequestDidDestroy:)]) {
902    [(id<ABI48_0_0RCTImageURLLoaderWithAttribution>)loadHandler trackURLImageRequestDidDestroy:loaderRequest];
903  }
904}
905
906- (void)trackURLImageDidDestroy:(ABI48_0_0RCTImageURLLoaderRequest *)loaderRequest
907{
908  if (!loaderRequest) {
909    return;
910  }
911
912  id<ABI48_0_0RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL];
913  if ([loadHandler respondsToSelector:@selector(trackURLImageDidDestroy:)]) {
914    [(id<ABI48_0_0RCTImageURLLoaderWithAttribution>)loadHandler trackURLImageDidDestroy:loaderRequest];
915  }
916}
917
918#pragma mark - ABI48_0_0RCTImageLoaderProtocol 3/3
919
920- (ABI48_0_0RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
921                                              size:(CGSize)size
922                                             scale:(CGFloat)scale
923                                           clipped:(BOOL)clipped
924                                        resizeMode:(ABI48_0_0RCTResizeMode)resizeMode
925                                   completionBlock:(ABI48_0_0RCTImageLoaderCompletionBlock)completionBlock
926{
927  if (data.length == 0) {
928    completionBlock(ABI48_0_0RCTErrorWithMessage(@"No image data"), nil);
929    return ^{
930    };
931  }
932
933  auto cancelled = std::make_shared<std::atomic<int>>(0);
934  void (^completionHandler)(NSError *, UIImage *) = ^(NSError *error, UIImage *image) {
935    if (ABI48_0_0RCTIsMainQueue()) {
936      // Most loaders do not return on the main thread, so caller is probably not
937      // expecting it, and may do expensive post-processing in the callback
938      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
939        if (!std::atomic_load(cancelled.get())) {
940          completionBlock(error, clipped ? ABI48_0_0RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image);
941        }
942      });
943    } else if (!std::atomic_load(cancelled.get())) {
944      completionBlock(error, clipped ? ABI48_0_0RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image);
945    }
946  };
947
948  id<ABI48_0_0RCTImageDataDecoder> imageDecoder = [self imageDataDecoderForData:data];
949  if (imageDecoder) {
950    return [imageDecoder decodeImageData:data
951                                    size:size
952                                   scale:scale
953                              resizeMode:resizeMode
954                       completionHandler:completionHandler]
955        ?: ^{
956          };
957  } else {
958    dispatch_block_t decodeBlock = ^{
959      // Calculate the size, in bytes, that the decompressed image will require
960      NSInteger decodedImageBytes = (NSInteger)((size.width * scale) * (size.height * scale) * 4);
961
962      // Mark these bytes as in-use
963      self->_activeBytes += decodedImageBytes;
964
965      // Do actual decompression on a concurrent background queue
966      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
967        if (!std::atomic_load(cancelled.get())) {
968          // Decompress the image data (this may be CPU and memory intensive)
969          UIImage *image = ABI48_0_0RCTDecodeImageWithData(data, size, scale, resizeMode);
970
971#if ABI48_0_0RCT_DEV
972          CGSize imagePixelSize = ABI48_0_0RCTSizeInPixels(image.size, image.scale);
973          CGSize screenPixelSize = ABI48_0_0RCTSizeInPixels(ABI48_0_0RCTScreenSize(), ABI48_0_0RCTScreenScale());
974          if (imagePixelSize.width * imagePixelSize.height > screenPixelSize.width * screenPixelSize.height) {
975            ABI48_0_0RCTLogInfo(
976                @"[PERF ASSETS] Loading image at size %@, which is larger "
977                 "than the screen size %@",
978                NSStringFromCGSize(imagePixelSize),
979                NSStringFromCGSize(screenPixelSize));
980          }
981#endif
982
983          if (image) {
984            completionHandler(nil, image);
985          } else {
986            NSString *errorMessage =
987                [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length];
988            NSError *finalError = ABI48_0_0RCTErrorWithMessage(errorMessage);
989            completionHandler(finalError, nil);
990          }
991        }
992
993        // We're no longer retaining the uncompressed data, so now we'll mark
994        // the decoding as complete so that the loading task queue can resume.
995        dispatch_async(self->_URLRequestQueue, ^{
996          self->_scheduledDecodes--;
997          self->_activeBytes -= decodedImageBytes;
998          [self dequeueTasks];
999        });
1000      });
1001    };
1002
1003    if (!_URLRequestQueue) {
1004      [self setUp];
1005    }
1006    dispatch_async(_URLRequestQueue, ^{
1007      // The decode operation retains the compressed image data until it's
1008      // complete, so we'll mark it as having started, in order to block
1009      // further image loads from happening until we're done with the data.
1010      self->_scheduledDecodes++;
1011
1012      if (!self->_pendingDecodes) {
1013        self->_pendingDecodes = [NSMutableArray new];
1014      }
1015      NSInteger activeDecodes = self->_scheduledDecodes - self->_pendingDecodes.count - 1;
1016      if (activeDecodes == 0 ||
1017          (self->_activeBytes <= self->_maxConcurrentDecodingBytes &&
1018           activeDecodes <= self->_maxConcurrentDecodingTasks)) {
1019        decodeBlock();
1020      } else {
1021        [self->_pendingDecodes addObject:decodeBlock];
1022      }
1023    });
1024
1025    return ^{
1026      std::atomic_store(cancelled.get(), 1);
1027    };
1028  }
1029}
1030
1031- (ABI48_0_0RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest
1032                                                       block:(void (^)(NSError *error, CGSize size))callback
1033{
1034  void (^completion)(NSError *, id, id, BOOL, NSURLResponse *) =
1035      ^(NSError *error, id imageOrData, id imageMetadata, BOOL cacheResult, NSURLResponse *response) {
1036        CGSize size;
1037        if ([imageOrData isKindOfClass:[NSData class]]) {
1038          NSDictionary *meta = ABI48_0_0RCTGetImageMetadata(imageOrData);
1039
1040          NSInteger imageOrientation = [meta[(id)kCGImagePropertyOrientation] integerValue];
1041          switch (imageOrientation) {
1042            case kCGImagePropertyOrientationLeft:
1043            case kCGImagePropertyOrientationRight:
1044            case kCGImagePropertyOrientationLeftMirrored:
1045            case kCGImagePropertyOrientationRightMirrored:
1046              // swap width and height
1047              size = (CGSize){
1048                  [meta[(id)kCGImagePropertyPixelHeight] floatValue],
1049                  [meta[(id)kCGImagePropertyPixelWidth] floatValue],
1050              };
1051              break;
1052            case kCGImagePropertyOrientationUp:
1053            case kCGImagePropertyOrientationDown:
1054            case kCGImagePropertyOrientationUpMirrored:
1055            case kCGImagePropertyOrientationDownMirrored:
1056            default:
1057              size = (CGSize){
1058                  [meta[(id)kCGImagePropertyPixelWidth] floatValue],
1059                  [meta[(id)kCGImagePropertyPixelHeight] floatValue],
1060              };
1061              break;
1062          }
1063        } else {
1064          UIImage *image = imageOrData;
1065          size = (CGSize){
1066              image.size.width * image.scale,
1067              image.size.height * image.scale,
1068          };
1069        }
1070        callback(error, size);
1071      };
1072
1073  ABI48_0_0RCTImageURLLoaderRequest *loaderRequest = [self _loadImageOrDataWithURLRequest:imageURLRequest
1074                                                                            size:CGSizeZero
1075                                                                           scale:1
1076                                                                      resizeMode:ABI48_0_0RCTResizeModeStretch
1077                                                                        priority:ABI48_0_0RCTImageLoaderPriorityImmediate
1078                                                                     attribution:{}
1079                                                                   progressBlock:NULL
1080                                                                partialLoadBlock:NULL
1081                                                                 completionBlock:completion];
1082  return loaderRequest.cancellationBlock;
1083}
1084
1085- (NSDictionary *)getImageCacheStatus:(NSArray *)requests
1086{
1087  NSMutableDictionary *results = [NSMutableDictionary dictionary];
1088  for (id request in requests) {
1089    NSURLRequest *urlRequest = [ABI48_0_0RCTConvert NSURLRequest:request];
1090    if (urlRequest) {
1091      NSCachedURLResponse *cachedResponse = [NSURLCache.sharedURLCache cachedResponseForRequest:urlRequest];
1092      if (cachedResponse) {
1093        if (cachedResponse.storagePolicy == NSURLCacheStorageAllowedInMemoryOnly) {
1094          results[urlRequest.URL.absoluteString] = @"memory";
1095        } else if (NSURLCache.sharedURLCache.currentMemoryUsage == 0) {
1096          // We can't check whether the file is cached on disk or memory.
1097          // However, if currentMemoryUsage is disabled, it must be read from disk.
1098          results[urlRequest.URL.absoluteString] = @"disk";
1099        } else {
1100          results[urlRequest.URL.absoluteString] = @"disk/memory";
1101        }
1102      }
1103    }
1104  }
1105  return results;
1106}
1107
1108#pragma mark - ABI48_0_0RCTURLRequestHandler
1109
1110- (BOOL)canHandleRequest:(NSURLRequest *)request
1111{
1112  NSURL *requestURL = request.URL;
1113
1114  // If the data being loaded is a video, return NO
1115  // Even better may be to implement this on the ABI48_0_0RCTImageURLLoader that would try to load it,
1116  // but we'd have to run the logic both in ABI48_0_0RCTPhotoLibraryImageLoader and
1117  // ABI48_0_0RCTAssetsLibraryRequestHandler. Once we drop iOS7 though, we'd drop
1118  // ABI48_0_0RCTAssetsLibraryRequestHandler and can move it there.
1119  static NSRegularExpression *videoRegex;
1120  static dispatch_once_t onceToken;
1121  dispatch_once(&onceToken, ^{
1122    NSError *error = nil;
1123    videoRegex = [NSRegularExpression regularExpressionWithPattern:@"(?:&|^)ext=MOV(?:&|$)"
1124                                                           options:NSRegularExpressionCaseInsensitive
1125                                                             error:&error];
1126    if (error) {
1127      ABI48_0_0RCTLogError(@"%@", error);
1128    }
1129  });
1130
1131  NSString *query = requestURL.query;
1132  if (query != nil && [videoRegex firstMatchInString:query options:0 range:NSMakeRange(0, query.length)]) {
1133    return NO;
1134  }
1135
1136  for (id<ABI48_0_0RCTImageURLLoader> loader in _loaders) {
1137    // Don't use ABI48_0_0RCTImageURLLoader protocol for modules that already conform to
1138    // ABI48_0_0RCTURLRequestHandler as it's inefficient to decode an image and then
1139    // convert it back into data
1140    if (![loader conformsToProtocol:@protocol(ABI48_0_0RCTURLRequestHandler)] && [loader canLoadImageURL:requestURL]) {
1141      return YES;
1142    }
1143  }
1144
1145  return NO;
1146}
1147
1148- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<ABI48_0_0RCTURLRequestDelegate>)delegate
1149{
1150  __block ABI48_0_0RCTImageLoaderCancellationBlock requestToken;
1151  requestToken = [self loadImageWithURLRequest:request
1152                                      callback:^(NSError *error, UIImage *image) {
1153                                        if (error) {
1154                                          [delegate URLRequest:requestToken didCompleteWithError:error];
1155                                          return;
1156                                        }
1157
1158                                        NSString *mimeType = nil;
1159                                        NSData *imageData = nil;
1160                                        if (ABI48_0_0RCTImageHasAlpha(image.CGImage)) {
1161                                          mimeType = @"image/png";
1162                                          imageData = UIImagePNGRepresentation(image);
1163                                        } else {
1164                                          mimeType = @"image/jpeg";
1165                                          imageData = UIImageJPEGRepresentation(image, 1.0);
1166                                        }
1167
1168                                        NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
1169                                                                                            MIMEType:mimeType
1170                                                                               expectedContentLength:imageData.length
1171                                                                                    textEncodingName:nil];
1172
1173                                        [delegate URLRequest:requestToken didReceiveResponse:response];
1174                                        [delegate URLRequest:requestToken didReceiveData:imageData];
1175                                        [delegate URLRequest:requestToken didCompleteWithError:nil];
1176                                      }];
1177
1178  return requestToken;
1179}
1180
1181- (void)cancelRequest:(id)requestToken
1182{
1183  if (requestToken) {
1184    ((ABI48_0_0RCTImageLoaderCancellationBlock)requestToken)();
1185  }
1186}
1187
1188- (std::shared_ptr<ABI48_0_0facebook::ABI48_0_0React::TurboModule>)getTurboModule:
1189    (const ABI48_0_0facebook::ABI48_0_0React::ObjCTurboModule::InitParams &)params
1190{
1191  return std::make_shared<ABI48_0_0facebook::ABI48_0_0React::NativeImageLoaderIOSSpecJSI>(params);
1192}
1193
1194ABI48_0_0RCT_EXPORT_METHOD(getSize
1195                  : (NSString *)uri resolve
1196                  : (ABI48_0_0RCTPromiseResolveBlock)resolve reject
1197                  : (ABI48_0_0RCTPromiseRejectBlock)reject)
1198{
1199  NSURLRequest *request = [ABI48_0_0RCTConvert NSURLRequest:uri];
1200  [self getImageSizeForURLRequest:request
1201                            block:^(NSError *error, CGSize size) {
1202                              if (error) {
1203                                reject(
1204                                    @"E_GET_SIZE_FAILURE",
1205                                    [NSString stringWithFormat:@"Failed to getSize of %@", uri],
1206                                    error);
1207                              } else {
1208                                resolve(@[ @(size.width), @(size.height) ]);
1209                              }
1210                            }];
1211}
1212
1213ABI48_0_0RCT_EXPORT_METHOD(getSizeWithHeaders
1214                  : (NSString *)uri headers
1215                  : (NSDictionary *)headers resolve
1216                  : (ABI48_0_0RCTPromiseResolveBlock)resolve reject
1217                  : (ABI48_0_0RCTPromiseRejectBlock)reject)
1218{
1219  NSURL *URL = [ABI48_0_0RCTConvert NSURL:uri];
1220  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
1221  [headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
1222    [request addValue:[ABI48_0_0RCTConvert NSString:value] forHTTPHeaderField:key];
1223  }];
1224  [self getImageSizeForURLRequest:request
1225                            block:^(NSError *error, CGSize size) {
1226                              if (error) {
1227                                reject(@"E_GET_SIZE_FAILURE", nil, error);
1228                                return;
1229                              }
1230                              resolve(@{@"width" : @(size.width), @"height" : @(size.height)});
1231                            }];
1232}
1233
1234ABI48_0_0RCT_EXPORT_METHOD(prefetchImage
1235                  : (NSString *)uri resolve
1236                  : (ABI48_0_0RCTPromiseResolveBlock)resolve reject
1237                  : (ABI48_0_0RCTPromiseRejectBlock)reject)
1238{
1239  [self prefetchImageWithMetadata:uri queryRootName:nil rootTag:0 resolve:resolve reject:reject];
1240}
1241
1242ABI48_0_0RCT_EXPORT_METHOD(prefetchImageWithMetadata
1243                  : (NSString *)uri queryRootName
1244                  : (NSString *)queryRootName rootTag
1245                  : (double)rootTag resolve
1246                  : (ABI48_0_0RCTPromiseResolveBlock)resolve reject
1247                  : (ABI48_0_0RCTPromiseRejectBlock)reject)
1248{
1249  NSURLRequest *request = [ABI48_0_0RCTConvert NSURLRequest:uri];
1250  [self loadImageWithURLRequest:request
1251                           size:CGSizeZero
1252                          scale:1
1253                        clipped:YES
1254                     resizeMode:ABI48_0_0RCTResizeModeStretch
1255                       priority:ABI48_0_0RCTImageLoaderPriorityPrefetch
1256                    attribution:{
1257                                    .queryRootName = queryRootName ? [queryRootName UTF8String] : "",
1258                                    .surfaceId = (int)rootTag,
1259                                }
1260                  progressBlock:nil
1261               partialLoadBlock:nil
1262                completionBlock:^(NSError *error, UIImage *image, id completionMetadata) {
1263                  if (error) {
1264                    reject(@"E_PREFETCH_FAILURE", nil, error);
1265                    return;
1266                  }
1267                  resolve(@YES);
1268                }];
1269}
1270
1271ABI48_0_0RCT_EXPORT_METHOD(queryCache
1272                  : (NSArray *)uris resolve
1273                  : (ABI48_0_0RCTPromiseResolveBlock)resolve reject
1274                  : (ABI48_0_0RCTPromiseRejectBlock)reject)
1275{
1276  resolve([self getImageCacheStatus:uris]);
1277}
1278@end
1279
1280/**
1281 * DEPRECATED!! DO NOT USE
1282 * Instead use `[_bridge moduleForClass:[ABI48_0_0RCTImageLoader class]]`
1283 */
1284@implementation ABI48_0_0RCTBridge (ABI48_0_0RCTImageLoader)
1285
1286- (ABI48_0_0RCTImageLoader *)imageLoader
1287{
1288  ABI48_0_0RCTLogWarn(
1289      @"Calling bridge.imageLoader is deprecated and will not work in newer versions of ABI48_0_0RN. Please update to the "
1290       "moduleForClass API or turboModuleRegistry API.");
1291  return [self moduleForClass:[ABI48_0_0RCTImageLoader class]];
1292}
1293
1294@end
1295
1296Class ABI48_0_0RCTImageLoaderCls(void)
1297{
1298  return ABI48_0_0RCTImageLoader.class;
1299}
1300