<http://webkit.org/b/91015> Remove BUILDING_ON / TARGETING macros in favor of system...
[WebKit-https.git] / Source / WebKit2 / UIProcess / API / mac / PDFViewController.mm
1 /*
2  * Copyright (C) 2010, 2011 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 "PDFViewController.h"
28
29 #import "DataReference.h"
30 #import "WKAPICast.h"
31 #import "WKViewPrivate.h"
32 #import "WebData.h"
33 #import "WebEventFactory.h"
34 #import "WebPageGroup.h"
35 #import "WebPageProxy.h"
36 #import "WebPreferences.h"
37 #import <PDFKit/PDFKit.h>
38 #import <WebCore/LocalizedStrings.h>
39 #import <objc/runtime.h>
40 #import <wtf/text/CString.h>
41 #import <wtf/text/WTFString.h>
42
43 // Redeclarations of PDFKit notifications. We can't use the API since we use a weak link to the framework.
44 #define _webkit_PDFViewDisplayModeChangedNotification @"PDFViewDisplayModeChanged"
45 #define _webkit_PDFViewScaleChangedNotification @"PDFViewScaleChanged"
46 #define _webkit_PDFViewPageChangedNotification @"PDFViewChangedPage"
47
48 using namespace WebKit;
49
50 @class PDFDocument;
51 @class PDFView;
52
53 @interface PDFDocument (PDFDocumentDetails)
54 - (NSPrintOperation *)getPrintOperationForPrintInfo:(NSPrintInfo *)printInfo autoRotate:(BOOL)doRotate;
55 @end
56
57 extern "C" NSString *_NSPathForSystemFramework(NSString *framework);
58
59 // MARK: C UTILITY FUNCTIONS
60
61 static void _applicationInfoForMIMEType(NSString *type, NSString **name, NSImage **image)
62 {
63     ASSERT(name);
64     ASSERT(image);
65     
66     CFURLRef appURL = 0;
67
68     OSStatus error = LSCopyApplicationForMIMEType((CFStringRef)type, kLSRolesAll, &appURL);
69     if (error != noErr)
70         return;
71
72     NSString *appPath = [(NSURL *)appURL path];
73     if (appURL)
74         CFRelease(appURL);
75
76     *image = [[NSWorkspace sharedWorkspace] iconForFile:appPath];
77     [*image setSize:NSMakeSize(16, 16)];
78
79     *name = [[NSFileManager defaultManager] displayNameAtPath:appPath];
80 }
81
82 // FIXME 4182876: We can eliminate this function in favor if -isEqual: if [PDFSelection isEqual:] is overridden
83 // to compare contents.
84 static BOOL _PDFSelectionsAreEqual(PDFSelection *selectionA, PDFSelection *selectionB)
85 {
86     NSArray *aPages = [selectionA pages];
87     NSArray *bPages = [selectionB pages];
88
89     if (![aPages isEqual:bPages])
90         return NO;
91
92     NSUInteger count = [aPages count];
93     for (NSUInteger i = 0; i < count; ++i) {
94         NSRect aBounds = [selectionA boundsForPage:[aPages objectAtIndex:i]];
95         NSRect bBounds = [selectionB boundsForPage:[bPages objectAtIndex:i]];
96         if (!NSEqualRects(aBounds, bBounds))
97             return NO;
98     }
99
100     return YES;
101 }
102
103 @interface WKPDFView : NSView
104 {
105     PDFViewController* _pdfViewController;
106
107     RetainPtr<NSView> _pdfPreviewView;
108     PDFView *_pdfView;
109     BOOL _ignoreScaleAndDisplayModeAndPageNotifications;
110     BOOL _willUpdatePreferencesSoon;
111 }
112
113 - (id)initWithFrame:(NSRect)frame PDFViewController:(PDFViewController*)pdfViewController;
114 - (void)invalidate;
115 - (PDFView *)pdfView;
116 - (void)setDocument:(PDFDocument *)pdfDocument;
117
118 - (BOOL)forwardScrollWheelEvent:(NSEvent *)wheelEvent;
119 - (void)_applyPDFPreferences;
120 - (PDFSelection *)_nextMatchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag fromSelection:(PDFSelection *)initialSelection startInSelection:(BOOL)startInSelection;
121 @end
122
123 @implementation WKPDFView
124
125 - (id)initWithFrame:(NSRect)frame PDFViewController:(PDFViewController*)pdfViewController
126 {
127     if ((self = [super initWithFrame:frame])) {
128         _pdfViewController = pdfViewController;
129
130         [self setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
131
132         Class previewViewClass = PDFViewController::pdfPreviewViewClass();
133         ASSERT(previewViewClass);
134
135         _pdfPreviewView.adoptNS([[previewViewClass alloc] initWithFrame:frame]);
136         [_pdfPreviewView.get() setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
137         [self addSubview:_pdfPreviewView.get()];
138
139         _pdfView = [_pdfPreviewView.get() performSelector:@selector(pdfView)];
140         [_pdfView setDelegate:self];
141     }
142
143     return self;
144 }
145
146 - (void)invalidate
147 {
148     _pdfViewController = 0;
149 }
150
151 - (PDFView *)pdfView
152 {
153     return _pdfView;
154 }
155
156 - (void)setDocument:(PDFDocument *)pdfDocument
157 {
158     _ignoreScaleAndDisplayModeAndPageNotifications = YES;
159     [_pdfView setDocument:pdfDocument];
160     [self _applyPDFPreferences];
161     _ignoreScaleAndDisplayModeAndPageNotifications = NO;
162 }
163
164 - (void)_applyPDFPreferences
165 {
166     if (!_pdfViewController)
167         return;
168
169     WebPreferences *preferences = _pdfViewController->page()->pageGroup()->preferences();
170
171     CGFloat scaleFactor = preferences->pdfScaleFactor();
172     if (!scaleFactor)
173         [_pdfView setAutoScales:YES];
174     else {
175         [_pdfView setAutoScales:NO];
176         [_pdfView setScaleFactor:scaleFactor];
177     }
178     [_pdfView setDisplayMode:preferences->pdfDisplayMode()];
179 }
180
181 - (void)_updatePreferences:(id)ignored
182 {
183     _willUpdatePreferencesSoon = NO;
184
185     if (!_pdfViewController)
186         return;
187
188     WebPreferences* preferences = _pdfViewController->page()->pageGroup()->preferences();
189
190     CGFloat scaleFactor = [_pdfView autoScales] ? 0 : [_pdfView scaleFactor];
191     preferences->setPDFScaleFactor(scaleFactor);
192     preferences->setPDFDisplayMode([_pdfView displayMode]);
193 }
194
195 - (void)_updatePreferencesSoon
196 {   
197     if (_willUpdatePreferencesSoon)
198         return;
199
200     [self performSelector:@selector(_updatePreferences:) withObject:nil afterDelay:0];
201     _willUpdatePreferencesSoon = YES;
202 }
203
204 - (void)_scaleOrDisplayModeOrPageChanged:(NSNotification *)notification
205 {
206     ASSERT_ARG(notification, [notification object] == _pdfView);
207     if (!_ignoreScaleAndDisplayModeAndPageNotifications)
208         [self _updatePreferencesSoon];
209 }
210
211 - (void)_openWithFinder:(id)sender
212 {
213     _pdfViewController->openPDFInFinder();
214 }
215
216 - (PDFSelection *)_nextMatchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag fromSelection:(PDFSelection *)initialSelection startInSelection:(BOOL)startInSelection
217 {
218     if (![string length])
219         return nil;
220
221     int options = 0;
222     if (!forward)
223         options |= NSBackwardsSearch;
224
225     if (!caseFlag)
226         options |= NSCaseInsensitiveSearch;
227
228     PDFDocument *document = [_pdfView document];
229
230     PDFSelection *selectionForInitialSearch = [initialSelection copy];
231     if (startInSelection) {
232         // Initially we want to include the selected text in the search.  So we must modify the starting search 
233         // selection to fit PDFDocument's search requirements: selection must have a length >= 1, begin before 
234         // the current selection (if searching forwards) or after (if searching backwards).
235         int initialSelectionLength = [[initialSelection string] length];
236         if (forward) {
237             [selectionForInitialSearch extendSelectionAtStart:1];
238             [selectionForInitialSearch extendSelectionAtEnd:-initialSelectionLength];
239         } else {
240             [selectionForInitialSearch extendSelectionAtEnd:1];
241             [selectionForInitialSearch extendSelectionAtStart:-initialSelectionLength];
242         }
243     }
244     PDFSelection *foundSelection = [document findString:string fromSelection:selectionForInitialSearch withOptions:options];
245     [selectionForInitialSearch release];
246
247     // If we first searched in the selection, and we found the selection, search again from just past the selection
248     if (startInSelection && _PDFSelectionsAreEqual(foundSelection, initialSelection))
249         foundSelection = [document findString:string fromSelection:initialSelection withOptions:options];
250
251     if (!foundSelection && wrapFlag)
252         foundSelection = [document findString:string fromSelection:nil withOptions:options];
253
254     return foundSelection;
255 }
256
257 - (NSUInteger)_countMatches:(NSString *)string caseSensitive:(BOOL)caseFlag
258 {
259     if (![string length])
260         return 0;
261
262     int options = caseFlag ? 0 : NSCaseInsensitiveSearch;
263
264     return [[[_pdfView document] findString:string withOptions:options] count];
265 }
266
267 // MARK: NSView overrides
268
269 - (void)viewDidMoveToWindow
270 {
271     if (![self window])
272         return;
273
274     NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
275     [notificationCenter addObserver:self selector:@selector(_scaleOrDisplayModeOrPageChanged:) name:_webkit_PDFViewScaleChangedNotification object:_pdfView];
276     [notificationCenter addObserver:self selector:@selector(_scaleOrDisplayModeOrPageChanged:) name:_webkit_PDFViewDisplayModeChangedNotification object:_pdfView];
277     [notificationCenter addObserver:self selector:@selector(_scaleOrDisplayModeOrPageChanged:) name:_webkit_PDFViewPageChangedNotification object:_pdfView];
278 }
279
280 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
281 {
282     if (![self window])
283         return;
284
285     NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
286     [notificationCenter removeObserver:self name:_webkit_PDFViewScaleChangedNotification object:_pdfView];
287     [notificationCenter removeObserver:self name:_webkit_PDFViewDisplayModeChangedNotification object:_pdfView];
288     [notificationCenter removeObserver:self name:_webkit_PDFViewPageChangedNotification object:_pdfView];
289 }
290
291 - (NSView *)hitTest:(NSPoint)point
292 {
293     // Override hitTest so we can override menuForEvent.
294     NSEvent *event = [NSApp currentEvent];
295     NSEventType type = [event type];
296     if (type == NSRightMouseDown || (type == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask)))
297         return self;
298
299     return [super hitTest:point];
300 }
301
302 static void insertOpenWithDefaultPDFMenuItem(NSMenu *menu, NSUInteger index)
303 {
304     // Add in an "Open with <default PDF viewer>" item
305     NSString *appName = nil;
306     NSImage *appIcon = nil;
307     
308     _applicationInfoForMIMEType(@"application/pdf", &appName, &appIcon);
309     if (!appName)
310         appName = WEB_UI_STRING("Finder", "Default application name for Open With context menu");
311     
312     // To match the PDFKit style, we'll add Open with Preview even when there's no document yet to view, and
313     // disable it using validateUserInterfaceItem.
314     NSString *title = [NSString stringWithFormat:WEB_UI_STRING("Open with %@", "context menu item for PDF"), appName];
315     
316     NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:@selector(_openWithFinder:) keyEquivalent:@""];
317     if (appIcon)
318         [item setImage:appIcon];
319     [menu insertItem:item atIndex:index];
320     [item release];
321 }
322
323 - (NSMenu *)menuForEvent:(NSEvent *)theEvent
324 {
325     NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
326
327     bool insertedOpenWithItem = false;
328     
329     NSEnumerator *menuItemEnumerator = [[[_pdfView menuForEvent:theEvent] itemArray] objectEnumerator];
330     while (NSMenuItem *item = [menuItemEnumerator nextObject]) {
331         NSMenuItem *itemCopy = [item copy];
332         [menu addItem:itemCopy];
333         [itemCopy release];
334
335         if (insertedOpenWithItem)
336             continue;
337         
338         // If a "Copy" item is present, place the "Open With" item just after it, with an intervening separator.
339         if ([item action] != @selector(copy:))
340             continue;
341         
342         [menu addItem:[NSMenuItem separatorItem]];
343         insertOpenWithDefaultPDFMenuItem(menu, [menu numberOfItems]);
344         insertedOpenWithItem = true;
345     }
346     
347     if (!insertedOpenWithItem) {
348         // No "Copy" item was found; place the "Open With" item at the top of the menu, with a trailing separator.
349         insertOpenWithDefaultPDFMenuItem(menu, 0);
350         [menu insertItem:[NSMenuItem separatorItem] atIndex:1];
351     }
352
353     return [menu autorelease];
354 }
355
356 // MARK: NSUserInterfaceValidations PROTOCOL IMPLEMENTATION
357
358 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
359 {
360     SEL action = [item action];
361     if (action == @selector(_openWithFinder:))
362         return [_pdfView document] != nil;
363     return YES;
364 }
365
366 // MARK: PDFView delegate methods
367
368 - (void)PDFViewWillClickOnLink:(PDFView *)sender withURL:(NSURL *)URL
369 {
370     _pdfViewController->linkClicked([URL absoluteString]);
371 }
372
373 - (void)PDFViewOpenPDFInNativeApplication:(PDFView *)sender
374 {
375     _pdfViewController->openPDFInFinder();
376 }
377
378 - (void)PDFViewSavePDFToDownloadFolder:(PDFView *)sender
379 {
380     _pdfViewController->savePDFToDownloadsFolder();
381 }
382
383 - (void)PDFViewPerformPrint:(PDFView *)sender
384 {
385     _pdfViewController->print();
386 }
387
388 - (BOOL)forwardScrollWheelEvent:(NSEvent *)wheelEvent
389 {
390     return _pdfViewController->forwardScrollWheelEvent(wheelEvent);
391 }
392
393 @end
394
395 namespace WebKit {
396
397 PassOwnPtr<PDFViewController> PDFViewController::create(WKView *wkView)
398 {
399     return adoptPtr(new PDFViewController(wkView));
400 }
401
402 PDFViewController::PDFViewController(WKView *wkView)
403     : m_wkView(wkView)
404     , m_wkPDFView(AdoptNS, [[WKPDFView alloc] initWithFrame:[m_wkView bounds] PDFViewController:this])
405     , m_pdfView([m_wkPDFView.get() pdfView])
406     , m_hasWrittenPDFToDisk(false)
407 {
408     [m_wkView addSubview:m_wkPDFView.get()];
409 }
410
411 PDFViewController::~PDFViewController()
412 {
413     [m_wkPDFView.get() removeFromSuperview];
414     [m_wkPDFView.get() invalidate];
415     m_wkPDFView = nullptr;
416 }
417
418 WebPageProxy* PDFViewController::page() const
419 {
420     return toImpl([m_wkView pageRef]);
421 }
422
423 NSView* PDFViewController::pdfView() const
424
425     return m_wkPDFView.get(); 
426 }
427     
428 static RetainPtr<CFDataRef> convertPostScriptDataSourceToPDF(const CoreIPC::DataReference& dataReference)
429 {
430     // Convert PostScript to PDF using Quartz 2D API
431     // http://developer.apple.com/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_ps_convert/chapter_16_section_1.html
432     
433     CGPSConverterCallbacks callbacks = { 0, 0, 0, 0, 0, 0, 0, 0 };    
434     RetainPtr<CGPSConverterRef> converter(AdoptCF, CGPSConverterCreate(0, &callbacks, 0));
435     ASSERT(converter);
436
437     RetainPtr<NSData> nsData(AdoptNS, [[NSData alloc] initWithBytesNoCopy:const_cast<uint8_t*>(dataReference.data()) length:dataReference.size() freeWhenDone:NO]);   
438
439     RetainPtr<CGDataProviderRef> provider(AdoptCF, CGDataProviderCreateWithCFData((CFDataRef)nsData.get()));
440     ASSERT(provider);
441
442     RetainPtr<CFMutableDataRef> result(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0));
443     ASSERT(result);
444     
445     RetainPtr<CGDataConsumerRef> consumer(AdoptCF, CGDataConsumerCreateWithCFData(result.get()));
446     ASSERT(consumer);
447     
448     CGPSConverterConvert(converter.get(), provider.get(), consumer.get(), 0);
449
450     if (!result)
451         return 0;
452
453     return result;
454 }
455
456 void PDFViewController::setPDFDocumentData(const String& mimeType, const String& suggestedFilename, const CoreIPC::DataReference& dataReference)
457 {
458     if (equalIgnoringCase(mimeType, "application/postscript")) {
459         m_pdfData = convertPostScriptDataSourceToPDF(dataReference);
460         if (!m_pdfData)
461             return;
462         m_suggestedFilename = String(suggestedFilename + ".pdf");
463     } else {
464         // Make sure to copy the data.
465         m_pdfData.adoptCF(CFDataCreate(0, dataReference.data(), dataReference.size()));
466         m_suggestedFilename = suggestedFilename;
467     }
468
469     RetainPtr<PDFDocument> pdfDocument(AdoptNS, [[pdfDocumentClass() alloc] initWithData:(NSData *)m_pdfData.get()]);
470     [m_wkPDFView.get() setDocument:pdfDocument.get()];
471 }
472
473 double PDFViewController::zoomFactor() const
474 {
475     return [m_pdfView scaleFactor];
476 }
477
478 void PDFViewController::setZoomFactor(double zoomFactor)
479 {
480     [m_pdfView setScaleFactor:zoomFactor];
481 }
482
483 Class PDFViewController::pdfDocumentClass()
484 {
485     static Class pdfDocumentClass = [pdfKitBundle() classNamed:@"PDFDocument"];
486     
487     return pdfDocumentClass;
488 }
489
490 Class PDFViewController::pdfPreviewViewClass()
491 {
492     static Class pdfPreviewViewClass = [pdfKitBundle() classNamed:@"PDFPreviewView"];
493     
494     return pdfPreviewViewClass;
495 }
496
497 bool PDFViewController::forwardScrollWheelEvent(NSEvent *wheelEvent)
498 {
499     CGFloat deltaX = [wheelEvent deltaX];
500     if ((deltaX > 0 && !page()->canGoBack()) || (deltaX < 0 && !page()->canGoForward()))
501         return false;
502
503     [m_wkView scrollWheel:wheelEvent];
504     return true;
505 }
506
507 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
508 static IMP oldPDFViewScrollView_scrollWheel;
509
510 static WKPDFView *findEnclosingWKPDFView(NSView *view)
511 {
512     for (NSView *superview = [view superview]; superview; superview = [superview superview]) {
513         if ([superview isKindOfClass:[WKPDFView class]])
514             return static_cast<WKPDFView *>(superview);
515     }
516
517     return nil;
518 }
519
520 static void PDFViewScrollView_scrollWheel(NSScrollView* self, SEL _cmd, NSEvent *wheelEvent)
521 {
522     CGFloat deltaX = [wheelEvent deltaX];
523     CGFloat deltaY = [wheelEvent deltaY];
524
525     NSSize contentsSize = [[self documentView] bounds].size;
526     NSRect visibleRect = [self documentVisibleRect];
527
528     // We only want to forward the wheel events if the horizontal delta is non-zero,
529     // and only if we're pinned to either the left or right side.
530     // We also never want to forward momentum scroll events.
531     if ([wheelEvent momentumPhase] == NSEventPhaseNone && deltaX && fabsf(deltaY) < fabsf(deltaX)
532         && ((deltaX > 0 && visibleRect.origin.x <= 0) || (deltaX < 0 && contentsSize.width <= NSMaxX(visibleRect)))) {
533     
534         if (WKPDFView *pdfView = findEnclosingWKPDFView(self)) {
535             if ([pdfView forwardScrollWheelEvent:wheelEvent])
536                 return;
537         }
538     }
539
540     oldPDFViewScrollView_scrollWheel(self, _cmd, wheelEvent);
541 }
542 #endif
543
544 NSBundle* PDFViewController::pdfKitBundle()
545 {
546     static NSBundle *pdfKitBundle;
547     if (pdfKitBundle)
548         return pdfKitBundle;
549
550     NSString *pdfKitPath = [_NSPathForSystemFramework(@"Quartz.framework") stringByAppendingString:@"/Frameworks/PDFKit.framework"];
551     if (!pdfKitPath) {
552         LOG_ERROR("Couldn't find PDFKit.framework");
553         return nil;
554     }
555
556     pdfKitBundle = [NSBundle bundleWithPath:pdfKitPath];
557     if (![pdfKitBundle load])
558         LOG_ERROR("Couldn't load PDFKit.framework");
559
560 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
561     if (Class pdfViewScrollViewClass = [pdfKitBundle classNamed:@"PDFViewScrollView"]) {
562         if (Method scrollWheel = class_getInstanceMethod(pdfViewScrollViewClass, @selector(scrollWheel:)))
563             oldPDFViewScrollView_scrollWheel = method_setImplementation(scrollWheel, reinterpret_cast<IMP>(PDFViewScrollView_scrollWheel));
564     }
565 #endif
566
567     return pdfKitBundle;
568 }
569
570 NSPrintOperation *PDFViewController::makePrintOperation(NSPrintInfo *printInfo)
571 {
572     return [[m_pdfView document] getPrintOperationForPrintInfo:printInfo autoRotate:YES];
573 }
574
575 void PDFViewController::openPDFInFinder()
576 {
577     // We don't want to open the PDF until we have a document to write. (see 4892525).
578     if (![m_pdfView document]) {
579         NSBeep();
580         return;
581     }
582
583     NSString *path = pathToPDFOnDisk();
584     if (!path)
585         return;
586
587     if (!m_hasWrittenPDFToDisk) {
588         // Create a PDF file with the minimal permissions (only accessible to the current user, see 4145714).
589         RetainPtr<NSNumber> permissions(AdoptNS, [[NSNumber alloc] initWithInt:S_IRUSR]);
590         RetainPtr<NSDictionary> fileAttributes(AdoptNS, [[NSDictionary alloc] initWithObjectsAndKeys:permissions.get(), NSFilePosixPermissions, nil]);
591
592         if (![[NSFileManager defaultManager] createFileAtPath:path contents:(NSData *)m_pdfData.get() attributes:fileAttributes.get()])
593             return;
594
595         m_hasWrittenPDFToDisk = true;
596     }
597
598     [[NSWorkspace sharedWorkspace] openFile:path];
599 }
600
601 static void releaseCFData(unsigned char*, const void* data)
602 {
603     ASSERT(CFGetTypeID(data) == CFDataGetTypeID());
604
605     // Balanced by CFRetain in savePDFToDownloadsFolder.
606     CFRelease(data);
607 }
608
609 void PDFViewController::savePDFToDownloadsFolder()
610 {
611     // We don't want to write the file until we have a document to write. (see 5267607).
612     if (![m_pdfView document]) {
613         NSBeep();
614         return;
615     }
616
617     ASSERT(m_pdfData);
618
619     // Balanced by CFRelease in releaseCFData.
620     CFRetain(m_pdfData.get());
621
622     RefPtr<WebData> data = WebData::createWithoutCopying(CFDataGetBytePtr(m_pdfData.get()), CFDataGetLength(m_pdfData.get()), releaseCFData, m_pdfData.get());
623
624     page()->saveDataToFileInDownloadsFolder(m_suggestedFilename.get(), page()->mainFrame()->mimeType(), page()->mainFrame()->url(), data.get());
625 }
626
627 static NSString *temporaryPDFDirectoryPath()
628 {
629     static NSString *temporaryPDFDirectoryPath;
630
631     if (!temporaryPDFDirectoryPath) {
632         NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPDFs-XXXXXX"];
633         CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];
634
635         if (mkdtemp(templateRepresentation.mutableData()))
636             temporaryPDFDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy];
637     }
638
639     return temporaryPDFDirectoryPath;
640 }
641
642 NSString *PDFViewController::pathToPDFOnDisk()
643 {
644     if (m_pathToPDFOnDisk)
645         return m_pathToPDFOnDisk.get();
646
647     NSString *pdfDirectoryPath = temporaryPDFDirectoryPath();
648     if (!pdfDirectoryPath)
649         return nil;
650
651     NSString *path = [pdfDirectoryPath stringByAppendingPathComponent:m_suggestedFilename.get()];
652
653     NSFileManager *fileManager = [NSFileManager defaultManager];
654     if ([fileManager fileExistsAtPath:path]) {
655         NSString *pathTemplatePrefix = [pdfDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
656         NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:m_suggestedFilename.get()];
657         CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];
658
659         int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
660         if (fd < 0)
661             return nil;
662
663         close(fd);
664         path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
665     }
666
667     m_pathToPDFOnDisk.adoptNS([path copy]);
668     return path;
669 }
670
671 void PDFViewController::linkClicked(const String& url)
672 {
673     NSEvent* nsEvent = [NSApp currentEvent];
674     WebMouseEvent event;
675     switch ([nsEvent type]) {
676     case NSLeftMouseUp:
677     case NSRightMouseUp:
678     case NSOtherMouseUp:
679         event = WebEventFactory::createWebMouseEvent(nsEvent, m_pdfView);
680     default:
681         // For non mouse-clicks or for keyboard events, pass an empty WebMouseEvent
682         // through.  The event is only used by the WebFrameLoaderClient to determine
683         // the modifier keys and which mouse button is down.  These queries will be
684         // valid with an empty event.
685         break;
686     }
687     
688     page()->linkClicked(url, event);
689 }
690
691 void PDFViewController::print()
692 {
693     page()->printMainFrame();
694 }
695
696 void PDFViewController::findString(const String& string, FindOptions options, unsigned maxMatchCount)
697 {
698     BOOL forward = !(options & FindOptionsBackwards);
699     BOOL caseFlag = !(options & FindOptionsCaseInsensitive);
700     BOOL wrapFlag = options & FindOptionsWrapAround;
701
702     PDFSelection *selection = [m_wkPDFView.get() _nextMatchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag fromSelection:[m_pdfView currentSelection] startInSelection:NO];
703     if (!selection) {
704         page()->didFailToFindString(string);
705         return;
706     }
707
708     NSUInteger matchCount;
709     if (!maxMatchCount) {
710         // If the max was zero, any result means we exceeded the max. We can skip computing the actual count.
711         matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
712     } else {
713         matchCount = [m_wkPDFView.get() _countMatches:string caseSensitive:caseFlag];
714         if (matchCount > maxMatchCount)
715             matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
716     }
717
718     [m_pdfView setCurrentSelection:selection];
719     [m_pdfView scrollSelectionToVisible:nil];
720     page()->didFindString(string, matchCount);
721 }
722
723 void PDFViewController::countStringMatches(const String& string, FindOptions options, unsigned maxMatchCount)
724 {
725     BOOL caseFlag = !(options & FindOptionsCaseInsensitive);
726
727     NSUInteger matchCount = [m_wkPDFView.get() _countMatches:string caseSensitive:caseFlag];
728     if (matchCount > maxMatchCount)
729         matchCount = maxMatchCount;
730     page()->didCountStringMatches(string, matchCount);
731 }
732
733 } // namespace WebKit