a5b5c14aa837b80bce1e444d367209b844dc09ba
[WebKit-https.git] / Source / WebKit2 / UIProcess / ios / forms / WKFileUploadPanel.mm
1 /*
2  * Copyright (C) 2014 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 "APIString.h"
34 #import "WKContentViewInteraction.h"
35 #import "WKData.h"
36 #import "WKStringCF.h"
37 #import "WKURLCF.h"
38 #import "WebOpenPanelParameters.h"
39 #import "WebOpenPanelResultListenerProxy.h"
40 #import "WebPageProxy.h"
41 #import <AVFoundation/AVFoundation.h>
42 #import <CoreMedia/CoreMedia.h>
43 #import <MobileCoreServices/MobileCoreServices.h>
44 #import <UIKit/UIApplication_Private.h>
45 #import <UIKit/UIImagePickerController_Private.h>
46 #import <UIKit/UIImage_Private.h>
47 #import <UIKit/UIViewController_Private.h>
48 #import <UIKit/UIWindow_Private.h>
49 #import <WebCore/LocalizedStrings.h>
50 #import <WebCore/SoftLinking.h>
51 #import <WebKit/WebNSFileManagerExtras.h>
52 #import <wtf/RetainPtr.h>
53
54 using namespace WebKit;
55
56 SOFT_LINK_FRAMEWORK(AVFoundation);
57 SOFT_LINK_CLASS(AVFoundation, AVAssetImageGenerator);
58 SOFT_LINK_CLASS(AVFoundation, AVURLAsset);
59
60 SOFT_LINK_FRAMEWORK(CoreMedia);
61 SOFT_LINK_CONSTANT(CoreMedia, kCMTimeZero, CMTime);
62 #define kCMTimeZero getkCMTimeZero()
63
64
65 #pragma mark - _WKFileUploadItem
66
67 static CGRect squareCropRectForSize(CGSize size)
68 {
69     CGFloat smallerSide = MIN(size.width, size.height);
70     CGRect cropRect = CGRectMake(0, 0, smallerSide, smallerSide);
71
72     if (size.width < size.height)
73         cropRect.origin.y = rintf((size.height - smallerSide) / 2);
74     else
75         cropRect.origin.x = rintf((size.width - smallerSide) / 2);
76
77     return cropRect;
78 }
79
80 static UIImage *squareImage(UIImage *image)
81 {
82     if (!image)
83         return nil;
84
85     CGImageRef imageRef = [image CGImage];
86     CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
87     if (imageSize.width == imageSize.height)
88         return image;
89
90     CGRect squareCropRect = squareCropRectForSize(imageSize);
91     CGImageRef squareImageRef = CGImageCreateWithImageInRect(imageRef, squareCropRect);
92     UIImage *squareImage = [[UIImage alloc] initWithCGImage:squareImageRef imageOrientation:[image imageOrientation]];
93     CGImageRelease(squareImageRef);
94     return [squareImage autorelease];
95 }
96
97 static UIImage *thumbnailSizedImageForImage(UIImage *image)
98 {
99     UIImage *squaredImage = squareImage(image);
100     if (!squaredImage)
101         return nil;
102
103     CGRect destRect = CGRectMake(0, 0, 100, 100);
104     UIGraphicsBeginImageContext(destRect.size);
105     CGContextSetInterpolationQuality(UIGraphicsGetCurrentContext(), kCGInterpolationHigh);
106     [squaredImage drawInRect:destRect];
107     UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
108     UIGraphicsEndImageContext();
109     return resultImage;
110 }
111
112
113 @interface _WKFileUploadItem : NSObject
114 @property (nonatomic, readonly, getter=isVideo) BOOL video;
115 @property (nonatomic, readonly) NSURL *fileURL;
116 @property (nonatomic, readonly) UIImage *displayImage;
117 @end
118
119 @implementation _WKFileUploadItem
120
121 - (BOOL)isVideo
122 {
123     ASSERT_NOT_REACHED();
124     return NO;
125 }
126
127 - (NSURL *)fileURL
128 {
129     ASSERT_NOT_REACHED();
130     return nil;
131 }
132
133 - (UIImage *)displayImage
134 {
135     ASSERT_NOT_REACHED();
136     return nil;
137 }
138
139 @end
140
141
142 @interface _WKImageFileUploadItem : _WKFileUploadItem
143 - (instancetype)initWithFilePath:(NSString *)filePath originalImage:(UIImage *)originalImage;
144 @end
145
146 @implementation _WKImageFileUploadItem {
147     RetainPtr<NSString> _filePath;
148     RetainPtr<UIImage> _originalImage;
149 }
150
151 - (instancetype)initWithFilePath:(NSString *)filePath originalImage:(UIImage *)originalImage
152 {
153     if (!(self = [super init]))
154         return nil;
155     _filePath = filePath;
156     _originalImage = originalImage;
157     return self;
158 }
159
160 - (BOOL)isVideo
161 {
162     return NO;
163 }
164
165 - (NSURL *)fileURL
166 {
167     return [NSURL fileURLWithPath:_filePath.get()];
168 }
169
170 - (UIImage *)displayImage
171 {
172     return thumbnailSizedImageForImage(_originalImage.get());
173 }
174
175 @end
176
177
178 @interface _WKVideoFileUploadItem : _WKFileUploadItem
179 - (instancetype)initWithFilePath:(NSString *)filePath mediaURL:(NSURL *)mediaURL;
180 @end
181
182 @implementation _WKVideoFileUploadItem {
183     RetainPtr<NSString> _filePath;
184     RetainPtr<NSURL> _mediaURL;
185 }
186
187 - (instancetype)initWithFilePath:(NSString *)filePath mediaURL:(NSURL *)mediaURL
188 {
189     if (!(self = [super init]))
190         return nil;
191     _filePath = filePath;
192     _mediaURL = mediaURL;
193     return self;
194 }
195
196 - (BOOL)isVideo
197 {
198     return YES;
199 }
200
201 - (NSURL *)fileURL
202 {
203     return [NSURL fileURLWithPath:_filePath.get()];
204 }
205
206 - (UIImage *)displayImage
207 {
208     RetainPtr<AVURLAsset> asset = adoptNS([allocAVURLAssetInstance() initWithURL:_mediaURL.get() options:nil]);
209     RetainPtr<AVAssetImageGenerator> generator = adoptNS([allocAVAssetImageGeneratorInstance() initWithAsset:asset.get()]);
210     [generator setAppliesPreferredTrackTransform:YES];
211
212     NSError *error = nil;
213     RetainPtr<CGImageRef> imageRef = adoptCF([generator copyCGImageAtTime:kCMTimeZero actualTime:nil error:&error]);
214     if (error) {
215         LOG_ERROR("_WKVideoFileUploadItem: Error creating image for video: %@", _mediaURL.get());
216         return nil;
217     }
218
219     RetainPtr<UIImage> image = adoptNS([[UIImage alloc] initWithCGImage:imageRef.get()]);
220     return thumbnailSizedImageForImage(image.get());
221 }
222
223 @end
224
225
226 #pragma mark - WKFileUploadPanel
227
228 @interface WKFileUploadPanel () <UIPopoverControllerDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate>
229 @end
230
231 @implementation WKFileUploadPanel {
232     WKContentView *_view;
233     WebKit::WebOpenPanelResultListenerProxy* _listener;
234     RetainPtr<NSArray> _mimeTypes;
235     CGPoint _interactionPoint;
236     BOOL _allowMultipleFiles;
237     BOOL _usingCamera;
238     RetainPtr<UIImagePickerController> _imagePicker;
239     RetainPtr<UIAlertController> _actionSheetController;
240     RetainPtr<UIViewController> _presentationViewController; // iPhone always. iPad for Fullscreen Camera.
241     RetainPtr<UIPopoverController> _presentationPopover; // iPad for action sheet and Photo Library.
242 }
243
244 - (instancetype)initWithView:(WKContentView *)view
245 {
246     if (!(self = [super init]))
247         return nil;
248     _view = view;
249     return self;
250 }
251
252 - (void)dealloc
253 {
254     [_imagePicker setDelegate:nil];
255     [_presentationPopover setDelegate:nil];
256     [super dealloc];
257 }
258
259 - (void)_dispatchDidDismiss
260 {
261     if ([_delegate respondsToSelector:@selector(fileUploadPanelDidDismiss:)])
262         [_delegate fileUploadPanelDidDismiss:self];
263 }
264
265 #pragma mark - Panel Completion (one of these must be called)
266
267 - (void)_cancel
268 {
269     _listener->cancel();
270     [self _dispatchDidDismiss];
271 }
272
273 - (void)_chooseFiles:(NSArray *)fileURLs displayString:(NSString *)displayString iconImage:(UIImage *)iconImage
274 {
275     NSUInteger count = [fileURLs count];
276     if (!count) {
277         [self _cancel];
278         return;
279     }
280
281     Vector<RefPtr<API::Object>> urls;
282     urls.reserveInitialCapacity(count);
283     for (NSURL *fileURL in fileURLs)
284         urls.uncheckedAppend(adoptRef(toImpl(WKURLCreateWithCFURL((CFURLRef)fileURL))));
285     RefPtr<API::Array> fileURLsRef = API::Array::create(WTF::move(urls));
286
287     NSData *jpeg = UIImageJPEGRepresentation(iconImage, 1.0);
288     RefPtr<API::Data> iconImageDataRef = adoptRef(toImpl(WKDataCreate(reinterpret_cast<const unsigned char*>([jpeg bytes]), [jpeg length])));
289
290     RefPtr<API::String> displayStringRef = adoptRef(toImpl(WKStringCreateWithCFString((CFStringRef)displayString)));
291
292     _listener->chooseFiles(fileURLsRef.get(), displayStringRef.get(), iconImageDataRef.get());
293     [self _dispatchDidDismiss];
294 }
295
296 #pragma mark - Present / Dismiss API
297
298 - (void)presentWithParameters:(WebKit::WebOpenPanelParameters*)parameters resultListener:(WebKit::WebOpenPanelResultListenerProxy*)listener
299 {
300     ASSERT(!_listener);
301
302     _listener = listener;
303     _allowMultipleFiles = parameters->allowMultipleFiles();
304     _interactionPoint = [_view lastInteractionLocation];
305
306     RefPtr<API::Array> acceptMimeTypes = parameters->acceptMIMETypes();
307     NSMutableArray *mimeTypes = [NSMutableArray arrayWithCapacity:acceptMimeTypes->size()];
308     for (const auto& mimeType : acceptMimeTypes->elementsOfType<API::String>())
309         [mimeTypes addObject:mimeType->string()];
310     _mimeTypes = adoptNS([mimeTypes copy]);
311
312     // If there is no camera or this is type=multiple, just show the image picker for the photo library.
313     // Otherwise, show an action sheet for the user to choose between camera or library.
314     if (_allowMultipleFiles || ![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
315         [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
316     else
317         [self _showMediaSourceSelectionSheet];
318 }
319
320 - (void)dismiss
321 {
322     [self _dismissDisplayAnimated:NO];
323     [self _cancel];
324 }
325
326 - (void)_dismissDisplayAnimated:(BOOL)animated
327 {
328     if (_presentationPopover) {
329         [_presentationPopover dismissPopoverAnimated:animated];
330         [_presentationPopover setDelegate:nil];
331         _presentationPopover = nil;
332     }
333
334     if (_presentationViewController) {
335         [_presentationViewController dismissViewControllerAnimated:animated completion:^{
336             _presentationViewController = nil;
337         }];
338     }
339 }
340
341 #pragma mark - Action Sheet
342
343 static bool stringHasPrefixCaseInsensitive(NSString *str, NSString *prefix)
344 {
345     NSRange range = [str rangeOfString:prefix options:(NSCaseInsensitiveSearch | NSAnchoredSearch)];
346     return range.location != NSNotFound;
347 }
348
349 - (NSArray *)_mediaTypesForPickerSourceType:(UIImagePickerControllerSourceType)sourceType
350 {
351     // The HTML5 spec mentions the literal "image/*" and "video/*" strings.
352     // We support these and go a step further, if the MIME type starts with
353     // "image/" or "video/" we adjust the picker's image or video filters.
354     // So, "image/jpeg" would make the picker display all images types.
355     NSMutableSet *mediaTypes = [NSMutableSet set];
356     for (NSString *mimeType in _mimeTypes.get()) {
357         if (stringHasPrefixCaseInsensitive(mimeType, @"image/"))
358             [mediaTypes addObject:(NSString *)kUTTypeImage];
359         else if (stringHasPrefixCaseInsensitive(mimeType, @"video/"))
360             [mediaTypes addObject:(NSString *)kUTTypeMovie];
361     }
362
363     if ([mediaTypes count])
364         return [mediaTypes allObjects];
365
366     // Fallback to every supported media type if there is no filter.
367     return [UIImagePickerController availableMediaTypesForSourceType:sourceType];
368 }
369
370 - (void)_showMediaSourceSelectionSheet
371 {
372     NSString *existingString = 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");
373     NSString *cancelString = WEB_UI_STRING_KEY("Cancel", "Cancel (file upload action sheet)", "File Upload alert sheet button string to cancel");
374
375     // Choose the appropriate string for the camera button.
376     NSString *cameraString;
377     NSArray *filteredMediaTypes = [self _mediaTypesForPickerSourceType:UIImagePickerControllerSourceTypeCamera];
378     BOOL containsImageMediaType = [filteredMediaTypes containsObject:(NSString *)kUTTypeImage];
379     BOOL containsVideoMediaType = [filteredMediaTypes containsObject:(NSString *)kUTTypeMovie];
380     ASSERT(containsImageMediaType || containsVideoMediaType);
381     if (containsImageMediaType && containsVideoMediaType)
382         cameraString = 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");
383     else if (containsVideoMediaType)
384         cameraString = WEB_UI_STRING_KEY("Take Video", "Take Video (file upload action sheet)", "File Upload alert sheet camera button string for taking only videos");
385     else
386         cameraString = WEB_UI_STRING_KEY("Take Photo", "Take Photo (file upload action sheet)", "File Upload alert sheet camera button string for taking only photos");
387
388     _actionSheetController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
389
390     UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelString style:UIAlertActionStyleCancel handler:^(UIAlertAction *){
391         [self _cancel];
392         // We handled cancel ourselves. Prevent the popover controller delegate from cancelling when the popover dismissed.
393         [_presentationPopover setDelegate:nil];
394     }];
395
396     UIAlertAction *cameraAction = [UIAlertAction actionWithTitle:cameraString style:UIAlertActionStyleDefault handler:^(UIAlertAction *){
397         _usingCamera = YES;
398         [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypeCamera];
399     }];
400
401     UIAlertAction *photoLibraryAction = [UIAlertAction actionWithTitle:existingString style:UIAlertActionStyleDefault handler:^(UIAlertAction *){
402         [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
403     }];
404
405     [_actionSheetController addAction:cancelAction];
406     [_actionSheetController addAction:cameraAction];
407     [_actionSheetController addAction:photoLibraryAction];
408
409     if (UICurrentUserInterfaceIdiomIsPad())
410         [self _presentPopoverWithContentViewController:_actionSheetController.get() animated:YES];
411     else
412         [self _presentFullscreenViewController:_actionSheetController.get() animated:YES];
413 }
414
415 #pragma mark - Image Picker
416
417 - (void)_showPhotoPickerWithSourceType:(UIImagePickerControllerSourceType)sourceType
418 {
419     _imagePicker = adoptNS([[UIImagePickerController alloc] init]);
420     [_imagePicker setDelegate:self];
421     [_imagePicker setSourceType:sourceType];
422     [_imagePicker setAllowsEditing:NO];
423     [_imagePicker setModalPresentationStyle:UIModalPresentationFullScreen];
424     [_imagePicker _setAllowsMultipleSelection:_allowMultipleFiles];
425     [_imagePicker setMediaTypes:[self _mediaTypesForPickerSourceType:sourceType]];
426
427     // Use a popover on the iPad if the source type is not the camera.
428     // The camera will use a fullscreen, modal view controller.
429     BOOL usePopover = UICurrentUserInterfaceIdiomIsPad() && sourceType != UIImagePickerControllerSourceTypeCamera;
430     if (usePopover)
431         [self _presentPopoverWithContentViewController:_imagePicker.get() animated:YES];
432     else
433         [self _presentFullscreenViewController:_imagePicker.get() animated:YES];
434 }
435
436 #pragma mark - Presenting View Controllers
437
438 - (void)_presentPopoverWithContentViewController:(UIViewController *)contentViewController animated:(BOOL)animated
439 {
440     [self _dismissDisplayAnimated:animated];
441
442     _presentationPopover = adoptNS([[UIPopoverController alloc] initWithContentViewController:contentViewController]);
443     [_presentationPopover setDelegate:self];
444     [_presentationPopover presentPopoverFromRect:CGRectIntegral(CGRectMake(_interactionPoint.x, _interactionPoint.y, 1, 1)) inView:_view permittedArrowDirections:UIPopoverArrowDirectionAny animated:animated];
445 }
446
447 - (void)_presentFullscreenViewController:(UIViewController *)viewController animated:(BOOL)animated
448 {
449     [self _dismissDisplayAnimated:animated];
450
451     _presentationViewController = [UIViewController _viewControllerForFullScreenPresentationFromView:_view];
452     [_presentationViewController presentViewController:viewController animated:animated completion:nil];
453 }
454
455 #pragma mark - UIPopoverControllerDelegate
456
457 - (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
458 {
459     [self _cancel];
460 }
461
462 #pragma mark - UIImagePickerControllerDelegate implementation
463
464 - (BOOL)_willMultipleSelectionDelegateBeCalled
465 {
466     // The multiple selection delegate will not be called when the UIImagePicker was not multiple selection.
467     if (!_allowMultipleFiles)
468         return NO;
469
470     // The multiple selection delegate will not be called when we used the camera in the UIImagePicker.
471     if (_usingCamera)
472         return NO;
473
474     return YES;
475 }
476
477 - (void)imagePickerController:(UIImagePickerController *)imagePicker didFinishPickingMediaWithInfo:(NSDictionary *)info
478 {
479     // Sometimes both delegates get called, sometimes just one. Always let the
480     // multiple selection delegate handle everything if it will get called.
481     if ([self _willMultipleSelectionDelegateBeCalled])
482         return;
483
484     [self _dismissDisplayAnimated:YES];
485
486     [self _processMediaInfoDictionaries:[NSArray arrayWithObject:info]
487         successBlock:^(NSArray *processedResults, NSString *displayString) {
488             ASSERT([processedResults count] == 1);
489             _WKFileUploadItem *result = [processedResults objectAtIndex:0];
490             dispatch_async(dispatch_get_main_queue(), ^{
491                 [self _chooseFiles:[NSArray arrayWithObject:result.fileURL] displayString:displayString iconImage:result.displayImage];
492             });
493         }
494         failureBlock:^{
495             dispatch_async(dispatch_get_main_queue(), ^{
496                 [self _cancel];
497             });
498         }
499     ];
500 }
501
502 - (void)imagePickerController:(UIImagePickerController *)imagePicker didFinishPickingMultipleMediaWithInfo:(NSArray *)infos
503 {
504     [self _dismissDisplayAnimated:YES];
505
506     [self _processMediaInfoDictionaries:infos
507         successBlock:^(NSArray *processedResults, NSString *displayString) {
508             UIImage *iconImage = nil;
509             NSMutableArray *fileURLs = [NSMutableArray array];
510             for (_WKFileUploadItem *result in processedResults) {
511                 NSURL *fileURL = result.fileURL;
512                 if (!fileURL)
513                     continue;
514                 [fileURLs addObject:result.fileURL];
515                 if (!iconImage)
516                     iconImage = result.displayImage;
517             }
518
519             dispatch_async(dispatch_get_main_queue(), ^{
520                 [self _chooseFiles:fileURLs displayString:displayString iconImage:iconImage];
521             });
522         }
523         failureBlock:^{
524             dispatch_async(dispatch_get_main_queue(), ^{
525                 [self _cancel];
526             });
527         }
528     ];
529 }
530
531 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)imagePicker
532 {
533     [self _dismissDisplayAnimated:YES];
534     [self _cancel];
535 }
536
537 #pragma mark - Process UIImagePicker results
538
539 - (void)_processMediaInfoDictionaries:(NSArray *)infos successBlock:(void (^)(NSArray *processedResults, NSString *displayString))successBlock failureBlock:(void (^)())failureBlock
540 {
541     [self _processMediaInfoDictionaries:infos atIndex:0 processedResults:[NSMutableArray array] processedImageCount:0 processedVideoCount:0 successBlock:successBlock failureBlock:failureBlock];
542 }
543
544 - (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 (^)())failureBlock
545 {
546     NSUInteger count = [infos count];
547     if (index == count) {
548         NSString *displayString = [self _displayStringForPhotos:processedImageCount videos:processedVideoCount];
549         successBlock(processedResults, displayString);
550         return;
551     }
552
553     NSDictionary *info = [infos objectAtIndex:index];
554     ASSERT(index < count);
555     index++;
556
557     auto uploadItemSuccessBlock = ^(_WKFileUploadItem *uploadItem) {
558         NSUInteger newProcessedVideoCount = processedVideoCount + (uploadItem.isVideo ? 1 : 0);
559         NSUInteger newProcessedImageCount = processedImageCount + (uploadItem.isVideo ? 0 : 1);
560         [processedResults addObject:uploadItem];
561         [self _processMediaInfoDictionaries:infos atIndex:index processedResults:processedResults processedImageCount:newProcessedImageCount processedVideoCount:newProcessedVideoCount successBlock:successBlock failureBlock:failureBlock];
562     };
563
564     [self _uploadItemFromMediaInfo:info successBlock:uploadItemSuccessBlock failureBlock:failureBlock];
565 }
566
567 - (void)_uploadItemFromMediaInfo:(NSDictionary *)info successBlock:(void (^)(_WKFileUploadItem *))successBlock failureBlock:(void (^)())failureBlock
568 {
569     NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
570
571     // For videos from the existing library or camera, the media URL will give us a file path.
572     if (UTTypeConformsTo((CFStringRef)mediaType, kUTTypeMovie)) {
573         NSURL *mediaURL = [info objectForKey:UIImagePickerControllerMediaURL];
574         if (![mediaURL isFileURL]) {
575             LOG_ERROR("WKFileUploadPanel: Expected media URL to be a file path, it was not");
576             ASSERT_NOT_REACHED();
577             failureBlock();
578             return;
579         }
580
581         NSString *filePath = [mediaURL path];
582         successBlock(adoptNS([[_WKVideoFileUploadItem alloc] initWithFilePath:filePath mediaURL:mediaURL]).get());
583         return;
584     }
585
586     // For images, we create a temporary file path and use the original image.
587     if (UTTypeConformsTo((CFStringRef)mediaType, kUTTypeImage)) {
588         UIImage *originalImage = [info objectForKey:UIImagePickerControllerOriginalImage];
589         if (!originalImage) {
590             LOG_ERROR("WKFileUploadPanel: Expected image data but there was none");
591             ASSERT_NOT_REACHED();
592             failureBlock();
593             return;
594         }
595
596         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
597             NSString * const kTemporaryDirectoryName = @"WKWebFileUpload";
598             NSString * const kUploadImageName = @"image.jpg";
599
600             // Build temporary file path.
601             // FIXME: Should we get the ALAsset for the mediaURL and get the actual filename for the photo
602             // instead of naming each of the individual uploads image.jpg? This won't work for photos
603             // taken with the camera, but would work for photos picked from the library.
604             NSFileManager *fileManager = [NSFileManager defaultManager];
605             NSString *temporaryDirectory = [fileManager _webkit_createTemporaryDirectoryWithTemplatePrefix:kTemporaryDirectoryName];
606             NSString *filePath = [temporaryDirectory stringByAppendingPathComponent:kUploadImageName];
607             if (!filePath) {
608                 LOG_ERROR("WKFileUploadPanel: Failed to create temporary directory to save image");
609                 failureBlock();
610                 return;
611             }
612
613             // Compress to JPEG format.
614             // FIXME: Different compression for different devices?
615             // FIXME: Different compression for different UIImage sizes?
616             // FIXME: Should EXIF data be maintained?
617             const CGFloat compression = 0.8;
618             NSData *jpeg = UIImageJPEGRepresentation(originalImage, compression);
619             if (!jpeg) {
620                 LOG_ERROR("WKFileUploadPanel: Failed to create JPEG representation for image");
621                 failureBlock();
622                 return;
623             }
624
625             // Save the image to the temporary file.
626             NSError *error = nil;
627             [jpeg writeToFile:filePath options:NSDataWritingAtomic error:&error];
628             if (error) {
629                 LOG_ERROR("WKFileUploadPanel: Error writing image data to temporary file: %@", error);
630                 failureBlock();
631                 return;
632             }
633
634             successBlock(adoptNS([[_WKImageFileUploadItem alloc] initWithFilePath:filePath originalImage:originalImage]).get());
635         });
636         return;
637     }
638
639     // Unknown media type.
640     LOG_ERROR("WKFileUploadPanel: Unexpected media type. Expected image or video, got: %@", mediaType);
641     failureBlock();
642 }
643
644 - (NSString *)_displayStringForPhotos:(NSUInteger)imageCount videos:(NSUInteger)videoCount
645 {
646     if (!imageCount && !videoCount)
647         return nil;
648
649     NSString *title;
650     NSString *countString;
651     NSString *imageString;
652     NSString *videoString;
653     NSUInteger numberOfTypes = 2;
654
655     RetainPtr<NSNumberFormatter> countFormatter = adoptNS([[NSNumberFormatter alloc] init]);
656     [countFormatter setLocale:[NSLocale currentLocale]];
657     [countFormatter setGeneratesDecimalNumbers:YES];
658     [countFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
659
660     // Generate the individual counts for each type.
661     switch (imageCount) {
662     case 0:
663         imageString = nil;
664         --numberOfTypes;
665         break;
666     case 1:
667         imageString = WEB_UI_STRING_KEY("1 Photo", "1 Photo (file upload on page label for one photo)", "File Upload single photo label");
668         break;
669     default:
670         countString = [countFormatter stringFromNumber:@(imageCount)];
671         imageString = [NSString stringWithFormat:WEB_UI_STRING_KEY("%@ Photos", "# Photos (file upload on page label for multiple photos)", "File Upload multiple photos label"), countString];
672         break;
673     }
674
675     switch (videoCount) {
676     case 0:
677         videoString = nil;
678         --numberOfTypes;
679         break;
680     case 1:
681         videoString = WEB_UI_STRING_KEY("1 Video", "1 Video (file upload on page label for one video)", "File Upload single video label");
682         break;
683     default:
684         countString = [countFormatter stringFromNumber:@(videoCount)];
685         videoString = [NSString stringWithFormat:WEB_UI_STRING_KEY("%@ Videos", "# Videos (file upload on page label for multiple videos)", "File Upload multiple videos label"), countString];
686         break;
687     }
688
689     // Combine into a single result string if needed.
690     switch (numberOfTypes) {
691     case 2:
692         // FIXME: For localization we should build a complete string. We should have a localized string for each different combination.
693         title = [NSString stringWithFormat:WEB_UI_STRING_KEY("%@ and %@", "# Photos and # Videos (file upload on page label for image and videos)", "File Upload images and videos label"), imageString, videoString];
694         break;
695     case 1:
696         title = imageString ? imageString : videoString;
697         break;
698     default:
699         ASSERT_NOT_REACHED();
700         title = nil;
701         break;
702     }
703
704     return [title lowercaseString];
705 }
706
707 @end
708
709 #endif // PLATFORM(IOS)