[iOS] Improve our file picker
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 Mar 2019 20:20:59 +0000 (20:20 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 Mar 2019 20:20:59 +0000 (20:20 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195284
<rdar://problem/45655856>

Reviewed by Tim Horton and Wenson Hsieh.

Source/WebCore:

Export UTIUtilities.h so that it can be used from WebKit2.

* WebCore.xcodeproj/project.pbxproj:

Source/WebKit:

Improve our file picker on iOS so that:
- Accepted file extensions specified in the HTML (e.g. <input type="file"> accept=".pdf">) are now
  properly reflected in the file picker. Previously, we only we only supported MIME types in the
  accept attribute that only Image / Video ones.
- If accepted types are specified in the HTML and not of them are Video or Image types, then bypass
  the UIDocumentMenuViewController and show the file picker directly (as if the user had tapped on
  "Browse..." on that menu). Other menu items such as "Take Photo or Video" and "Photo Library" do
  not make sense if the page only accepts PDF files for example.

Things that we should do but are not fixed in this patch:
- Stop using UIDocumentMenuViewController entirely since it was deprecated in favor of using
  UIDocumentPickerViewController directly.
- Add multiple selection support, which is supported both on the HTML side and in the
  UIDocumentPickerViewController API.

* UIProcess/ios/forms/WKFileUploadPanel.mm:
(arrayContainsUTIThatConformsTo):
(-[WKFileUploadPanel dealloc]):
(-[WKFileUploadPanel presentWithParameters:resultListener:]):
(UTIsForMIMETypes):
(-[WKFileUploadPanel _mediaTypesForPickerSourceType:]):
(-[WKFileUploadPanel _cameraButtonLabelAllowingPhoto:allowingVideo:]):
(-[WKFileUploadPanel _showDocumentPickerMenu]):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@242369 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/platform/network/mac/UTIUtilities.h
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/ios/forms/WKFileUploadPanel.mm

index 312648a..0f6599e 100644 (file)
@@ -1,3 +1,15 @@
+2019-03-04  Chris Dumez  <cdumez@apple.com>
+
+        [iOS] Improve our file picker
+        https://bugs.webkit.org/show_bug.cgi?id=195284
+        <rdar://problem/45655856>
+
+        Reviewed by Tim Horton and Wenson Hsieh.
+
+        Export UTIUtilities.h so that it can be used from WebKit2.
+
+        * WebCore.xcodeproj/project.pbxproj:
+
 2019-03-04  Zalan Bujtas  <zalan@apple.com>
 
         [ContentChangeObserver] Decouple mouseMoved event and the "is observing content change" status.
index 4a624f9..ac04cd4 100644 (file)
                1F3C3BEB135CAF3C00B8C1AC /* MediaControls.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F3C3BE9135CAF3C00B8C1AC /* MediaControls.h */; };
                1F72BF0B187FD45C0009BCB3 /* TileControllerMemoryHandlerIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F72BF09187FD4270009BCB3 /* TileControllerMemoryHandlerIOS.h */; settings = {ATTRIBUTES = (Private, ); }; };
                1F8756B21E22C3350042C40D /* WebSQLiteDatabaseTrackerClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F8756B11E22BEEF0042C40D /* WebSQLiteDatabaseTrackerClient.h */; settings = {ATTRIBUTES = (Private, ); }; };
-               1FAFBF1915A5FA7400083A20 /* UTIUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FAFBF1615A5FA5200083A20 /* UTIUtilities.h */; };
+               1FAFBF1915A5FA7400083A20 /* UTIUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FAFBF1615A5FA5200083A20 /* UTIUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; };
                1FC40FBA1655CCB90040F29E /* SubimageCacheWithTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC40FB71655C5910040F29E /* SubimageCacheWithTimer.h */; };
                20D629271253690B00081543 /* InspectorInstrumentation.h in Headers */ = {isa = PBXBuildFile; fileRef = 20D629251253690B00081543 /* InspectorInstrumentation.h */; };
                225A16B50D5C11E900090295 /* WebEventRegion.h in Headers */ = {isa = PBXBuildFile; fileRef = 225A16B30D5C11E900090295 /* WebEventRegion.h */; settings = {ATTRIBUTES = (Private, ); }; };
index 78b8756..f552196 100644 (file)
@@ -32,7 +32,7 @@
 namespace WebCore {
 String MIMETypeFromUTI(const String&);
 String MIMETypeFromUTITree(const String&);
-String UTIFromMIMEType(const String&);
+WEBCORE_EXPORT String UTIFromMIMEType(const String&);
 bool isDeclaredUTI(const String&);
 }
 
index cbc09af..6eab1d4 100644 (file)
@@ -1,3 +1,35 @@
+2019-03-04  Chris Dumez  <cdumez@apple.com>
+
+        [iOS] Improve our file picker
+        https://bugs.webkit.org/show_bug.cgi?id=195284
+        <rdar://problem/45655856>
+
+        Reviewed by Tim Horton and Wenson Hsieh.
+
+        Improve our file picker on iOS so that:
+        - Accepted file extensions specified in the HTML (e.g. <input type="file"> accept=".pdf">) are now
+          properly reflected in the file picker. Previously, we only we only supported MIME types in the
+          accept attribute that only Image / Video ones.
+        - If accepted types are specified in the HTML and not of them are Video or Image types, then bypass
+          the UIDocumentMenuViewController and show the file picker directly (as if the user had tapped on
+          "Browse..." on that menu). Other menu items such as "Take Photo or Video" and "Photo Library" do
+          not make sense if the page only accepts PDF files for example.
+
+        Things that we should do but are not fixed in this patch:
+        - Stop using UIDocumentMenuViewController entirely since it was deprecated in favor of using
+          UIDocumentPickerViewController directly.
+        - Add multiple selection support, which is supported both on the HTML side and in the
+          UIDocumentPickerViewController API.
+
+        * UIProcess/ios/forms/WKFileUploadPanel.mm:
+        (arrayContainsUTIThatConformsTo):
+        (-[WKFileUploadPanel dealloc]):
+        (-[WKFileUploadPanel presentWithParameters:resultListener:]):
+        (UTIsForMIMETypes):
+        (-[WKFileUploadPanel _mediaTypesForPickerSourceType:]):
+        (-[WKFileUploadPanel _cameraButtonLabelAllowingPhoto:allowingVideo:]):
+        (-[WKFileUploadPanel _showDocumentPickerMenu]):
+
 2019-03-04  Alex Christensen  <achristensen@webkit.org>
 
         REGRESSION: ( r240978-r240985 ) [ iOS Release ] Layout Test imported/w3c/web-platform-tests/xhr/send-redirect-post-upload.htm is crashing
index ef6a542..e30b355 100644 (file)
 #import "WebOpenPanelResultListenerProxy.h"
 #import "WebPageProxy.h"
 #import <MobileCoreServices/MobileCoreServices.h>
+#import <UIKit/UIDocumentPickerViewController.h>
 #import <WebCore/LocalizedStrings.h>
+#import <WebCore/MIMETypeRegistry.h>
+#import <WebCore/UTIUtilities.h>
 #import <wtf/RetainPtr.h>
+#import <wtf/text/StringView.h>
 
 using namespace WebKit;
 
@@ -53,6 +57,15 @@ static inline UIImagePickerControllerCameraDevice cameraDeviceForMediaCaptureTyp
     return mediaCaptureType == WebCore::MediaCaptureTypeUser ? UIImagePickerControllerCameraDeviceFront : UIImagePickerControllerCameraDeviceRear;
 }
 
+static bool arrayContainsUTIThatConformsTo(NSArray<NSString *> *typeIdentifiers, CFStringRef conformToUTI)
+{
+    for (NSString *uti in typeIdentifiers) {
+        if (UTTypeConformsTo((__bridge CFStringRef)uti, conformToUTI))
+            return true;
+    }
+    return false;
+}
+
 #pragma mark - Document picker icons
 
 static inline UIImage *photoLibraryIcon()
@@ -163,6 +176,7 @@ static inline UIImage *cameraIcon()
     RetainPtr<UIPopoverController> _presentationPopover; // iPad for action sheet and Photo Library.
     ALLOW_DEPRECATED_DECLARATIONS_END
     RetainPtr<UIDocumentMenuViewController> _documentMenuController;
+    RetainPtr<UIDocumentPickerViewController> _documentPickerController;
     WebCore::MediaCaptureType _mediaCaptureType;
 }
 
@@ -179,6 +193,7 @@ static inline UIImage *cameraIcon()
     [_imagePicker setDelegate:nil];
     [_presentationPopover setDelegate:nil];
     [_documentMenuController setDelegate:nil];
+    [_documentPickerController setDelegate:nil];
 
     [super dealloc];
 }
@@ -233,6 +248,14 @@ static inline UIImage *cameraIcon()
     NSMutableArray *mimeTypes = [NSMutableArray arrayWithCapacity:acceptMimeTypes->size()];
     for (auto mimeType : acceptMimeTypes->elementsOfType<API::String>())
         [mimeTypes addObject:mimeType->string()];
+
+    Ref<API::Array> acceptFileExtensions = parameters->acceptFileExtensions();
+    for (auto extension : acceptFileExtensions->elementsOfType<API::String>()) {
+        String mimeType = WebCore::MIMETypeRegistry::getMIMETypeForExtension(extension->stringView().substring(1).toString());
+        if (!mimeType.isEmpty())
+            [mimeTypes addObject:mimeType];
+    }
+
     _mimeTypes = adoptNS([mimeTypes copy]);
 
     _mediaCaptureType = WebCore::MediaCaptureTypeNone;
@@ -291,27 +314,14 @@ static inline UIImage *cameraIcon()
 
 #pragma mark - Media Types
 
-static bool stringHasPrefixCaseInsensitive(NSString *str, NSString *prefix)
-{
-    NSRange range = [str rangeOfString:prefix options:(NSCaseInsensitiveSearch | NSAnchoredSearch)];
-    return range.location != NSNotFound;
-}
-
 static NSArray *UTIsForMIMETypes(NSArray *mimeTypes)
 {
-    // The HTML5 spec mentions the literal "image/*" and "video/*" strings.
-    // We support these and go a step further, if the MIME type starts with
-    // "image/" or "video/" we adjust the picker's image or video filters.
-    // So, "image/jpeg" would make the picker display all images types.
     NSMutableSet *mediaTypes = [NSMutableSet set];
     for (NSString *mimeType in mimeTypes) {
-        // FIXME: We should support more MIME type -> UTI mappings. <http://webkit.org/b/142614>
-        if (stringHasPrefixCaseInsensitive(mimeType, @"image/"))
-            [mediaTypes addObject:(NSString *)kUTTypeImage];
-        else if (stringHasPrefixCaseInsensitive(mimeType, @"video/"))
-            [mediaTypes addObject:(NSString *)kUTTypeMovie];
+        auto uti = WebCore::UTIFromMIMEType(mimeType);
+        if (!uti.isEmpty())
+            [mediaTypes addObject:(__bridge NSString *)uti];
     }
-
     return mediaTypes.allObjects;
 }
 
@@ -325,16 +335,6 @@ static NSArray *UTIsForMIMETypes(NSArray *mimeTypes)
     return [UIImagePickerController availableMediaTypesForSourceType:sourceType];
 }
 
-- (NSArray *)_documentPickerMenuMediaTypes
-{
-    NSArray *mediaTypes = UTIsForMIMETypes(_mimeTypes.get());
-    if (mediaTypes.count)
-        return mediaTypes;
-
-    // Fallback to every supported media type if there is no filter.
-    return @[@"public.item"];
-}
-
 #pragma mark - Source selection menu
 
 - (NSString *)_photoLibraryButtonLabel
@@ -342,20 +342,13 @@ static NSArray *UTIsForMIMETypes(NSArray *mimeTypes)
     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");
 }
 
-- (NSString *)_cameraButtonLabel
+- (NSString *)_cameraButtonLabelAllowingPhoto:(BOOL)allowPhoto allowingVideo:(BOOL)allowVideo
 {
-    if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
-        return nil;
-
-    // Choose the appropriate string for the camera button.
-    NSArray *filteredMediaTypes = [self _mediaTypesForPickerSourceType:UIImagePickerControllerSourceTypeCamera];
-    BOOL containsImageMediaType = [filteredMediaTypes containsObject:(NSString *)kUTTypeImage];
-    BOOL containsVideoMediaType = [filteredMediaTypes containsObject:(NSString *)kUTTypeMovie];
-    ASSERT(containsImageMediaType || containsVideoMediaType);
-    if (containsImageMediaType && containsVideoMediaType)
+    ASSERT(allowPhoto || allowVideo);
+    if (allowPhoto && allowVideo)
         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");
 
-    if (containsVideoMediaType)
+    if (allowVideo)
         return WEB_UI_STRING_KEY("Take Video", "Take Video (file upload action sheet)", "File Upload alert sheet camera button string for taking only videos");
 
     return WEB_UI_STRING_KEY("Take Photo", "Take Photo (file upload action sheet)", "File Upload alert sheet camera button string for taking only photos");
@@ -363,25 +356,40 @@ static NSArray *UTIsForMIMETypes(NSArray *mimeTypes)
 
 - (void)_showDocumentPickerMenu
 {
-    // FIXME: Support multiple file selection when implemented. <rdar://17177981>
-    _documentMenuController = adoptNS([[UIDocumentMenuViewController alloc] _initIgnoringApplicationEntitlementForImportOfTypes:[self _documentPickerMenuMediaTypes]]);
-    [_documentMenuController setDelegate:self];
+    NSArray *mediaTypes = UTIsForMIMETypes(_mimeTypes.get());
 
-    [_documentMenuController addOptionWithTitle:[self _photoLibraryButtonLabel] image:photoLibraryIcon() order:UIDocumentMenuOrderFirst handler:^{
-        [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
-    }];
+    BOOL containsImageMediaType = !mediaTypes.count || arrayContainsUTIThatConformsTo(mediaTypes, kUTTypeImage);
+    BOOL containsVideoMediaType = !mediaTypes.count || arrayContainsUTIThatConformsTo(mediaTypes, kUTTypeMovie);
 
-    if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
-        if (NSString *cameraString = [self _cameraButtonLabel]) {
+    NSArray *documentTypes = mediaTypes.count ? mediaTypes : @[(__bridge NSString *)kUTTypeItem];
+    if (containsImageMediaType || containsVideoMediaType) {
+        // FIXME: UIDocumentMenuViewController is deprecated, we should use UIDocumentPickerViewController instead.
+        // FIXME: Support multiple file selection when implemented. <rdar://17177981>
+        _documentMenuController = adoptNS([[UIDocumentMenuViewController alloc] _initIgnoringApplicationEntitlementForImportOfTypes:documentTypes]);
+        [_documentMenuController setDelegate:self];
+
+        [_documentMenuController addOptionWithTitle:[self _photoLibraryButtonLabel] image:photoLibraryIcon() order:UIDocumentMenuOrderFirst handler:^{
+            [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
+        }];
+
+        if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
+            NSString *cameraString = [self _cameraButtonLabelAllowingPhoto:containsImageMediaType allowingVideo:containsVideoMediaType];
             [_documentMenuController addOptionWithTitle:cameraString image:cameraIcon() order:UIDocumentMenuOrderFirst handler:^{
                 _usingCamera = YES;
                 [self _showPhotoPickerWithSourceType:UIImagePickerControllerSourceTypeCamera];
             }];
         }
+
+        [self _presentMenuOptionForCurrentInterfaceIdiom:_documentMenuController.get()];
+    } else {
+        // Image and Video types are not accepted so bypass the menu and open the file picker directly.
+        // FIXME: Support multiple file selection when implemented. <rdar://17177981>
+        _documentPickerController = adoptNS([[UIDocumentPickerViewController alloc] initWithDocumentTypes:documentTypes inMode:UIDocumentPickerModeImport]);
+        [_documentPickerController setDelegate:self];
+        [self _presentFullscreenViewController:_documentPickerController.get() animated:YES];
     }
 
-    [self _presentMenuOptionForCurrentInterfaceIdiom:_documentMenuController.get()];
-    // Clear out the view controller we just presented. Don't save a reference to the UIDocumentMenuViewController as it is self dismissing.
+    // Clear out the view controller we just presented. Don't save a reference to the UIDocumentMenuViewController / UIDocumentPickerViewController as it is self dismissing.
     _presentationViewController = nil;
 }