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