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