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