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