[iOS] WebKit2 File Upload Support
[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/UIAlertController_Private.h>
45 #import <UIKit/UIApplication_Private.h>
46 #import <UIKit/UIImagePickerController_Private.h>
47 #import <UIKit/UIImage_Private.h>
48 #import <UIKit/UIViewController_Private.h>
49 #import <UIKit/UIWindow_Private.h>
50 #import <WebCore/LocalizedStrings.h>
51 #import <WebCore/SoftLinking.h>
52 #import <WebKit/WebNSFileManagerExtras.h>
53 #import <wtf/RetainPtr.h>
54
55 using namespace WebKit;
56
57 SOFT_LINK_FRAMEWORK(AVFoundation);
58 SOFT_LINK_CLASS(AVFoundation, AVAssetImageGenerator);
59 SOFT_LINK_CLASS(AVFoundation, AVURLAsset);
60 #define AVAssetImageGenerator_class getAVAssetImageGeneratorClass()
61 #define AVURLAsset_class getAVURLAssetClass()
62
63 SOFT_LINK_FRAMEWORK(CoreMedia);
64 SOFT_LINK_CONSTANT(CoreMedia, kCMTimeZero, CMTime);
65 #define kCMTimeZero getkCMTimeZero()
66
67
68 #pragma mark - _WKFileUploadItem
69
70 static CGRect squareCropRectForSize(CGSize size)
71 {
72     CGFloat smallerSide = MIN(size.width, size.height);
73     CGRect cropRect = CGRectMake(0, 0, smallerSide, smallerSide);
74
75     if (size.width < size.height)
76         cropRect.origin.y = rintf((size.height - smallerSide) / 2);
77     else
78         cropRect.origin.x = rintf((size.width - smallerSide) / 2);
79
80     return cropRect;
81 }
82
83 static UIImage *squareImage(UIImage *image)
84 {
85     if (!image)
86         return nil;
87
88     CGImageRef imageRef = [image CGImage];
89     CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
90     if (imageSize.width == imageSize.height)
91         return image;
92
93     CGRect squareCropRect = squareCropRectForSize(imageSize);
94     CGImageRef squareImageRef = CGImageCreateWithImageInRect(imageRef, squareCropRect);
95     UIImage *squareImage = [[UIImage alloc] initWithCGImage:squareImageRef imageOrientation:[image imageOrientation]];
96     CGImageRelease(squareImageRef);
97     return [squareImage autorelease];
98 }
99
100 static UIImage *thumbnailSizedImageForImage(UIImage *image)
101 {
102     UIImage *squaredImage = squareImage(image);
103     if (!squaredImage)
104         return nil;
105
106     CGRect destRect = CGRectMake(0, 0, 100, 100);
107     UIGraphicsBeginImageContext(destRect.size);
108     CGContextSetInterpolationQuality(UIGraphicsGetCurrentContext(), kCGInterpolationHigh);
109     [squaredImage drawInRect:destRect];
110     UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
111     UIGraphicsEndImageContext();
112     return resultImage;
113 }
114
115
116 @interface _WKFileUploadItem : NSObject
117 @property (nonatomic, readonly, getter=isVideo) BOOL video;
118 @property (nonatomic, readonly) NSURL *fileURL;
119 @property (nonatomic, readonly) UIImage *displayImage;
120 @end
121
122 @implementation _WKFileUploadItem
123
124 - (BOOL)isVideo
125 {
126     ASSERT_NOT_REACHED();
127     return NO;
128 }
129
130 - (NSURL *)fileURL
131 {
132     ASSERT_NOT_REACHED();
133     return nil;
134 }
135
136 - (UIImage *)displayImage
137 {
138     ASSERT_NOT_REACHED();
139     return nil;
140 }
141
142 @end
143
144
145 @interface _WKImageFileUploadItem : _WKFileUploadItem
146 - (instancetype)initWithFilePath:(NSString *)filePath originalImage:(UIImage *)originalImage;
147 @end
148
149 @implementation _WKImageFileUploadItem {
150     RetainPtr<NSString> _filePath;
151     RetainPtr<UIImage> _originalImage;
152 }
153
154 - (instancetype)initWithFilePath:(NSString *)filePath originalImage:(UIImage *)originalImage
155 {
156     if (!(self = [super init]))
157         return nil;
158     _filePath = filePath;
159     _originalImage = originalImage;
160     return self;
161 }
162
163 - (BOOL)isVideo
164 {
165     return NO;
166 }
167
168 - (NSURL *)fileURL
169 {
170     return [NSURL fileURLWithPath:_filePath.get()];
171 }
172
173 - (UIImage *)displayImage
174 {
175     return thumbnailSizedImageForImage(_originalImage.get());
176 }
177
178 @end
179
180
181 @interface _WKVideoFileUploadItem : _WKFileUploadItem
182 - (instancetype)initWithFilePath:(NSString *)filePath mediaURL:(NSURL *)mediaURL;
183 @end
184
185 @implementation _WKVideoFileUploadItem {
186     RetainPtr<NSString> _filePath;
187     RetainPtr<NSURL> _mediaURL;
188 }
189
190 - (instancetype)initWithFilePath:(NSString *)filePath mediaURL:(NSURL *)mediaURL
191 {
192     if (!(self = [super init]))
193         return nil;
194     _filePath = filePath;
195     _mediaURL = mediaURL;
196     return self;
197 }
198
199 - (BOOL)isVideo
200 {
201     return YES;
202 }
203
204 - (NSURL *)fileURL
205 {
206     return [NSURL fileURLWithPath:_filePath.get()];
207 }
208
209 - (UIImage *)displayImage
210 {
211     RetainPtr<AVURLAsset> asset = adoptNS([[AVURLAsset_class alloc] initWithURL:_mediaURL.get() options:nil]);
212     RetainPtr<AVAssetImageGenerator> generator = adoptNS([[AVAssetImageGenerator_class alloc] initWithAsset:asset.get()]);
213     [generator setAppliesPreferredTrackTransform:YES];
214
215     NSError *error = nil;
216     RetainPtr<CGImageRef> imageRef = adoptCF([generator copyCGImageAtTime:kCMTimeZero actualTime:nil error:&error]);
217     if (error) {
218         LOG_ERROR("_WKVideoFileUploadItem: Error creating image for video: %@", _mediaURL.get());
219         return nil;
220     }
221
222     RetainPtr<UIImage> image = adoptNS([[UIImage alloc] initWithCGImage:imageRef.get()]);
223     return thumbnailSizedImageForImage(image.get());
224 }
225
226 @end
227
228
229 #pragma mark - WKFileUploadPanel
230
231 @interface WKFileUploadPanel () <UIPopoverControllerDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate>
232 @end
233
234 @implementation WKFileUploadPanel {
235     WKContentView *_view;
236     WebKit::WebOpenPanelResultListenerProxy* _listener;
237     RetainPtr<NSArray> _mimeTypes;
238     CGPoint _interactionPoint;
239     BOOL _allowMultipleFiles;
240     BOOL _usingCamera;
241     RetainPtr<UIImagePickerController> _imagePicker;
242     RetainPtr<UIAlertController> _actionSheetController;
243     RetainPtr<UIViewController> _presentationViewController; // iPhone always. iPad for Fullscreen Camera.
244     RetainPtr<UIPopoverController> _presentationPopover; // iPad for action sheet and Photo Library.
245 }
246
247 - (instancetype)initWithView:(WKContentView *)view
248 {
249     if (!(self = [super init]))
250         return nil;
251     _view = view;
252     return self;
253 }
254
255 - (void)dealloc
256 {
257     [_imagePicker setDelegate:nil];
258     [_presentationPopover setDelegate:nil];
259     [super dealloc];
260 }
261
262 - (void)_dispatchDidDismiss
263 {
264     if ([_delegate respondsToSelector:@selector(fileUploadPanelDidDismiss:)])
265         [_delegate fileUploadPanelDidDismiss:self];
266 }
267
268 #pragma mark - Panel Completion (one of these must be called)
269
270 - (void)_cancel
271 {
272     _listener->cancel();
273     [self _dispatchDidDismiss];
274 }
275
276 - (void)_chooseFiles:(NSArray *)fileURLs displayString:(NSString *)displayString iconImage:(UIImage *)iconImage
277 {
278     NSUInteger count = [fileURLs count];
279     if (!count) {
280         [self _cancel];
281         return;
282     }
283
284     Vector<RefPtr<API::Object>> urls;
285     urls.reserveInitialCapacity(count);
286     for (NSURL *fileURL in fileURLs)
287         urls.uncheckedAppend(adoptRef(toImpl(WKURLCreateWithCFURL((CFURLRef)fileURL))));
288     RefPtr<API::Array> fileURLsRef = API::Array::create(std::move(urls));
289
290     NSData *jpeg = UIImageJPEGRepresentation(iconImage, 1.0);
291     RefPtr<API::Data> iconImageDataRef = adoptRef(toImpl(WKDataCreate(reinterpret_cast<const unsigned char*>([jpeg bytes]), [jpeg length])));
292
293     RefPtr<API::String> displayStringRef = adoptRef(toImpl(WKStringCreateWithCFString((CFStringRef)displayString)));
294
295     _listener->chooseFiles(fileURLsRef.get(), displayStringRef.get(), iconImageDataRef.get());
296     [self _dispatchDidDismiss];
297 }
298
299 #pragma mark - Present / Dismiss API
300
301 - (void)presentWithParameters:(WebKit::WebOpenPanelParameters*)parameters resultListener:(WebKit::WebOpenPanelResultListenerProxy*)listener
302 {
303     ASSERT(!_listener);
304
305     _listener = listener;
306     _allowMultipleFiles = parameters->allowMultipleFiles();
307     _interactionPoint = [_view lastInteractionLocation];
308
309     RefPtr<API::Array> acceptMimeTypes = parameters->acceptMIMETypes();
310     NSMutableArray *mimeTypes = [NSMutableArray arrayWithCapacity:acceptMimeTypes->size()];
311     for (const auto& mimeType : acceptMimeTypes->elementsOfType<API::String>())
312         [mimeTypes addObject:mimeType->string()];
313     _mimeTypes = adoptNS([mimeTypes copy]);
314
315     // If there is no camera or this is type=multiple, just show the image picker for the photo library.
316     // Otherwise, show an action sheet for the user to choose between camera or library.
317     if (_allowMultipleFiles || ![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
318         [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
319     else
320         [self _showMediaSourceSelectionSheet];
321 }
322
323 - (void)dismiss
324 {
325     [self _dismissDisplayAnimated:NO];
326     [self _cancel];
327 }
328
329 - (void)_dismissDisplayAnimated:(BOOL)animated
330 {
331     if (_presentationPopover) {
332         [_presentationPopover dismissPopoverAnimated:animated];
333         [_presentationPopover setDelegate:nil];
334         _presentationPopover = nil;
335     }
336
337     if (_presentationViewController) {
338         [_presentationViewController dismissViewControllerAnimated:animated completion:^{
339             _presentationViewController = nil;
340         }];
341     }
342 }
343
344 #pragma mark - Action Sheet
345
346 static bool stringHasPrefixCaseInsensitive(NSString *str, NSString *prefix)
347 {
348     NSRange range = [str rangeOfString:prefix options:(NSCaseInsensitiveSearch | NSAnchoredSearch)];
349     return range.location != NSNotFound;
350 }
351
352 - (NSArray *)_mediaTypesForPickerSourceType:(UIImagePickerControllerSourceType)sourceType
353 {
354     // The HTML5 spec mentions the literal "image/*" and "video/*" strings.
355     // We support these and go a step further, if the MIME type starts with
356     // "image/" or "video/" we adjust the picker's image or video filters.
357     // So, "image/jpeg" would make the picker display all images types.
358     NSMutableSet *mediaTypes = [NSMutableSet set];
359     for (NSString *mimeType in _mimeTypes.get()) {
360         if (stringHasPrefixCaseInsensitive(mimeType, @"image/"))
361             [mediaTypes addObject:(NSString *)kUTTypeImage];
362         else if (stringHasPrefixCaseInsensitive(mimeType, @"video/"))
363             [mediaTypes addObject:(NSString *)kUTTypeMovie];
364     }
365
366     if ([mediaTypes count])
367         return [mediaTypes allObjects];
368
369     // Fallback to every supported media type if there is no filter.
370     return [UIImagePickerController availableMediaTypesForSourceType:sourceType];
371 }
372
373 - (void)_showMediaSourceSelectionSheet
374 {
375     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");
376     NSString *cancelString = WEB_UI_STRING_KEY("Cancel", "Cancel (file upload action sheet)", "File Upload alert sheet button string to cancel");
377
378     // Choose the appropriate string for the camera button.
379     NSString *cameraString;
380     NSArray *filteredMediaTypes = [self _mediaTypesForPickerSourceType:UIImagePickerControllerSourceTypeCamera];
381     BOOL containsImageMediaType = [filteredMediaTypes containsObject:(NSString *)kUTTypeImage];
382     BOOL containsVideoMediaType = [filteredMediaTypes containsObject:(NSString *)kUTTypeMovie];
383     ASSERT(containsImageMediaType || containsVideoMediaType);
384     if (containsImageMediaType && containsVideoMediaType)
385         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");
386     else if (containsVideoMediaType)
387         cameraString = WEB_UI_STRING_KEY("Take Video", "Take Video (file upload action sheet)", "File Upload alert sheet camera button string for taking only videos");
388     else
389         cameraString = WEB_UI_STRING_KEY("Take Photo", "Take Photo (file upload action sheet)", "File Upload alert sheet camera button string for taking only photos");
390
391     _actionSheetController = [UIAlertController _alertControllerWithTitle:nil message:nil];
392     [_actionSheetController setPreferredStyle:UIAlertControllerStyleActionSheet];
393
394     [_actionSheetController _addActionWithTitle:cancelString style:UIAlertActionStyleCancel handler:^{
395         [self _cancel];
396         // We handled cancel ourselves. Prevent the popover controller delegate from cancelling when the popover dismissed.
397         [_presentationPopover setDelegate:nil];
398     }];
399
400     [_actionSheetController _addActionWithTitle:cameraString style:UIAlertActionStyleDefault handler:^{
401         _usingCamera = YES;
402         [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypeCamera];
403     }];
404
405     [_actionSheetController _addActionWithTitle:existingString style:UIAlertActionStyleDefault handler:^{
406         [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
407     }];
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)