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