1/*
2 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2013 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 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#if !PLATFORM(IOS)
30
31#import "WebPDFView.h"
32
33#import "DOMNodeInternal.h"
34#import "DOMRangeInternal.h"
35#import "WebDataSourceInternal.h"
36#import "WebDelegateImplementationCaching.h"
37#import "WebDocumentInternal.h"
38#import "WebDocumentPrivate.h"
39#import "WebFrame.h"
40#import "WebFrameInternal.h"
41#import "WebFrameView.h"
42#import "WebLocalizableStringsInternal.h"
43#import "WebNSArrayExtras.h"
44#import "WebNSPasteboardExtras.h"
45#import "WebNSViewExtras.h"
46#import "WebPDFRepresentation.h"
47#import "WebPreferencesPrivate.h"
48#import "WebUIDelegate.h"
49#import "WebUIDelegatePrivate.h"
50#import "WebView.h"
51#import "WebViewInternal.h"
52#import <PDFKit/PDFKit.h>
53#import <WebCore/DataTransfer.h>
54#import <WebCore/EventNames.h>
55#import <WebCore/FormState.h>
56#import <WebCore/Frame.h>
57#import <WebCore/FrameLoadRequest.h>
58#import <WebCore/FrameLoader.h>
59#import <WebCore/HTMLFormElement.h>
60#import <WebCore/HTMLFrameOwnerElement.h>
61#import <WebCore/URL.h>
62#import <WebCore/KeyboardEvent.h>
63#import <WebCore/MouseEvent.h>
64#import <WebCore/PlatformEventFactoryMac.h>
65#import <WebCore/RuntimeApplicationChecks.h>
66#import <WebCore/WebNSAttributedStringExtras.h>
67#import <wtf/Assertions.h>
68#import <wtf/CurrentTime.h>
69
70#ifdef __has_include
71#if __has_include(<ApplicationServices/ApplicationServicesPriv.h>)
72#import <ApplicationServices/ApplicationServicesPriv.h>
73#endif
74#endif
75
76extern "C" {
77    bool CGContextGetAllowsFontSmoothing(CGContextRef context);
78    bool CGContextGetAllowsFontSubpixelQuantization(CGContextRef context);
79}
80
81using namespace WebCore;
82
83// Redeclarations of PDFKit notifications. We can't use the API since we use a weak link to the framework.
84#define _webkit_PDFViewDisplayModeChangedNotification @"PDFViewDisplayModeChanged"
85#define _webkit_PDFViewScaleChangedNotification @"PDFViewScaleChanged"
86#define _webkit_PDFViewPageChangedNotification @"PDFViewChangedPage"
87
88@interface PDFDocument (PDFKitSecretsIKnow)
89- (NSPrintOperation *)getPrintOperationForPrintInfo:(NSPrintInfo *)printInfo autoRotate:(BOOL)doRotate;
90@end
91
92extern "C" NSString *_NSPathForSystemFramework(NSString *framework);
93
94@interface WebPDFView (FileInternal)
95+ (Class)_PDFPreviewViewClass;
96+ (Class)_PDFViewClass;
97- (void)_applyPDFDefaults;
98- (BOOL)_canLookUpInDictionary;
99- (NSClipView *)_clipViewForPDFDocumentView;
100- (NSEvent *)_fakeKeyEventWithFunctionKey:(unichar)functionKey;
101- (NSMutableArray *)_menuItemsFromPDFKitForEvent:(NSEvent *)theEvent;
102- (PDFSelection *)_nextMatchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag fromSelection:(PDFSelection *)initialSelection startInSelection:(BOOL)startInSelection;
103- (void)_openWithFinder:(id)sender;
104- (NSString *)_path;
105- (void)_PDFDocumentViewMightHaveScrolled:(NSNotification *)notification;
106- (BOOL)_pointIsInSelection:(NSPoint)point;
107- (NSAttributedString *)_scaledAttributedString:(NSAttributedString *)unscaledAttributedString;
108- (void)_setTextMatches:(NSArray *)array;
109- (NSString *)_temporaryPDFDirectoryPath;
110- (void)_trackFirstResponder;
111- (void)_updatePreferencesSoon;
112- (NSSet *)_visiblePDFPages;
113@end;
114
115@interface NSView (Details)
116- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView;
117- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect;
118- (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView;
119- (void)_recursive:(BOOL)recurseX displayRectIgnoringOpacity:(NSRect)displayRect inGraphicsContext:(NSGraphicsContext *)graphicsContext CGContext:(CGContextRef)ctx topView:(BOOL)isTopView shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor;
120@end
121
122// WebPDFPrefUpdatingProxy is a class that forwards everything it gets to a target and updates the PDF viewing prefs
123// after each of those messages.  We use it as a way to hook all the places that the PDF viewing attrs change.
124@interface WebPDFPrefUpdatingProxy : NSProxy {
125    WebPDFView *view;
126}
127- (id)initWithView:(WebPDFView *)view;
128@end
129
130// MARK: C UTILITY FUNCTIONS
131
132static void _applicationInfoForMIMEType(NSString *type, NSString **name, NSImage **image)
133{
134    NSURL *appURL = nil;
135
136#pragma clang diagnostic push
137#pragma clang diagnostic ignored "-Wdeprecated-declarations"
138    OSStatus error = LSCopyApplicationForMIMEType((CFStringRef)type, kLSRolesAll, (CFURLRef *)&appURL);
139#pragma clang diagnostic pop
140    if (error != noErr)
141        return;
142
143    NSString *appPath = [appURL path];
144    CFRelease (appURL);
145
146    *image = [[NSWorkspace sharedWorkspace] iconForFile:appPath];
147    [*image setSize:NSMakeSize(16.f,16.f)];
148
149    NSString *appName = [[NSFileManager defaultManager] displayNameAtPath:appPath];
150    *name = appName;
151}
152
153// FIXME 4182876: We can eliminate this function in favor if -isEqual: if [PDFSelection isEqual:] is overridden
154// to compare contents.
155static BOOL _PDFSelectionsAreEqual(PDFSelection *selectionA, PDFSelection *selectionB)
156{
157    NSArray *aPages = [selectionA pages];
158    NSArray *bPages = [selectionB pages];
159
160    if (![aPages isEqual:bPages])
161        return NO;
162
163    int count = [aPages count];
164    int i;
165    for (i = 0; i < count; ++i) {
166        NSRect aBounds = [selectionA boundsForPage:[aPages objectAtIndex:i]];
167        NSRect bBounds = [selectionB boundsForPage:[bPages objectAtIndex:i]];
168        if (!NSEqualRects(aBounds, bBounds)) {
169            return NO;
170        }
171    }
172
173    return YES;
174}
175
176@implementation WebPDFView
177
178// MARK: WebPDFView API
179
180+ (NSBundle *)PDFKitBundle
181{
182    static NSBundle *PDFKitBundle = nil;
183    if (PDFKitBundle == nil) {
184        NSString *PDFKitPath = [_NSPathForSystemFramework(@"Quartz.framework") stringByAppendingString:@"/Frameworks/PDFKit.framework"];
185        if (PDFKitPath == nil) {
186            LOG_ERROR("Couldn't find PDFKit.framework");
187            return nil;
188        }
189        PDFKitBundle = [NSBundle bundleWithPath:PDFKitPath];
190        if (![PDFKitBundle load]) {
191            LOG_ERROR("Couldn't load PDFKit.framework");
192        }
193    }
194    return PDFKitBundle;
195}
196
197+ (NSArray *)supportedMIMETypes
198{
199    return [WebPDFRepresentation supportedMIMETypes];
200}
201
202- (void)setPDFDocument:(PDFDocument *)doc
203{
204    // Both setDocument: and _applyPDFDefaults will trigger scale and mode-changed notifications.
205    // Those aren't reflecting user actions, so we need to ignore them.
206    _ignoreScaleAndDisplayModeAndPageNotifications = YES;
207    [PDFSubview setDocument:doc];
208    [self _applyPDFDefaults];
209    _ignoreScaleAndDisplayModeAndPageNotifications = NO;
210}
211
212- (PDFDocument *)PDFDocument
213{
214    return [PDFSubview document];
215}
216
217// MARK: NSObject OVERRIDES
218
219- (void)dealloc
220{
221    [dataSource release];
222    [previewView release];
223    [PDFSubview setDelegate:nil];
224    [PDFSubview release];
225    [path release];
226    [PDFSubviewProxy release];
227    [textMatches release];
228    [super dealloc];
229}
230
231// MARK: NSResponder OVERRIDES
232
233- (void)centerSelectionInVisibleArea:(id)sender
234{
235    [PDFSubview scrollSelectionToVisible:nil];
236}
237
238- (void)scrollPageDown:(id)sender
239{
240    // PDFView doesn't support this responder method directly, so we pass it a fake key event
241    [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSPageDownFunctionKey]];
242}
243
244- (void)scrollPageUp:(id)sender
245{
246    // PDFView doesn't support this responder method directly, so we pass it a fake key event
247    [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSPageUpFunctionKey]];
248}
249
250- (void)scrollLineDown:(id)sender
251{
252    // PDFView doesn't support this responder method directly, so we pass it a fake key event
253    [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSDownArrowFunctionKey]];
254}
255
256- (void)scrollLineUp:(id)sender
257{
258    // PDFView doesn't support this responder method directly, so we pass it a fake key event
259    [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSUpArrowFunctionKey]];
260}
261
262- (void)scrollToBeginningOfDocument:(id)sender
263{
264    // PDFView doesn't support this responder method directly, so we pass it a fake key event
265    [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSHomeFunctionKey]];
266}
267
268- (void)scrollToEndOfDocument:(id)sender
269{
270    // PDFView doesn't support this responder method directly, so we pass it a fake key event
271    [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSEndFunctionKey]];
272}
273
274// jumpToSelection is the old name for what AppKit now calls centerSelectionInVisibleArea. Safari
275// was using the old jumpToSelection selector in its menu. Newer versions of Safari will us the
276// selector centerSelectionInVisibleArea. We'll leave this old selector in place for two reasons:
277// (1) compatibility between older Safari and newer WebKit; (2) other WebKit-based applications
278// might be using the jumpToSelection: selector, and we don't want to break them.
279- (void)jumpToSelection:(id)sender
280{
281    [self centerSelectionInVisibleArea:nil];
282}
283
284// MARK: NSView OVERRIDES
285
286- (BOOL)acceptsFirstResponder {
287    return YES;
288}
289
290- (BOOL)becomeFirstResponder
291{
292    // This works together with setNextKeyView to splice our PDFSubview into
293    // the key loop similar to the way NSScrollView does this.
294    NSWindow *window = [self window];
295    id newFirstResponder = nil;
296
297    if ([window keyViewSelectionDirection] == NSSelectingPrevious) {
298        NSView *previousValidKeyView = [self previousValidKeyView];
299        if ((previousValidKeyView != self) && (previousValidKeyView != PDFSubview))
300            newFirstResponder = previousValidKeyView;
301    } else {
302        NSView *PDFDocumentView = [PDFSubview documentView];
303        if ([PDFDocumentView acceptsFirstResponder])
304            newFirstResponder = PDFDocumentView;
305    }
306
307    if (!newFirstResponder)
308        return NO;
309
310    if (![window makeFirstResponder:newFirstResponder])
311        return NO;
312
313    [[dataSource webFrame] _clearSelectionInOtherFrames];
314
315    return YES;
316}
317
318- (NSView *)hitTest:(NSPoint)point
319{
320    // Override hitTest so we can override menuForEvent.
321    NSEvent *event = [NSApp currentEvent];
322    NSEventType type = [event type];
323    if (type == NSRightMouseDown || (type == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask)))
324        return self;
325
326    return [super hitTest:point];
327}
328
329- (id)initWithFrame:(NSRect)frame
330{
331    self = [super initWithFrame:frame];
332    if (self) {
333        [self setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
334
335        Class previewViewClass = [[self class] _PDFPreviewViewClass];
336
337        // We might not have found a previewViewClass, but if we did find it
338        // then we should be able to create an instance.
339        if (previewViewClass) {
340            previewView = [[previewViewClass alloc] initWithFrame:frame];
341            ASSERT(previewView);
342        }
343
344        NSView *topLevelPDFKitView = nil;
345        if (previewView) {
346            // We'll retain the PDFSubview here so that it is equally retained in all
347            // code paths. That way we don't need to worry about conditionally releasing
348            // it later.
349            PDFSubview = [[previewView performSelector:@selector(pdfView)] retain];
350            topLevelPDFKitView = previewView;
351        } else {
352            PDFSubview = [[[[self class] _PDFViewClass] alloc] initWithFrame:frame];
353            topLevelPDFKitView = PDFSubview;
354        }
355
356        ASSERT(PDFSubview);
357
358        [topLevelPDFKitView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
359        [self addSubview:topLevelPDFKitView];
360
361        [PDFSubview setDelegate:self];
362        written = NO;
363        // Messaging this proxy is the same as messaging PDFSubview, with the side effect that the
364        // PDF viewing defaults are updated afterwards
365        PDFSubviewProxy = (PDFView *)[[WebPDFPrefUpdatingProxy alloc] initWithView:self];
366    }
367
368    return self;
369}
370
371// These states can be mutated by PDFKit but are not saved
372// on the context's state stack. (<rdar://problem/14951759>)
373
374- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView
375{
376    CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
377
378    bool allowsSmoothing = CGContextGetAllowsFontSmoothing(context);
379    bool allowsSubpixelQuantization = CGContextGetAllowsFontSubpixelQuantization(context);
380
381    [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect rectIsVisibleRectForView:visibleView topView:topView];
382
383    CGContextSetAllowsFontSmoothing(context, allowsSmoothing);
384    CGContextSetAllowsFontSubpixelQuantization(context, allowsSubpixelQuantization);
385}
386
387- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect
388{
389    CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
390
391    bool allowsSmoothing = CGContextGetAllowsFontSmoothing(context);
392    bool allowsSubpixelQuantization = CGContextGetAllowsFontSubpixelQuantization(context);
393
394    [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect];
395
396    CGContextSetAllowsFontSmoothing(context, allowsSmoothing);
397    CGContextSetAllowsFontSubpixelQuantization(context, allowsSubpixelQuantization);
398}
399
400- (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)graphicsContext topView:(BOOL)topView
401{
402    CGContextRef context = (CGContextRef)[graphicsContext graphicsPort];
403
404    bool allowsSmoothing = CGContextGetAllowsFontSmoothing(context);
405    bool allowsSubpixelQuantization = CGContextGetAllowsFontSubpixelQuantization(context);
406
407    [super _recursive:recurse displayRectIgnoringOpacity:displayRect inContext:graphicsContext topView:topView];
408
409    CGContextSetAllowsFontSmoothing(context, allowsSmoothing);
410    CGContextSetAllowsFontSubpixelQuantization(context, allowsSubpixelQuantization);
411}
412
413- (void)_recursive:(BOOL)recurseX displayRectIgnoringOpacity:(NSRect)displayRect inGraphicsContext:(NSGraphicsContext *)graphicsContext CGContext:(CGContextRef)context topView:(BOOL)isTopView shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor
414{
415    bool allowsSmoothing = CGContextGetAllowsFontSmoothing(context);
416    bool allowsSubpixelQuantization = CGContextGetAllowsFontSubpixelQuantization(context);
417
418    [super _recursive:recurseX displayRectIgnoringOpacity:displayRect inGraphicsContext:graphicsContext CGContext:context topView:isTopView shouldChangeFontReferenceColor:shouldChangeFontReferenceColor];
419
420    CGContextSetAllowsFontSmoothing(context, allowsSmoothing);
421    CGContextSetAllowsFontSubpixelQuantization(context, allowsSubpixelQuantization);
422}
423
424- (NSMenu *)menuForEvent:(NSEvent *)theEvent
425{
426    // Start with the menu items supplied by PDFKit, with WebKit tags applied
427    NSMutableArray *items = [self _menuItemsFromPDFKitForEvent:theEvent];
428
429    // Add in an "Open with <default PDF viewer>" item
430    NSString *appName = nil;
431    NSImage *appIcon = nil;
432
433    _applicationInfoForMIMEType([dataSource _responseMIMEType], &appName, &appIcon);
434    if (!appName)
435        appName = UI_STRING_INTERNAL("Finder", "Default application name for Open With context menu");
436
437    // To match the PDFKit style, we'll add Open with Preview even when there's no document yet to view, and
438    // disable it using validateUserInterfaceItem.
439    NSString *title = [NSString stringWithFormat:UI_STRING_INTERNAL("Open with %@", "context menu item for PDF"), appName];
440    NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:@selector(_openWithFinder:) keyEquivalent:@""];
441    [item setTag:WebMenuItemTagOpenWithDefaultApplication];
442    if (appIcon)
443        [item setImage:appIcon];
444    [items insertObject:item atIndex:0];
445    [item release];
446
447    [items insertObject:[NSMenuItem separatorItem] atIndex:1];
448
449    // pass the items off to the WebKit context menu mechanism
450    WebView *webView = [[dataSource webFrame] webView];
451    ASSERT(webView);
452    return [webView _menuForElement:[self elementAtPoint:[self convertPoint:[theEvent locationInWindow] fromView:nil]] defaultItems:items];
453}
454
455- (void)setNextKeyView:(NSView *)aView
456{
457    // This works together with becomeFirstResponder to splice PDFSubview into
458    // the key loop similar to the way NSScrollView and NSClipView do this.
459    NSView *documentView = [PDFSubview documentView];
460    if (documentView) {
461        [documentView setNextKeyView:aView];
462
463        // We need to make the documentView be the next view in the keyview loop.
464        // It would seem more sensible to do this in our init method, but it turns out
465        // that [NSClipView setDocumentView] won't call this method if our next key view
466        // is already set, so we wait until we're called before adding this connection.
467        // We'll also clear it when we're called with nil, so this could go through the
468        // same code path more than once successfully.
469        [super setNextKeyView: aView ? documentView : nil];
470    } else
471        [super setNextKeyView:aView];
472}
473
474- (void)viewDidMoveToWindow
475{
476    // FIXME 2573089: we can observe a notification for first responder changes
477    // instead of the very frequent NSWindowDidUpdateNotification if/when 2573089 is addressed.
478    NSWindow *newWindow = [self window];
479    if (!newWindow)
480        return;
481
482    [self _trackFirstResponder];
483    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
484    [notificationCenter addObserver:self
485                           selector:@selector(_trackFirstResponder)
486                               name:NSWindowDidUpdateNotification
487                             object:newWindow];
488
489    [notificationCenter addObserver:self
490                           selector:@selector(_scaleOrDisplayModeOrPageChanged:)
491                               name:_webkit_PDFViewScaleChangedNotification
492                             object:PDFSubview];
493
494    [notificationCenter addObserver:self
495                           selector:@selector(_scaleOrDisplayModeOrPageChanged:)
496                               name:_webkit_PDFViewDisplayModeChangedNotification
497                             object:PDFSubview];
498
499    [notificationCenter addObserver:self
500                           selector:@selector(_scaleOrDisplayModeOrPageChanged:)
501                               name:_webkit_PDFViewPageChangedNotification
502                             object:PDFSubview];
503
504    [notificationCenter addObserver:self
505                           selector:@selector(_PDFDocumentViewMightHaveScrolled:)
506                               name:NSViewBoundsDidChangeNotification
507                             object:[self _clipViewForPDFDocumentView]];
508}
509
510- (void)viewWillMoveToWindow:(NSWindow *)window
511{
512    // FIXME 2573089: we can observe a notification for changes to the first responder
513    // instead of the very frequent NSWindowDidUpdateNotification if/when 2573089 is addressed.
514    NSWindow *oldWindow = [self window];
515    if (!oldWindow)
516        return;
517
518    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
519    [notificationCenter removeObserver:self
520                                  name:NSWindowDidUpdateNotification
521                                object:oldWindow];
522    [notificationCenter removeObserver:self
523                                  name:_webkit_PDFViewScaleChangedNotification
524                                object:PDFSubview];
525    [notificationCenter removeObserver:self
526                                  name:_webkit_PDFViewDisplayModeChangedNotification
527                                object:PDFSubview];
528    [notificationCenter removeObserver:self
529                                  name:_webkit_PDFViewPageChangedNotification
530                                object:PDFSubview];
531
532    [notificationCenter removeObserver:self
533                                  name:NSViewBoundsDidChangeNotification
534                                object:[self _clipViewForPDFDocumentView]];
535
536    firstResponderIsPDFDocumentView = NO;
537}
538
539// MARK: NSUserInterfaceValidations PROTOCOL IMPLEMENTATION
540
541- (BOOL)validateUserInterfaceItemWithoutDelegate:(id <NSValidatedUserInterfaceItem>)item
542{
543    SEL action = [item action];
544    if (action == @selector(takeFindStringFromSelection:) || action == @selector(centerSelectionInVisibleArea:) || action == @selector(jumpToSelection:))
545        return [PDFSubview currentSelection] != nil;
546
547    if (action == @selector(_openWithFinder:))
548        return [PDFSubview document] != nil;
549
550    if (action == @selector(_lookUpInDictionaryFromMenu:))
551        return [self _canLookUpInDictionary];
552
553    return YES;
554}
555
556- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
557{
558    // This can be called during teardown when _webView is nil. Return NO when this happens, because CallUIDelegateReturningBoolean
559    // assumes the WebVIew is non-nil.
560    if (![self _webView])
561        return NO;
562    BOOL result = [self validateUserInterfaceItemWithoutDelegate:item];
563    return CallUIDelegateReturningBoolean(result, [self _webView], @selector(webView:validateUserInterfaceItem:defaultValidation:), item, result);
564}
565
566// MARK: INTERFACE BUILDER ACTIONS FOR SAFARI
567
568// Surprisingly enough, this isn't defined in any superclass, though it is defined in assorted AppKit classes since
569// it's a standard menu item IBAction.
570- (IBAction)copy:(id)sender
571{
572    [PDFSubview copy:sender];
573}
574
575// This used to be a standard IBAction (for Use Selection For Find), but AppKit now uses performFindPanelAction:
576// with a menu item tag for this purpose.
577- (IBAction)takeFindStringFromSelection:(id)sender
578{
579    [NSPasteboard _web_setFindPasteboardString:[[PDFSubview currentSelection] string] withOwner:self];
580}
581
582// MARK: WebFrameView UNDECLARED "DELEGATE METHODS"
583
584// This is tested in -[WebFrameView canPrintHeadersAndFooters], but isn't declared anywhere (yuck)
585- (BOOL)canPrintHeadersAndFooters
586{
587    return NO;
588}
589
590// This is tested in -[WebFrameView printOperationWithPrintInfo:], but isn't declared anywhere (yuck)
591- (NSPrintOperation *)printOperationWithPrintInfo:(NSPrintInfo *)printInfo
592{
593    return [[PDFSubview document] getPrintOperationForPrintInfo:printInfo autoRotate:YES];
594}
595
596// MARK: WebDocumentView PROTOCOL IMPLEMENTATION
597
598- (void)setDataSource:(WebDataSource *)ds
599{
600    if (dataSource == ds)
601        return;
602
603    dataSource = [ds retain];
604
605    // FIXME: There must be some better place to put this. There is no comment in ChangeLog
606    // explaining why it's in this method.
607    [self setFrame:[[self superview] frame]];
608}
609
610- (void)dataSourceUpdated:(WebDataSource *)dataSource
611{
612}
613
614- (void)setNeedsLayout:(BOOL)flag
615{
616}
617
618- (void)layout
619{
620}
621
622- (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
623{
624}
625
626- (void)viewDidMoveToHostWindow
627{
628}
629
630// MARK: WebDocumentElement PROTOCOL IMPLEMENTATION
631
632- (NSDictionary *)elementAtPoint:(NSPoint)point
633{
634    WebFrame *frame = [dataSource webFrame];
635    ASSERT(frame);
636
637    return [NSDictionary dictionaryWithObjectsAndKeys:
638        frame, WebElementFrameKey,
639        [NSNumber numberWithBool:[self _pointIsInSelection:point]], WebElementIsSelectedKey,
640        nil];
641}
642
643- (NSDictionary *)elementAtPoint:(NSPoint)point allowShadowContent:(BOOL)allow
644{
645    return [self elementAtPoint:point];
646}
647
648// MARK: WebDocumentSearching PROTOCOL IMPLEMENTATION
649
650- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag
651{
652    return [self searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:NO];
653}
654
655// MARK: WebDocumentIncrementalSearching PROTOCOL IMPLEMENTATION
656
657- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection
658{
659    PDFSelection *selection = [self _nextMatchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag fromSelection:[PDFSubview currentSelection] startInSelection:startInSelection];
660    if (!selection)
661        return NO;
662
663    [PDFSubview setCurrentSelection:selection];
664    [PDFSubview scrollSelectionToVisible:nil];
665    return YES;
666}
667
668// MARK: WebMultipleTextMatches PROTOCOL IMPLEMENTATION
669
670- (void)setMarkedTextMatchesAreHighlighted:(BOOL)newValue
671{
672    // This method is part of the WebMultipleTextMatches algorithm, but this class doesn't support
673    // highlighting text matches inline.
674#ifndef NDEBUG
675    if (newValue)
676        LOG_ERROR("[WebPDFView setMarkedTextMatchesAreHighlighted:] called with YES, which isn't supported");
677#endif
678}
679
680- (BOOL)markedTextMatchesAreHighlighted
681{
682    return NO;
683}
684
685static BOOL isFrameInRange(WebFrame *frame, DOMRange *range)
686{
687    BOOL inRange = NO;
688    for (HTMLFrameOwnerElement* ownerElement = core(frame)->ownerElement(); ownerElement; ownerElement = ownerElement->document().frame()->ownerElement()) {
689        if (&ownerElement->document() == &core(range)->ownerDocument()) {
690            inRange = [range intersectsNode:kit(ownerElement)];
691            break;
692        }
693    }
694    return inRange;
695}
696
697- (NSUInteger)countMatchesForText:(NSString *)string inDOMRange:(DOMRange *)range options:(WebFindOptions)options limit:(NSUInteger)limit markMatches:(BOOL)markMatches
698{
699    if (range && !isFrameInRange([dataSource webFrame], range))
700        return 0;
701
702    PDFSelection *previousMatch = nil;
703    NSMutableArray *matches = [[NSMutableArray alloc] initWithCapacity:limit];
704
705    for (;;) {
706        PDFSelection *nextMatch = [self _nextMatchFor:string direction:YES caseSensitive:!(options & WebFindOptionsCaseInsensitive) wrap:NO fromSelection:previousMatch startInSelection:NO];
707        if (!nextMatch)
708            break;
709
710        [matches addObject:nextMatch];
711        previousMatch = nextMatch;
712
713        if ([matches count] >= limit)
714            break;
715    }
716
717    [self _setTextMatches:matches];
718    [matches release];
719
720    return [matches count];
721}
722
723- (void)unmarkAllTextMatches
724{
725    [self _setTextMatches:nil];
726}
727
728- (NSArray *)rectsForTextMatches
729{
730    NSMutableArray *result = [NSMutableArray arrayWithCapacity:[textMatches count]];
731    NSSet *visiblePages = [self _visiblePDFPages];
732    NSEnumerator *matchEnumerator = [textMatches objectEnumerator];
733    PDFSelection *match;
734
735    while ((match = [matchEnumerator nextObject]) != nil) {
736        NSEnumerator *pages = [[match pages] objectEnumerator];
737        PDFPage *page;
738        while ((page = [pages nextObject]) != nil) {
739
740            // Skip pages that aren't visible (needed for non-continuous modes, see 5362989)
741            if (![visiblePages containsObject:page])
742                continue;
743
744            NSRect selectionOnPageInPDFViewCoordinates = [PDFSubview convertRect:[match boundsForPage:page] fromPage:page];
745            [result addObject:[NSValue valueWithRect:selectionOnPageInPDFViewCoordinates]];
746        }
747    }
748
749    return result;
750}
751
752// MARK: WebDocumentText PROTOCOL IMPLEMENTATION
753
754- (BOOL)supportsTextEncoding
755{
756    return NO;
757}
758
759- (NSString *)string
760{
761    return [[PDFSubview document] string];
762}
763
764- (NSAttributedString *)attributedString
765{
766    // changing the selection is a hack, but the only way to get an attr string is via PDFSelection
767
768    // must copy this selection object because we change the selection which seems to release it
769    PDFSelection *savedSelection = [[PDFSubview currentSelection] copy];
770    [PDFSubview selectAll:nil];
771    NSAttributedString *result = [[PDFSubview currentSelection] attributedString];
772    if (savedSelection) {
773        [PDFSubview setCurrentSelection:savedSelection];
774        [savedSelection release];
775    } else {
776        // FIXME: behavior of setCurrentSelection:nil is not documented - check 4182934 for progress
777        // Otherwise, we could collapse this code with the case above.
778        [PDFSubview clearSelection];
779    }
780
781    result = [self _scaledAttributedString:result];
782
783    return result;
784}
785
786- (NSString *)selectedString
787{
788    return [[PDFSubview currentSelection] string];
789}
790
791- (NSAttributedString *)selectedAttributedString
792{
793    return [self _scaledAttributedString:[[PDFSubview currentSelection] attributedString]];
794}
795
796- (void)selectAll
797{
798    [PDFSubview selectAll:nil];
799}
800
801- (void)deselectAll
802{
803    [PDFSubview clearSelection];
804}
805
806// MARK: WebDocumentViewState PROTOCOL IMPLEMENTATION
807
808// Even though to WebKit we are the "docView", in reality a PDFView contains its own scrollview and docView.
809// And it even turns out there is another PDFKit view between the docView and its enclosing ScrollView, so
810// we have to be sure to do our calculations based on that view, immediately inside the ClipView.  We try
811// to make as few assumptions about the PDFKit view hierarchy as possible.
812
813- (NSPoint)scrollPoint
814{
815    NSView *realDocView = [PDFSubview documentView];
816    NSClipView *clipView = [[realDocView enclosingScrollView] contentView];
817    return [clipView bounds].origin;
818}
819
820- (void)setScrollPoint:(NSPoint)p
821{
822    WebFrame *frame = [dataSource webFrame];
823    //FIXME:  We only restore scroll state in the non-frames case because otherwise we get a crash due to
824    // PDFKit calling display from within its drawRect:. See bugzilla 4164.
825    if (![frame parentFrame]) {
826        NSView *realDocView = [PDFSubview documentView];
827        [(NSView *)[[realDocView enclosingScrollView] documentView] scrollPoint:p];
828    }
829}
830
831- (id)viewState
832{
833    NSMutableArray *state = [NSMutableArray arrayWithCapacity:4];
834    PDFDisplayMode mode = [PDFSubview displayMode];
835    [state addObject:[NSNumber numberWithInt:mode]];
836    if (mode == kPDFDisplaySinglePage || mode == kPDFDisplayTwoUp) {
837        unsigned int pageIndex = [[PDFSubview document] indexForPage:[PDFSubview currentPage]];
838        [state addObject:[NSNumber numberWithUnsignedInt:pageIndex]];
839    }  // else in continuous modes, scroll position gets us to the right page
840    BOOL autoScaleFlag = [PDFSubview autoScales];
841    [state addObject:[NSNumber numberWithBool:autoScaleFlag]];
842    if (!autoScaleFlag)
843        [state addObject:[NSNumber numberWithFloat:[PDFSubview scaleFactor]]];
844
845    return state;
846}
847
848- (void)setViewState:(id)statePList
849{
850    ASSERT([statePList isKindOfClass:[NSArray class]]);
851    NSArray *state = statePList;
852    int i = 0;
853    PDFDisplayMode mode = [[state objectAtIndex:i++] intValue];
854    [PDFSubview setDisplayMode:mode];
855    if (mode == kPDFDisplaySinglePage || mode == kPDFDisplayTwoUp) {
856        unsigned int pageIndex = [[state objectAtIndex:i++] unsignedIntValue];
857        [PDFSubview goToPage:[[PDFSubview document] pageAtIndex:pageIndex]];
858    }  // else in continuous modes, scroll position gets us to the right page
859    BOOL autoScaleFlag = [[state objectAtIndex:i++] boolValue];
860    [PDFSubview setAutoScales:autoScaleFlag];
861    if (!autoScaleFlag)
862        [PDFSubview setScaleFactor:[[state objectAtIndex:i++] floatValue]];
863}
864
865// MARK: _WebDocumentTextSizing PROTOCOL IMPLEMENTATION
866
867- (IBAction)_zoomOut:(id)sender
868{
869    [PDFSubviewProxy zoomOut:sender];
870}
871
872- (IBAction)_zoomIn:(id)sender
873{
874    [PDFSubviewProxy zoomIn:sender];
875}
876
877- (IBAction)_resetZoom:(id)sender
878{
879    [PDFSubviewProxy setScaleFactor:1.0f];
880}
881
882- (BOOL)_canZoomOut
883{
884    return [PDFSubview canZoomOut];
885}
886
887- (BOOL)_canZoomIn
888{
889    return [PDFSubview canZoomIn];
890}
891
892- (BOOL)_canResetZoom
893{
894    return [PDFSubview scaleFactor] != 1.0;
895}
896
897// MARK: WebDocumentSelection PROTOCOL IMPLEMENTATION
898
899- (NSRect)selectionRect
900{
901    NSRect result = NSZeroRect;
902    PDFSelection *selection = [PDFSubview currentSelection];
903    NSEnumerator *pages = [[selection pages] objectEnumerator];
904    PDFPage *page;
905    while ((page = [pages nextObject]) != nil) {
906        NSRect selectionOnPageInPDFViewCoordinates = [PDFSubview convertRect:[selection boundsForPage:page] fromPage:page];
907        if (NSIsEmptyRect(result))
908            result = selectionOnPageInPDFViewCoordinates;
909        else
910            result = NSUnionRect(result, selectionOnPageInPDFViewCoordinates);
911    }
912
913    // Convert result to be in documentView (selectionView) coordinates
914    result = [PDFSubview convertRect:result toView:[PDFSubview documentView]];
915
916    return result;
917}
918
919- (NSArray *)selectionTextRects
920{
921    // FIXME: We'd need new PDFKit API/SPI to get multiple text rects for selections that intersect more than one line
922    return [NSArray arrayWithObject:[NSValue valueWithRect:[self selectionRect]]];
923}
924
925- (NSView *)selectionView
926{
927    return [PDFSubview documentView];
928}
929
930- (NSImage *)selectionImageForcingBlackText:(BOOL)forceBlackText
931{
932    // Convert the selection to an attributed string, and draw that.
933    // FIXME 4621154: this doesn't handle italics (and maybe other styles)
934    // FIXME 4604366: this doesn't handle text at non-actual size
935    NSMutableAttributedString *attributedString = [[self selectedAttributedString] mutableCopy];
936    NSRange wholeStringRange = NSMakeRange(0, [attributedString length]);
937
938    // Modify the styles in the attributed string to draw black text, no background, and no underline. We draw
939    // no underline because it would look ugly.
940    [attributedString beginEditing];
941    [attributedString removeAttribute:NSBackgroundColorAttributeName range:wholeStringRange];
942    [attributedString removeAttribute:NSUnderlineStyleAttributeName range:wholeStringRange];
943    if (forceBlackText)
944        [attributedString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithDeviceWhite:0.0f alpha:1.0f] range:wholeStringRange];
945    [attributedString endEditing];
946
947    NSImage* selectionImage = [[[NSImage alloc] initWithSize:[self selectionRect].size] autorelease];
948
949    [selectionImage lockFocus];
950    [attributedString drawAtPoint:NSZeroPoint];
951    [selectionImage unlockFocus];
952
953    [attributedString release];
954
955    return selectionImage;
956}
957
958- (NSRect)selectionImageRect
959{
960    // FIXME: deal with clipping?
961    return [self selectionRect];
962}
963
964- (NSArray *)pasteboardTypesForSelection
965{
966    return [NSArray arrayWithObjects:NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil];
967}
968
969- (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard
970{
971    NSAttributedString *attributedString = [self selectedAttributedString];
972
973    if ([types containsObject:NSRTFDPboardType]) {
974        NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
975        [pasteboard setData:RTFDData forType:NSRTFDPboardType];
976    }
977
978    if ([types containsObject:NSRTFPboardType]) {
979        if ([attributedString containsAttachments])
980            attributedString = attributedStringByStrippingAttachmentCharacters(attributedString);
981
982        NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
983        [pasteboard setData:RTFData forType:NSRTFPboardType];
984    }
985
986    if ([types containsObject:NSStringPboardType])
987        [pasteboard setString:[self selectedString] forType:NSStringPboardType];
988}
989
990// MARK: PDFView DELEGATE METHODS
991
992- (void)PDFViewWillClickOnLink:(PDFView *)sender withURL:(NSURL *)URL
993{
994    if (!URL)
995        return;
996
997    NSWindow *window = [sender window];
998    NSEvent *nsEvent = [window currentEvent];
999    const int noButton = -1;
1000    int button = noButton;
1001    RefPtr<Event> event;
1002    switch ([nsEvent type]) {
1003        case NSLeftMouseUp:
1004            button = 0;
1005            break;
1006        case NSRightMouseUp:
1007            button = 1;
1008            break;
1009        case NSOtherMouseUp:
1010            button = [nsEvent buttonNumber];
1011            break;
1012        case NSKeyDown: {
1013            PlatformKeyboardEvent pe = PlatformEventFactory::createPlatformKeyboardEvent(nsEvent);
1014            pe.disambiguateKeyDownEvent(PlatformEvent::RawKeyDown);
1015            event = KeyboardEvent::create(pe, 0);
1016            break;
1017        }
1018        default:
1019            break;
1020    }
1021    if (button != noButton) {
1022        event = MouseEvent::create(eventNames().clickEvent, true, true, currentTime(), 0, [nsEvent clickCount], 0, 0, 0, 0,
1023#if ENABLE(POINTER_LOCK)
1024            0, 0,
1025#endif
1026            [nsEvent modifierFlags] & NSControlKeyMask,
1027            [nsEvent modifierFlags] & NSAlternateKeyMask,
1028            [nsEvent modifierFlags] & NSShiftKeyMask,
1029            [nsEvent modifierFlags] & NSCommandKeyMask,
1030            button, 0, 0, true);
1031    }
1032
1033    // Call to the frame loader because this is where our security checks are made.
1034    Frame* frame = core([dataSource webFrame]);
1035    frame->loader().loadFrameRequest(FrameLoadRequest(frame->document()->securityOrigin(), ResourceRequest(URL)), LockHistory::No, LockBackForwardList::No, event.get(), 0, MaybeSendReferrer, AllowNavigationToInvalidURL::Yes);
1036}
1037
1038- (void)PDFViewOpenPDFInNativeApplication:(PDFView *)sender
1039{
1040    // Delegate method sent when the user requests opening the PDF file in the system's default app
1041    [self _openWithFinder:sender];
1042}
1043
1044- (void)PDFViewPerformPrint:(PDFView *)sender
1045{
1046    CallUIDelegate([self _webView], @selector(webView:printFrameView:), [[dataSource webFrame] frameView]);
1047}
1048
1049- (void)PDFViewSavePDFToDownloadFolder:(PDFView *)sender
1050{
1051    // We don't want to write the file until we have a document to write (see 5267607).
1052    if (![PDFSubview document]) {
1053        NSBeep();
1054        return;
1055    }
1056
1057    // Delegate method sent when the user requests downloading the PDF file to disk. We pass NO for
1058    // showingPanel: so that the PDF file is saved to the standard location without user intervention.
1059    CallUIDelegate([self _webView], @selector(webView:saveFrameView:showingPanel:), [[dataSource webFrame] frameView], NO);
1060}
1061
1062@end
1063
1064@implementation WebPDFView (FileInternal)
1065
1066+ (Class)_PDFPreviewViewClass
1067{
1068    static Class PDFPreviewViewClass = nil;
1069    static BOOL checkedForPDFPreviewViewClass = NO;
1070
1071    if (!checkedForPDFPreviewViewClass) {
1072        checkedForPDFPreviewViewClass = YES;
1073        PDFPreviewViewClass = [[WebPDFView PDFKitBundle] classNamed:@"PDFPreviewView"];
1074    }
1075
1076    // This class might not be available; callers need to deal with a nil return here.
1077    return PDFPreviewViewClass;
1078}
1079
1080+ (Class)_PDFViewClass
1081{
1082    static Class PDFViewClass = nil;
1083    if (PDFViewClass == nil) {
1084        PDFViewClass = [[WebPDFView PDFKitBundle] classNamed:@"PDFView"];
1085        if (!PDFViewClass)
1086            LOG_ERROR("Couldn't find PDFView class in PDFKit.framework");
1087    }
1088    return PDFViewClass;
1089}
1090
1091- (void)_applyPDFDefaults
1092{
1093    // Set up default viewing params
1094    WebPreferences *prefs = [[dataSource _webView] preferences];
1095    float scaleFactor = [prefs PDFScaleFactor];
1096    if (scaleFactor == 0)
1097        [PDFSubview setAutoScales:YES];
1098    else {
1099        [PDFSubview setAutoScales:NO];
1100        [PDFSubview setScaleFactor:scaleFactor];
1101    }
1102    [PDFSubview setDisplayMode:[prefs PDFDisplayMode]];
1103}
1104
1105- (BOOL)_canLookUpInDictionary
1106{
1107    return [PDFSubview respondsToSelector:@selector(_searchInDictionary:)];
1108}
1109
1110- (NSClipView *)_clipViewForPDFDocumentView
1111{
1112    NSClipView *clipView = (NSClipView *)[[PDFSubview documentView] _web_superviewOfClass:[NSClipView class]];
1113    ASSERT(clipView);
1114    return clipView;
1115}
1116
1117- (NSEvent *)_fakeKeyEventWithFunctionKey:(unichar)functionKey
1118{
1119    // FIXME 4400480: when PDFView implements the standard scrolling selectors that this
1120    // method is used to mimic, we can eliminate this method and call them directly.
1121    NSString *keyAsString = [NSString stringWithCharacters:&functionKey length:1];
1122    return [NSEvent keyEventWithType:NSKeyDown
1123                            location:NSZeroPoint
1124                       modifierFlags:0
1125                           timestamp:0
1126                        windowNumber:0
1127                             context:nil
1128                          characters:keyAsString
1129         charactersIgnoringModifiers:keyAsString
1130                           isARepeat:NO
1131                             keyCode:0];
1132}
1133
1134- (void)_lookUpInDictionaryFromMenu:(id)sender
1135{
1136    // This method is used by WebKit's context menu item. Here we map to the method that
1137    // PDFView uses. Since the PDFView method isn't API, and isn't available on all versions
1138    // of PDFKit, we use performSelector after a respondsToSelector check, rather than calling it directly.
1139    if ([self _canLookUpInDictionary])
1140        [PDFSubview performSelector:@selector(_searchInDictionary:) withObject:sender];
1141}
1142
1143- (NSMutableArray *)_menuItemsFromPDFKitForEvent:(NSEvent *)theEvent
1144{
1145    NSMutableArray *copiedItems = [NSMutableArray array];
1146    NSDictionary *actionsToTags = [[NSDictionary alloc] initWithObjectsAndKeys:
1147        [NSNumber numberWithInt:WebMenuItemPDFActualSize], NSStringFromSelector(@selector(_setActualSize:)),
1148        [NSNumber numberWithInt:WebMenuItemPDFZoomIn], NSStringFromSelector(@selector(zoomIn:)),
1149        [NSNumber numberWithInt:WebMenuItemPDFZoomOut], NSStringFromSelector(@selector(zoomOut:)),
1150        [NSNumber numberWithInt:WebMenuItemPDFAutoSize], NSStringFromSelector(@selector(_setAutoSize:)),
1151        [NSNumber numberWithInt:WebMenuItemPDFSinglePage], NSStringFromSelector(@selector(_setSinglePage:)),
1152        [NSNumber numberWithInt:WebMenuItemPDFSinglePageScrolling], NSStringFromSelector(@selector(_setSinglePageScrolling:)),
1153        [NSNumber numberWithInt:WebMenuItemPDFFacingPages], NSStringFromSelector(@selector(_setDoublePage:)),
1154        [NSNumber numberWithInt:WebMenuItemPDFFacingPagesScrolling], NSStringFromSelector(@selector(_setDoublePageScrolling:)),
1155        [NSNumber numberWithInt:WebMenuItemPDFContinuous], NSStringFromSelector(@selector(_toggleContinuous:)),
1156        [NSNumber numberWithInt:WebMenuItemPDFNextPage], NSStringFromSelector(@selector(goToNextPage:)),
1157        [NSNumber numberWithInt:WebMenuItemPDFPreviousPage], NSStringFromSelector(@selector(goToPreviousPage:)),
1158        nil];
1159
1160    // Leave these menu items out, since WebKit inserts equivalent ones. Note that we leave out PDFKit's "Look Up in Dictionary"
1161    // item here because WebKit already includes an item with the same title and purpose. We map WebKit's to PDFKit's
1162    // "Look Up in Dictionary" via the implementation of -[WebPDFView _lookUpInDictionaryFromMenu:].
1163    NSSet *unwantedActions = [[NSSet alloc] initWithObjects:
1164                              NSStringFromSelector(@selector(_searchInSpotlight:)),
1165                              NSStringFromSelector(@selector(_searchInGoogle:)),
1166                              NSStringFromSelector(@selector(_searchInDictionary:)),
1167                              NSStringFromSelector(@selector(copy:)),
1168                              nil];
1169
1170    NSEnumerator *e = [[[PDFSubview menuForEvent:theEvent] itemArray] objectEnumerator];
1171    NSMenuItem *item;
1172    while ((item = [e nextObject]) != nil) {
1173
1174        NSString *actionString = NSStringFromSelector([item action]);
1175
1176        if ([unwantedActions containsObject:actionString])
1177            continue;
1178
1179        // Copy items since a menu item can be in only one menu at a time, and we don't
1180        // want to modify the original menu supplied by PDFKit.
1181        NSMenuItem *itemCopy = [item copy];
1182        [copiedItems addObject:itemCopy];
1183        [itemCopy release];
1184
1185        // Include all of PDFKit's separators for now. At the end we'll remove any ones that were made
1186        // useless by removing PDFKit's menu items.
1187        if ([itemCopy isSeparatorItem])
1188            continue;
1189
1190        NSNumber *tagNumber = [actionsToTags objectForKey:actionString];
1191
1192        int tag;
1193        if (tagNumber != nil)
1194            tag = [tagNumber intValue];
1195        else {
1196            // This should happen only if PDFKit updates behind WebKit's back. It's non-ideal because clients that only include tags
1197            // that they recognize (like Safari) won't get these PDFKit additions until WebKit is updated to match.
1198            tag = WebMenuItemTagOther;
1199            LOG_ERROR("no WebKit menu item tag found for PDF context menu item action \"%@\", using WebMenuItemTagOther", actionString);
1200        }
1201
1202        if ([itemCopy tag] == 0) {
1203            [itemCopy setTag:tag];
1204            if ([itemCopy target] == PDFSubview) {
1205                // Note that updating the defaults is cheap because it catches redundant settings, so installing
1206                // the proxy for actions that don't impact the defaults is OK
1207                [itemCopy setTarget:PDFSubviewProxy];
1208            }
1209        } else
1210            LOG_ERROR("PDF context menu item %@ came with tag %d, so no WebKit tag was applied. This could mean that the item doesn't appear in clients such as Safari.", [itemCopy title], [itemCopy tag]);
1211    }
1212
1213    [actionsToTags release];
1214    [unwantedActions release];
1215
1216    // Since we might have removed elements supplied by PDFKit, and we want to minimize our hardwired
1217    // knowledge of the order and arrangement of PDFKit's menu items, we need to remove any bogus
1218    // separators that were left behind.
1219    [copiedItems _webkit_removeUselessMenuItemSeparators];
1220
1221    return copiedItems;
1222}
1223
1224- (PDFSelection *)_nextMatchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag fromSelection:(PDFSelection *)initialSelection startInSelection:(BOOL)startInSelection
1225{
1226    if (![string length])
1227        return nil;
1228
1229    int options = 0;
1230    if (!forward)
1231        options |= NSBackwardsSearch;
1232
1233    if (!caseFlag)
1234        options |= NSCaseInsensitiveSearch;
1235
1236    PDFDocument *document = [PDFSubview document];
1237
1238    PDFSelection *selectionForInitialSearch = [initialSelection copy];
1239    if (startInSelection) {
1240        // Initially we want to include the selected text in the search. PDFDocument's API always searches from just
1241        // past the passed-in selection, so we need to pass a selection that's modified appropriately.
1242        // FIXME 4182863: Ideally we'd use a zero-length selection at the edge of the current selection, but zero-length
1243        // selections don't work in PDFDocument. So instead we make a one-length selection just before or after the
1244        // current selection, which works for our purposes even when the current selection is at an edge of the
1245        // document.
1246        int initialSelectionLength = [[initialSelection string] length];
1247        if (forward) {
1248            [selectionForInitialSearch extendSelectionAtStart:1];
1249            [selectionForInitialSearch extendSelectionAtEnd:-initialSelectionLength];
1250        } else {
1251            [selectionForInitialSearch extendSelectionAtEnd:1];
1252            [selectionForInitialSearch extendSelectionAtStart:-initialSelectionLength];
1253        }
1254    }
1255    PDFSelection *foundSelection = [document findString:string fromSelection:selectionForInitialSearch withOptions:options];
1256    [selectionForInitialSearch release];
1257
1258    // If we first searched in the selection, and we found the selection, search again from just past the selection
1259    if (startInSelection && _PDFSelectionsAreEqual(foundSelection, initialSelection))
1260        foundSelection = [document findString:string fromSelection:initialSelection withOptions:options];
1261
1262    if (!foundSelection && wrapFlag)
1263        foundSelection = [document findString:string fromSelection:nil withOptions:options];
1264
1265    return foundSelection;
1266}
1267
1268- (void)_openWithFinder:(id)sender
1269{
1270    // We don't want to write the file until we have a document to write (see 4892525).
1271    if (![PDFSubview document]) {
1272        NSBeep();
1273        return;
1274    }
1275
1276    NSString *opath = [self _path];
1277
1278    if (opath) {
1279        if (!written) {
1280            // Create a PDF file with the minimal permissions (only accessible to the current user, see 4145714)
1281            NSNumber *permissions = [[NSNumber alloc] initWithInt:S_IRUSR];
1282            NSDictionary *fileAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:permissions, NSFilePosixPermissions, nil];
1283            [permissions release];
1284
1285            [[NSFileManager defaultManager] createFileAtPath:opath contents:[dataSource data] attributes:fileAttributes];
1286
1287            [fileAttributes release];
1288            written = YES;
1289        }
1290
1291        if (![[NSWorkspace sharedWorkspace] openFile:opath]) {
1292            // NSWorkspace couldn't open file.  Do we need an alert
1293            // here?  We ignore the error elsewhere.
1294        }
1295    }
1296}
1297
1298- (NSString *)_path
1299{
1300    // Generate path once.
1301    if (path)
1302        return path;
1303
1304    NSString *filename = [[dataSource response] suggestedFilename];
1305    NSFileManager *manager = [NSFileManager defaultManager];
1306    NSString *temporaryPDFDirectoryPath = [self _temporaryPDFDirectoryPath];
1307
1308    if (!temporaryPDFDirectoryPath) {
1309        // This should never happen; if it does we'll fail silently on non-debug builds.
1310        ASSERT_NOT_REACHED();
1311        return nil;
1312    }
1313
1314    path = [temporaryPDFDirectoryPath stringByAppendingPathComponent:filename];
1315    if ([manager fileExistsAtPath:path]) {
1316        NSString *pathTemplatePrefix = [temporaryPDFDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
1317        NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:filename];
1318        // fileSystemRepresentation returns a const char *; copy it into a char * so we can modify it safely
1319        char *cPath = strdup([pathTemplate fileSystemRepresentation]);
1320        int fd = mkstemps(cPath, strlen(cPath) - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
1321        if (fd < 0) {
1322            // Couldn't create a temporary file! Should never happen; if it does we'll fail silently on non-debug builds.
1323            ASSERT_NOT_REACHED();
1324            path = nil;
1325        } else {
1326            close(fd);
1327            path = [manager stringWithFileSystemRepresentation:cPath length:strlen(cPath)];
1328        }
1329        free(cPath);
1330    }
1331
1332    [path retain];
1333
1334    return path;
1335}
1336
1337- (void)_PDFDocumentViewMightHaveScrolled:(NSNotification *)notification
1338{
1339    NSClipView *clipView = [self _clipViewForPDFDocumentView];
1340    ASSERT([notification object] == clipView);
1341
1342    NSPoint scrollPosition = [clipView bounds].origin;
1343    if (NSEqualPoints(scrollPosition, lastScrollPosition))
1344        return;
1345
1346    lastScrollPosition = scrollPosition;
1347    WebView *webView = [self _webView];
1348    [[webView _UIDelegateForwarder] webView:webView didScrollDocumentInFrameView:[[dataSource webFrame] frameView]];
1349}
1350
1351- (PDFView *)_PDFSubview
1352{
1353    return PDFSubview;
1354}
1355
1356- (BOOL)_pointIsInSelection:(NSPoint)point
1357{
1358    PDFPage *page = [PDFSubview pageForPoint:point nearest:NO];
1359    if (!page)
1360        return NO;
1361
1362    NSRect selectionRect = [PDFSubview convertRect:[[PDFSubview currentSelection] boundsForPage:page] fromPage:page];
1363
1364    return NSPointInRect(point, selectionRect);
1365}
1366
1367- (void)_scaleOrDisplayModeOrPageChanged:(NSNotification *)notification
1368{
1369    ASSERT([notification object] == PDFSubview);
1370    if (!_ignoreScaleAndDisplayModeAndPageNotifications) {
1371        [self _updatePreferencesSoon];
1372        // Notify UI delegate that the entire page has been redrawn, since (unlike for WebHTMLView)
1373        // we can't hook into the drawing mechanism itself. This fixes 5337529.
1374        WebView *webView = [self _webView];
1375        [[webView _UIDelegateForwarder] webView:webView didDrawRect:[webView bounds]];
1376    }
1377}
1378
1379- (NSAttributedString *)_scaledAttributedString:(NSAttributedString *)unscaledAttributedString
1380{
1381    if (!unscaledAttributedString)
1382        return nil;
1383
1384    float scaleFactor = [PDFSubview scaleFactor];
1385    if (scaleFactor == 1.0)
1386        return unscaledAttributedString;
1387
1388    NSMutableAttributedString *result = [[unscaledAttributedString mutableCopy] autorelease];
1389    unsigned int length = [result length];
1390    NSRange effectiveRange = NSMakeRange(0,0);
1391
1392    [result beginEditing];
1393    while (NSMaxRange(effectiveRange) < length) {
1394        NSFont *unscaledFont = [result attribute:NSFontAttributeName atIndex:NSMaxRange(effectiveRange) effectiveRange:&effectiveRange];
1395
1396        if (!unscaledFont) {
1397            // FIXME: We can't scale the font if we don't know what it is. We should always know what it is,
1398            // but sometimes don't due to PDFKit issue 5089411. When that's addressed, we can remove this
1399            // early continue.
1400            LOG_ERROR("no font attribute found in range %@ for attributed string \"%@\" on page %@ (see radar 5089411)", NSStringFromRange(effectiveRange), result, [[dataSource request] URL]);
1401            continue;
1402        }
1403
1404        NSFont *scaledFont = [NSFont fontWithName:[unscaledFont fontName] size:[unscaledFont pointSize]*scaleFactor];
1405        [result addAttribute:NSFontAttributeName value:scaledFont range:effectiveRange];
1406    }
1407    [result endEditing];
1408
1409    return result;
1410}
1411
1412- (void)_setTextMatches:(NSArray *)array
1413{
1414    [array retain];
1415    [textMatches release];
1416    textMatches = array;
1417}
1418
1419- (NSString *)_temporaryPDFDirectoryPath
1420{
1421    // Returns nil if the temporary PDF directory didn't exist and couldn't be created
1422
1423    static NSString *_temporaryPDFDirectoryPath = nil;
1424
1425    if (!_temporaryPDFDirectoryPath) {
1426        NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPDFs-XXXXXX"];
1427        char *cTemplate = strdup([temporaryDirectoryTemplate fileSystemRepresentation]);
1428
1429        if (!mkdtemp(cTemplate)) {
1430            // This should never happen; if it does we'll fail silently on non-debug builds.
1431            ASSERT_NOT_REACHED();
1432        } else {
1433            // cTemplate has now been modified to be the just-created directory name. This directory has 700 permissions,
1434            // so only the current user can add to it or view its contents.
1435            _temporaryPDFDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:cTemplate length:strlen(cTemplate)] retain];
1436        }
1437
1438        free(cTemplate);
1439    }
1440
1441    return _temporaryPDFDirectoryPath;
1442}
1443
1444- (void)_trackFirstResponder
1445{
1446    ASSERT([self window]);
1447    BOOL newFirstResponderIsPDFDocumentView = [[self window] firstResponder] == [PDFSubview documentView];
1448    if (newFirstResponderIsPDFDocumentView == firstResponderIsPDFDocumentView)
1449        return;
1450
1451    // This next clause is the entire purpose of _trackFirstResponder. In other WebDocument
1452    // view classes this is done in a resignFirstResponder override, but in this case the
1453    // first responder view is a PDFKit class that we can't subclass.
1454    if (newFirstResponderIsPDFDocumentView && ![[dataSource _webView] maintainsInactiveSelection])
1455        [self deselectAll];
1456
1457    firstResponderIsPDFDocumentView = newFirstResponderIsPDFDocumentView;
1458}
1459
1460- (void)_updatePreferences:(WebPreferences *)prefs
1461{
1462    float scaleFactor = [PDFSubview autoScales] ? 0.0f : [PDFSubview scaleFactor];
1463    [prefs setPDFScaleFactor:scaleFactor];
1464    [prefs setPDFDisplayMode:[PDFSubview displayMode]];
1465    _willUpdatePreferencesSoon = NO;
1466    [prefs release];
1467    [self release];
1468}
1469
1470- (void)_updatePreferencesSoon
1471{
1472    // Consolidate calls; due to the WebPDFPrefUpdatingProxy method, this can be called multiple times with a single user action
1473    // such as showing the context menu.
1474    if (_willUpdatePreferencesSoon)
1475        return;
1476
1477    WebPreferences *prefs = [[dataSource _webView] preferences];
1478
1479    [self retain];
1480    [prefs retain];
1481    [self performSelector:@selector(_updatePreferences:) withObject:prefs afterDelay:0];
1482    _willUpdatePreferencesSoon = YES;
1483}
1484
1485- (NSSet *)_visiblePDFPages
1486{
1487    // Returns the set of pages that are at least partly visible, used to avoid processing non-visible pages
1488    PDFDocument *pdfDocument = [PDFSubview document];
1489    if (!pdfDocument)
1490        return nil;
1491
1492    NSRect pdfViewBounds = [PDFSubview bounds];
1493    PDFPage *topLeftPage = [PDFSubview pageForPoint:NSMakePoint(NSMinX(pdfViewBounds), NSMaxY(pdfViewBounds)) nearest:YES];
1494    PDFPage *bottomRightPage = [PDFSubview pageForPoint:NSMakePoint(NSMaxX(pdfViewBounds), NSMinY(pdfViewBounds)) nearest:YES];
1495
1496    // only page-free documents should return nil for either of these two since we passed YES for nearest:
1497    if (!topLeftPage) {
1498        ASSERT(!bottomRightPage);
1499        return nil;
1500    }
1501
1502    NSUInteger firstVisiblePageIndex = [pdfDocument indexForPage:topLeftPage];
1503    NSUInteger lastVisiblePageIndex = [pdfDocument indexForPage:bottomRightPage];
1504
1505    if (firstVisiblePageIndex > lastVisiblePageIndex) {
1506        NSUInteger swap = firstVisiblePageIndex;
1507        firstVisiblePageIndex = lastVisiblePageIndex;
1508        lastVisiblePageIndex = swap;
1509    }
1510
1511    NSMutableSet *result = [NSMutableSet set];
1512    NSUInteger pageIndex;
1513    for (pageIndex = firstVisiblePageIndex; pageIndex <= lastVisiblePageIndex; ++pageIndex)
1514        [result addObject:[pdfDocument pageAtIndex:pageIndex]];
1515
1516    return result;
1517}
1518
1519@end
1520
1521@implementation WebPDFPrefUpdatingProxy
1522
1523- (id)initWithView:(WebPDFView *)aView
1524{
1525    // No [super init], since we inherit from NSProxy
1526    view = aView;
1527    return self;
1528}
1529
1530- (void)forwardInvocation:(NSInvocation *)invocation
1531{
1532    [invocation invokeWithTarget:[view _PDFSubview]];
1533    [view _updatePreferencesSoon];
1534}
1535
1536- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
1537{
1538    return [[view _PDFSubview] methodSignatureForSelector:sel];
1539}
1540
1541@end
1542
1543#endif // !PLATFORM(IOS)
1544