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