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