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