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 63using 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 74extern "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 108static 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. 128static 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); 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