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