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