[Extra zoom mode] Suppress UI for inputs of type file in extra zoom mode
[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 platformSupportsPickerViewController]) {
247         [self _cancel];
248         return;
249     }
250
251     if ([self _shouldMediaCaptureOpenMediaDevice]) {
252         [self _adjustMediaCaptureType];
253
254         _usingCamera = YES;
255         [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypeCamera];
256
257         return;
258     }
259
260     [self _showDocumentPickerMenu];
261 }
262
263 - (void)dismiss
264 {
265     // Dismiss any view controller that is being presented. This works for all types of view controllers, popovers, etc.
266     // If there is any kind of view controller presented on this view, it will be removed. 
267     
268     [[UIViewController _viewControllerForFullScreenPresentationFromView:_view] dismissViewControllerAnimated:NO completion:nil];
269     
270     [_presentationPopover setDelegate:nil];
271     _presentationPopover = nil;
272     _presentationViewController = nil;
273     
274     [self _cancel];
275 }
276
277 - (void)_dismissDisplayAnimated:(BOOL)animated
278 {
279     if (_presentationPopover) {
280         [_presentationPopover dismissPopoverAnimated:animated];
281         [_presentationPopover setDelegate:nil];
282         _presentationPopover = nil;
283     }
284
285     if (_presentationViewController) {
286         [_presentationViewController dismissViewControllerAnimated:animated completion:^{
287             _presentationViewController = nil;
288         }];
289     }
290 }
291
292 #pragma mark - Media Types
293
294 static bool stringHasPrefixCaseInsensitive(NSString *str, NSString *prefix)
295 {
296     NSRange range = [str rangeOfString:prefix options:(NSCaseInsensitiveSearch | NSAnchoredSearch)];
297     return range.location != NSNotFound;
298 }
299
300 static NSArray *UTIsForMIMETypes(NSArray *mimeTypes)
301 {
302     // The HTML5 spec mentions the literal "image/*" and "video/*" strings.
303     // We support these and go a step further, if the MIME type starts with
304     // "image/" or "video/" we adjust the picker's image or video filters.
305     // So, "image/jpeg" would make the picker display all images types.
306     NSMutableSet *mediaTypes = [NSMutableSet set];
307     for (NSString *mimeType in mimeTypes) {
308         // FIXME: We should support more MIME type -> UTI mappings. <http://webkit.org/b/142614>
309         if (stringHasPrefixCaseInsensitive(mimeType, @"image/"))
310             [mediaTypes addObject:(NSString *)kUTTypeImage];
311         else if (stringHasPrefixCaseInsensitive(mimeType, @"video/"))
312             [mediaTypes addObject:(NSString *)kUTTypeMovie];
313     }
314
315     return mediaTypes.allObjects;
316 }
317
318 - (NSArray *)_mediaTypesForPickerSourceType:(UIImagePickerControllerSourceType)sourceType
319 {
320     NSArray *mediaTypes = UTIsForMIMETypes(_mimeTypes.get());
321     if (mediaTypes.count)
322         return mediaTypes;
323
324     // Fallback to every supported media type if there is no filter.
325     return [UIImagePickerController availableMediaTypesForSourceType:sourceType];
326 }
327
328 - (NSArray *)_documentPickerMenuMediaTypes
329 {
330     NSArray *mediaTypes = UTIsForMIMETypes(_mimeTypes.get());
331     if (mediaTypes.count)
332         return mediaTypes;
333
334     // Fallback to every supported media type if there is no filter.
335     return @[@"public.item"];
336 }
337
338 #pragma mark - Source selection menu
339
340 - (NSString *)_photoLibraryButtonLabel
341 {
342     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");
343 }
344
345 - (NSString *)_cameraButtonLabel
346 {
347     if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
348         return nil;
349
350     // Choose the appropriate string for the camera button.
351     NSArray *filteredMediaTypes = [self _mediaTypesForPickerSourceType:UIImagePickerControllerSourceTypeCamera];
352     BOOL containsImageMediaType = [filteredMediaTypes containsObject:(NSString *)kUTTypeImage];
353     BOOL containsVideoMediaType = [filteredMediaTypes containsObject:(NSString *)kUTTypeMovie];
354     ASSERT(containsImageMediaType || containsVideoMediaType);
355     if (containsImageMediaType && containsVideoMediaType)
356         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");
357
358     if (containsVideoMediaType)
359         return WEB_UI_STRING_KEY("Take Video", "Take Video (file upload action sheet)", "File Upload alert sheet camera button string for taking only videos");
360
361     return WEB_UI_STRING_KEY("Take Photo", "Take Photo (file upload action sheet)", "File Upload alert sheet camera button string for taking only photos");
362 }
363
364 - (void)_showDocumentPickerMenu
365 {
366     // FIXME: Support multiple file selection when implemented. <rdar://17177981>
367     _documentMenuController = adoptNS([[UIDocumentMenuViewController alloc] _initIgnoringApplicationEntitlementForImportOfTypes:[self _documentPickerMenuMediaTypes]]);
368     [_documentMenuController setDelegate:self];
369
370     [_documentMenuController addOptionWithTitle:[self _photoLibraryButtonLabel] image:photoLibraryIcon() order:UIDocumentMenuOrderFirst handler:^{
371         [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
372     }];
373
374     if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
375         if (NSString *cameraString = [self _cameraButtonLabel]) {
376             [_documentMenuController addOptionWithTitle:cameraString image:cameraIcon() order:UIDocumentMenuOrderFirst handler:^{
377                 _usingCamera = YES;
378                 [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypeCamera];
379             }];
380         }
381     }
382
383     [self _presentMenuOptionForCurrentInterfaceIdiom:_documentMenuController.get()];
384     // Clear out the view controller we just presented. Don't save a reference to the UIDocumentMenuViewController as it is self dismissing.
385     _presentationViewController = nil;
386 }
387
388 #pragma mark - Image Picker
389
390 - (void)_adjustMediaCaptureType
391 {
392     if ([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront] || [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear]) {
393         if (![UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront])
394             _mediaCaptureType = WebCore::MediaCaptureTypeEnvironment;
395         
396         if (![UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear])
397             _mediaCaptureType = WebCore::MediaCaptureTypeUser;
398         
399         return;
400     }
401     
402     _mediaCaptureType = WebCore::MediaCaptureTypeNone;
403 }
404
405 - (BOOL)_shouldMediaCaptureOpenMediaDevice
406 {
407     if (_mediaCaptureType == WebCore::MediaCaptureTypeNone || ![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
408         return NO;
409     
410     return YES;
411 }
412
413 - (void)_showPhotoPickerWithSourceType:(UIImagePickerControllerSourceType)sourceType
414 {
415     ASSERT([UIImagePickerController isSourceTypeAvailable:sourceType]);
416     
417     _imagePicker = adoptNS([[UIImagePickerController alloc] init]);
418     [_imagePicker setDelegate:self];
419     [_imagePicker setSourceType:sourceType];
420     [_imagePicker setAllowsEditing:NO];
421     [_imagePicker setModalPresentationStyle:UIModalPresentationFullScreen];
422     [_imagePicker _setAllowsMultipleSelection:_allowMultipleFiles];
423     [_imagePicker setMediaTypes:[self _mediaTypesForPickerSourceType:sourceType]];
424
425     if (_mediaCaptureType != WebCore::MediaCaptureTypeNone)
426         [_imagePicker setCameraDevice:cameraDeviceForMediaCaptureType(_mediaCaptureType)];
427     
428     // Use a popover on the iPad if the source type is not the camera.
429     // The camera will use a fullscreen, modal view controller.
430     BOOL usePopover = currentUserInterfaceIdiomIsPad() && sourceType != UIImagePickerControllerSourceTypeCamera;
431     if (usePopover)
432         [self _presentPopoverWithContentViewController:_imagePicker.get() animated:YES];
433     else
434         [self _presentFullscreenViewController:_imagePicker.get() animated:YES];
435 }
436
437 #pragma mark - Presenting View Controllers
438
439 - (void)_presentMenuOptionForCurrentInterfaceIdiom:(UIViewController *)viewController
440 {
441     if (currentUserInterfaceIdiomIsPad())
442         [self _presentPopoverWithContentViewController:viewController animated:YES];
443     else
444         [self _presentFullscreenViewController:viewController animated:YES];
445 }
446
447 - (void)_presentPopoverWithContentViewController:(UIViewController *)contentViewController animated:(BOOL)animated
448 {
449     [self _dismissDisplayAnimated:animated];
450
451 #pragma clang diagnostic push
452 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
453     _presentationPopover = adoptNS([[UIPopoverController alloc] initWithContentViewController:contentViewController]);
454 #pragma clang diagnostic pop
455     [_presentationPopover setDelegate:self];
456     [_presentationPopover presentPopoverFromRect:CGRectIntegral(CGRectMake(_interactionPoint.x, _interactionPoint.y, 1, 1)) inView:_view permittedArrowDirections:UIPopoverArrowDirectionAny animated:animated];
457 }
458
459 - (void)_presentFullscreenViewController:(UIViewController *)viewController animated:(BOOL)animated
460 {
461     [self _dismissDisplayAnimated:animated];
462
463     _presentationViewController = [UIViewController _viewControllerForFullScreenPresentationFromView:_view];
464     [_presentationViewController presentViewController:viewController animated:animated completion:nil];
465 }
466
467 #pragma mark - UIPopoverControllerDelegate
468
469 - (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
470 {
471     [self _cancel];
472 }
473
474 #pragma mark - UIDocumentMenuDelegate implementation
475
476 - (void)documentMenu:(UIDocumentMenuViewController *)documentMenu didPickDocumentPicker:(UIDocumentPickerViewController *)documentPicker
477 {
478     documentPicker.delegate = self;
479     [self _presentFullscreenViewController:documentPicker animated:YES];
480 }
481
482 - (void)documentMenuWasCancelled:(UIDocumentMenuViewController *)documentMenu
483 {
484     [self _dismissDisplayAnimated:YES];
485     [self _cancel];
486 }
487
488 #pragma mark - UIDocumentPickerControllerDelegate implementation
489
490 - (void)documentPicker:(UIDocumentPickerViewController *)documentPicker didPickDocumentAtURL:(NSURL *)url
491 {
492     [self _dismissDisplayAnimated:YES];
493     [self _chooseFiles:@[url] displayString:url.lastPathComponent iconImage:iconForFile(url)];
494 }
495
496 - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)documentPicker
497 {
498     [self _dismissDisplayAnimated:YES];
499     [self _cancel];
500 }
501
502 #pragma mark - UIImagePickerControllerDelegate implementation
503
504 - (BOOL)_willMultipleSelectionDelegateBeCalled
505 {
506     // The multiple selection delegate will not be called when the UIImagePicker was not multiple selection.
507     if (!_allowMultipleFiles)
508         return NO;
509
510     // The multiple selection delegate will not be called when we used the camera in the UIImagePicker.
511     if (_usingCamera)
512         return NO;
513
514     return YES;
515 }
516
517 - (void)imagePickerController:(UIImagePickerController *)imagePicker didFinishPickingMediaWithInfo:(NSDictionary *)info
518 {
519     // Sometimes both delegates get called, sometimes just one. Always let the
520     // multiple selection delegate handle everything if it will get called.
521     if ([self _willMultipleSelectionDelegateBeCalled])
522         return;
523
524     [self _dismissDisplayAnimated:YES];
525
526     [self _processMediaInfoDictionaries:[NSArray arrayWithObject:info]
527         successBlock:^(NSArray *processedResults, NSString *displayString) {
528             ASSERT([processedResults count] == 1);
529             _WKFileUploadItem *result = [processedResults objectAtIndex:0];
530             dispatch_async(dispatch_get_main_queue(), ^{
531                 [self _chooseFiles:[NSArray arrayWithObject:result.fileURL] displayString:displayString iconImage:result.displayImage];
532             });
533         }
534         failureBlock:^{
535             dispatch_async(dispatch_get_main_queue(), ^{
536                 [self _cancel];
537             });
538         }
539     ];
540 }
541
542 - (void)imagePickerController:(UIImagePickerController *)imagePicker didFinishPickingMultipleMediaWithInfo:(NSArray *)infos
543 {
544     [self _dismissDisplayAnimated:YES];
545
546     [self _processMediaInfoDictionaries:infos
547         successBlock:^(NSArray *processedResults, NSString *displayString) {
548             UIImage *iconImage = nil;
549             NSMutableArray *fileURLs = [NSMutableArray array];
550             for (_WKFileUploadItem *result in processedResults) {
551                 NSURL *fileURL = result.fileURL;
552                 if (!fileURL)
553                     continue;
554                 [fileURLs addObject:result.fileURL];
555                 if (!iconImage)
556                     iconImage = result.displayImage;
557             }
558
559             dispatch_async(dispatch_get_main_queue(), ^{
560                 [self _chooseFiles:fileURLs displayString:displayString iconImage:iconImage];
561             });
562         }
563         failureBlock:^{
564             dispatch_async(dispatch_get_main_queue(), ^{
565                 [self _cancel];
566             });
567         }
568     ];
569 }
570
571 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)imagePicker
572 {
573     [self _dismissDisplayAnimated:YES];
574     [self _cancel];
575 }
576
577 #pragma mark - Process UIImagePicker results
578
579 - (void)_processMediaInfoDictionaries:(NSArray *)infos successBlock:(void (^)(NSArray *processedResults, NSString *displayString))successBlock failureBlock:(void (^)(void))failureBlock
580 {
581     [self _processMediaInfoDictionaries:infos atIndex:0 processedResults:[NSMutableArray array] processedImageCount:0 processedVideoCount:0 successBlock:successBlock failureBlock:failureBlock];
582 }
583
584 - (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
585 {
586     NSUInteger count = [infos count];
587     if (index == count) {
588         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;
589         successBlock(processedResults, displayString);
590         return;
591     }
592
593     NSDictionary *info = [infos objectAtIndex:index];
594     ASSERT(index < count);
595     index++;
596
597     auto uploadItemSuccessBlock = ^(_WKFileUploadItem *uploadItem) {
598         NSUInteger newProcessedVideoCount = processedVideoCount + (uploadItem.isVideo ? 1 : 0);
599         NSUInteger newProcessedImageCount = processedImageCount + (uploadItem.isVideo ? 0 : 1);
600         [processedResults addObject:uploadItem];
601         [self _processMediaInfoDictionaries:infos atIndex:index processedResults:processedResults processedImageCount:newProcessedImageCount processedVideoCount:newProcessedVideoCount successBlock:successBlock failureBlock:failureBlock];
602     };
603
604     [self _uploadItemFromMediaInfo:info successBlock:uploadItemSuccessBlock failureBlock:failureBlock];
605 }
606
607 - (void)_uploadItemForImageData:(NSData *)imageData imageName:(NSString *)imageName successBlock:(void (^)(_WKFileUploadItem *))successBlock failureBlock:(void (^)(void))failureBlock
608 {
609     ASSERT_ARG(imageData, imageData);
610     ASSERT(!RunLoop::isMain());
611
612     NSString * const kTemporaryDirectoryName = @"WKWebFileUpload";
613
614     // Build temporary file path.
615     NSString *temporaryDirectory = WebCore::FileSystem::createTemporaryDirectory(kTemporaryDirectoryName);
616     NSString *filePath = [temporaryDirectory stringByAppendingPathComponent:imageName];
617     if (!filePath) {
618         LOG_ERROR("WKFileUploadPanel: Failed to create temporary directory to save image");
619         failureBlock();
620         return;
621     }
622
623     // Save the image to the temporary file.
624     NSError *error = nil;
625     [imageData writeToFile:filePath options:NSDataWritingAtomic error:&error];
626     if (error) {
627         LOG_ERROR("WKFileUploadPanel: Error writing image data to temporary file: %@", error);
628         failureBlock();
629         return;
630     }
631
632     successBlock(adoptNS([[_WKImageFileUploadItem alloc] initWithFileURL:[NSURL fileURLWithPath:filePath]]).get());
633 }
634
635 - (void)_uploadItemForJPEGRepresentationOfImage:(UIImage *)image successBlock:(void (^)(_WKFileUploadItem *))successBlock failureBlock:(void (^)(void))failureBlock
636 {
637     ASSERT_ARG(image, image);
638
639     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
640         // FIXME: Different compression for different devices?
641         // FIXME: Different compression for different UIImage sizes?
642         // FIXME: Should EXIF data be maintained?
643         const CGFloat compression = 0.8;
644         NSData *jpeg = UIImageJPEGRepresentation(image, compression);
645         if (!jpeg) {
646             LOG_ERROR("WKFileUploadPanel: Failed to create JPEG representation for image");
647             failureBlock();
648             return;
649         }
650
651         // FIXME: Should we get the photo asset and get the actual filename for the photo instead of
652         // naming each of the individual uploads image.jpg? This won't work for photos taken with
653         // the camera, but would work for photos picked from the library.
654         NSString * const kUploadImageName = @"image.jpg";
655         [self _uploadItemForImageData:jpeg imageName:kUploadImageName successBlock:successBlock failureBlock:failureBlock];
656     });
657 }
658
659 - (void)_uploadItemFromMediaInfo:(NSDictionary *)info successBlock:(void (^)(_WKFileUploadItem *))successBlock failureBlock:(void (^)(void))failureBlock
660 {
661     NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
662
663     // For videos from the existing library or camera, the media URL will give us a file path.
664     if (UTTypeConformsTo((CFStringRef)mediaType, kUTTypeMovie)) {
665         NSURL *mediaURL = [info objectForKey:UIImagePickerControllerMediaURL];
666         if (![mediaURL isFileURL]) {
667             LOG_ERROR("WKFileUploadPanel: Expected media URL to be a file path, it was not");
668             ASSERT_NOT_REACHED();
669             failureBlock();
670             return;
671         }
672
673         successBlock(adoptNS([[_WKVideoFileUploadItem alloc] initWithFileURL:mediaURL]).get());
674         return;
675     }
676
677     if (!UTTypeConformsTo((CFStringRef)mediaType, kUTTypeImage)) {
678         LOG_ERROR("WKFileUploadPanel: Unexpected media type. Expected image or video, got: %@", mediaType);
679         ASSERT_NOT_REACHED();
680         failureBlock();
681         return;
682     }
683
684 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
685     if (NSURL *imageURL = info[UIImagePickerControllerImageURL]) {
686         if (!imageURL.isFileURL) {
687             LOG_ERROR("WKFileUploadPanel: Expected image URL to be a file path, it was not");
688             ASSERT_NOT_REACHED();
689             failureBlock();
690             return;
691         }
692
693         successBlock(adoptNS([[_WKImageFileUploadItem alloc] initWithFileURL:imageURL]).get());
694         return;
695     }
696 #endif
697
698     UIImage *originalImage = [info objectForKey:UIImagePickerControllerOriginalImage];
699     if (!originalImage) {
700         LOG_ERROR("WKFileUploadPanel: Expected image data but there was none");
701         ASSERT_NOT_REACHED();
702         failureBlock();
703         return;
704     }
705
706     // Photos taken with the camera will not have an image URL. Fall back to a JPEG representation.
707     [self _uploadItemForJPEGRepresentationOfImage:originalImage successBlock:successBlock failureBlock:failureBlock];
708 }
709
710 - (BOOL)platformSupportsPickerViewController
711 {
712 #if ENABLE(EXTRA_ZOOM_MODE)
713     return NO;
714 #else
715     return YES;
716 #endif
717 }
718
719 @end
720
721 #pragma clang diagnostic pop
722
723 #endif // PLATFORM(IOS)