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