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