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