| /* |
| * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2013 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "WebPDFView.h" |
| |
| #if PLATFORM(MAC) |
| |
| #import "DOMNodeInternal.h" |
| #import "DOMRangeInternal.h" |
| #import "PDFViewSPI.h" |
| #import "WebDataSourceInternal.h" |
| #import "WebDelegateImplementationCaching.h" |
| #import "WebDocumentInternal.h" |
| #import "WebDocumentPrivate.h" |
| #import "WebFrame.h" |
| #import "WebFrameInternal.h" |
| #import "WebFrameView.h" |
| #import "WebLocalizableStringsInternal.h" |
| #import "WebNSPasteboardExtras.h" |
| #import "WebNSViewExtras.h" |
| #import "WebPDFRepresentation.h" |
| #import "WebPreferencesPrivate.h" |
| #import "WebUIDelegate.h" |
| #import "WebUIDelegatePrivate.h" |
| #import "WebView.h" |
| #import "WebViewInternal.h" |
| #import <WebCore/DataTransfer.h> |
| #import <WebCore/EventNames.h> |
| #import <WebCore/FormState.h> |
| #import <WebCore/Frame.h> |
| #import <WebCore/FrameLoadRequest.h> |
| #import <WebCore/FrameLoader.h> |
| #import <WebCore/HTMLFormElement.h> |
| #import <WebCore/HTMLFrameOwnerElement.h> |
| #import <WebCore/KeyboardEvent.h> |
| #import <WebCore/LegacyNSPasteboardTypes.h> |
| #import <WebCore/MouseEvent.h> |
| #import <WebCore/PlatformEventFactoryMac.h> |
| #import <WebCore/Range.h> |
| #import <WebCore/ReferrerPolicy.h> |
| #import <WebCore/RuntimeApplicationChecks.h> |
| #import <WebCore/SimpleRange.h> |
| #import <WebCore/WebNSAttributedStringExtras.h> |
| #import <wtf/Assertions.h> |
| #import <wtf/URL.h> |
| |
| extern "C" { |
| bool CGContextGetAllowsFontSmoothing(CGContextRef context); |
| bool CGContextGetAllowsFontSubpixelQuantization(CGContextRef context); |
| } |
| |
| // Redeclarations of PDFKit notifications. We can't use the API since we use a weak link to the framework. |
| #define _webkit_PDFViewDisplayModeChangedNotification @"PDFViewDisplayModeChanged" |
| #define _webkit_PDFViewScaleChangedNotification @"PDFViewScaleChanged" |
| #define _webkit_PDFViewPageChangedNotification @"PDFViewChangedPage" |
| |
| @interface PDFDocument (PDFKitSecretsIKnow) |
| - (NSPrintOperation *)getPrintOperationForPrintInfo:(NSPrintInfo *)printInfo autoRotate:(BOOL)doRotate; |
| @end |
| |
| extern "C" NSString *_NSPathForSystemFramework(NSString *framework); |
| |
| @interface NSView () |
| - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView; |
| - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect; |
| - (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView; |
| - (void)_recursive:(BOOL)recurseX displayRectIgnoringOpacity:(NSRect)displayRect inGraphicsContext:(NSGraphicsContext *)graphicsContext CGContext:(CGContextRef)ctx topView:(BOOL)isTopView shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor; |
| @end |
| |
| // WebPDFPrefUpdatingProxy is a class that forwards everything it gets to a target and updates the PDF viewing prefs |
| // after each of those messages. We use it as a way to hook all the places that the PDF viewing attrs change. |
| @interface WebPDFPrefUpdatingProxy : NSProxy { |
| WebPDFView *view; |
| } |
| - (id)initWithView:(WebPDFView *)view; |
| @end |
| |
| // MARK: C UTILITY FUNCTIONS |
| |
| static void _applicationInfoForMIMEType(NSString *type, NSString **name, NSImage **image) |
| { |
| CFURLRef appURL = nullptr; |
| |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| OSStatus error = LSCopyApplicationForMIMEType((__bridge CFStringRef)type, kLSRolesAll, &appURL); |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| if (error != noErr) |
| return; |
| |
| NSString *appPath = [(__bridge NSURL *)appURL path]; |
| CFRelease(appURL); |
| |
| *image = [[NSWorkspace sharedWorkspace] iconForFile:appPath]; |
| [*image setSize:NSMakeSize(16.f,16.f)]; |
| |
| NSString *appName = [[NSFileManager defaultManager] displayNameAtPath:appPath]; |
| *name = appName; |
| } |
| |
| // FIXME 4182876: We can eliminate this function in favor if -isEqual: if [PDFSelection isEqual:] is overridden |
| // to compare contents. |
| static BOOL _PDFSelectionsAreEqual(PDFSelection *selectionA, PDFSelection *selectionB) |
| { |
| NSArray *aPages = [selectionA pages]; |
| NSArray *bPages = [selectionB pages]; |
| |
| if (![aPages isEqual:bPages]) |
| return NO; |
| |
| int count = [aPages count]; |
| int i; |
| for (i = 0; i < count; ++i) { |
| NSRect aBounds = [selectionA boundsForPage:[aPages objectAtIndex:i]]; |
| NSRect bBounds = [selectionB boundsForPage:[bPages objectAtIndex:i]]; |
| if (!NSEqualRects(aBounds, bBounds)) { |
| return NO; |
| } |
| } |
| |
| return YES; |
| } |
| |
| @implementation WebPDFView |
| |
| // MARK: WebPDFView API |
| |
| + (NSBundle *)PDFKitBundle |
| { |
| static NSBundle *PDFKitBundle = nil; |
| if (PDFKitBundle == nil) { |
| NSString *PDFKitPath = [_NSPathForSystemFramework(@"Quartz.framework") stringByAppendingString:@"/Frameworks/PDFKit.framework"]; |
| if (PDFKitPath == nil) { |
| LOG_ERROR("Couldn't find PDFKit.framework"); |
| return nil; |
| } |
| PDFKitBundle = [NSBundle bundleWithPath:PDFKitPath]; |
| if (![PDFKitBundle load]) { |
| LOG_ERROR("Couldn't load PDFKit.framework"); |
| } |
| } |
| return PDFKitBundle; |
| } |
| |
| + (NSArray *)supportedMIMETypes |
| { |
| return [WebPDFRepresentation supportedMIMETypes]; |
| } |
| |
| - (void)setPDFDocument:(PDFDocument *)doc |
| { |
| // Both setDocument: and _applyPDFDefaults will trigger scale and mode-changed notifications. |
| // Those aren't reflecting user actions, so we need to ignore them. |
| _ignoreScaleAndDisplayModeAndPageNotifications = YES; |
| [PDFSubview setDocument:doc]; |
| [self _applyPDFDefaults]; |
| _ignoreScaleAndDisplayModeAndPageNotifications = NO; |
| } |
| |
| - (PDFDocument *)PDFDocument |
| { |
| return [PDFSubview document]; |
| } |
| |
| // MARK: NSObject OVERRIDES |
| |
| - (void)dealloc |
| { |
| [dataSource release]; |
| [PDFSubview setDelegate:nil]; |
| [PDFSubview release]; |
| [path release]; |
| [PDFSubviewProxy release]; |
| [textMatches release]; |
| [super dealloc]; |
| } |
| |
| // MARK: NSResponder OVERRIDES |
| |
| - (void)centerSelectionInVisibleArea:(id)sender |
| { |
| // FIXME: Get rid of this once <rdar://problem/25149294> has been fixed. |
| IGNORE_NULL_CHECK_WARNINGS_BEGIN |
| [PDFSubview scrollSelectionToVisible:nil]; |
| IGNORE_NULL_CHECK_WARNINGS_END |
| } |
| |
| - (void)scrollPageDown:(id)sender |
| { |
| // PDFView doesn't support this responder method directly, so we pass it a fake key event |
| [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSPageDownFunctionKey]]; |
| } |
| |
| - (void)scrollPageUp:(id)sender |
| { |
| // PDFView doesn't support this responder method directly, so we pass it a fake key event |
| [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSPageUpFunctionKey]]; |
| } |
| |
| - (void)scrollLineDown:(id)sender |
| { |
| // PDFView doesn't support this responder method directly, so we pass it a fake key event |
| [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSDownArrowFunctionKey]]; |
| } |
| |
| - (void)scrollLineUp:(id)sender |
| { |
| // PDFView doesn't support this responder method directly, so we pass it a fake key event |
| [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSUpArrowFunctionKey]]; |
| } |
| |
| - (void)scrollToBeginningOfDocument:(id)sender |
| { |
| // PDFView doesn't support this responder method directly, so we pass it a fake key event |
| [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSHomeFunctionKey]]; |
| } |
| |
| - (void)scrollToEndOfDocument:(id)sender |
| { |
| // PDFView doesn't support this responder method directly, so we pass it a fake key event |
| [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSEndFunctionKey]]; |
| } |
| |
| // jumpToSelection is the old name for what AppKit now calls centerSelectionInVisibleArea. Safari |
| // was using the old jumpToSelection selector in its menu. Newer versions of Safari will us the |
| // selector centerSelectionInVisibleArea. We'll leave this old selector in place for two reasons: |
| // (1) compatibility between older Safari and newer WebKit; (2) other WebKit-based applications |
| // might be using the jumpToSelection: selector, and we don't want to break them. |
| - (void)jumpToSelection:(id)sender |
| { |
| [self centerSelectionInVisibleArea:nil]; |
| } |
| |
| // MARK: NSView OVERRIDES |
| |
| - (BOOL)acceptsFirstResponder { |
| return YES; |
| } |
| |
| - (BOOL)becomeFirstResponder |
| { |
| // This works together with setNextKeyView to splice our PDFSubview into |
| // the key loop similar to the way NSScrollView does this. |
| NSWindow *window = [self window]; |
| id newFirstResponder = nil; |
| |
| if ([window keyViewSelectionDirection] == NSSelectingPrevious) { |
| NSView *previousValidKeyView = [self previousValidKeyView]; |
| if ((previousValidKeyView != self) && (previousValidKeyView != PDFSubview)) |
| newFirstResponder = previousValidKeyView; |
| } else { |
| NSView *PDFDocumentView = [PDFSubview documentView]; |
| if ([PDFDocumentView acceptsFirstResponder]) |
| newFirstResponder = PDFDocumentView; |
| } |
| |
| if (!newFirstResponder) |
| return NO; |
| |
| if (![window makeFirstResponder:newFirstResponder]) |
| return NO; |
| |
| [[dataSource webFrame] _clearSelectionInOtherFrames]; |
| |
| return YES; |
| } |
| |
| - (NSView *)hitTest:(NSPoint)point |
| { |
| // Override hitTest so we can override menuForEvent. |
| NSEvent *event = [NSApp currentEvent]; |
| NSEventType type = [event type]; |
| if (type == NSEventTypeRightMouseDown || (type == NSEventTypeLeftMouseDown && ([event modifierFlags] & NSEventModifierFlagControl))) |
| return self; |
| |
| return [super hitTest:point]; |
| } |
| |
| - (id)initWithFrame:(NSRect)frame |
| { |
| self = [super initWithFrame:frame]; |
| if (self) { |
| [self setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; |
| |
| PDFSubview = [[[[self class] _PDFViewClass] alloc] initWithFrame:frame]; |
| |
| ASSERT(PDFSubview); |
| |
| [PDFSubview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; |
| [self addSubview:PDFSubview]; |
| |
| [PDFSubview setDelegate:self]; |
| written = NO; |
| // Messaging this proxy is the same as messaging PDFSubview, with the side effect that the |
| // PDF viewing defaults are updated afterwards |
| PDFSubviewProxy = (PDFView *)[[WebPDFPrefUpdatingProxy alloc] initWithView:self]; |
| } |
| |
| return self; |
| } |
| |
| // These states can be mutated by PDFKit but are not saved |
| // on the context's state stack. (<rdar://problem/14951759>) |
| |
| - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView |
| { |
| CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; |
| |
| bool allowsSmoothing = CGContextGetAllowsFontSmoothing(context); |
| bool allowsSubpixelQuantization = CGContextGetAllowsFontSubpixelQuantization(context); |
| |
| [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect rectIsVisibleRectForView:visibleView topView:topView]; |
| |
| CGContextSetAllowsFontSmoothing(context, allowsSmoothing); |
| CGContextSetAllowsFontSubpixelQuantization(context, allowsSubpixelQuantization); |
| } |
| |
| - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect |
| { |
| CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; |
| |
| bool allowsSmoothing = CGContextGetAllowsFontSmoothing(context); |
| bool allowsSubpixelQuantization = CGContextGetAllowsFontSubpixelQuantization(context); |
| |
| [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect]; |
| |
| CGContextSetAllowsFontSmoothing(context, allowsSmoothing); |
| CGContextSetAllowsFontSubpixelQuantization(context, allowsSubpixelQuantization); |
| } |
| |
| - (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)graphicsContext topView:(BOOL)topView |
| { |
| CGContextRef context = [graphicsContext CGContext]; |
| |
| bool allowsSmoothing = CGContextGetAllowsFontSmoothing(context); |
| bool allowsSubpixelQuantization = CGContextGetAllowsFontSubpixelQuantization(context); |
| |
| [super _recursive:recurse displayRectIgnoringOpacity:displayRect inContext:graphicsContext topView:topView]; |
| |
| CGContextSetAllowsFontSmoothing(context, allowsSmoothing); |
| CGContextSetAllowsFontSubpixelQuantization(context, allowsSubpixelQuantization); |
| } |
| |
| - (void)_recursive:(BOOL)recurseX displayRectIgnoringOpacity:(NSRect)displayRect inGraphicsContext:(NSGraphicsContext *)graphicsContext CGContext:(CGContextRef)context topView:(BOOL)isTopView shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor |
| { |
| bool allowsSmoothing = CGContextGetAllowsFontSmoothing(context); |
| bool allowsSubpixelQuantization = CGContextGetAllowsFontSubpixelQuantization(context); |
| |
| [super _recursive:recurseX displayRectIgnoringOpacity:displayRect inGraphicsContext:graphicsContext CGContext:context topView:isTopView shouldChangeFontReferenceColor:shouldChangeFontReferenceColor]; |
| |
| CGContextSetAllowsFontSmoothing(context, allowsSmoothing); |
| CGContextSetAllowsFontSubpixelQuantization(context, allowsSubpixelQuantization); |
| } |
| |
| - (NSMenu *)menuForEvent:(NSEvent *)theEvent |
| { |
| // Start with the menu items supplied by PDFKit, with WebKit tags applied |
| NSMutableArray *items = [self _menuItemsFromPDFKitForEvent:theEvent]; |
| |
| // Add in an "Open with <default PDF viewer>" item |
| NSString *appName = nil; |
| NSImage *appIcon = nil; |
| |
| _applicationInfoForMIMEType([dataSource _responseMIMEType], &appName, &appIcon); |
| if (!appName) |
| appName = UI_STRING_INTERNAL("Finder", "Default application name for Open With context menu"); |
| |
| // To match the PDFKit style, we'll add Open with Preview even when there's no document yet to view, and |
| // disable it using validateUserInterfaceItem. |
| NSString *title = [NSString stringWithFormat:UI_STRING_INTERNAL("Open with %@", "context menu item for PDF"), appName]; |
| auto item = adoptNS([[NSMenuItem alloc] initWithTitle:title action:@selector(_openWithFinder:) keyEquivalent:@""]); |
| [item setTag:WebMenuItemTagOpenWithDefaultApplication]; |
| if (appIcon) |
| [item setImage:appIcon]; |
| [items insertObject:item.get() atIndex:0]; |
| |
| [items insertObject:[NSMenuItem separatorItem] atIndex:1]; |
| |
| // pass the items off to the WebKit context menu mechanism |
| WebView *webView = [[dataSource webFrame] webView]; |
| ASSERT(webView); |
| return [webView _menuForElement:[self elementAtPoint:[self convertPoint:[theEvent locationInWindow] fromView:nil]] defaultItems:items]; |
| } |
| |
| - (void)setNextKeyView:(NSView *)aView |
| { |
| // This works together with becomeFirstResponder to splice PDFSubview into |
| // the key loop similar to the way NSScrollView and NSClipView do this. |
| NSView *documentView = [PDFSubview documentView]; |
| if (documentView) { |
| [documentView setNextKeyView:aView]; |
| |
| // We need to make the documentView be the next view in the keyview loop. |
| // It would seem more sensible to do this in our init method, but it turns out |
| // that [NSClipView setDocumentView] won't call this method if our next key view |
| // is already set, so we wait until we're called before adding this connection. |
| // We'll also clear it when we're called with nil, so this could go through the |
| // same code path more than once successfully. |
| [super setNextKeyView: aView ? documentView : nil]; |
| } else |
| [super setNextKeyView:aView]; |
| } |
| |
| - (void)viewDidMoveToWindow |
| { |
| // FIXME 2573089: we can observe a notification for first responder changes |
| // instead of the very frequent NSWindowDidUpdateNotification if/when 2573089 is addressed. |
| NSWindow *newWindow = [self window]; |
| if (!newWindow) |
| return; |
| |
| [self _trackFirstResponder]; |
| NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; |
| [notificationCenter addObserver:self |
| selector:@selector(_trackFirstResponder) |
| name:NSWindowDidUpdateNotification |
| object:newWindow]; |
| |
| [notificationCenter addObserver:self |
| selector:@selector(_scaleOrDisplayModeOrPageChanged:) |
| name:_webkit_PDFViewScaleChangedNotification |
| object:PDFSubview]; |
| |
| [notificationCenter addObserver:self |
| selector:@selector(_scaleOrDisplayModeOrPageChanged:) |
| name:_webkit_PDFViewDisplayModeChangedNotification |
| object:PDFSubview]; |
| |
| [notificationCenter addObserver:self |
| selector:@selector(_scaleOrDisplayModeOrPageChanged:) |
| name:_webkit_PDFViewPageChangedNotification |
| object:PDFSubview]; |
| |
| [notificationCenter addObserver:self |
| selector:@selector(_PDFDocumentViewMightHaveScrolled:) |
| name:NSViewBoundsDidChangeNotification |
| object:[self _clipViewForPDFDocumentView]]; |
| } |
| |
| - (void)viewWillMoveToWindow:(NSWindow *)window |
| { |
| // FIXME 2573089: we can observe a notification for changes to the first responder |
| // instead of the very frequent NSWindowDidUpdateNotification if/when 2573089 is addressed. |
| NSWindow *oldWindow = [self window]; |
| if (!oldWindow) |
| return; |
| |
| NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; |
| [notificationCenter removeObserver:self |
| name:NSWindowDidUpdateNotification |
| object:oldWindow]; |
| [notificationCenter removeObserver:self |
| name:_webkit_PDFViewScaleChangedNotification |
| object:PDFSubview]; |
| [notificationCenter removeObserver:self |
| name:_webkit_PDFViewDisplayModeChangedNotification |
| object:PDFSubview]; |
| [notificationCenter removeObserver:self |
| name:_webkit_PDFViewPageChangedNotification |
| object:PDFSubview]; |
| |
| [notificationCenter removeObserver:self |
| name:NSViewBoundsDidChangeNotification |
| object:[self _clipViewForPDFDocumentView]]; |
| |
| firstResponderIsPDFDocumentView = NO; |
| } |
| |
| // MARK: NSUserInterfaceValidations PROTOCOL IMPLEMENTATION |
| |
| - (BOOL)validateUserInterfaceItemWithoutDelegate:(id <NSValidatedUserInterfaceItem>)item |
| { |
| SEL action = [item action]; |
| if (action == @selector(takeFindStringFromSelection:) || action == @selector(centerSelectionInVisibleArea:) || action == @selector(jumpToSelection:)) |
| return [PDFSubview currentSelection] != nil; |
| |
| if (action == @selector(_openWithFinder:)) |
| return [PDFSubview document] != nil; |
| |
| if (action == @selector(_lookUpInDictionaryFromMenu:)) |
| return [self _canLookUpInDictionary]; |
| |
| return YES; |
| } |
| |
| - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item |
| { |
| // This can be called during teardown when _webView is nil. Return NO when this happens, because CallUIDelegateReturningBoolean |
| // assumes the WebVIew is non-nil. |
| if (![self _webView]) |
| return NO; |
| BOOL result = [self validateUserInterfaceItemWithoutDelegate:item]; |
| return CallUIDelegateReturningBoolean(result, [self _webView], @selector(webView:validateUserInterfaceItem:defaultValidation:), item, result); |
| } |
| |
| // MARK: INTERFACE BUILDER ACTIONS FOR SAFARI |
| |
| // Surprisingly enough, this isn't defined in any superclass, though it is defined in assorted AppKit classes since |
| // it's a standard menu item IBAction. |
| - (IBAction)copy:(id)sender |
| { |
| [PDFSubview copy:sender]; |
| } |
| |
| // This used to be a standard IBAction (for Use Selection For Find), but AppKit now uses performFindPanelAction: |
| // with a menu item tag for this purpose. |
| - (IBAction)takeFindStringFromSelection:(id)sender |
| { |
| [NSPasteboard _web_setFindPasteboardString:[[PDFSubview currentSelection] string] withOwner:self]; |
| } |
| |
| // MARK: WebFrameView UNDECLARED "DELEGATE METHODS" |
| |
| // This is tested in -[WebFrameView canPrintHeadersAndFooters], but isn't declared anywhere (yuck) |
| - (BOOL)canPrintHeadersAndFooters |
| { |
| return NO; |
| } |
| |
| // This is tested in -[WebFrameView printOperationWithPrintInfo:], but isn't declared anywhere (yuck) |
| - (NSPrintOperation *)printOperationWithPrintInfo:(NSPrintInfo *)printInfo |
| { |
| return [[PDFSubview document] getPrintOperationForPrintInfo:printInfo autoRotate:YES]; |
| } |
| |
| // MARK: WebDocumentView PROTOCOL IMPLEMENTATION |
| |
| - (void)setDataSource:(WebDataSource *)ds |
| { |
| if (dataSource == ds) |
| return; |
| |
| dataSource = [ds retain]; |
| |
| // FIXME: There must be some better place to put this. There is no comment in ChangeLog |
| // explaining why it's in this method. |
| [self setFrame:[[self superview] frame]]; |
| } |
| |
| - (void)dataSourceUpdated:(WebDataSource *)dataSource |
| { |
| } |
| |
| - (void)setNeedsLayout:(BOOL)flag |
| { |
| } |
| |
| - (void)layout |
| { |
| } |
| |
| - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow |
| { |
| } |
| |
| - (void)viewDidMoveToHostWindow |
| { |
| } |
| |
| // MARK: WebDocumentElement PROTOCOL IMPLEMENTATION |
| |
| - (NSDictionary *)elementAtPoint:(NSPoint)point |
| { |
| return @{ |
| WebElementFrameKey: [dataSource webFrame], |
| WebElementIsSelectedKey: @([self _pointIsInSelection:point]), |
| }; |
| } |
| |
| - (NSDictionary *)elementAtPoint:(NSPoint)point allowShadowContent:(BOOL)allow |
| { |
| return [self elementAtPoint:point]; |
| } |
| |
| // MARK: WebDocumentSearching PROTOCOL IMPLEMENTATION |
| |
| - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag |
| { |
| return [self searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:NO]; |
| } |
| |
| // MARK: WebDocumentIncrementalSearching PROTOCOL IMPLEMENTATION |
| |
| - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection |
| { |
| PDFSelection *selection = [self _nextMatchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag fromSelection:[PDFSubview currentSelection] startInSelection:startInSelection]; |
| if (!selection) |
| return NO; |
| |
| [PDFSubview setCurrentSelection:selection]; |
| |
| // FIXME: Get rid of this once <rdar://problem/25149294> has been fixed. |
| IGNORE_NULL_CHECK_WARNINGS_BEGIN |
| [PDFSubview scrollSelectionToVisible:nil]; |
| IGNORE_NULL_CHECK_WARNINGS_END |
| return YES; |
| } |
| |
| // MARK: WebMultipleTextMatches PROTOCOL IMPLEMENTATION |
| |
| - (void)setMarkedTextMatchesAreHighlighted:(BOOL)newValue |
| { |
| // This method is part of the WebMultipleTextMatches algorithm, but this class doesn't support |
| // highlighting text matches inline. |
| #ifndef NDEBUG |
| if (newValue) |
| LOG_ERROR("[WebPDFView setMarkedTextMatchesAreHighlighted:] called with YES, which isn't supported"); |
| #endif |
| } |
| |
| - (BOOL)markedTextMatchesAreHighlighted |
| { |
| return NO; |
| } |
| |
| - (NSUInteger)countMatchesForText:(NSString *)string inDOMRange:(DOMRange *)range options:(WebFindOptions)options limit:(NSUInteger)limit markMatches:(BOOL)markMatches |
| { |
| if (range && !containsCrossingDocumentBoundaries(makeSimpleRange(*core(range)), *core([dataSource webFrame])->document())) |
| return 0; |
| |
| PDFSelection *previousMatch = nil; |
| auto matches = adoptNS([[NSMutableArray alloc] initWithCapacity:limit]); |
| |
| for (;;) { |
| PDFSelection *nextMatch = [self _nextMatchFor:string direction:YES caseSensitive:!(options & WebFindOptionsCaseInsensitive) wrap:NO fromSelection:previousMatch startInSelection:NO]; |
| if (!nextMatch) |
| break; |
| |
| [matches addObject:nextMatch]; |
| previousMatch = nextMatch; |
| |
| if ([matches count] >= limit) |
| break; |
| } |
| |
| [self _setTextMatches:matches.get()]; |
| |
| return [matches count]; |
| } |
| |
| - (void)unmarkAllTextMatches |
| { |
| [self _setTextMatches:nil]; |
| } |
| |
| - (NSArray *)rectsForTextMatches |
| { |
| NSMutableArray *result = [NSMutableArray arrayWithCapacity:[textMatches count]]; |
| NSSet *visiblePages = [self _visiblePDFPages]; |
| NSEnumerator *matchEnumerator = [textMatches objectEnumerator]; |
| PDFSelection *match; |
| |
| while ((match = [matchEnumerator nextObject]) != nil) { |
| NSEnumerator *pages = [[match pages] objectEnumerator]; |
| PDFPage *page; |
| while ((page = [pages nextObject]) != nil) { |
| |
| // Skip pages that aren't visible (needed for non-continuous modes, see 5362989) |
| if (![visiblePages containsObject:page]) |
| continue; |
| |
| NSRect selectionOnPageInPDFViewCoordinates = [PDFSubview convertRect:[match boundsForPage:page] fromPage:page]; |
| [result addObject:[NSValue valueWithRect:selectionOnPageInPDFViewCoordinates]]; |
| } |
| } |
| |
| return result; |
| } |
| |
| // MARK: WebDocumentText PROTOCOL IMPLEMENTATION |
| |
| - (BOOL)supportsTextEncoding |
| { |
| return NO; |
| } |
| |
| - (NSString *)string |
| { |
| return [[PDFSubview document] string]; |
| } |
| |
| - (NSAttributedString *)attributedString |
| { |
| // changing the selection is a hack, but the only way to get an attr string is via PDFSelection |
| |
| // must copy this selection object because we change the selection which seems to release it |
| auto savedSelection = adoptNS([[PDFSubview currentSelection] copy]); |
| [PDFSubview selectAll:nil]; |
| NSAttributedString *result = [[PDFSubview currentSelection] attributedString]; |
| if (savedSelection) { |
| [PDFSubview setCurrentSelection:savedSelection.get()]; |
| } else { |
| // FIXME: behavior of setCurrentSelection:nil is not documented - check 4182934 for progress |
| // Otherwise, we could collapse this code with the case above. |
| [PDFSubview clearSelection]; |
| } |
| |
| result = [self _scaledAttributedString:result]; |
| |
| return result; |
| } |
| |
| - (NSString *)selectedString |
| { |
| return [[PDFSubview currentSelection] string]; |
| } |
| |
| - (NSAttributedString *)selectedAttributedString |
| { |
| return [self _scaledAttributedString:[[PDFSubview currentSelection] attributedString]]; |
| } |
| |
| - (void)selectAll |
| { |
| [PDFSubview selectAll:nil]; |
| } |
| |
| - (void)deselectAll |
| { |
| [PDFSubview clearSelection]; |
| } |
| |
| // MARK: WebDocumentViewState PROTOCOL IMPLEMENTATION |
| |
| // Even though to WebKit we are the "docView", in reality a PDFView contains its own scrollview and docView. |
| // And it even turns out there is another PDFKit view between the docView and its enclosing ScrollView, so |
| // we have to be sure to do our calculations based on that view, immediately inside the ClipView. We try |
| // to make as few assumptions about the PDFKit view hierarchy as possible. |
| |
| - (NSPoint)scrollPoint |
| { |
| NSView *realDocView = [PDFSubview documentView]; |
| NSClipView *clipView = [[realDocView enclosingScrollView] contentView]; |
| return [clipView bounds].origin; |
| } |
| |
| - (void)setScrollPoint:(NSPoint)p |
| { |
| WebFrame *frame = [dataSource webFrame]; |
| //FIXME: We only restore scroll state in the non-frames case because otherwise we get a crash due to |
| // PDFKit calling display from within its drawRect:. See bugzilla 4164. |
| if (![frame parentFrame]) { |
| NSView *realDocView = [PDFSubview documentView]; |
| [(NSView *)[[realDocView enclosingScrollView] documentView] scrollPoint:p]; |
| } |
| } |
| |
| - (id)viewState |
| { |
| NSMutableArray *state = [NSMutableArray arrayWithCapacity:4]; |
| PDFDisplayMode mode = [PDFSubview displayMode]; |
| [state addObject:@(mode)]; |
| if (mode == kPDFDisplaySinglePage || mode == kPDFDisplayTwoUp) { |
| unsigned int pageIndex = [[PDFSubview document] indexForPage:[PDFSubview currentPage]]; |
| [state addObject:@(pageIndex)]; |
| } // else in continuous modes, scroll position gets us to the right page |
| BOOL autoScaleFlag = [PDFSubview autoScales]; |
| [state addObject:[NSNumber numberWithBool:autoScaleFlag]]; |
| if (!autoScaleFlag) |
| [state addObject:[NSNumber numberWithFloat:[PDFSubview scaleFactor]]]; |
| |
| return state; |
| } |
| |
| - (void)setViewState:(id)statePList |
| { |
| ASSERT([statePList isKindOfClass:[NSArray class]]); |
| NSArray *state = statePList; |
| int i = 0; |
| PDFDisplayMode mode = static_cast<PDFDisplayMode>([[state objectAtIndex:i++] intValue]); |
| [PDFSubview setDisplayMode:mode]; |
| if (mode == kPDFDisplaySinglePage || mode == kPDFDisplayTwoUp) { |
| unsigned int pageIndex = [[state objectAtIndex:i++] unsignedIntValue]; |
| [PDFSubview goToPage:[[PDFSubview document] pageAtIndex:pageIndex]]; |
| } // else in continuous modes, scroll position gets us to the right page |
| BOOL autoScaleFlag = [[state objectAtIndex:i++] boolValue]; |
| [PDFSubview setAutoScales:autoScaleFlag]; |
| if (!autoScaleFlag) |
| [PDFSubview setScaleFactor:[[state objectAtIndex:i++] floatValue]]; |
| } |
| |
| // MARK: _WebDocumentTextSizing PROTOCOL IMPLEMENTATION |
| |
| - (IBAction)_zoomOut:(id)sender |
| { |
| [PDFSubviewProxy zoomOut:sender]; |
| } |
| |
| - (IBAction)_zoomIn:(id)sender |
| { |
| [PDFSubviewProxy zoomIn:sender]; |
| } |
| |
| - (IBAction)_resetZoom:(id)sender |
| { |
| [PDFSubviewProxy setScaleFactor:1.0f]; |
| } |
| |
| - (BOOL)_canZoomOut |
| { |
| return [PDFSubview canZoomOut]; |
| } |
| |
| - (BOOL)_canZoomIn |
| { |
| return [PDFSubview canZoomIn]; |
| } |
| |
| - (BOOL)_canResetZoom |
| { |
| return [PDFSubview scaleFactor] != 1.0; |
| } |
| |
| // MARK: WebDocumentSelection PROTOCOL IMPLEMENTATION |
| |
| - (NSRect)selectionRect |
| { |
| NSRect result = NSZeroRect; |
| PDFSelection *selection = [PDFSubview currentSelection]; |
| NSEnumerator *pages = [[selection pages] objectEnumerator]; |
| PDFPage *page; |
| while ((page = [pages nextObject]) != nil) { |
| NSRect selectionOnPageInPDFViewCoordinates = [PDFSubview convertRect:[selection boundsForPage:page] fromPage:page]; |
| if (NSIsEmptyRect(result)) |
| result = selectionOnPageInPDFViewCoordinates; |
| else |
| result = NSUnionRect(result, selectionOnPageInPDFViewCoordinates); |
| } |
| |
| // Convert result to be in documentView (selectionView) coordinates |
| result = [PDFSubview convertRect:result toView:[PDFSubview documentView]]; |
| |
| return result; |
| } |
| |
| - (NSArray *)selectionTextRects |
| { |
| // FIXME: We'd need new PDFKit API/SPI to get multiple text rects for selections that intersect more than one line |
| return @[[NSValue valueWithRect:[self selectionRect]]]; |
| } |
| |
| - (NSView *)selectionView |
| { |
| return [PDFSubview documentView]; |
| } |
| |
| - (NSImage *)selectionImageForcingBlackText:(BOOL)forceBlackText |
| { |
| // Convert the selection to an attributed string, and draw that. |
| // FIXME 4621154: this doesn't handle italics (and maybe other styles) |
| // FIXME 4604366: this doesn't handle text at non-actual size |
| RetainPtr<NSMutableAttributedString> attributedString = adoptNS([[self selectedAttributedString] mutableCopy]); |
| NSRange wholeStringRange = NSMakeRange(0, [attributedString length]); |
| |
| // Modify the styles in the attributed string to draw black text, no background, and no underline. We draw |
| // no underline because it would look ugly. |
| [attributedString beginEditing]; |
| [attributedString removeAttribute:NSBackgroundColorAttributeName range:wholeStringRange]; |
| [attributedString removeAttribute:NSUnderlineStyleAttributeName range:wholeStringRange]; |
| if (forceBlackText) |
| [attributedString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithDeviceWhite:0.0f alpha:1.0f] range:wholeStringRange]; |
| [attributedString endEditing]; |
| |
| auto selectionImage = adoptNS([[NSImage alloc] initWithSize:[self selectionRect].size]); |
| |
| [selectionImage lockFocus]; |
| [attributedString drawAtPoint:NSZeroPoint]; |
| [selectionImage unlockFocus]; |
| |
| return selectionImage.autorelease(); |
| } |
| |
| - (NSRect)selectionImageRect |
| { |
| // FIXME: deal with clipping? |
| return [self selectionRect]; |
| } |
| |
| - (NSArray *)pasteboardTypesForSelection |
| { |
| return @[WebCore::legacyRTFDPasteboardType(), WebCore::legacyRTFPasteboardType(), WebCore::legacyStringPasteboardType()]; |
| } |
| |
| - (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard |
| { |
| NSAttributedString *attributedString = [self selectedAttributedString]; |
| |
| if ([types containsObject:WebCore::legacyRTFDPasteboardType()]) { |
| NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:@{ }]; |
| [pasteboard setData:RTFDData forType:WebCore::legacyRTFDPasteboardType()]; |
| } |
| |
| if ([types containsObject:WebCore::legacyRTFPasteboardType()]) { |
| if ([attributedString containsAttachments]) |
| attributedString = WebCore::attributedStringByStrippingAttachmentCharacters(attributedString); |
| |
| NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:@{ }]; |
| [pasteboard setData:RTFData forType:WebCore::legacyRTFPasteboardType()]; |
| } |
| |
| if ([types containsObject:WebCore::legacyStringPasteboardType()]) |
| [pasteboard setString:[self selectedString] forType:WebCore::legacyStringPasteboardType()]; |
| } |
| |
| // MARK: PDFView DELEGATE METHODS |
| |
| - (void)PDFViewWillClickOnLink:(PDFView *)sender withURL:(NSURL *)URL |
| { |
| if (!URL) |
| return; |
| |
| NSWindow *window = [sender window]; |
| NSEvent *nsEvent = [window currentEvent]; |
| const int noButton = -2; |
| int button = noButton; |
| RefPtr<WebCore::Event> event; |
| switch ([nsEvent type]) { |
| case NSEventTypeLeftMouseUp: |
| button = 0; |
| break; |
| case NSEventTypeRightMouseUp: |
| button = 1; |
| break; |
| case NSEventTypeOtherMouseUp: |
| button = [nsEvent buttonNumber]; |
| break; |
| case NSEventTypeKeyDown: { |
| auto pe = WebCore::PlatformEventFactory::createPlatformKeyboardEvent(nsEvent); |
| pe.disambiguateKeyDownEvent(WebCore::PlatformEvent::RawKeyDown); |
| event = WebCore::KeyboardEvent::create(pe, nullptr); |
| break; |
| } |
| default: |
| break; |
| } |
| if (button != noButton) { |
| // FIXME: Use createPlatformMouseEvent instead. |
| event = WebCore::MouseEvent::create(WebCore::eventNames().clickEvent, WebCore::Event::CanBubble::Yes, WebCore::Event::IsCancelable::Yes, WebCore::Event::IsComposed::Yes, |
| MonotonicTime::now(), nullptr, [nsEvent clickCount], { }, { }, { }, WebCore::modifiersForEvent(nsEvent), |
| button, [NSEvent pressedMouseButtons], nullptr, WebCore::ForceAtClick, 0, WebCore::MouseEvent::IsSimulated::Yes); |
| } |
| |
| // Call to the frame loader because this is where our security checks are made. |
| auto* frame = core([dataSource webFrame]); |
| WebCore::FrameLoadRequest frameLoadRequest { *frame->document(), frame->document()->securityOrigin(), { URL }, { }, WebCore::InitiatedByMainFrame::Unknown }; |
| frameLoadRequest.setReferrerPolicy(WebCore::ReferrerPolicy::NoReferrer); |
| frame->loader().loadFrameRequest(WTFMove(frameLoadRequest), event.get(), nullptr); |
| } |
| |
| - (void)PDFViewOpenPDFInNativeApplication:(PDFView *)sender |
| { |
| // Delegate method sent when the user requests opening the PDF file in the system's default app |
| [self _openWithFinder:sender]; |
| } |
| |
| - (void)PDFViewPerformPrint:(PDFView *)sender |
| { |
| CallUIDelegate([self _webView], @selector(webView:printFrameView:), [[dataSource webFrame] frameView]); |
| } |
| |
| - (void)PDFViewSavePDFToDownloadFolder:(PDFView *)sender |
| { |
| // We don't want to write the file until we have a document to write (see 5267607). |
| if (![PDFSubview document]) { |
| NSBeep(); |
| return; |
| } |
| |
| // Delegate method sent when the user requests downloading the PDF file to disk. We pass NO for |
| // showingPanel: so that the PDF file is saved to the standard location without user intervention. |
| CallUIDelegate([self _webView], @selector(webView:saveFrameView:showingPanel:), [[dataSource webFrame] frameView], NO); |
| } |
| |
| + (Class)_PDFViewClass |
| { |
| static Class PDFViewClass = nil; |
| if (PDFViewClass == nil) { |
| PDFViewClass = [[WebPDFView PDFKitBundle] classNamed:@"PDFView"]; |
| if (!PDFViewClass) |
| LOG_ERROR("Couldn't find PDFView class in PDFKit.framework"); |
| } |
| return PDFViewClass; |
| } |
| |
| + (Class)_PDFSelectionClass |
| { |
| static Class PDFSelectionClass = nil; |
| if (PDFSelectionClass == nil) { |
| PDFSelectionClass = [[WebPDFView PDFKitBundle] classNamed:@"PDFSelection"]; |
| if (!PDFSelectionClass) |
| LOG_ERROR("Couldn't find PDFSelectionClass class in PDFKit.framework"); |
| } |
| return PDFSelectionClass; |
| } |
| |
| - (void)_applyPDFDefaults |
| { |
| // Set up default viewing params |
| WebPreferences *prefs = [[dataSource _webView] preferences]; |
| float scaleFactor = [prefs PDFScaleFactor]; |
| if (scaleFactor == 0) |
| [PDFSubview setAutoScales:YES]; |
| else { |
| [PDFSubview setAutoScales:NO]; |
| [PDFSubview setScaleFactor:scaleFactor]; |
| } |
| [PDFSubview setDisplayMode:[prefs PDFDisplayMode]]; |
| } |
| |
| - (BOOL)_canLookUpInDictionary |
| { |
| IGNORE_WARNINGS_BEGIN("undeclared-selector") |
| return [PDFSubview respondsToSelector:@selector(_searchInDictionary:)]; |
| IGNORE_WARNINGS_END |
| } |
| |
| - (NSClipView *)_clipViewForPDFDocumentView |
| { |
| NSClipView *clipView = (NSClipView *)[[PDFSubview documentScrollView] contentView]; |
| ASSERT(clipView); |
| return clipView; |
| } |
| |
| - (NSEvent *)_fakeKeyEventWithFunctionKey:(unichar)functionKey |
| { |
| // FIXME 4400480: when PDFView implements the standard scrolling selectors that this |
| // method is used to mimic, we can eliminate this method and call them directly. |
| NSString *keyAsString = [NSString stringWithCharacters:&functionKey length:1]; |
| return [NSEvent keyEventWithType:NSEventTypeKeyDown |
| location:NSZeroPoint |
| modifierFlags:0 |
| timestamp:0 |
| windowNumber:0 |
| context:nil |
| characters:keyAsString |
| charactersIgnoringModifiers:keyAsString |
| isARepeat:NO |
| keyCode:0]; |
| } |
| |
| - (void)_lookUpInDictionaryFromMenu:(id)sender |
| { |
| // This method is used by WebKit's context menu item. Here we map to the method that |
| // PDFView uses. Since the PDFView method isn't API, and isn't available on all versions |
| // of PDFKit, we use performSelector after a respondsToSelector check, rather than calling it directly. |
| IGNORE_WARNINGS_BEGIN("undeclared-selector") |
| if ([self _canLookUpInDictionary]) |
| [PDFSubview performSelector:@selector(_searchInDictionary:) withObject:sender]; |
| IGNORE_WARNINGS_END |
| } |
| |
| static void removeUselessMenuItemSeparators(NSMutableArray *menuItems) |
| { |
| // Starting with a mutable array of NSMenuItems, removes any separators at the start, |
| // removes any separators at the end, and collapses any other adjacent separators to |
| // a single separator. |
| |
| // Start this with YES so very last item will be removed if it's a separator. |
| BOOL removePreviousItemIfSeparator = YES; |
| |
| for (NSInteger index = menuItems.count - 1; index >= 0; --index) { |
| NSMenuItem *item = [menuItems objectAtIndex:index]; |
| ASSERT([item isKindOfClass:[NSMenuItem class]]); |
| |
| BOOL itemIsSeparator = [item isSeparatorItem]; |
| if (itemIsSeparator && (removePreviousItemIfSeparator || !index)) |
| [menuItems removeObjectAtIndex:index]; |
| |
| removePreviousItemIfSeparator = itemIsSeparator; |
| } |
| |
| // This could leave us with one initial separator; kill it off too |
| if (menuItems.count && [[menuItems objectAtIndex:0] isSeparatorItem]) |
| [menuItems removeObjectAtIndex:0]; |
| } |
| |
| - (NSMutableArray *)_menuItemsFromPDFKitForEvent:(NSEvent *)theEvent |
| { |
| NSMutableArray *copiedItems = [NSMutableArray array]; |
| |
| IGNORE_WARNINGS_BEGIN("undeclared-selector") |
| auto actionsToTags = @{ |
| NSStringFromSelector(@selector(_setActualSize:)): @(WebMenuItemPDFActualSize), |
| NSStringFromSelector(@selector(zoomIn:)): @(WebMenuItemPDFZoomIn), |
| NSStringFromSelector(@selector(zoomOut:)): @(WebMenuItemPDFZoomOut), |
| NSStringFromSelector(@selector(_setAutoSize:)): @(WebMenuItemPDFAutoSize), |
| NSStringFromSelector(@selector(_setSinglePage:)): @(WebMenuItemPDFSinglePage), |
| NSStringFromSelector(@selector(_setSinglePageScrolling:)): @(WebMenuItemPDFSinglePageScrolling), |
| NSStringFromSelector(@selector(_setDoublePage:)): @(WebMenuItemPDFFacingPages), |
| NSStringFromSelector(@selector(_setDoublePageScrolling:)): @(WebMenuItemPDFFacingPagesScrolling), |
| NSStringFromSelector(@selector(_toggleContinuous:)): @(WebMenuItemPDFContinuous), |
| NSStringFromSelector(@selector(goToNextPage:)): @(WebMenuItemPDFNextPage), |
| NSStringFromSelector(@selector(goToPreviousPage:)): @(WebMenuItemPDFPreviousPage), |
| }; |
| IGNORE_WARNINGS_END |
| |
| // Leave these menu items out, since WebKit inserts equivalent ones. Note that we leave out PDFKit's "Look Up in Dictionary" |
| // item here because WebKit already includes an item with the same title and purpose. We map WebKit's to PDFKit's |
| // "Look Up in Dictionary" via the implementation of -[WebPDFView _lookUpInDictionaryFromMenu:]. |
| auto unwantedActions = adoptNS([[NSSet alloc] initWithObjects: |
| IGNORE_WARNINGS_BEGIN("undeclared-selector") |
| NSStringFromSelector(@selector(_searchInSpotlight:)), |
| NSStringFromSelector(@selector(_searchInGoogle:)), |
| NSStringFromSelector(@selector(_searchInDictionary:)), |
| IGNORE_WARNINGS_END |
| NSStringFromSelector(@selector(copy:)), |
| nil]); |
| |
| NSEnumerator *e = [[[PDFSubview menuForEvent:theEvent] itemArray] objectEnumerator]; |
| NSMenuItem *item; |
| while ((item = [e nextObject]) != nil) { |
| |
| NSString *actionString = NSStringFromSelector([item action]); |
| |
| if ([unwantedActions containsObject:actionString]) |
| continue; |
| |
| // Copy items since a menu item can be in only one menu at a time, and we don't |
| // want to modify the original menu supplied by PDFKit. |
| RetainPtr<NSMenuItem> itemCopy = adoptNS([item copy]); |
| [copiedItems addObject:itemCopy.get()]; |
| |
| // Include all of PDFKit's separators for now. At the end we'll remove any ones that were made |
| // useless by removing PDFKit's menu items. |
| if ([itemCopy isSeparatorItem]) |
| continue; |
| |
| NSNumber *tagNumber = [actionsToTags objectForKey:actionString]; |
| |
| int tag; |
| if (tagNumber != nil) |
| tag = [tagNumber intValue]; |
| else { |
| // This should happen only if PDFKit updates behind WebKit's back. It's non-ideal because clients that only include tags |
| // that they recognize (like Safari) won't get these PDFKit additions until WebKit is updated to match. |
| tag = WebMenuItemTagOther; |
| LOG_ERROR("no WebKit menu item tag found for PDF context menu item action \"%@\", using WebMenuItemTagOther", actionString); |
| } |
| |
| if ([itemCopy tag] == 0) { |
| [itemCopy setTag:tag]; |
| if ([itemCopy target] == PDFSubview) { |
| // Note that updating the defaults is cheap because it catches redundant settings, so installing |
| // the proxy for actions that don't impact the defaults is OK |
| [itemCopy setTarget:PDFSubviewProxy]; |
| } |
| } else |
| 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]); |
| } |
| |
| // Since we might have removed elements supplied by PDFKit, and we want to minimize our hardwired |
| // knowledge of the order and arrangement of PDFKit's menu items, we need to remove any bogus |
| // separators that were left behind. |
| removeUselessMenuItemSeparators(copiedItems); |
| |
| return copiedItems; |
| } |
| |
| - (PDFSelection *)_nextMatchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag fromSelection:(PDFSelection *)initialSelection startInSelection:(BOOL)startInSelection |
| { |
| if (![string length]) |
| return nil; |
| |
| int options = 0; |
| if (!forward) |
| options |= NSBackwardsSearch; |
| |
| if (!caseFlag) |
| options |= NSCaseInsensitiveSearch; |
| |
| PDFDocument *document = [PDFSubview document]; |
| |
| auto selectionForInitialSearch = adoptNS([initialSelection copy]); |
| if (startInSelection) { |
| // Initially we want to include the selected text in the search. PDFDocument's API always searches from just |
| // past the passed-in selection, so we need to pass a selection that's modified appropriately. |
| // FIXME 4182863: Ideally we'd use a zero-length selection at the edge of the current selection, but zero-length |
| // selections don't work in PDFDocument. So instead we make a one-length selection just before or after the |
| // current selection, which works for our purposes even when the current selection is at an edge of the |
| // document. |
| int initialSelectionLength = [[initialSelection string] length]; |
| if (forward) { |
| [selectionForInitialSearch extendSelectionAtStart:1]; |
| [selectionForInitialSearch extendSelectionAtEnd:-initialSelectionLength]; |
| } else { |
| [selectionForInitialSearch extendSelectionAtEnd:1]; |
| [selectionForInitialSearch extendSelectionAtStart:-initialSelectionLength]; |
| } |
| } |
| PDFSelection *foundSelection = [document findString:string fromSelection:selectionForInitialSearch.get() withOptions:options]; |
| |
| // If we first searched in the selection, and we found the selection, search again from just past the selection |
| if (startInSelection && _PDFSelectionsAreEqual(foundSelection, initialSelection)) |
| foundSelection = [document findString:string fromSelection:initialSelection withOptions:options]; |
| |
| if (!foundSelection && wrapFlag) { |
| auto emptySelection = adoptNS([[[[self class] _PDFViewClass] alloc] initWithDocument:document]); |
| foundSelection = [document findString:string fromSelection:emptySelection.get() withOptions:options]; |
| } |
| |
| return foundSelection; |
| } |
| |
| - (void)_openWithFinder:(id)sender |
| { |
| // We don't want to write the file until we have a document to write (see 4892525). |
| if (![PDFSubview document]) { |
| NSBeep(); |
| return; |
| } |
| |
| NSString *opath = [self _path]; |
| |
| if (opath) { |
| if (!written) { |
| // Create a PDF file with the minimal permissions (only accessible to the current user, see 4145714) |
| [[NSFileManager defaultManager] createFileAtPath:opath contents:[dataSource data] attributes:@{ NSFilePosixPermissions: @(S_IRUSR) }]; |
| written = YES; |
| } |
| |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| if (![[NSWorkspace sharedWorkspace] openFile:opath]) { |
| // NSWorkspace couldn't open file. Do we need an alert here? We ignore the error elsewhere. |
| } |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| } |
| } |
| |
| - (NSString *)_path |
| { |
| // Generate path once. |
| if (path) |
| return path; |
| |
| NSString *filename = [[dataSource response] suggestedFilename]; |
| NSFileManager *manager = [NSFileManager defaultManager]; |
| NSString *temporaryPDFDirectoryPath = [self _temporaryPDFDirectoryPath]; |
| |
| if (!temporaryPDFDirectoryPath) { |
| // This should never happen; if it does we'll fail silently on non-debug builds. |
| ASSERT_NOT_REACHED(); |
| return nil; |
| } |
| |
| path = [temporaryPDFDirectoryPath stringByAppendingPathComponent:filename]; |
| if ([manager fileExistsAtPath:path]) { |
| NSString *pathTemplatePrefix = [temporaryPDFDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"]; |
| NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:filename]; |
| // fileSystemRepresentation returns a const char *; copy it into a char * so we can modify it safely |
| char *cPath = strdup([pathTemplate fileSystemRepresentation]); |
| int fd = mkstemps(cPath, strlen(cPath) - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1); |
| if (fd < 0) { |
| // Couldn't create a temporary file! Should never happen; if it does we'll fail silently on non-debug builds. |
| ASSERT_NOT_REACHED(); |
| path = nil; |
| } else { |
| close(fd); |
| path = [manager stringWithFileSystemRepresentation:cPath length:strlen(cPath)]; |
| } |
| free(cPath); |
| } |
| |
| [path retain]; |
| |
| return path; |
| } |
| |
| - (void)_PDFDocumentViewMightHaveScrolled:(NSNotification *)notification |
| { |
| NSClipView *clipView = [self _clipViewForPDFDocumentView]; |
| ASSERT([notification object] == clipView); |
| |
| NSPoint scrollPosition = [clipView bounds].origin; |
| if (NSEqualPoints(scrollPosition, lastScrollPosition)) |
| return; |
| |
| lastScrollPosition = scrollPosition; |
| WebView *webView = [self _webView]; |
| [[webView _UIDelegateForwarder] webView:webView didScrollDocumentInFrameView:[[dataSource webFrame] frameView]]; |
| } |
| |
| - (PDFView *)_PDFSubview |
| { |
| return PDFSubview; |
| } |
| |
| - (BOOL)_pointIsInSelection:(NSPoint)point |
| { |
| PDFPage *page = [PDFSubview pageForPoint:point nearest:NO]; |
| return page && NSPointInRect(point, [PDFSubview convertRect:[[PDFSubview currentSelection] boundsForPage:page] fromPage:page]); |
| } |
| |
| - (void)_scaleOrDisplayModeOrPageChanged:(NSNotification *)notification |
| { |
| ASSERT([notification object] == PDFSubview); |
| if (!_ignoreScaleAndDisplayModeAndPageNotifications) { |
| [self _updatePreferencesSoon]; |
| // Notify UI delegate that the entire page has been redrawn, since (unlike for WebHTMLView) |
| // we can't hook into the drawing mechanism itself. This fixes 5337529. |
| WebView *webView = [self _webView]; |
| [[webView _UIDelegateForwarder] webView:webView didDrawRect:[webView bounds]]; |
| } |
| } |
| |
| - (NSAttributedString *)_scaledAttributedString:(NSAttributedString *)unscaledAttributedString |
| { |
| if (!unscaledAttributedString) |
| return nil; |
| |
| float scaleFactor = [PDFSubview scaleFactor]; |
| if (scaleFactor == 1.0) |
| return unscaledAttributedString; |
| |
| auto result = adoptNS([unscaledAttributedString mutableCopy]); |
| unsigned int length = [result length]; |
| NSRange effectiveRange = NSMakeRange(0,0); |
| |
| [result beginEditing]; |
| while (NSMaxRange(effectiveRange) < length) { |
| NSFont *unscaledFont = [result attribute:NSFontAttributeName atIndex:NSMaxRange(effectiveRange) effectiveRange:&effectiveRange]; |
| |
| if (!unscaledFont) { |
| // FIXME: We can't scale the font if we don't know what it is. We should always know what it is, |
| // but sometimes don't due to PDFKit issue 5089411. When that's addressed, we can remove this |
| // early continue. |
| LOG_ERROR("no font attribute found in range %@ for attributed string \"%@\" on page %@ (see radar 5089411)", NSStringFromRange(effectiveRange), result.get(), [[dataSource request] URL]); |
| continue; |
| } |
| |
| NSFont *scaledFont = [NSFont fontWithName:[unscaledFont fontName] size:[unscaledFont pointSize]*scaleFactor]; |
| [result addAttribute:NSFontAttributeName value:scaledFont range:effectiveRange]; |
| } |
| [result endEditing]; |
| |
| return result.autorelease(); |
| } |
| |
| - (void)_setTextMatches:(NSArray *)array |
| { |
| [array retain]; |
| [textMatches release]; |
| textMatches = array; |
| } |
| |
| - (NSString *)_temporaryPDFDirectoryPath |
| { |
| // Returns nil if the temporary PDF directory didn't exist and couldn't be created |
| |
| static NSString *_temporaryPDFDirectoryPath = nil; |
| |
| if (!_temporaryPDFDirectoryPath) { |
| NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPDFs-XXXXXX"]; |
| char *cTemplate = strdup([temporaryDirectoryTemplate fileSystemRepresentation]); |
| |
| if (!mkdtemp(cTemplate)) { |
| // This should never happen; if it does we'll fail silently on non-debug builds. |
| ASSERT_NOT_REACHED(); |
| } else { |
| // cTemplate has now been modified to be the just-created directory name. This directory has 700 permissions, |
| // so only the current user can add to it or view its contents. |
| _temporaryPDFDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:cTemplate length:strlen(cTemplate)] retain]; |
| } |
| |
| free(cTemplate); |
| } |
| |
| return _temporaryPDFDirectoryPath; |
| } |
| |
| - (void)_trackFirstResponder |
| { |
| ASSERT([self window]); |
| BOOL newFirstResponderIsPDFDocumentView = [[self window] firstResponder] == [PDFSubview documentView]; |
| if (newFirstResponderIsPDFDocumentView == firstResponderIsPDFDocumentView) |
| return; |
| |
| // This next clause is the entire purpose of _trackFirstResponder. In other WebDocument |
| // view classes this is done in a resignFirstResponder override, but in this case the |
| // first responder view is a PDFKit class that we can't subclass. |
| if (newFirstResponderIsPDFDocumentView && ![[dataSource _webView] maintainsInactiveSelection]) |
| [self deselectAll]; |
| |
| firstResponderIsPDFDocumentView = newFirstResponderIsPDFDocumentView; |
| } |
| |
| - (void)_updatePreferences:(WebPreferences *)prefs |
| { |
| float scaleFactor = [PDFSubview autoScales] ? 0.0f : [PDFSubview scaleFactor]; |
| [prefs setPDFScaleFactor:scaleFactor]; |
| [prefs setPDFDisplayMode:[PDFSubview displayMode]]; |
| _willUpdatePreferencesSoon = NO; |
| [prefs release]; |
| [self release]; |
| } |
| |
| - (void)_updatePreferencesSoon |
| { |
| // Consolidate calls; due to the WebPDFPrefUpdatingProxy method, this can be called multiple times with a single user action |
| // such as showing the context menu. |
| if (_willUpdatePreferencesSoon) |
| return; |
| |
| WebPreferences *prefs = [[dataSource _webView] preferences]; |
| |
| [self retain]; |
| [prefs retain]; |
| [self performSelector:@selector(_updatePreferences:) withObject:prefs afterDelay:0]; |
| _willUpdatePreferencesSoon = YES; |
| } |
| |
| - (NSSet *)_visiblePDFPages |
| { |
| // Returns the set of pages that are at least partly visible, used to avoid processing non-visible pages |
| PDFDocument *pdfDocument = [PDFSubview document]; |
| if (!pdfDocument) |
| return nil; |
| |
| NSRect pdfViewBounds = [PDFSubview bounds]; |
| PDFPage *topLeftPage = [PDFSubview pageForPoint:NSMakePoint(NSMinX(pdfViewBounds), NSMaxY(pdfViewBounds)) nearest:YES]; |
| PDFPage *bottomRightPage = [PDFSubview pageForPoint:NSMakePoint(NSMaxX(pdfViewBounds), NSMinY(pdfViewBounds)) nearest:YES]; |
| |
| // only page-free documents should return nil for either of these two since we passed YES for nearest: |
| if (!topLeftPage) { |
| ASSERT(!bottomRightPage); |
| return nil; |
| } |
| |
| NSUInteger firstVisiblePageIndex = [pdfDocument indexForPage:topLeftPage]; |
| NSUInteger lastVisiblePageIndex = [pdfDocument indexForPage:bottomRightPage]; |
| |
| if (firstVisiblePageIndex > lastVisiblePageIndex) { |
| NSUInteger swap = firstVisiblePageIndex; |
| firstVisiblePageIndex = lastVisiblePageIndex; |
| lastVisiblePageIndex = swap; |
| } |
| |
| NSMutableSet *result = [NSMutableSet set]; |
| NSUInteger pageIndex; |
| for (pageIndex = firstVisiblePageIndex; pageIndex <= lastVisiblePageIndex; ++pageIndex) |
| [result addObject:[pdfDocument pageAtIndex:pageIndex]]; |
| |
| return result; |
| } |
| |
| @end |
| |
| @implementation WebPDFPrefUpdatingProxy |
| |
| - (id)initWithView:(WebPDFView *)aView |
| { |
| // No [super init], since we inherit from NSProxy |
| view = aView; |
| return self; |
| } |
| |
| - (void)forwardInvocation:(NSInvocation *)invocation |
| { |
| [invocation invokeWithTarget:[view _PDFSubview]]; |
| [view _updatePreferencesSoon]; |
| } |
| |
| - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel |
| { |
| return [[view _PDFSubview] methodSignatureForSelector:sel]; |
| } |
| |
| @end |
| |
| #endif // PLATFORM(MAC) |