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