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