d9ce32b64463dbbcfed91e3b124a08e558584598
[WebKit-https.git] / Source / WebKit / UIProcess / ios / forms / WKFileUploadPanel.mm
1 /*
2  * Copyright (C) 2014-2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WKFileUploadPanel.h"
28
29 #if PLATFORM(IOS)
30
31 #import "APIArray.h"
32 #import "APIData.h"
33 #import "APIOpenPanelParameters.h"
34 #import "APIString.h"
35 #import "UIKitSPI.h"
36 #import "WKContentViewInteraction.h"
37 #import "WKData.h"
38 #import "WKStringCF.h"
39 #import "WKURLCF.h"
40 #import "WebIconUtilities.h"
41 #import "WebOpenPanelResultListenerProxy.h"
42 #import "WebPageProxy.h"
43 #import <MobileCoreServices/MobileCoreServices.h>
44 #import <WebCore/LocalizedStrings.h>
45 #import <wtf/RetainPtr.h>
46
47 using namespace WebKit;
48
49 #pragma clang diagnostic push
50 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
51
52 static inline UIImagePickerControllerCameraDevice cameraDeviceForMediaCaptureType(WebCore::MediaCaptureType mediaCaptureType)
53 {
54     return mediaCaptureType == WebCore::MediaCaptureTypeUser ? UIImagePickerControllerCameraDeviceFront : UIImagePickerControllerCameraDeviceRear;
55 }
56
57 #pragma mark - Document picker icons
58
59 static inline UIImage *photoLibraryIcon()
60 {
61     return _UIImageGetWebKitPhotoLibraryIcon();
62 }
63
64 static inline UIImage *cameraIcon()
65 {
66     return _UIImageGetWebKitTakePhotoOrVideoIcon();
67 }
68
69 #pragma mark - _WKFileUploadItem
70
71 @interface _WKFileUploadItem : NSObject
72 - (instancetype)initWithFileURL:(NSURL *)fileURL;
73 @property (nonatomic, readonly, getter=isVideo) BOOL video;
74 @property (nonatomic, readonly) NSURL *fileURL;
75 @property (nonatomic, readonly) UIImage *displayImage;
76 @end
77
78 @implementation _WKFileUploadItem {
79     RetainPtr<NSURL> _fileURL;
80 }
81
82 - (instancetype)initWithFileURL:(NSURL *)fileURL
83 {
84     if (!(self = [super init]))
85         return nil;
86
87     ASSERT([fileURL isFileURL]);
88     ASSERT([[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]);
89     _fileURL = fileURL;
90     return self;
91 }
92
93 - (BOOL)isVideo
94 {
95     ASSERT_NOT_REACHED();
96     return NO;
97 }
98
99 - (NSURL *)fileURL
100 {
101     return _fileURL.get();
102 }
103
104 - (UIImage *)displayImage
105 {
106     ASSERT_NOT_REACHED();
107     return nil;
108 }
109
110 @end
111
112
113 @interface _WKImageFileUploadItem : _WKFileUploadItem
114 @end
115
116 @implementation _WKImageFileUploadItem
117
118 - (BOOL)isVideo
119 {
120     return NO;
121 }
122
123 - (UIImage *)displayImage
124 {
125     return iconForImageFile(self.fileURL);
126 }
127
128 @end
129
130
131 @interface _WKVideoFileUploadItem : _WKFileUploadItem
132 @end
133
134 @implementation _WKVideoFileUploadItem
135
136 - (BOOL)isVideo
137 {
138     return YES;
139 }
140
141 - (UIImage *)displayImage
142 {
143     return iconForVideoFile(self.fileURL);
144 }
145
146 @end
147
148
149 #pragma mark - WKFileUploadPanel
150
151 @interface WKFileUploadPanel () <UIPopoverControllerDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIDocumentPickerDelegate, UIDocumentMenuDelegate>
152 @end
153
154 @implementation WKFileUploadPanel {
155     WKContentView *_view;
156     RefPtr<WebKit::WebOpenPanelResultListenerProxy> _listener;
157     RetainPtr<NSArray> _mimeTypes;
158     CGPoint _interactionPoint;
159     BOOL _allowMultipleFiles;
160     BOOL _usingCamera;
161     RetainPtr<UIImagePickerController> _imagePicker;
162     RetainPtr<UIViewController> _presentationViewController; // iPhone always. iPad for Fullscreen Camera.
163 #pragma clang diagnostic push
164 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
165     RetainPtr<UIPopoverController> _presentationPopover; // iPad for action sheet and Photo Library.
166 #pragma clang diagnostic pop
167     RetainPtr<UIDocumentMenuViewController> _documentMenuController;
168     RetainPtr<UIAlertController> _actionSheetController;
169     WebCore::MediaCaptureType _mediaCaptureType;
170 }
171
172 - (instancetype)initWithView:(WKContentView *)view
173 {
174     if (!(self = [super init]))
175         return nil;
176     _view = view;
177     return self;
178 }
179
180 - (void)dealloc
181 {
182     [_imagePicker setDelegate:nil];
183     [_presentationPopover setDelegate:nil];
184     [_documentMenuController setDelegate:nil];
185
186     [super dealloc];
187 }
188
189 - (void)_dispatchDidDismiss
190 {
191     if ([_delegate respondsToSelector:@selector(fileUploadPanelDidDismiss:)])
192         [_delegate fileUploadPanelDidDismiss:self];
193 }
194
195 #pragma mark - Panel Completion (one of these must be called)
196
197 - (void)_cancel
198 {
199     if (_listener)
200         _listener->cancel();
201     
202     [self _dispatchDidDismiss];
203 }
204
205 - (void)_chooseFiles:(NSArray *)fileURLs displayString:(NSString *)displayString iconImage:(UIImage *)iconImage
206 {
207     NSUInteger count = [fileURLs count];
208     if (!count) {
209         [self _cancel];
210         return;
211     }
212
213     Vector<String> filenames;
214     filenames.reserveInitialCapacity(count);
215     for (NSURL *fileURL in fileURLs)
216         filenames.uncheckedAppend(String::fromUTF8(fileURL.fileSystemRepresentation));
217
218     NSData *jpeg = UIImageJPEGRepresentation(iconImage, 1.0);
219     RefPtr<API::Data> iconImageDataRef = adoptRef(toImpl(WKDataCreate(reinterpret_cast<const unsigned char*>([jpeg bytes]), [jpeg length])));
220
221     _listener->chooseFiles(filenames, displayString, iconImageDataRef.get());
222     [self _dispatchDidDismiss];
223 }
224
225 #pragma mark - Present / Dismiss API
226
227 - (void)presentWithParameters:(API::OpenPanelParameters*)parameters resultListener:(WebKit::WebOpenPanelResultListenerProxy*)listener
228 {
229     ASSERT(!_listener);
230
231     _listener = listener;
232     _allowMultipleFiles = parameters->allowMultipleFiles();
233     _interactionPoint = [_view lastInteractionLocation];
234
235     Ref<API::Array> acceptMimeTypes = parameters->acceptMIMETypes();
236     NSMutableArray *mimeTypes = [NSMutableArray arrayWithCapacity:acceptMimeTypes->size()];
237     for (auto mimeType : acceptMimeTypes->elementsOfType<API::String>())
238         [mimeTypes addObject:mimeType->string()];
239     _mimeTypes = adoptNS([mimeTypes copy]);
240
241     _mediaCaptureType = WebCore::MediaCaptureTypeNone;
242 #if ENABLE(MEDIA_CAPTURE)
243     _mediaCaptureType = parameters->mediaCaptureType();
244 #endif
245
246     if ([self _shouldMediaCaptureOpenMediaDevice]) {
247         [self _adjustMediaCaptureType];
248
249         _usingCamera = YES;
250         [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypeCamera];
251
252         return;
253     }
254
255     [self _showDocumentPickerMenu];
256 }
257
258 - (void)dismiss
259 {
260     // Dismiss any view controller that is being presented. This works for all types of view controllers, popovers, etc.
261     // If there is any kind of view controller presented on this view, it will be removed. 
262     
263     [[UIViewController _viewControllerForFullScreenPresentationFromView:_view] dismissViewControllerAnimated:NO completion:nil];
264     
265     [_presentationPopover setDelegate:nil];
266     _presentationPopover = nil;
267     _presentationViewController = nil;
268     
269     [self _cancel];
270 }
271
272 - (void)_dismissDisplayAnimated:(BOOL)animated
273 {
274     if (_presentationPopover) {
275         [_presentationPopover dismissPopoverAnimated:animated];
276         [_presentationPopover setDelegate:nil];
277         _presentationPopover = nil;
278     }
279
280     if (_presentationViewController) {
281         [_presentationViewController dismissViewControllerAnimated:animated completion:^{
282             _presentationViewController = nil;
283         }];
284     }
285 }
286
287 #pragma mark - Media Types
288
289 static bool stringHasPrefixCaseInsensitive(NSString *str, NSString *prefix)
290 {
291     NSRange range = [str rangeOfString:prefix options:(NSCaseInsensitiveSearch | NSAnchoredSearch)];
292     return range.location != NSNotFound;
293 }
294
295 static NSArray *UTIsForMIMETypes(NSArray *mimeTypes)
296 {
297     // The HTML5 spec mentions the literal "image/*" and "video/*" strings.
298     // We support these and go a step further, if the MIME type starts with
299     // "image/" or "video/" we adjust the picker's image or video filters.
300     // So, "image/jpeg" would make the picker display all images types.
301     NSMutableSet *mediaTypes = [NSMutableSet set];
302     for (NSString *mimeType in mimeTypes) {
303         // FIXME: We should support more MIME type -> UTI mappings. <http://webkit.org/b/142614>
304         if (stringHasPrefixCaseInsensitive(mimeType, @"image/"))
305             [mediaTypes addObject:(NSString *)kUTTypeImage];
306         else if (stringHasPrefixCaseInsensitive(mimeType, @"video/"))
307             [mediaTypes addObject:(NSString *)kUTTypeMovie];
308     }
309
310     return mediaTypes.allObjects;
311 }
312
313 - (NSArray *)_mediaTypesForPickerSourceType:(UIImagePickerControllerSourceType)sourceType
314 {
315     NSArray *mediaTypes = UTIsForMIMETypes(_mimeTypes.get());
316     if (mediaTypes.count)
317         return mediaTypes;
318
319     // Fallback to every supported media type if there is no filter.
320     return [UIImagePickerController availableMediaTypesForSourceType:sourceType];
321 }
322
323 - (NSArray *)_documentPickerMenuMediaTypes
324 {
325     NSArray *mediaTypes = UTIsForMIMETypes(_mimeTypes.get());
326     if (mediaTypes.count)
327         return mediaTypes;
328
329     // Fallback to every supported media type if there is no filter.
330     return @[@"public.item"];
331 }
332
333 #pragma mark - Source selection menu
334
335 - (NSString *)_photoLibraryButtonLabel
336 {
337     return WEB_UI_STRING_KEY("Photo Library", "Photo Library (file upload action sheet)", "File Upload alert sheet button string for choosing an existing media item from the Photo Library");
338 }
339
340 - (NSString *)_cameraButtonLabel
341 {
342     if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
343         return nil;
344
345     // Choose the appropriate string for the camera button.
346     NSArray *filteredMediaTypes = [self _mediaTypesForPickerSourceType:UIImagePickerControllerSourceTypeCamera];
347     BOOL containsImageMediaType = [filteredMediaTypes containsObject:(NSString *)kUTTypeImage];
348     BOOL containsVideoMediaType = [filteredMediaTypes containsObject:(NSString *)kUTTypeMovie];
349     ASSERT(containsImageMediaType || containsVideoMediaType);
350     if (containsImageMediaType && containsVideoMediaType)
351         return WEB_UI_STRING_KEY("Take Photo or Video", "Take Photo or Video (file upload action sheet)", "File Upload alert sheet camera button string for taking photos or videos");
352
353     if (containsVideoMediaType)
354         return WEB_UI_STRING_KEY("Take Video", "Take Video (file upload action sheet)", "File Upload alert sheet camera button string for taking only videos");
355
356     return WEB_UI_STRING_KEY("Take Photo", "Take Photo (file upload action sheet)", "File Upload alert sheet camera button string for taking only photos");
357 }
358
359 - (void)_showDocumentPickerMenu
360 {
361     // FIXME: Support multiple file selection when implemented. <rdar://17177981>
362     _documentMenuController = adoptNS([[UIDocumentMenuViewController alloc] _initIgnoringApplicationEntitlementForImportOfTypes:[self _documentPickerMenuMediaTypes]]);
363     [_documentMenuController setDelegate:self];
364
365     [_documentMenuController addOptionWithTitle:[self _photoLibraryButtonLabel] image:photoLibraryIcon() order:UIDocumentMenuOrderFirst handler:^{
366         [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
367     }];
368
369     if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
370         if (NSString *cameraString = [self _cameraButtonLabel]) {
371             [_documentMenuController addOptionWithTitle:cameraString image:cameraIcon() order:UIDocumentMenuOrderFirst handler:^{
372                 _usingCamera = YES;
373                 [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypeCamera];
374             }];
375         }
376     }
377
378     [self _presentMenuOptionForCurrentInterfaceIdiom:_documentMenuController.get()];
379     // Clear out the view controller we just presented. Don't save a reference to the UIDocumentMenuViewController as it is self dismissing.
380     _presentationViewController = nil;
381 }
382
383 #pragma mark - Image Picker
384
385 - (void)_adjustMediaCaptureType
386 {
387     if ([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront] || [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear]) {
388         if (![UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront])
389             _mediaCaptureType = WebCore::MediaCaptureTypeEnvironment;
390         
391         if (![UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear])
392             _mediaCaptureType = WebCore::MediaCaptureTypeUser;
393         
394         return;
395     }
396     
397     _mediaCaptureType = WebCore::MediaCaptureTypeNone;
398 }
399
400 - (BOOL)_shouldMediaCaptureOpenMediaDevice
401 {
402     if (_mediaCaptureType == WebCore::MediaCaptureTypeNone || ![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
403         return NO;
404     
405     return YES;
406 }
407
408 - (void)_showPhotoPickerWithSourceType:(UIImagePickerControllerSourceType)sourceType
409 {
410     ASSERT([UIImagePickerController isSourceTypeAvailable:sourceType]);
411     
412     _imagePicker = adoptNS([[UIImagePickerController alloc] init]);
413     [_imagePicker setDelegate:self];
414     [_imagePicker setSourceType:sourceType];
415     [_imagePicker setAllowsEditing:NO];
416     [_imagePicker setModalPresentationStyle:UIModalPresentationFullScreen];
417     [_imagePicker _setAllowsMultipleSelection:_allowMultipleFiles];
418     [_imagePicker setMediaTypes:[self _mediaTypesForPickerSourceType:sourceType]];
419
420     if (_mediaCaptureType != WebCore::MediaCaptureTypeNone)
421         [_imagePicker setCameraDevice:cameraDeviceForMediaCaptureType(_mediaCaptureType)];
422     
423     // Use a popover on the iPad if the source type is not the camera.
424     // The camera will use a fullscreen, modal view controller.
425     BOOL usePopover = currentUserInterfaceIdiomIsPad() && sourceType != UIImagePickerControllerSourceTypeCamera;
426     if (usePopover)
427         [self _presentPopoverWithContentViewController:_imagePicker.get() animated:YES];
428     else
429         [self _presentFullscreenViewController:_imagePicker.get() animated:YES];
430 }
431
432 #pragma mark - Presenting View Controllers
433
434 - (void)_presentMenuOptionForCurrentInterfaceIdiom:(UIViewController *)viewController
435 {
436     if (currentUserInterfaceIdiomIsPad())
437         [self _presentPopoverWithContentViewController:viewController animated:YES];
438     else
439         [self _presentFullscreenViewController:viewController animated:YES];
440 }
441
442 - (void)_presentPopoverWithContentViewController:(UIViewController *)contentViewController animated:(BOOL)animated
443 {
444     [self _dismissDisplayAnimated:animated];
445
446 #pragma clang diagnostic push
447 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
448     _presentationPopover = adoptNS([[UIPopoverController alloc] initWithContentViewController:contentViewController]);
449 #pragma clang diagnostic pop
450     [_presentationPopover setDelegate:self];
451     [_presentationPopover presentPopoverFromRect:CGRectIntegral(CGRectMake(_interactionPoint.x, _interactionPoint.y, 1, 1)) inView:_view permittedArrowDirections:UIPopoverArrowDirectionAny animated:animated];
452 }
453
454 - (void)_presentFullscreenViewController:(UIViewController *)viewController animated:(BOOL)animated
455 {
456     [self _dismissDisplayAnimated:animated];
457
458     _presentationViewController = [UIViewController _viewControllerForFullScreenPresentationFromView:_view];
459     [_presentationViewController presentViewController:viewController animated:animated completion:nil];
460 }
461
462 #pragma mark - UIPopoverControllerDelegate
463
464 - (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
465 {
466     [self _cancel];
467 }
468
469 #pragma mark - UIDocumentMenuDelegate implementation
470
471 - (void)documentMenu:(UIDocumentMenuViewController *)documentMenu didPickDocumentPicker:(UIDocumentPickerViewController *)documentPicker
472 {
473     documentPicker.delegate = self;
474     [self _presentFullscreenViewController:documentPicker animated:YES];
475 }
476
477 - (void)documentMenuWasCancelled:(UIDocumentMenuViewController *)documentMenu
478 {
479     [self _dismissDisplayAnimated:YES];
480     [self _cancel];
481 }
482
483 #pragma mark - UIDocumentPickerControllerDelegate implementation
484
485 - (void)documentPicker:(UIDocumentPickerViewController *)documentPicker didPickDocumentAtURL:(NSURL *)url
486 {
487     [self _dismissDisplayAnimated:YES];
488     [self _chooseFiles:@[url] displayString:url.lastPathComponent iconImage:iconForFile(url)];
489 }
490
491 - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)documentPicker
492 {
493     [self _dismissDisplayAnimated:YES];
494     [self _cancel];
495 }
496
497 #pragma mark - UIImagePickerControllerDelegate implementation
498
499 - (BOOL)_willMultipleSelectionDelegateBeCalled
500 {
501     // The multiple selection delegate will not be called when the UIImagePicker was not multiple selection.
502     if (!_allowMultipleFiles)
503         return NO;
504
505     // The multiple selection delegate will not be called when we used the camera in the UIImagePicker.
506     if (_usingCamera)
507         return NO;
508
509     return YES;
510 }
511
512 - (void)imagePickerController:(UIImagePickerController *)imagePicker didFinishPickingMediaWithInfo:(NSDictionary *)info
513 {
514     // Sometimes both delegates get called, sometimes just one. Always let the
515     // multiple selection delegate handle everything if it will get called.
516     if ([self _willMultipleSelectionDelegateBeCalled])
517         return;
518
519     [self _dismissDisplayAnimated:YES];
520
521     [self _processMediaInfoDictionaries:[NSArray arrayWithObject:info]
522         successBlock:^(NSArray *processedResults, NSString *displayString) {
523             ASSERT([processedResults count] == 1);
524             _WKFileUploadItem *result = [processedResults objectAtIndex:0];
525             dispatch_async(dispatch_get_main_queue(), ^{
526                 [self _chooseFiles:[NSArray arrayWithObject:result.fileURL] displayString:displayString iconImage:result.displayImage];
527             });
528         }
529         failureBlock:^{
530             dispatch_async(dispatch_get_main_queue(), ^{
531                 [self _cancel];
532             });
533         }
534     ];
535 }
536
537 - (void)imagePickerController:(UIImagePickerController *)imagePicker didFinishPickingMultipleMediaWithInfo:(NSArray *)infos
538 {
539     [self _dismissDisplayAnimated:YES];
540
541     [self _processMediaInfoDictionaries:infos
542         successBlock:^(NSArray *processedResults, NSString *displayString) {
543             UIImage *iconImage = nil;
544             NSMutableArray *fileURLs = [NSMutableArray array];
545             for (_WKFileUploadItem *result in processedResults) {
546                 NSURL *fileURL = result.fileURL;
547                 if (!fileURL)
548                     continue;
549                 [fileURLs addObject:result.fileURL];
550                 if (!iconImage)
551                     iconImage = result.displayImage;
552             }
553
554             dispatch_async(dispatch_get_main_queue(), ^{
555                 [self _chooseFiles:fileURLs displayString:displayString iconImage:iconImage];
556             });
557         }
558         failureBlock:^{
559             dispatch_async(dispatch_get_main_queue(), ^{
560                 [self _cancel];
561             });
562         }
563     ];
564 }
565
566 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)imagePicker
567 {
568     [self _dismissDisplayAnimated:YES];
569     [self _cancel];
570 }
571
572 #pragma mark - Process UIImagePicker results
573
574 - (void)_processMediaInfoDictionaries:(NSArray *)infos successBlock:(void (^)(NSArray *processedResults, NSString *displayString))successBlock failureBlock:(void (^)(void))failureBlock
575 {
576     [self _processMediaInfoDictionaries:infos atIndex:0 processedResults:[NSMutableArray array] processedImageCount:0 processedVideoCount:0 successBlock:successBlock failureBlock:failureBlock];
577 }
578
579 - (void)_processMediaInfoDictionaries:(NSArray *)infos atIndex:(NSUInteger)index processedResults:(NSMutableArray *)processedResults processedImageCount:(NSUInteger)processedImageCount processedVideoCount:(NSUInteger)processedVideoCount successBlock:(void (^)(NSArray *processedResults, NSString *displayString))successBlock failureBlock:(void (^)(void))failureBlock
580 {
581     NSUInteger count = [infos count];
582     if (index == count) {
583         NSString *displayString = (processedImageCount || processedVideoCount) ? [NSString localizedStringWithFormat:WEB_UI_NSSTRING(@"%lu photo(s) and %lu video(s)", "label next to file upload control; parameters are the number of photos and the number of videos"), (unsigned long)processedImageCount, (unsigned long)processedVideoCount] : nil;
584         successBlock(processedResults, displayString);
585         return;
586     }
587
588     NSDictionary *info = [infos objectAtIndex:index];
589     ASSERT(index < count);
590     index++;
591
592     auto uploadItemSuccessBlock = ^(_WKFileUploadItem *uploadItem) {
593         NSUInteger newProcessedVideoCount = processedVideoCount + (uploadItem.isVideo ? 1 : 0);
594         NSUInteger newProcessedImageCount = processedImageCount + (uploadItem.isVideo ? 0 : 1);
595         [processedResults addObject:uploadItem];
596         [self _processMediaInfoDictionaries:infos atIndex:index processedResults:processedResults processedImageCount:newProcessedImageCount processedVideoCount:newProcessedVideoCount successBlock:successBlock failureBlock:failureBlock];
597     };
598
599     [self _uploadItemFromMediaInfo:info successBlock:uploadItemSuccessBlock failureBlock:failureBlock];
600 }
601
602 - (void)_uploadItemForImageData:(NSData *)imageData imageName:(NSString *)imageName successBlock:(void (^)(_WKFileUploadItem *))successBlock failureBlock:(void (^)(void))failureBlock
603 {
604     ASSERT_ARG(imageData, imageData);
605     ASSERT(!RunLoop::isMain());
606
607     NSString * const kTemporaryDirectoryName = @"WKWebFileUpload";
608
609     // Build temporary file path.
610     NSString *temporaryDirectory = WebCore::FileSystem::createTemporaryDirectory(kTemporaryDirectoryName);
611     NSString *filePath = [temporaryDirectory stringByAppendingPathComponent:imageName];
612     if (!filePath) {
613         LOG_ERROR("WKFileUploadPanel: Failed to create temporary directory to save image");
614         failureBlock();
615         return;
616     }
617
618     // Save the image to the temporary file.
619     NSError *error = nil;
620     [imageData writeToFile:filePath options:NSDataWritingAtomic error:&error];
621     if (error) {
622         LOG_ERROR("WKFileUploadPanel: Error writing image data to temporary file: %@", error);
623         failureBlock();
624         return;
625     }
626
627     successBlock(adoptNS([[_WKImageFileUploadItem alloc] initWithFileURL:[NSURL fileURLWithPath:filePath]]).get());
628 }
629
630 - (void)_uploadItemForJPEGRepresentationOfImage:(UIImage *)image successBlock:(void (^)(_WKFileUploadItem *))successBlock failureBlock:(void (^)(void))failureBlock
631 {
632     ASSERT_ARG(image, image);
633
634     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
635         // FIXME: Different compression for different devices?
636         // FIXME: Different compression for different UIImage sizes?
637         // FIXME: Should EXIF data be maintained?
638         const CGFloat compression = 0.8;
639         NSData *jpeg = UIImageJPEGRepresentation(image, compression);
640         if (!jpeg) {
641             LOG_ERROR("WKFileUploadPanel: Failed to create JPEG representation for image");
642             failureBlock();
643             return;
644         }
645
646         // FIXME: Should we get the photo asset and get the actual filename for the photo instead of
647         // naming each of the individual uploads image.jpg? This won't work for photos taken with
648         // the camera, but would work for photos picked from the library.
649         NSString * const kUploadImageName = @"image.jpg";
650         [self _uploadItemForImageData:jpeg imageName:kUploadImageName successBlock:successBlock failureBlock:failureBlock];
651     });
652 }
653
654 - (void)_uploadItemFromMediaInfo:(NSDictionary *)info successBlock:(void (^)(_WKFileUploadItem *))successBlock failureBlock:(void (^)(void))failureBlock
655 {
656     NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
657
658     // For videos from the existing library or camera, the media URL will give us a file path.
659     if (UTTypeConformsTo((CFStringRef)mediaType, kUTTypeMovie)) {
660         NSURL *mediaURL = [info objectForKey:UIImagePickerControllerMediaURL];
661         if (![mediaURL isFileURL]) {
662             LOG_ERROR("WKFileUploadPanel: Expected media URL to be a file path, it was not");
663             ASSERT_NOT_REACHED();
664             failureBlock();
665             return;
666         }
667
668         successBlock(adoptNS([[_WKVideoFileUploadItem alloc] initWithFileURL:mediaURL]).get());
669         return;
670     }
671
672     if (!UTTypeConformsTo((CFStringRef)mediaType, kUTTypeImage)) {
673         LOG_ERROR("WKFileUploadPanel: Unexpected media type. Expected image or video, got: %@", mediaType);
674         ASSERT_NOT_REACHED();
675         failureBlock();
676         return;
677     }
678
679 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
680     if (NSURL *imageURL = info[UIImagePickerControllerImageURL]) {
681         if (!imageURL.isFileURL) {
682             LOG_ERROR("WKFileUploadPanel: Expected image URL to be a file path, it was not");
683             ASSERT_NOT_REACHED();
684             failureBlock();
685             return;
686         }
687
688         successBlock(adoptNS([[_WKImageFileUploadItem alloc] initWithFileURL:imageURL]).get());
689         return;
690     }
691 #endif
692
693     UIImage *originalImage = [info objectForKey:UIImagePickerControllerOriginalImage];
694     if (!originalImage) {
695         LOG_ERROR("WKFileUploadPanel: Expected image data but there was none");
696         ASSERT_NOT_REACHED();
697         failureBlock();
698         return;
699     }
700
701     // Photos taken with the camera will not have an image URL. Fall back to a JPEG representation.
702     [self _uploadItemForJPEGRepresentationOfImage:originalImage successBlock:successBlock failureBlock:failureBlock];
703 }
704
705 @end
706
707 #pragma clang diagnostic pop
708
709 #endif // PLATFORM(IOS)