2 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #import "WebPDFView.h"
31 #import "DOMNodeInternal.h"
32 #import "DOMRangeInternal.h"
33 #import "WebDataSourceInternal.h"
34 #import "WebDelegateImplementationCaching.h"
35 #import "WebDocumentInternal.h"
36 #import "WebDocumentPrivate.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"
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>
66 using namespace WebCore;
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"
73 @interface PDFDocument (PDFKitSecretsIKnow)
74 - (NSPrintOperation *)getPrintOperationForPrintInfo:(NSPrintInfo *)printInfo autoRotate:(BOOL)doRotate;
77 extern "C" NSString *_NSPathForSystemFramework(NSString *framework);
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;
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;
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 {
106 - (id)initWithView:(WebPDFView *)view;
109 // MARK: C UTILITY FUNCTIONS
111 static void _applicationInfoForMIMEType(NSString *type, NSString **name, NSImage **image)
115 OSStatus error = LSCopyApplicationForMIMEType((CFStringRef)type, kLSRolesAll, (CFURLRef *)&appURL);
119 NSString *appPath = [appURL path];
122 *image = [[NSWorkspace sharedWorkspace] iconForFile:appPath];
123 [*image setSize:NSMakeSize(16.f,16.f)];
125 NSString *appName = [[NSFileManager defaultManager] displayNameAtPath:appPath];
129 // FIXME 4182876: We can eliminate this function in favor if -isEqual: if [PDFSelection isEqual:] is overridden
130 // to compare contents.
131 static BOOL _PDFSelectionsAreEqual(PDFSelection *selectionA, PDFSelection *selectionB)
133 NSArray *aPages = [selectionA pages];
134 NSArray *bPages = [selectionB pages];
136 if (![aPages isEqual:bPages])
139 int count = [aPages count];
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)) {
152 @implementation WebPDFView
154 // MARK: WebPDFView API
156 + (NSBundle *)PDFKitBundle
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");
165 PDFKitBundle = [NSBundle bundleWithPath:PDFKitPath];
166 if (![PDFKitBundle load]) {
167 LOG_ERROR("Couldn't load PDFKit.framework");
173 + (NSArray *)supportedMIMETypes
175 return [WebPDFRepresentation supportedMIMETypes];
178 - (void)setPDFDocument:(PDFDocument *)doc
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;
188 - (PDFDocument *)PDFDocument
190 return [PDFSubview document];
193 // MARK: NSObject OVERRIDES
197 [dataSource release];
198 [previewView release];
199 [PDFSubview setDelegate:nil];
200 [PDFSubview release];
202 [PDFSubviewProxy release];
203 [textMatches release];
207 // MARK: NSResponder OVERRIDES
209 - (void)centerSelectionInVisibleArea:(id)sender
211 [PDFSubview scrollSelectionToVisible:nil];
214 - (void)scrollPageDown:(id)sender
216 // PDFView doesn't support this responder method directly, so we pass it a fake key event
217 [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSPageDownFunctionKey]];
220 - (void)scrollPageUp:(id)sender
222 // PDFView doesn't support this responder method directly, so we pass it a fake key event
223 [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSPageUpFunctionKey]];
226 - (void)scrollLineDown:(id)sender
228 // PDFView doesn't support this responder method directly, so we pass it a fake key event
229 [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSDownArrowFunctionKey]];
232 - (void)scrollLineUp:(id)sender
234 // PDFView doesn't support this responder method directly, so we pass it a fake key event
235 [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSUpArrowFunctionKey]];
238 - (void)scrollToBeginningOfDocument:(id)sender
240 // PDFView doesn't support this responder method directly, so we pass it a fake key event
241 [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSHomeFunctionKey]];
244 - (void)scrollToEndOfDocument:(id)sender
246 // PDFView doesn't support this responder method directly, so we pass it a fake key event
247 [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSEndFunctionKey]];
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
257 [self centerSelectionInVisibleArea:nil];
260 // MARK: NSView OVERRIDES
262 - (BOOL)acceptsFirstResponder {
266 - (BOOL)becomeFirstResponder
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;
273 if ([window keyViewSelectionDirection] == NSSelectingPrevious) {
274 NSView *previousValidKeyView = [self previousValidKeyView];
275 if ((previousValidKeyView != self) && (previousValidKeyView != PDFSubview))
276 newFirstResponder = previousValidKeyView;
278 NSView *PDFDocumentView = [PDFSubview documentView];
279 if ([PDFDocumentView acceptsFirstResponder])
280 newFirstResponder = PDFDocumentView;
283 if (!newFirstResponder)
286 if (![window makeFirstResponder:newFirstResponder])
289 [[dataSource webFrame] _clearSelectionInOtherFrames];
294 - (NSView *)hitTest:(NSPoint)point
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)))
302 return [super hitTest:point];
305 - (id)initWithFrame:(NSRect)frame
307 self = [super initWithFrame:frame];
309 [self setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
311 Class previewViewClass = [[self class] _PDFPreviewViewClass];
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];
320 NSView *topLevelPDFKitView = nil;
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
325 PDFSubview = [[previewView performSelector:@selector(pdfView)] retain];
326 topLevelPDFKitView = previewView;
328 PDFSubview = [[[[self class] _PDFViewClass] alloc] initWithFrame:frame];
329 topLevelPDFKitView = PDFSubview;
334 [topLevelPDFKitView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
335 [self addSubview:topLevelPDFKitView];
337 [PDFSubview setDelegate:self];
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];
347 - (NSMenu *)menuForEvent:(NSEvent *)theEvent
349 // Start with the menu items supplied by PDFKit, with WebKit tags applied
350 NSMutableArray *items = [self _menuItemsFromPDFKitForEvent:theEvent];
352 // Add in an "Open with <default PDF viewer>" item
353 NSString *appName = nil;
354 NSImage *appIcon = nil;
356 _applicationInfoForMIMEType([dataSource _responseMIMEType], &appName, &appIcon);
358 appName = UI_STRING_INTERNAL("Finder", "Default application name for Open With context menu");
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];
366 [item setImage:appIcon];
367 [items insertObject:item atIndex:0];
370 [items insertObject:[NSMenuItem separatorItem] atIndex:1];
372 // pass the items off to the WebKit context menu mechanism
373 WebView *webView = [[dataSource webFrame] webView];
375 NSMenu *menu = [webView _menuForElement:[self elementAtPoint:[self convertPoint:[theEvent locationInWindow] fromView:nil]] defaultItems:items];
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 NSMenuItem *menuItemCopy = [menuItem copy];
396 [menu addItem:menuItemCopy];
397 [menuItemCopy release];
404 - (void)setNextKeyView:(NSView *)aView
406 // This works together with becomeFirstResponder to splice PDFSubview into
407 // the key loop similar to the way NSScrollView and NSClipView do this.
408 NSView *documentView = [PDFSubview documentView];
410 [documentView setNextKeyView:aView];
412 // We need to make the documentView be the next view in the keyview loop.
413 // It would seem more sensible to do this in our init method, but it turns out
414 // that [NSClipView setDocumentView] won't call this method if our next key view
415 // is already set, so we wait until we're called before adding this connection.
416 // We'll also clear it when we're called with nil, so this could go through the
417 // same code path more than once successfully.
418 [super setNextKeyView: aView ? documentView : nil];
420 [super setNextKeyView:aView];
423 - (void)viewDidMoveToWindow
425 // FIXME 2573089: we can observe a notification for first responder changes
426 // instead of the very frequent NSWindowDidUpdateNotification if/when 2573089 is addressed.
427 NSWindow *newWindow = [self window];
431 [self _trackFirstResponder];
432 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
433 [notificationCenter addObserver:self
434 selector:@selector(_trackFirstResponder)
435 name:NSWindowDidUpdateNotification
438 [notificationCenter addObserver:self
439 selector:@selector(_scaleOrDisplayModeOrPageChanged:)
440 name:_webkit_PDFViewScaleChangedNotification
443 [notificationCenter addObserver:self
444 selector:@selector(_scaleOrDisplayModeOrPageChanged:)
445 name:_webkit_PDFViewDisplayModeChangedNotification
448 [notificationCenter addObserver:self
449 selector:@selector(_scaleOrDisplayModeOrPageChanged:)
450 name:_webkit_PDFViewPageChangedNotification
453 [notificationCenter addObserver:self
454 selector:@selector(_PDFDocumentViewMightHaveScrolled:)
455 name:NSViewBoundsDidChangeNotification
456 object:[self _clipViewForPDFDocumentView]];
459 - (void)viewWillMoveToWindow:(NSWindow *)window
461 // FIXME 2573089: we can observe a notification for changes to the first responder
462 // instead of the very frequent NSWindowDidUpdateNotification if/when 2573089 is addressed.
463 NSWindow *oldWindow = [self window];
467 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
468 [notificationCenter removeObserver:self
469 name:NSWindowDidUpdateNotification
471 [notificationCenter removeObserver:self
472 name:_webkit_PDFViewScaleChangedNotification
474 [notificationCenter removeObserver:self
475 name:_webkit_PDFViewDisplayModeChangedNotification
477 [notificationCenter removeObserver:self
478 name:_webkit_PDFViewPageChangedNotification
481 [notificationCenter removeObserver:self
482 name:NSViewBoundsDidChangeNotification
483 object:[self _clipViewForPDFDocumentView]];
485 firstResponderIsPDFDocumentView = NO;
488 // MARK: NSUserInterfaceValidations PROTOCOL IMPLEMENTATION
490 - (BOOL)validateUserInterfaceItemWithoutDelegate:(id <NSValidatedUserInterfaceItem>)item
492 SEL action = [item action];
493 if (action == @selector(takeFindStringFromSelection:) || action == @selector(centerSelectionInVisibleArea:) || action == @selector(jumpToSelection:))
494 return [PDFSubview currentSelection] != nil;
496 if (action == @selector(_openWithFinder:))
497 return [PDFSubview document] != nil;
499 if (action == @selector(_lookUpInDictionaryFromMenu:))
500 return [self _canLookUpInDictionary];
505 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
507 // This can be called during teardown when _webView is nil. Return NO when this happens, because CallUIDelegateReturningBoolean
508 // assumes the WebVIew is non-nil.
509 if (![self _webView])
511 BOOL result = [self validateUserInterfaceItemWithoutDelegate:item];
512 return CallUIDelegateReturningBoolean(result, [self _webView], @selector(webView:validateUserInterfaceItem:defaultValidation:), item, result);
515 // MARK: INTERFACE BUILDER ACTIONS FOR SAFARI
517 // Surprisingly enough, this isn't defined in any superclass, though it is defined in assorted AppKit classes since
518 // it's a standard menu item IBAction.
519 - (IBAction)copy:(id)sender
521 [PDFSubview copy:sender];
524 // This used to be a standard IBAction (for Use Selection For Find), but AppKit now uses performFindPanelAction:
525 // with a menu item tag for this purpose.
526 - (IBAction)takeFindStringFromSelection:(id)sender
528 [NSPasteboard _web_setFindPasteboardString:[[PDFSubview currentSelection] string] withOwner:self];
531 // MARK: WebFrameView UNDECLARED "DELEGATE METHODS"
533 // This is tested in -[WebFrameView canPrintHeadersAndFooters], but isn't declared anywhere (yuck)
534 - (BOOL)canPrintHeadersAndFooters
539 // This is tested in -[WebFrameView printOperationWithPrintInfo:], but isn't declared anywhere (yuck)
540 - (NSPrintOperation *)printOperationWithPrintInfo:(NSPrintInfo *)printInfo
542 return [[PDFSubview document] getPrintOperationForPrintInfo:printInfo autoRotate:YES];
545 // MARK: WebDocumentView PROTOCOL IMPLEMENTATION
547 - (void)setDataSource:(WebDataSource *)ds
549 if (dataSource == ds)
552 dataSource = [ds retain];
554 // FIXME: There must be some better place to put this. There is no comment in ChangeLog
555 // explaining why it's in this method.
556 [self setFrame:[[self superview] frame]];
559 - (void)dataSourceUpdated:(WebDataSource *)dataSource
563 - (void)setNeedsLayout:(BOOL)flag
571 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
575 - (void)viewDidMoveToHostWindow
579 // MARK: WebDocumentElement PROTOCOL IMPLEMENTATION
581 - (NSDictionary *)elementAtPoint:(NSPoint)point
583 WebFrame *frame = [dataSource webFrame];
586 return [NSDictionary dictionaryWithObjectsAndKeys:
587 frame, WebElementFrameKey,
588 [NSNumber numberWithBool:[self _pointIsInSelection:point]], WebElementIsSelectedKey,
592 - (NSDictionary *)elementAtPoint:(NSPoint)point allowShadowContent:(BOOL)allow
594 return [self elementAtPoint:point];
597 // MARK: WebDocumentSearching PROTOCOL IMPLEMENTATION
599 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag
601 return [self searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:NO];
604 // MARK: WebDocumentIncrementalSearching PROTOCOL IMPLEMENTATION
606 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection
608 PDFSelection *selection = [self _nextMatchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag fromSelection:[PDFSubview currentSelection] startInSelection:startInSelection];
612 [PDFSubview setCurrentSelection:selection];
613 [PDFSubview scrollSelectionToVisible:nil];
617 // MARK: WebMultipleTextMatches PROTOCOL IMPLEMENTATION
619 - (void)setMarkedTextMatchesAreHighlighted:(BOOL)newValue
621 // This method is part of the WebMultipleTextMatches algorithm, but this class doesn't support
622 // highlighting text matches inline.
625 LOG_ERROR("[WebPDFView setMarkedTextMatchesAreHighlighted:] called with YES, which isn't supported");
629 - (BOOL)markedTextMatchesAreHighlighted
634 static BOOL isFrameInRange(WebFrame *frame, DOMRange *range)
637 for (HTMLFrameOwnerElement* ownerElement = core(frame)->ownerElement(); ownerElement; ownerElement = ownerElement->document()->frame()->ownerElement()) {
638 if (ownerElement->document() == core(range)->ownerDocument()) {
639 inRange = [range intersectsNode:kit(ownerElement)];
646 - (NSUInteger)countMatchesForText:(NSString *)string inDOMRange:(DOMRange *)range options:(WebFindOptions)options limit:(NSUInteger)limit markMatches:(BOOL)markMatches
648 if (range && !isFrameInRange([dataSource webFrame], range))
651 PDFSelection *previousMatch = nil;
652 NSMutableArray *matches = [[NSMutableArray alloc] initWithCapacity:limit];
655 PDFSelection *nextMatch = [self _nextMatchFor:string direction:YES caseSensitive:!(options & WebFindOptionsCaseInsensitive) wrap:NO fromSelection:previousMatch startInSelection:NO];
659 [matches addObject:nextMatch];
660 previousMatch = nextMatch;
662 if ([matches count] >= limit)
666 [self _setTextMatches:matches];
669 return [matches count];
672 - (void)unmarkAllTextMatches
674 [self _setTextMatches:nil];
677 - (NSArray *)rectsForTextMatches
679 NSMutableArray *result = [NSMutableArray arrayWithCapacity:[textMatches count]];
680 NSSet *visiblePages = [self _visiblePDFPages];
681 NSEnumerator *matchEnumerator = [textMatches objectEnumerator];
684 while ((match = [matchEnumerator nextObject]) != nil) {
685 NSEnumerator *pages = [[match pages] objectEnumerator];
687 while ((page = [pages nextObject]) != nil) {
689 // Skip pages that aren't visible (needed for non-continuous modes, see 5362989)
690 if (![visiblePages containsObject:page])
693 NSRect selectionOnPageInPDFViewCoordinates = [PDFSubview convertRect:[match boundsForPage:page] fromPage:page];
694 [result addObject:[NSValue valueWithRect:selectionOnPageInPDFViewCoordinates]];
701 // MARK: WebDocumentText PROTOCOL IMPLEMENTATION
703 - (BOOL)supportsTextEncoding
710 return [[PDFSubview document] string];
713 - (NSAttributedString *)attributedString
715 // changing the selection is a hack, but the only way to get an attr string is via PDFSelection
717 // must copy this selection object because we change the selection which seems to release it
718 PDFSelection *savedSelection = [[PDFSubview currentSelection] copy];
719 [PDFSubview selectAll:nil];
720 NSAttributedString *result = [[PDFSubview currentSelection] attributedString];
721 if (savedSelection) {
722 [PDFSubview setCurrentSelection:savedSelection];
723 [savedSelection release];
725 // FIXME: behavior of setCurrentSelection:nil is not documented - check 4182934 for progress
726 // Otherwise, we could collapse this code with the case above.
727 [PDFSubview clearSelection];
730 result = [self _scaledAttributedString:result];
735 - (NSString *)selectedString
737 return [[PDFSubview currentSelection] string];
740 - (NSAttributedString *)selectedAttributedString
742 return [self _scaledAttributedString:[[PDFSubview currentSelection] attributedString]];
747 [PDFSubview selectAll:nil];
752 [PDFSubview clearSelection];
755 // MARK: WebDocumentViewState PROTOCOL IMPLEMENTATION
757 // Even though to WebKit we are the "docView", in reality a PDFView contains its own scrollview and docView.
758 // And it even turns out there is another PDFKit view between the docView and its enclosing ScrollView, so
759 // we have to be sure to do our calculations based on that view, immediately inside the ClipView. We try
760 // to make as few assumptions about the PDFKit view hierarchy as possible.
762 - (NSPoint)scrollPoint
764 NSView *realDocView = [PDFSubview documentView];
765 NSClipView *clipView = [[realDocView enclosingScrollView] contentView];
766 return [clipView bounds].origin;
769 - (void)setScrollPoint:(NSPoint)p
771 WebFrame *frame = [dataSource webFrame];
772 //FIXME: We only restore scroll state in the non-frames case because otherwise we get a crash due to
773 // PDFKit calling display from within its drawRect:. See bugzilla 4164.
774 if (![frame parentFrame]) {
775 NSView *realDocView = [PDFSubview documentView];
776 [(NSView *)[[realDocView enclosingScrollView] documentView] scrollPoint:p];
782 NSMutableArray *state = [NSMutableArray arrayWithCapacity:4];
783 PDFDisplayMode mode = [PDFSubview displayMode];
784 [state addObject:[NSNumber numberWithInt:mode]];
785 if (mode == kPDFDisplaySinglePage || mode == kPDFDisplayTwoUp) {
786 unsigned int pageIndex = [[PDFSubview document] indexForPage:[PDFSubview currentPage]];
787 [state addObject:[NSNumber numberWithUnsignedInt:pageIndex]];
788 } // else in continuous modes, scroll position gets us to the right page
789 BOOL autoScaleFlag = [PDFSubview autoScales];
790 [state addObject:[NSNumber numberWithBool:autoScaleFlag]];
792 [state addObject:[NSNumber numberWithFloat:[PDFSubview scaleFactor]]];
797 - (void)setViewState:(id)statePList
799 ASSERT([statePList isKindOfClass:[NSArray class]]);
800 NSArray *state = statePList;
802 PDFDisplayMode mode = [[state objectAtIndex:i++] intValue];
803 [PDFSubview setDisplayMode:mode];
804 if (mode == kPDFDisplaySinglePage || mode == kPDFDisplayTwoUp) {
805 unsigned int pageIndex = [[state objectAtIndex:i++] unsignedIntValue];
806 [PDFSubview goToPage:[[PDFSubview document] pageAtIndex:pageIndex]];
807 } // else in continuous modes, scroll position gets us to the right page
808 BOOL autoScaleFlag = [[state objectAtIndex:i++] boolValue];
809 [PDFSubview setAutoScales:autoScaleFlag];
811 [PDFSubview setScaleFactor:[[state objectAtIndex:i++] floatValue]];
814 // MARK: _WebDocumentTextSizing PROTOCOL IMPLEMENTATION
816 - (IBAction)_zoomOut:(id)sender
818 [PDFSubviewProxy zoomOut:sender];
821 - (IBAction)_zoomIn:(id)sender
823 [PDFSubviewProxy zoomIn:sender];
826 - (IBAction)_resetZoom:(id)sender
828 [PDFSubviewProxy setScaleFactor:1.0f];
833 return [PDFSubview canZoomOut];
838 return [PDFSubview canZoomIn];
841 - (BOOL)_canResetZoom
843 return [PDFSubview scaleFactor] != 1.0;
846 // MARK: WebDocumentSelection PROTOCOL IMPLEMENTATION
848 - (NSRect)selectionRect
850 NSRect result = NSZeroRect;
851 PDFSelection *selection = [PDFSubview currentSelection];
852 NSEnumerator *pages = [[selection pages] objectEnumerator];
854 while ((page = [pages nextObject]) != nil) {
855 NSRect selectionOnPageInPDFViewCoordinates = [PDFSubview convertRect:[selection boundsForPage:page] fromPage:page];
856 if (NSIsEmptyRect(result))
857 result = selectionOnPageInPDFViewCoordinates;
859 result = NSUnionRect(result, selectionOnPageInPDFViewCoordinates);
862 // Convert result to be in documentView (selectionView) coordinates
863 result = [PDFSubview convertRect:result toView:[PDFSubview documentView]];
868 - (NSArray *)selectionTextRects
870 // FIXME: We'd need new PDFKit API/SPI to get multiple text rects for selections that intersect more than one line
871 return [NSArray arrayWithObject:[NSValue valueWithRect:[self selectionRect]]];
874 - (NSView *)selectionView
876 return [PDFSubview documentView];
879 - (NSImage *)selectionImageForcingBlackText:(BOOL)forceBlackText
881 // Convert the selection to an attributed string, and draw that.
882 // FIXME 4621154: this doesn't handle italics (and maybe other styles)
883 // FIXME 4604366: this doesn't handle text at non-actual size
884 NSMutableAttributedString *attributedString = [[self selectedAttributedString] mutableCopy];
885 NSRange wholeStringRange = NSMakeRange(0, [attributedString length]);
887 // Modify the styles in the attributed string to draw black text, no background, and no underline. We draw
888 // no underline because it would look ugly.
889 [attributedString beginEditing];
890 [attributedString removeAttribute:NSBackgroundColorAttributeName range:wholeStringRange];
891 [attributedString removeAttribute:NSUnderlineStyleAttributeName range:wholeStringRange];
893 [attributedString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithDeviceWhite:0.0f alpha:1.0f] range:wholeStringRange];
894 [attributedString endEditing];
896 NSImage* selectionImage = [[[NSImage alloc] initWithSize:[self selectionRect].size] autorelease];
898 [selectionImage lockFocus];
899 [attributedString drawAtPoint:NSZeroPoint];
900 [selectionImage unlockFocus];
902 [attributedString release];
904 return selectionImage;
907 - (NSRect)selectionImageRect
909 // FIXME: deal with clipping?
910 return [self selectionRect];
913 - (NSArray *)pasteboardTypesForSelection
915 return [NSArray arrayWithObjects:NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil];
918 - (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard
920 NSAttributedString *attributedString = [self selectedAttributedString];
922 if ([types containsObject:NSRTFDPboardType]) {
923 NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
924 [pasteboard setData:RTFDData forType:NSRTFDPboardType];
927 if ([types containsObject:NSRTFPboardType]) {
928 if ([attributedString containsAttachments])
929 attributedString = attributedStringByStrippingAttachmentCharacters(attributedString);
931 NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
932 [pasteboard setData:RTFData forType:NSRTFPboardType];
935 if ([types containsObject:NSStringPboardType])
936 [pasteboard setString:[self selectedString] forType:NSStringPboardType];
939 // MARK: PDFView DELEGATE METHODS
941 - (void)PDFViewWillClickOnLink:(PDFView *)sender withURL:(NSURL *)URL
946 NSWindow *window = [sender window];
947 NSEvent *nsEvent = [window currentEvent];
948 const int noButton = -1;
949 int button = noButton;
951 switch ([nsEvent type]) {
959 button = [nsEvent buttonNumber];
962 PlatformKeyboardEvent pe(nsEvent);
963 pe.disambiguateKeyDownEvent(PlatformKeyboardEvent::RawKeyDown);
964 event = KeyboardEvent::create(eventNames().keydownEvent, true, true, 0,
965 pe.keyIdentifier(), pe.windowsVirtualKeyCode(),
966 pe.ctrlKey(), pe.altKey(), pe.shiftKey(), pe.metaKey(), false);
971 if (button != noButton) {
972 event = MouseEvent::create(eventNames().clickEvent, true, true, 0, [nsEvent clickCount], 0, 0, 0, 0,
973 [nsEvent modifierFlags] & NSControlKeyMask,
974 [nsEvent modifierFlags] & NSAlternateKeyMask,
975 [nsEvent modifierFlags] & NSShiftKeyMask,
976 [nsEvent modifierFlags] & NSCommandKeyMask,
980 // Call to the frame loader because this is where our security checks are made.
981 Frame* frame = core([dataSource webFrame]);
982 frame->loader()->loadFrameRequest(FrameLoadRequest(frame->document()->securityOrigin(), ResourceRequest(URL)), false, false, event.get(), 0, SendReferrer);
985 - (void)PDFViewOpenPDFInNativeApplication:(PDFView *)sender
987 // Delegate method sent when the user requests opening the PDF file in the system's default app
988 [self _openWithFinder:sender];
991 - (void)PDFViewPerformPrint:(PDFView *)sender
993 CallUIDelegate([self _webView], @selector(webView:printFrameView:), [[dataSource webFrame] frameView]);
996 - (void)PDFViewSavePDFToDownloadFolder:(PDFView *)sender
998 // We don't want to write the file until we have a document to write (see 5267607).
999 if (![PDFSubview document]) {
1004 // Delegate method sent when the user requests downloading the PDF file to disk. We pass NO for
1005 // showingPanel: so that the PDF file is saved to the standard location without user intervention.
1006 CallUIDelegate([self _webView], @selector(webView:saveFrameView:showingPanel:), [[dataSource webFrame] frameView], NO);
1011 @implementation WebPDFView (FileInternal)
1013 + (Class)_PDFPreviewViewClass
1015 static Class PDFPreviewViewClass = nil;
1016 static BOOL checkedForPDFPreviewViewClass = NO;
1018 if (!checkedForPDFPreviewViewClass) {
1019 checkedForPDFPreviewViewClass = YES;
1020 PDFPreviewViewClass = [[WebPDFView PDFKitBundle] classNamed:@"PDFPreviewView"];
1023 // This class might not be available; callers need to deal with a nil return here.
1024 return PDFPreviewViewClass;
1027 + (Class)_PDFViewClass
1029 static Class PDFViewClass = nil;
1030 if (PDFViewClass == nil) {
1031 PDFViewClass = [[WebPDFView PDFKitBundle] classNamed:@"PDFView"];
1033 LOG_ERROR("Couldn't find PDFView class in PDFKit.framework");
1035 return PDFViewClass;
1038 - (BOOL)_anyPDFTagsFoundInMenu:(NSMenu *)menu
1040 NSEnumerator *e = [[menu itemArray] objectEnumerator];
1042 while ((item = [e nextObject]) != nil) {
1043 switch ([item tag]) {
1044 case WebMenuItemTagOpenWithDefaultApplication:
1045 case WebMenuItemPDFActualSize:
1046 case WebMenuItemPDFZoomIn:
1047 case WebMenuItemPDFZoomOut:
1048 case WebMenuItemPDFAutoSize:
1049 case WebMenuItemPDFSinglePage:
1050 case WebMenuItemPDFSinglePageScrolling:
1051 case WebMenuItemPDFFacingPages:
1052 case WebMenuItemPDFFacingPagesScrolling:
1053 case WebMenuItemPDFContinuous:
1054 case WebMenuItemPDFNextPage:
1055 case WebMenuItemPDFPreviousPage:
1062 - (void)_applyPDFDefaults
1064 // Set up default viewing params
1065 WebPreferences *prefs = [[dataSource _webView] preferences];
1066 float scaleFactor = [prefs PDFScaleFactor];
1067 if (scaleFactor == 0)
1068 [PDFSubview setAutoScales:YES];
1070 [PDFSubview setAutoScales:NO];
1071 [PDFSubview setScaleFactor:scaleFactor];
1073 [PDFSubview setDisplayMode:[prefs PDFDisplayMode]];
1076 - (BOOL)_canLookUpInDictionary
1078 return [PDFSubview respondsToSelector:@selector(_searchInDictionary:)];
1081 - (NSClipView *)_clipViewForPDFDocumentView
1083 NSClipView *clipView = (NSClipView *)[[PDFSubview documentView] _web_superviewOfClass:[NSClipView class]];
1088 - (NSEvent *)_fakeKeyEventWithFunctionKey:(unichar)functionKey
1090 // FIXME 4400480: when PDFView implements the standard scrolling selectors that this
1091 // method is used to mimic, we can eliminate this method and call them directly.
1092 NSString *keyAsString = [NSString stringWithCharacters:&functionKey length:1];
1093 return [NSEvent keyEventWithType:NSKeyDown
1094 location:NSZeroPoint
1099 characters:keyAsString
1100 charactersIgnoringModifiers:keyAsString
1105 - (void)_lookUpInDictionaryFromMenu:(id)sender
1107 // This method is used by WebKit's context menu item. Here we map to the method that
1108 // PDFView uses. Since the PDFView method isn't API, and isn't available on all versions
1109 // of PDFKit, we use performSelector after a respondsToSelector check, rather than calling it directly.
1110 if ([self _canLookUpInDictionary])
1111 [PDFSubview performSelector:@selector(_searchInDictionary:) withObject:sender];
1114 - (NSMutableArray *)_menuItemsFromPDFKitForEvent:(NSEvent *)theEvent
1116 NSMutableArray *copiedItems = [NSMutableArray array];
1117 NSDictionary *actionsToTags = [[NSDictionary alloc] initWithObjectsAndKeys:
1118 [NSNumber numberWithInt:WebMenuItemPDFActualSize], NSStringFromSelector(@selector(_setActualSize:)),
1119 [NSNumber numberWithInt:WebMenuItemPDFZoomIn], NSStringFromSelector(@selector(zoomIn:)),
1120 [NSNumber numberWithInt:WebMenuItemPDFZoomOut], NSStringFromSelector(@selector(zoomOut:)),
1121 [NSNumber numberWithInt:WebMenuItemPDFAutoSize], NSStringFromSelector(@selector(_setAutoSize:)),
1122 [NSNumber numberWithInt:WebMenuItemPDFSinglePage], NSStringFromSelector(@selector(_setSinglePage:)),
1123 [NSNumber numberWithInt:WebMenuItemPDFSinglePageScrolling], NSStringFromSelector(@selector(_setSinglePageScrolling:)),
1124 [NSNumber numberWithInt:WebMenuItemPDFFacingPages], NSStringFromSelector(@selector(_setDoublePage:)),
1125 [NSNumber numberWithInt:WebMenuItemPDFFacingPagesScrolling], NSStringFromSelector(@selector(_setDoublePageScrolling:)),
1126 [NSNumber numberWithInt:WebMenuItemPDFContinuous], NSStringFromSelector(@selector(_toggleContinuous:)),
1127 [NSNumber numberWithInt:WebMenuItemPDFNextPage], NSStringFromSelector(@selector(goToNextPage:)),
1128 [NSNumber numberWithInt:WebMenuItemPDFPreviousPage], NSStringFromSelector(@selector(goToPreviousPage:)),
1131 // Leave these menu items out, since WebKit inserts equivalent ones. Note that we leave out PDFKit's "Look Up in Dictionary"
1132 // item here because WebKit already includes an item with the same title and purpose. We map WebKit's to PDFKit's
1133 // "Look Up in Dictionary" via the implementation of -[WebPDFView _lookUpInDictionaryFromMenu:].
1134 NSSet *unwantedActions = [[NSSet alloc] initWithObjects:
1135 NSStringFromSelector(@selector(_searchInSpotlight:)),
1136 NSStringFromSelector(@selector(_searchInGoogle:)),
1137 NSStringFromSelector(@selector(_searchInDictionary:)),
1138 NSStringFromSelector(@selector(copy:)),
1141 NSEnumerator *e = [[[PDFSubview menuForEvent:theEvent] itemArray] objectEnumerator];
1143 while ((item = [e nextObject]) != nil) {
1145 NSString *actionString = NSStringFromSelector([item action]);
1147 if ([unwantedActions containsObject:actionString])
1150 // Copy items since a menu item can be in only one menu at a time, and we don't
1151 // want to modify the original menu supplied by PDFKit.
1152 NSMenuItem *itemCopy = [item copy];
1153 [copiedItems addObject:itemCopy];
1155 // Include all of PDFKit's separators for now. At the end we'll remove any ones that were made
1156 // useless by removing PDFKit's menu items.
1157 if ([itemCopy isSeparatorItem])
1160 NSNumber *tagNumber = [actionsToTags objectForKey:actionString];
1163 if (tagNumber != nil)
1164 tag = [tagNumber intValue];
1166 // This should happen only if PDFKit updates behind WebKit's back. It's non-ideal because clients that only include tags
1167 // that they recognize (like Safari) won't get these PDFKit additions until WebKit is updated to match.
1168 tag = WebMenuItemTagOther;
1169 LOG_ERROR("no WebKit menu item tag found for PDF context menu item action \"%@\", using WebMenuItemTagOther", actionString);
1172 if ([itemCopy tag] == 0) {
1173 [itemCopy setTag:tag];
1174 if ([itemCopy target] == PDFSubview) {
1175 // Note that updating the defaults is cheap because it catches redundant settings, so installing
1176 // the proxy for actions that don't impact the defaults is OK
1177 [itemCopy setTarget:PDFSubviewProxy];
1180 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]);
1183 [actionsToTags release];
1184 [unwantedActions release];
1186 // Since we might have removed elements supplied by PDFKit, and we want to minimize our hardwired
1187 // knowledge of the order and arrangement of PDFKit's menu items, we need to remove any bogus
1188 // separators that were left behind.
1189 [copiedItems _webkit_removeUselessMenuItemSeparators];
1194 - (PDFSelection *)_nextMatchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag fromSelection:(PDFSelection *)initialSelection startInSelection:(BOOL)startInSelection
1196 if (![string length])
1201 options |= NSBackwardsSearch;
1204 options |= NSCaseInsensitiveSearch;
1206 PDFDocument *document = [PDFSubview document];
1208 PDFSelection *selectionForInitialSearch = [initialSelection copy];
1209 if (startInSelection) {
1210 // Initially we want to include the selected text in the search. PDFDocument's API always searches from just
1211 // past the passed-in selection, so we need to pass a selection that's modified appropriately.
1212 // FIXME 4182863: Ideally we'd use a zero-length selection at the edge of the current selection, but zero-length
1213 // selections don't work in PDFDocument. So instead we make a one-length selection just before or after the
1214 // current selection, which works for our purposes even when the current selection is at an edge of the
1216 int initialSelectionLength = [[initialSelection string] length];
1218 [selectionForInitialSearch extendSelectionAtStart:1];
1219 [selectionForInitialSearch extendSelectionAtEnd:-initialSelectionLength];
1221 [selectionForInitialSearch extendSelectionAtEnd:1];
1222 [selectionForInitialSearch extendSelectionAtStart:-initialSelectionLength];
1225 PDFSelection *foundSelection = [document findString:string fromSelection:selectionForInitialSearch withOptions:options];
1226 [selectionForInitialSearch release];
1228 // If we first searched in the selection, and we found the selection, search again from just past the selection
1229 if (startInSelection && _PDFSelectionsAreEqual(foundSelection, initialSelection))
1230 foundSelection = [document findString:string fromSelection:initialSelection withOptions:options];
1232 if (!foundSelection && wrapFlag)
1233 foundSelection = [document findString:string fromSelection:nil withOptions:options];
1235 return foundSelection;
1238 - (void)_openWithFinder:(id)sender
1240 // We don't want to write the file until we have a document to write (see 4892525).
1241 if (![PDFSubview document]) {
1246 NSString *opath = [self _path];
1250 // Create a PDF file with the minimal permissions (only accessible to the current user, see 4145714)
1251 NSNumber *permissions = [[NSNumber alloc] initWithInt:S_IRUSR];
1252 NSDictionary *fileAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:permissions, NSFilePosixPermissions, nil];
1253 [permissions release];
1255 [[NSFileManager defaultManager] createFileAtPath:opath contents:[dataSource data] attributes:fileAttributes];
1257 [fileAttributes release];
1261 if (![[NSWorkspace sharedWorkspace] openFile:opath]) {
1262 // NSWorkspace couldn't open file. Do we need an alert
1263 // here? We ignore the error elsewhere.
1270 // Generate path once.
1274 NSString *filename = [[dataSource response] suggestedFilename];
1275 NSFileManager *manager = [NSFileManager defaultManager];
1276 NSString *temporaryPDFDirectoryPath = [self _temporaryPDFDirectoryPath];
1278 if (!temporaryPDFDirectoryPath) {
1279 // This should never happen; if it does we'll fail silently on non-debug builds.
1280 ASSERT_NOT_REACHED();
1284 path = [temporaryPDFDirectoryPath stringByAppendingPathComponent:filename];
1285 if ([manager fileExistsAtPath:path]) {
1286 NSString *pathTemplatePrefix = [temporaryPDFDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
1287 NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:filename];
1288 // fileSystemRepresentation returns a const char *; copy it into a char * so we can modify it safely
1289 char *cPath = strdup([pathTemplate fileSystemRepresentation]);
1290 int fd = mkstemps(cPath, strlen(cPath) - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
1292 // Couldn't create a temporary file! Should never happen; if it does we'll fail silently on non-debug builds.
1293 ASSERT_NOT_REACHED();
1297 path = [manager stringWithFileSystemRepresentation:cPath length:strlen(cPath)];
1307 - (void)_PDFDocumentViewMightHaveScrolled:(NSNotification *)notification
1309 NSClipView *clipView = [self _clipViewForPDFDocumentView];
1310 ASSERT([notification object] == clipView);
1312 NSPoint scrollPosition = [clipView bounds].origin;
1313 if (NSEqualPoints(scrollPosition, lastScrollPosition))
1316 lastScrollPosition = scrollPosition;
1317 WebView *webView = [self _webView];
1318 [[webView _UIDelegateForwarder] webView:webView didScrollDocumentInFrameView:[[dataSource webFrame] frameView]];
1321 - (PDFView *)_PDFSubview
1326 - (BOOL)_pointIsInSelection:(NSPoint)point
1328 PDFPage *page = [PDFSubview pageForPoint:point nearest:NO];
1332 NSRect selectionRect = [PDFSubview convertRect:[[PDFSubview currentSelection] boundsForPage:page] fromPage:page];
1334 return NSPointInRect(point, selectionRect);
1337 - (void)_scaleOrDisplayModeOrPageChanged:(NSNotification *)notification
1339 ASSERT([notification object] == PDFSubview);
1340 if (!_ignoreScaleAndDisplayModeAndPageNotifications) {
1341 [self _updatePreferencesSoon];
1342 // Notify UI delegate that the entire page has been redrawn, since (unlike for WebHTMLView)
1343 // we can't hook into the drawing mechanism itself. This fixes 5337529.
1344 WebView *webView = [self _webView];
1345 [[webView _UIDelegateForwarder] webView:webView didDrawRect:[webView bounds]];
1349 - (NSAttributedString *)_scaledAttributedString:(NSAttributedString *)unscaledAttributedString
1351 if (!unscaledAttributedString)
1354 float scaleFactor = [PDFSubview scaleFactor];
1355 if (scaleFactor == 1.0)
1356 return unscaledAttributedString;
1358 NSMutableAttributedString *result = [[unscaledAttributedString mutableCopy] autorelease];
1359 unsigned int length = [result length];
1360 NSRange effectiveRange = NSMakeRange(0,0);
1362 [result beginEditing];
1363 while (NSMaxRange(effectiveRange) < length) {
1364 NSFont *unscaledFont = [result attribute:NSFontAttributeName atIndex:NSMaxRange(effectiveRange) effectiveRange:&effectiveRange];
1366 if (!unscaledFont) {
1367 // FIXME: We can't scale the font if we don't know what it is. We should always know what it is,
1368 // but sometimes don't due to PDFKit issue 5089411. When that's addressed, we can remove this
1370 LOG_ERROR("no font attribute found in range %@ for attributed string \"%@\" on page %@ (see radar 5089411)", NSStringFromRange(effectiveRange), result, [[dataSource request] URL]);
1374 NSFont *scaledFont = [NSFont fontWithName:[unscaledFont fontName] size:[unscaledFont pointSize]*scaleFactor];
1375 [result addAttribute:NSFontAttributeName value:scaledFont range:effectiveRange];
1377 [result endEditing];
1382 - (void)_setTextMatches:(NSArray *)array
1385 [textMatches release];
1386 textMatches = array;
1389 - (NSString *)_temporaryPDFDirectoryPath
1391 // Returns nil if the temporary PDF directory didn't exist and couldn't be created
1393 static NSString *_temporaryPDFDirectoryPath = nil;
1395 if (!_temporaryPDFDirectoryPath) {
1396 NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPDFs-XXXXXX"];
1397 char *cTemplate = strdup([temporaryDirectoryTemplate fileSystemRepresentation]);
1399 if (!mkdtemp(cTemplate)) {
1400 // This should never happen; if it does we'll fail silently on non-debug builds.
1401 ASSERT_NOT_REACHED();
1403 // cTemplate has now been modified to be the just-created directory name. This directory has 700 permissions,
1404 // so only the current user can add to it or view its contents.
1405 _temporaryPDFDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:cTemplate length:strlen(cTemplate)] retain];
1411 return _temporaryPDFDirectoryPath;
1414 - (void)_trackFirstResponder
1416 ASSERT([self window]);
1417 BOOL newFirstResponderIsPDFDocumentView = [[self window] firstResponder] == [PDFSubview documentView];
1418 if (newFirstResponderIsPDFDocumentView == firstResponderIsPDFDocumentView)
1421 // This next clause is the entire purpose of _trackFirstResponder. In other WebDocument
1422 // view classes this is done in a resignFirstResponder override, but in this case the
1423 // first responder view is a PDFKit class that we can't subclass.
1424 if (newFirstResponderIsPDFDocumentView && ![[dataSource _webView] maintainsInactiveSelection])
1427 firstResponderIsPDFDocumentView = newFirstResponderIsPDFDocumentView;
1430 - (void)_updatePreferences:(WebPreferences *)prefs
1432 float scaleFactor = [PDFSubview autoScales] ? 0.0f : [PDFSubview scaleFactor];
1433 [prefs setPDFScaleFactor:scaleFactor];
1434 [prefs setPDFDisplayMode:[PDFSubview displayMode]];
1435 _willUpdatePreferencesSoon = NO;
1440 - (void)_updatePreferencesSoon
1442 // Consolidate calls; due to the PDFPrefUpdatingProxy method, this can be called multiple times with a single user action
1443 // such as showing the context menu.
1444 if (_willUpdatePreferencesSoon)
1447 WebPreferences *prefs = [[dataSource _webView] preferences];
1451 [self performSelector:@selector(_updatePreferences:) withObject:prefs afterDelay:0];
1452 _willUpdatePreferencesSoon = YES;
1455 - (NSSet *)_visiblePDFPages
1457 // Returns the set of pages that are at least partly visible, used to avoid processing non-visible pages
1458 PDFDocument *pdfDocument = [PDFSubview document];
1462 NSRect pdfViewBounds = [PDFSubview bounds];
1463 PDFPage *topLeftPage = [PDFSubview pageForPoint:NSMakePoint(NSMinX(pdfViewBounds), NSMaxY(pdfViewBounds)) nearest:YES];
1464 PDFPage *bottomRightPage = [PDFSubview pageForPoint:NSMakePoint(NSMaxX(pdfViewBounds), NSMinY(pdfViewBounds)) nearest:YES];
1466 // only page-free documents should return nil for either of these two since we passed YES for nearest:
1468 ASSERT(!bottomRightPage);
1472 NSUInteger firstVisiblePageIndex = [pdfDocument indexForPage:topLeftPage];
1473 NSUInteger lastVisiblePageIndex = [pdfDocument indexForPage:bottomRightPage];
1475 if (firstVisiblePageIndex > lastVisiblePageIndex) {
1476 NSUInteger swap = firstVisiblePageIndex;
1477 firstVisiblePageIndex = lastVisiblePageIndex;
1478 lastVisiblePageIndex = swap;
1481 NSMutableSet *result = [NSMutableSet set];
1482 NSUInteger pageIndex;
1483 for (pageIndex = firstVisiblePageIndex; pageIndex <= lastVisiblePageIndex; ++pageIndex)
1484 [result addObject:[pdfDocument pageAtIndex:pageIndex]];
1491 @implementation PDFPrefUpdatingProxy
1493 - (id)initWithView:(WebPDFView *)aView
1495 // No [super init], since we inherit from NSProxy
1500 - (void)forwardInvocation:(NSInvocation *)invocation
1502 [invocation invokeWithTarget:[view _PDFSubview]];
1503 [view _updatePreferencesSoon];
1506 - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
1508 return [[view _PDFSubview] methodSignatureForSelector:sel];