WKPDFView does not support password-protected PDFs
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 24 Apr 2015 23:54:59 +0000 (23:54 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 24 Apr 2015 23:54:59 +0000 (23:54 +0000)
https://bugs.webkit.org/show_bug.cgi?id=144162
<rdar://problem/18411512>

Reviewed by Andy Estes.

* Platform/spi/ios/UIKitSPI.h:
Add some SPI.

* UIProcess/ios/WKPDFView.h:
* UIProcess/ios/WKPDFView.mm:
(-[WKPDFView _didLoadPDFDocument]):
(-[WKPDFView web_setContentProviderData:suggestedFilename:]):
Move creation of the UIPDFDocument (only possible if the CGPDFDocument is unlocked)
and initial setup of the page views out into _didLoadPDFDocument.

If the CGPDFDocument is locked, we'll show some UI to unlock it; otherwise
we'll continue on to _didLoadPDFDocument as previously.

(-[WKPDFView web_setMinimumSize:]):
Resize the password UI instead of the PDF pages if we have it.

(-[WKPDFView _computePageAndDocumentFrames]):
Don't bother doing any work revalidating PDF pages if the document is locked.

(-[WKPDFView _updatePasswordEntryField]):
Make sure that the UIDocumentPassword view is always the size of the scrollview.
This takes care of rotation.

(-[WKPDFView _keyboardDidShow:]):
Make sure that we scroll the password field around, if necessary, to keep it
on screen when editing begins.

(-[WKPDFView _showPasswordEntryField]):
(-[WKPDFView _hidePasswordEntryField]):
Adjust the background color (to match the UIDocumentPasswordView) and disable zooming
while it's up. The UIDocumentPassword view is installed into the scroll view
to match UIWebView behavior.

(-[WKPDFView userDidEnterPassword:forPasswordView:]):
(-[WKPDFView didBeginEditingPassword:inView:]):
(-[WKPDFView didEndEditingPassword:inView:]):
(-[WKPDFView _didFailToUnlock]):
Pop up a dialog informing the user that they entered the wrong password.

(-[WKPDFView _tryToUnlockWithPassword:]):
Try to unlock the document. If it succeeds, hide the unlock UI and go back to
_didLoadPDFDocument.

* English.lproj/Localizable.strings:
Add some localizable strings.

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

Source/WebCore/ChangeLog
Source/WebCore/English.lproj/Localizable.strings
Source/WebKit2/ChangeLog
Source/WebKit2/Platform/spi/ios/UIKitSPI.h
Source/WebKit2/UIProcess/ios/WKPDFView.h
Source/WebKit2/UIProcess/ios/WKPDFView.mm

index cd48abe..35260b7 100644 (file)
@@ -1,3 +1,14 @@
+2015-04-24  Tim Horton  <timothy_horton@apple.com>
+
+        WKPDFView does not support password-protected PDFs
+        https://bugs.webkit.org/show_bug.cgi?id=144162
+        <rdar://problem/18411512>
+
+        Reviewed by Andy Estes.
+
+        * English.lproj/Localizable.strings:
+        Add some localizable strings.
+
 2015-04-24  David Kilzer  <ddkilzer@apple.com>
 
         Fix iOS EWS builds after updating to iOS 8.3 SDK
index cfec300..42b5ee2 100644 (file)
 /* accessibility label for video element controller */
 "video playback" = "video playback";
 
+/* PDF password failure alert message */
+"The document could not be opened with that password." = "The document could not be opened with that password.";
+
+/* OK button label in PDF password failure alert */
+"OK (PDF password failure alert)" = "OK";
index 1ec78cc..6a1caa4 100644 (file)
@@ -1,3 +1,54 @@
+2015-04-24  Tim Horton  <timothy_horton@apple.com>
+
+        WKPDFView does not support password-protected PDFs
+        https://bugs.webkit.org/show_bug.cgi?id=144162
+        <rdar://problem/18411512>
+
+        Reviewed by Andy Estes.
+
+        * Platform/spi/ios/UIKitSPI.h:
+        Add some SPI.
+
+        * UIProcess/ios/WKPDFView.h:
+        * UIProcess/ios/WKPDFView.mm:
+        (-[WKPDFView _didLoadPDFDocument]):
+        (-[WKPDFView web_setContentProviderData:suggestedFilename:]):
+        Move creation of the UIPDFDocument (only possible if the CGPDFDocument is unlocked)
+        and initial setup of the page views out into _didLoadPDFDocument.
+
+        If the CGPDFDocument is locked, we'll show some UI to unlock it; otherwise
+        we'll continue on to _didLoadPDFDocument as previously.
+
+        (-[WKPDFView web_setMinimumSize:]):
+        Resize the password UI instead of the PDF pages if we have it.
+
+        (-[WKPDFView _computePageAndDocumentFrames]):
+        Don't bother doing any work revalidating PDF pages if the document is locked.
+
+        (-[WKPDFView _updatePasswordEntryField]):
+        Make sure that the UIDocumentPassword view is always the size of the scrollview.
+        This takes care of rotation.
+
+        (-[WKPDFView _keyboardDidShow:]):
+        Make sure that we scroll the password field around, if necessary, to keep it
+        on screen when editing begins.
+
+        (-[WKPDFView _showPasswordEntryField]):
+        (-[WKPDFView _hidePasswordEntryField]):
+        Adjust the background color (to match the UIDocumentPasswordView) and disable zooming
+        while it's up. The UIDocumentPassword view is installed into the scroll view
+        to match UIWebView behavior.
+
+        (-[WKPDFView userDidEnterPassword:forPasswordView:]):
+        (-[WKPDFView didBeginEditingPassword:inView:]):
+        (-[WKPDFView didEndEditingPassword:inView:]):
+        (-[WKPDFView _didFailToUnlock]):
+        Pop up a dialog informing the user that they entered the wrong password.
+
+        (-[WKPDFView _tryToUnlockWithPassword:]):
+        Try to unlock the document. If it succeeds, hide the unlock UI and go back to
+        _didLoadPDFDocument.
+
 2015-04-24  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r183266.
index ebf158b..40170b4 100644 (file)
@@ -32,6 +32,7 @@
 #import <UIKit/UIBarButtonItem_Private.h>
 #import <UIKit/UIDatePicker_Private.h>
 #import <UIKit/UIDevice_Private.h>
+#import <UIKit/UIDocumentPasswordView.h>
 #import <UIKit/UIFont_Private.h>
 #import <UIKit/UIGeometry_Private.h>
 #import <UIKit/UIGestureRecognizer_Private.h>
@@ -676,6 +677,33 @@ typedef enum {
 @property (nonatomic, assign, setter = _setIgnoreApplicationEntitlementForImport:, getter = _ignoreApplicationEntitlementForImport) BOOL _ignoreApplicationEntitlementForImport;
 @end
 
+@protocol UIDocumentPasswordViewDelegate;
+
+@interface UIDocumentPasswordView : UIView <UITextFieldDelegate>
+@end
+
+@interface UIDocumentPasswordView (Details)
+
+- (id)initWithDocumentName:(NSString *)documentName;
+
+@property (nonatomic, assign) NSObject<UIDocumentPasswordViewDelegate> *passwordDelegate;
+@property (nonatomic, readonly) UITextField *passwordField;
+
+@end
+
+@protocol UIDocumentPasswordViewDelegate
+
+@required
+
+- (void)userDidEnterPassword:(NSString *)password forPasswordView:(UIDocumentPasswordView *)passwordView;
+
+@optional
+
+- (void)didBeginEditingPassword:(UITextField *)passwordField inView:(UIDocumentPasswordView *)passwordView;
+- (void)didEndEditingPassword:(UITextField *)passwordField inView:(UIDocumentPasswordView *)passwordView;
+
+@end
+
 #endif // USE(APPLE_INTERNAL_SDK)
 
 @interface UIView (IPI)
index d83d9ff..3cd9900 100644 (file)
 #if PLATFORM(IOS)
 
 #import "CorePDFSPI.h"
+#import "UIKitSPI.h"
 #import "WKActionSheetAssistant.h"
 #import "WKWebViewContentProvider.h"
-#import <UIKit/UIView.h>
 
-@interface WKPDFView : UIView <WKWebViewContentProvider, UIPDFPageViewDelegate, UIPDFAnnotationControllerDelegate, WKActionSheetAssistantDelegate>
+@interface WKPDFView : UIView <WKWebViewContentProvider, UIPDFPageViewDelegate, UIPDFAnnotationControllerDelegate, WKActionSheetAssistantDelegate, UIDocumentPasswordViewDelegate>
 
 @property (nonatomic, readonly) NSString *suggestedFilename;
 @property (nonatomic, readonly) CGPDFDocumentRef pdfDocument;
index d494957..acfd82c 100644 (file)
@@ -40,6 +40,7 @@
 #import "_WKFindDelegate.h"
 #import <MobileCoreServices/UTCoreTypes.h>
 #import <WebCore/FloatRect.h>
+#import <WebCore/LocalizedStrings.h>
 #import <wtf/RetainPtr.h>
 #import <wtf/Vector.h>
 
@@ -49,6 +50,8 @@ const CGFloat pdfPageMargin = 8;
 const CGFloat pdfMinimumZoomScale = 1;
 const CGFloat pdfMaximumZoomScale = 5;
 
+const CGFloat passwordEntryFieldPadding = 10;
+
 const float overdrawHeightMultiplier = 1.5;
 
 static const CGFloat smartMagnificationElementPadding = 0.05;
@@ -65,10 +68,13 @@ typedef struct {
 @end
 
 @implementation WKPDFView {
+    RetainPtr<CGPDFDocumentRef> _cgPDFDocument;
     RetainPtr<UIPDFDocument> _pdfDocument;
     RetainPtr<NSString> _suggestedFilename;
     RetainPtr<WKPDFPageNumberIndicator> _pageNumberIndicator;
 
+    RetainPtr<UIDocumentPasswordView> _passwordView;
+
     Vector<PDFPageInfo> _pages;
     unsigned _centerPageNumber;
 
@@ -125,6 +131,8 @@ typedef struct {
 
 - (void)dealloc
 {
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+
     [self _clearPages];
     [_pageNumberIndicator removeFromSuperview];
     dispatch_release(_findQueue);
@@ -152,6 +160,17 @@ typedef struct {
     _pages.clear();
 }
 
+- (void)_didLoadPDFDocument
+{
+    _pdfDocument = adoptNS([[UIPDFDocument alloc] initWithCGPDFDocument:_cgPDFDocument.get()]);
+
+    // FIXME: Restore the scroll position and page scale if navigating from the back/forward list.
+
+    [self _computePageAndDocumentFrames];
+    [self _revalidateViews];
+    [self _scrollToFragment:_webView.URL.fragment];
+}
+
 - (void)web_setContentProviderData:(NSData *)data suggestedFilename:(NSString *)filename
 {
     _suggestedFilename = adoptNS([filename copy]);
@@ -159,18 +178,26 @@ typedef struct {
     [self _clearPages];
 
     RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateWithCFData((CFDataRef)data));
-    RetainPtr<CGPDFDocumentRef> cgPDFDocument = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get()));
-    _pdfDocument = adoptNS([[UIPDFDocument alloc] initWithCGPDFDocument:cgPDFDocument.get()]);
+    _cgPDFDocument = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get()));
 
-    // FIXME: restore the scroll position and page scale if navigating from the back/forward list.
+    if (!_cgPDFDocument)
+        return;
 
-    [self _computePageAndDocumentFrames];
-    [self _revalidateViews];
-    [self _scrollToFragment:_webView.URL.fragment];
+    if (CGPDFDocumentIsUnlocked(_cgPDFDocument.get())) {
+        [self _didLoadPDFDocument];
+        return;
+    }
+
+    [self _showPasswordEntryField];
 }
 
 - (void)web_setMinimumSize:(CGSize)size
 {
+    if (_passwordView) {
+        [self _updatePasswordEntryField];
+        return;
+    }
+
     _minimumSize = size;
 
     CGFloat oldDocumentLeftFraction = 0;
@@ -328,6 +355,9 @@ typedef struct {
 
 - (void)_computePageAndDocumentFrames
 {
+    if (_passwordView)
+        return;
+
     NSUInteger pageCount = [_pdfDocument numberOfPages];
     [_pageNumberIndicator setPageCount:pageCount];
     
@@ -693,6 +723,110 @@ static NSStringCompareOptions stringCompareOptions(_WKFindOptions options)
     return _webView->_page->uiClient().actionsForElement(element, WTF::move(defaultActions));
 }
 
+#pragma mark Password protection UI
+
+- (void)_updatePasswordEntryField
+{
+    [_passwordView setFrame:CGRectMake(0, 0, _webView.bounds.size.width, _webView.bounds.size.height)];
+    [_scrollView setContentSize:[_passwordView bounds].size];
+}
+
+- (void)_keyboardDidShow:(NSNotification *)notification
+{
+    UITextField *passwordField = [_passwordView passwordField];
+    if (!passwordField.isEditing)
+        return;
+
+    CGRect keyboardRect = [UIPeripheralHost visiblePeripheralFrame];
+    if (CGRectIsEmpty(keyboardRect))
+        return;
+
+    UIWindow *window = _scrollView.window;
+    keyboardRect = [window convertRect:keyboardRect fromWindow:nil];
+    keyboardRect = [_scrollView convertRect:keyboardRect fromView:window];
+
+    CGRect passwordFieldFrame = [passwordField convertRect:passwordField.bounds toView:_scrollView];
+
+    CGSize contentSize = [_passwordView bounds].size;
+    contentSize.height += CGRectGetHeight(keyboardRect);
+    [_scrollView setContentSize:contentSize];
+
+    if (CGRectIntersectsRect(passwordFieldFrame, keyboardRect)) {
+        CGFloat yDelta = CGRectGetMaxY(passwordFieldFrame) - CGRectGetMinY(keyboardRect);
+
+        CGPoint contentOffset = _scrollView.contentOffset;
+        contentOffset.y += yDelta + passwordEntryFieldPadding;
+
+        [_scrollView setContentOffset:contentOffset animated:YES];
+    }
+}
+
+- (void)_showPasswordEntryField
+{
+    [_scrollView setMinimumZoomScale:1];
+    [_scrollView setMaximumZoomScale:1];
+    [_scrollView setBackgroundColor:[UIColor groupTableViewBackgroundColor]];
+
+    _passwordView = adoptNS([[UIDocumentPasswordView alloc] initWithDocumentName:_suggestedFilename.get()]);
+    [_passwordView setPasswordDelegate:self];
+
+    [self _updatePasswordEntryField];
+
+    [self addSubview:_passwordView.get()];
+}
+
+- (void)_hidePasswordEntryField
+{
+    [_passwordView removeFromSuperview];
+    _passwordView = nil;
+
+    [_scrollView setMinimumZoomScale:pdfMinimumZoomScale];
+    [_scrollView setMaximumZoomScale:pdfMaximumZoomScale];
+    [_scrollView setBackgroundColor:[UIColor grayColor]];
+}
+
+- (void)userDidEnterPassword:(NSString *)password forPasswordView:(UIDocumentPasswordView *)passwordView
+{
+    [self _tryToUnlockWithPassword:password];
+}
+
+- (void)didBeginEditingPassword:(UITextField *)passwordField inView:(UIDocumentPasswordView *)passwordView
+{
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
+}
+
+- (void)didEndEditingPassword:(UITextField *)passwordField inView:(UIDocumentPasswordView *)passwordView
+{
+    [_scrollView setContentSize:[_passwordView frame].size];
+    [_scrollView setContentOffset:CGPointMake(-_scrollView.contentInset.left, -_scrollView.contentInset.top) animated:YES];
+
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil];
+}
+
+- (void)_didFailToUnlock
+{
+    [[_passwordView passwordField] setText:@""];
+    UIAlertController* alert = [UIAlertController alertControllerWithTitle:WEB_UI_STRING("The document could not be opened with that password.", "PDF password failure alert message") message:@"" preferredStyle:UIAlertControllerStyleAlert];
+
+    UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:WEB_UI_STRING_KEY("OK", "OK (PDF password failure alert)", "OK button label in PDF password failure alert") style:UIAlertActionStyleDefault handler:[](UIAlertAction *) { }];
+    
+    [alert addAction:defaultAction];
+
+    [self.window.rootViewController presentViewController:alert animated:YES completion:nil];
+}
+
+- (BOOL)_tryToUnlockWithPassword:(NSString *)password
+{
+    if (CGPDFDocumentUnlockWithPassword(_cgPDFDocument.get(), [password UTF8String])) {
+        [self _hidePasswordEntryField];
+        [self _didLoadPDFDocument];
+        return YES;
+    }
+
+    [self _didFailToUnlock];
+    return NO;
+}
+
 @end
 
 #endif /* PLATFORM(IOS) */