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