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