blob: e9989ec893fad6f01d05863e9ebc3ece0b972229 [file] [log] [blame]
/*
* Copyright (C) 2009, 2011, 2012 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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 "config.h"
#import "PDFPlugin.h"
#if ENABLE(PDFKIT_PLUGIN)
#import "ArgumentCoders.h"
#import "AttributedString.h"
#import "DataReference.h"
#import "DictionaryPopupInfo.h"
#import "PDFAnnotationTextWidgetDetails.h"
#import "PDFKitImports.h"
#import "PDFLayerControllerDetails.h"
#import "PDFPluginAnnotation.h"
#import "PDFPluginPasswordField.h"
#import "PluginView.h"
#import "WebContextMessages.h"
#import "WebCoreArgumentCoders.h"
#import "WebEvent.h"
#import "WebEventConversion.h"
#import "WebPage.h"
#import "WebPageProxyMessages.h"
#import "WebProcess.h"
#import <PDFKit/PDFKit.h>
#import <QuartzCore/QuartzCore.h>
#import <WebCore/Cursor.h>
#import <WebCore/FocusController.h>
#import <WebCore/FormState.h>
#import <WebCore/Frame.h>
#import <WebCore/FrameLoader.h>
#import <WebCore/FrameView.h>
#import <WebCore/GraphicsContext.h>
#import <WebCore/HTMLElement.h>
#import <WebCore/HTMLFormElement.h>
#import <WebCore/LocalizedStrings.h>
#import <WebCore/MouseEvent.h>
#import <WebCore/Page.h>
#import <WebCore/Pasteboard.h>
#import <WebCore/PluginDocument.h>
#import <WebCore/ScrollbarTheme.h>
#import <WebCore/UUID.h>
#import <WebKitSystemInterface.h>
using namespace WebCore;
// Set overflow: hidden on the annotation container so <input> elements scrolled out of view don't show
// scrollbars on the body. We can't add annotations directly to the body, because overflow: hidden on the body
// will break rubber-banding.
static const char* annotationStyle =
"#annotationContainer {"
" overflow: hidden; "
" position: absolute; "
" pointer-events: none; "
" top: 0; "
" left: 0; "
" right: 0; "
" bottom: 0; "
" display: -webkit-box; "
" -webkit-box-align: center; "
" -webkit-box-pack: center; "
"} "
".annotation { "
" position: absolute; "
" pointer-events: auto; "
"} "
"textarea.annotation { "
" resize: none; "
"} "
"input.annotation[type='password'] { "
" position: static; "
" width: 200px; "
" margin-top: 100px; "
"} ";
// In non-continuous modes, a single scroll event with a magnitude of >= 20px
// will jump to the next or previous page, to match PDFKit behavior.
static const int defaultScrollMagnitudeThresholdForPageFlip = 20;
@interface WKPDFPluginScrollbarLayer : CALayer
{
WebKit::PDFPlugin* _pdfPlugin;
}
@property(assign) WebKit::PDFPlugin* pdfPlugin;
@end
@implementation WKPDFPluginScrollbarLayer
@synthesize pdfPlugin=_pdfPlugin;
- (id)initWithPDFPlugin:(WebKit::PDFPlugin *)plugin
{
if (!(self = [super init]))
return nil;
_pdfPlugin = plugin;
return self;
}
- (id<CAAction>)actionForKey:(NSString *)key
{
return nil;
}
- (void)drawInContext:(CGContextRef)ctx
{
_pdfPlugin->paintControlForLayerInContext(self, ctx);
}
@end
@interface WKPDFLayerControllerDelegate : NSObject<PDFLayerControllerDelegate>
{
WebKit::PDFPlugin* _pdfPlugin;
}
@property(assign) WebKit::PDFPlugin* pdfPlugin;
@end
@implementation WKPDFLayerControllerDelegate
@synthesize pdfPlugin=_pdfPlugin;
- (id)initWithPDFPlugin:(WebKit::PDFPlugin *)plugin
{
if (!(self = [super init]))
return nil;
_pdfPlugin = plugin;
return self;
}
- (void)updateScrollPosition:(CGPoint)newPosition
{
_pdfPlugin->notifyScrollPositionChanged(IntPoint(newPosition));
}
- (void)writeItemsToPasteboard:(NSArray *)items withTypes:(NSArray *)types
{
_pdfPlugin->writeItemsToPasteboard(items, types);
}
- (void)showDefinitionForAttributedString:(NSAttributedString *)string atPoint:(CGPoint)point
{
_pdfPlugin->showDefinitionForAttributedString(string, point);
}
- (void)performWebSearch:(NSString *)string
{
_pdfPlugin->performWebSearch(string);
}
- (void)performSpotlightSearch:(NSString *)string
{
_pdfPlugin->performSpotlightSearch(string);
}
- (void)openWithNativeApplication
{
_pdfPlugin->openWithNativeApplication();
}
- (void)saveToPDF
{
_pdfPlugin->saveToPDF();
}
- (void)pdfLayerController:(PDFLayerController *)pdfLayerController clickedLinkWithURL:(NSURL *)url
{
_pdfPlugin->clickedLink(url);
}
- (void)pdfLayerController:(PDFLayerController *)pdfLayerController didChangeActiveAnnotation:(PDFAnnotation *)annotation
{
_pdfPlugin->setActiveAnnotation(annotation);
}
- (void)pdfLayerController:(PDFLayerController *)pdfLayerController didChangeContentScaleFactor:(CGFloat)scaleFactor
{
_pdfPlugin->notifyContentScaleFactorChanged(scaleFactor);
}
- (void)pdfLayerController:(PDFLayerController *)pdfLayerController didChangeDisplayMode:(int)mode
{
_pdfPlugin->notifyDisplayModeChanged(mode);
}
- (void)pdfLayerController:(PDFLayerController *)pdfLayerController didChangeSelection:(PDFSelection *)selection
{
_pdfPlugin->notifySelectionChanged(selection);
}
@end
namespace WebKit {
using namespace HTMLNames;
PassRefPtr<PDFPlugin> PDFPlugin::create(WebFrame* frame)
{
return adoptRef(new PDFPlugin(frame));
}
PDFPlugin::PDFPlugin(WebFrame* frame)
: SimplePDFPlugin(frame)
, m_containerLayer(adoptNS([[CALayer alloc] init]))
, m_contentLayer(adoptNS([[CALayer alloc] init]))
, m_scrollCornerLayer(adoptNS([[WKPDFPluginScrollbarLayer alloc] initWithPDFPlugin:this]))
, m_pdfLayerController(adoptNS([[pdfLayerControllerClass() alloc] init]))
, m_pdfLayerControllerDelegate(adoptNS([[WKPDFLayerControllerDelegate alloc] initWithPDFPlugin:this]))
{
m_pdfLayerController.get().delegate = m_pdfLayerControllerDelegate.get();
m_pdfLayerController.get().parentLayer = m_contentLayer.get();
if (supportsForms()) {
Document* document = webFrame()->coreFrame()->document();
m_annotationContainer = document->createElement(divTag, false);
m_annotationContainer->setAttribute(idAttr, "annotationContainer");
RefPtr<Element> m_annotationStyle = document->createElement(styleTag, false);
m_annotationStyle->setTextContent(annotationStyle, ASSERT_NO_EXCEPTION);
m_annotationContainer->appendChild(m_annotationStyle.get());
document->body()->appendChild(m_annotationContainer.get());
}
[m_containerLayer.get() addSublayer:m_contentLayer.get()];
[m_containerLayer.get() addSublayer:m_scrollCornerLayer.get()];
}
PDFPlugin::~PDFPlugin()
{
}
void PDFPlugin::updateScrollbars()
{
SimplePDFPlugin::updateScrollbars();
if (m_verticalScrollbarLayer) {
m_verticalScrollbarLayer.get().frame = verticalScrollbar()->frameRect();
[m_verticalScrollbarLayer.get() setNeedsDisplay];
}
if (m_horizontalScrollbarLayer) {
m_horizontalScrollbarLayer.get().frame = horizontalScrollbar()->frameRect();
[m_horizontalScrollbarLayer.get() setNeedsDisplay];
}
if (m_scrollCornerLayer) {
m_scrollCornerLayer.get().frame = scrollCornerRect();
[m_scrollCornerLayer.get() setNeedsDisplay];
}
}
PassRefPtr<Scrollbar> PDFPlugin::createScrollbar(ScrollbarOrientation orientation)
{
RefPtr<Scrollbar> widget = Scrollbar::createNativeScrollbar(this, orientation, RegularScrollbar);
if (orientation == HorizontalScrollbar) {
m_horizontalScrollbarLayer = adoptNS([[WKPDFPluginScrollbarLayer alloc] initWithPDFPlugin:this]);
[m_containerLayer.get() addSublayer:m_horizontalScrollbarLayer.get()];
} else {
m_verticalScrollbarLayer = adoptNS([[WKPDFPluginScrollbarLayer alloc] initWithPDFPlugin:this]);
[m_containerLayer.get() addSublayer:m_verticalScrollbarLayer.get()];
}
didAddScrollbar(widget.get(), orientation);
pluginView()->frame()->view()->addChild(widget.get());
return widget.release();
}
void PDFPlugin::destroyScrollbar(ScrollbarOrientation orientation)
{
SimplePDFPlugin::destroyScrollbar(orientation);
if (orientation == HorizontalScrollbar) {
[m_horizontalScrollbarLayer.get() removeFromSuperlayer];
m_horizontalScrollbarLayer = 0;
} else {
[m_verticalScrollbarLayer.get() removeFromSuperlayer];
m_verticalScrollbarLayer = 0;
}
}
void PDFPlugin::pdfDocumentDidLoad()
{
addArchiveResource();
RetainPtr<PDFDocument> document = adoptNS([[pdfDocumentClass() alloc] initWithData:rawData()]);
setPDFDocument(document);
updatePageAndDeviceScaleFactors();
[m_pdfLayerController.get() setFrameSize:size()];
m_pdfLayerController.get().document = document.get();
if (handlesPageScaleFactor())
pluginView()->setPageScaleFactor([m_pdfLayerController.get() contentScaleFactor], IntPoint());
notifyScrollPositionChanged(IntPoint([m_pdfLayerController.get() scrollPosition]));
calculateSizes();
updateScrollbars();
runScriptsInPDFDocument();
if ([document.get() isLocked])
createPasswordEntryForm();
}
void PDFPlugin::createPasswordEntryForm()
{
m_passwordField = PDFPluginPasswordField::create(m_pdfLayerController.get(), this);
m_passwordField->attach(m_annotationContainer.get());
}
void PDFPlugin::attemptToUnlockPDF(const String& password)
{
[m_pdfLayerController attemptToUnlockWithPassword:password];
if (![pdfDocument() isLocked]) {
m_passwordField = nullptr;
calculateSizes();
updateScrollbars();
}
}
void PDFPlugin::updatePageAndDeviceScaleFactors()
{
double newScaleFactor = controller()->contentsScaleFactor();
if (!handlesPageScaleFactor())
newScaleFactor *= webFrame()->page()->pageScaleFactor();
[m_pdfLayerController.get() setDeviceScaleFactor:newScaleFactor];
}
void PDFPlugin::contentsScaleFactorChanged(float)
{
updatePageAndDeviceScaleFactors();
}
void PDFPlugin::calculateSizes()
{
if ([pdfDocument() isLocked]) {
setPDFDocumentSize(IntSize(0, 0));
return;
}
// FIXME: This should come straight from PDFKit.
computePageBoxes();
setPDFDocumentSize(IntSize([m_pdfLayerController.get() contentSizeRespectingZoom]));
}
void PDFPlugin::destroy()
{
m_pdfLayerController.get().delegate = 0;
if (webFrame()) {
if (FrameView* frameView = webFrame()->coreFrame()->view())
frameView->removeScrollableArea(this);
}
m_activeAnnotation = 0;
m_annotationContainer = 0;
destroyScrollbar(HorizontalScrollbar);
destroyScrollbar(VerticalScrollbar);
[m_scrollCornerLayer.get() removeFromSuperlayer];
[m_contentLayer.get() removeFromSuperlayer];
}
void PDFPlugin::paint(GraphicsContext* graphicsContext, const IntRect& dirtyRect)
{
}
void PDFPlugin::paintControlForLayerInContext(CALayer *layer, CGContextRef context)
{
GraphicsContext graphicsContext(context);
GraphicsContextStateSaver stateSaver(graphicsContext);
graphicsContext.setIsCALayerContext(true);
if (layer == m_scrollCornerLayer) {
IntRect scrollCornerRect = this->scrollCornerRect();
graphicsContext.translate(-scrollCornerRect.x(), -scrollCornerRect.y());
ScrollbarTheme::theme()->paintScrollCorner(0, &graphicsContext, scrollCornerRect);
return;
}
Scrollbar* scrollbar = 0;
if (layer == m_verticalScrollbarLayer)
scrollbar = verticalScrollbar();
else if (layer == m_horizontalScrollbarLayer)
scrollbar = horizontalScrollbar();
if (!scrollbar)
return;
graphicsContext.translate(-scrollbar->x(), -scrollbar->y());
scrollbar->paint(&graphicsContext, scrollbar->frameRect());
}
PassRefPtr<ShareableBitmap> PDFPlugin::snapshot()
{
if (size().isEmpty())
return 0;
float contentsScaleFactor = controller()->contentsScaleFactor();
IntSize backingStoreSize = size();
backingStoreSize.scale(contentsScaleFactor);
RefPtr<ShareableBitmap> bitmap = ShareableBitmap::createShareable(backingStoreSize, ShareableBitmap::SupportsAlpha);
OwnPtr<GraphicsContext> context = bitmap->createGraphicsContext();
context->scale(FloatSize(contentsScaleFactor, -contentsScaleFactor));
context->translate(0, -size().height());
[m_pdfLayerController.get() snapshotInContext:context->platformContext()];
return bitmap.release();
}
PlatformLayer* PDFPlugin::pluginLayer()
{
return m_containerLayer.get();
}
IntPoint PDFPlugin::convertFromRootViewToPlugin(const IntPoint& point) const
{
return m_rootViewToPluginTransform.mapPoint(point);
}
IntPoint PDFPlugin::convertFromPluginToPDFView(const IntPoint& point) const
{
return IntPoint(point.x(), size().height() - point.y());
}
void PDFPlugin::geometryDidChange(const IntSize& pluginSize, const IntRect&, const AffineTransform& pluginToRootViewTransform)
{
if (size() == pluginSize && pluginView()->pageScaleFactor() == [m_pdfLayerController.get() contentScaleFactor])
return;
setSize(pluginSize);
m_rootViewToPluginTransform = pluginToRootViewTransform.inverse();
[m_pdfLayerController.get() setFrameSize:pluginSize];
[CATransaction begin];
[CATransaction setDisableActions:YES];
CATransform3D transform = CATransform3DMakeScale(1, -1, 1);
transform = CATransform3DTranslate(transform, 0, -pluginSize.height(), 0);
if (handlesPageScaleFactor()) {
CGFloat magnification = pluginView()->pageScaleFactor() - [m_pdfLayerController.get() contentScaleFactor];
// FIXME: Instead of m_lastMousePositionInPluginCoordinates, we should use the zoom origin from PluginView::setPageScaleFactor.
if (magnification)
[m_pdfLayerController.get() magnifyWithMagnification:magnification atPoint:convertFromPluginToPDFView(m_lastMousePositionInPluginCoordinates) immediately:NO];
} else {
// If we don't handle page scale ourselves, we need to respect our parent page's
// scale, which may have changed.
updatePageAndDeviceScaleFactors();
}
calculateSizes();
updateScrollbars();
if (m_activeAnnotation)
m_activeAnnotation->updateGeometry();
[m_contentLayer.get() setSublayerTransform:transform];
[CATransaction commit];
}
static NSUInteger modifierFlagsFromWebEvent(const WebEvent& event)
{
return (event.shiftKey() ? NSShiftKeyMask : 0)
| (event.controlKey() ? NSControlKeyMask : 0)
| (event.altKey() ? NSAlternateKeyMask : 0)
| (event.metaKey() ? NSCommandKeyMask : 0);
}
static NSEventType eventTypeFromWebEvent(const WebEvent& event)
{
switch (event.type()) {
case WebEvent::KeyDown:
return NSKeyDown;
case WebEvent::KeyUp:
return NSKeyUp;
case WebEvent::MouseDown:
switch (static_cast<const WebMouseEvent&>(event).button()) {
case WebMouseEvent::LeftButton:
return NSLeftMouseDown;
case WebMouseEvent::RightButton:
return NSRightMouseDown;
default:
return 0;
}
break;
case WebEvent::MouseUp:
switch (static_cast<const WebMouseEvent&>(event).button()) {
case WebMouseEvent::LeftButton:
return NSLeftMouseUp;
case WebMouseEvent::RightButton:
return NSRightMouseUp;
default:
return 0;
}
break;
case WebEvent::MouseMove:
switch (static_cast<const WebMouseEvent&>(event).button()) {
case WebMouseEvent::LeftButton:
return NSLeftMouseDragged;
case WebMouseEvent::RightButton:
return NSRightMouseDragged;
case WebMouseEvent::NoButton:
return NSMouseMoved;
default:
return 0;
}
break;
default:
return 0;
}
}
NSEvent *PDFPlugin::nsEventForWebMouseEvent(const WebMouseEvent& event)
{
m_lastMousePositionInPluginCoordinates = convertFromRootViewToPlugin(event.position());
IntPoint positionInPDFViewCoordinates(convertFromPluginToPDFView(m_lastMousePositionInPluginCoordinates));
NSEventType eventType = eventTypeFromWebEvent(event);
if (!eventType)
return 0;
NSUInteger modifierFlags = modifierFlagsFromWebEvent(event);
return [NSEvent mouseEventWithType:eventType location:positionInPDFViewCoordinates modifierFlags:modifierFlags timestamp:0 windowNumber:0 context:nil eventNumber:0 clickCount:event.clickCount() pressure:0];
}
void PDFPlugin::updateCursor(const WebMouseEvent& event, UpdateCursorMode mode)
{
HitTestResult hitTestResult = None;
PDFSelection *selectionUnderMouse = [m_pdfLayerController.get() getSelectionForWordAtPoint:convertFromPluginToPDFView(event.position())];
if (selectionUnderMouse && [[selectionUnderMouse string] length])
hitTestResult = Text;
if (hitTestResult == m_lastHitTestResult && mode == UpdateIfNeeded)
return;
webFrame()->page()->send(Messages::WebPageProxy::SetCursor(hitTestResult == Text ? iBeamCursor() : pointerCursor()));
m_lastHitTestResult = hitTestResult;
}
bool PDFPlugin::handleMouseEvent(const WebMouseEvent& event)
{
PlatformMouseEvent platformEvent = platform(event);
IntPoint mousePosition = convertFromRootViewToPlugin(event.position());
m_lastMouseEvent = event;
RefPtr<Scrollbar> targetScrollbar;
RefPtr<Scrollbar> targetScrollbarForLastMousePosition;
if (m_verticalScrollbarLayer) {
IntRect verticalScrollbarFrame(m_verticalScrollbarLayer.get().frame);
if (verticalScrollbarFrame.contains(mousePosition))
targetScrollbar = verticalScrollbar();
if (verticalScrollbarFrame.contains(m_lastMousePositionInPluginCoordinates))
targetScrollbarForLastMousePosition = verticalScrollbar();
}
if (m_horizontalScrollbarLayer) {
IntRect horizontalScrollbarFrame(m_horizontalScrollbarLayer.get().frame);
if (horizontalScrollbarFrame.contains(mousePosition))
targetScrollbar = horizontalScrollbar();
if (horizontalScrollbarFrame.contains(m_lastMousePositionInPluginCoordinates))
targetScrollbarForLastMousePosition = horizontalScrollbar();
}
if (m_scrollCornerLayer && IntRect(m_scrollCornerLayer.get().frame).contains(mousePosition))
return false;
if ([pdfDocument() isLocked])
return false;
// Right-clicks and Control-clicks always call handleContextMenuEvent as well.
if (event.button() == WebMouseEvent::RightButton || (event.button() == WebMouseEvent::LeftButton && event.controlKey()))
return true;
NSEvent *nsEvent = nsEventForWebMouseEvent(event);
switch (event.type()) {
case WebEvent::MouseMove:
mouseMovedInContentArea();
updateCursor(event);
if (targetScrollbar) {
if (!targetScrollbarForLastMousePosition) {
targetScrollbar->mouseEntered();
return true;
}
return targetScrollbar->mouseMoved(platformEvent);
}
if (!targetScrollbar && targetScrollbarForLastMousePosition)
targetScrollbarForLastMousePosition->mouseExited();
switch (event.button()) {
case WebMouseEvent::LeftButton:
[m_pdfLayerController.get() mouseDragged:nsEvent];
return true;
case WebMouseEvent::RightButton:
case WebMouseEvent::MiddleButton:
return false;
case WebMouseEvent::NoButton:
[m_pdfLayerController.get() mouseMoved:nsEvent];
return true;
}
case WebEvent::MouseDown:
switch (event.button()) {
case WebMouseEvent::LeftButton:
if (targetScrollbar)
return targetScrollbar->mouseDown(platformEvent);
[m_pdfLayerController.get() mouseDown:nsEvent];
return true;
case WebMouseEvent::RightButton:
[m_pdfLayerController.get() rightMouseDown:nsEvent];
return true;
case WebMouseEvent::MiddleButton:
case WebMouseEvent::NoButton:
return false;
}
case WebEvent::MouseUp:
switch (event.button()) {
case WebMouseEvent::LeftButton:
if (targetScrollbar)
return targetScrollbar->mouseUp(platformEvent);
[m_pdfLayerController.get() mouseUp:nsEvent];
return true;
case WebMouseEvent::RightButton:
case WebMouseEvent::MiddleButton:
case WebMouseEvent::NoButton:
return false;
}
default:
break;
}
return false;
}
bool PDFPlugin::handleMouseEnterEvent(const WebMouseEvent& event)
{
mouseEnteredContentArea();
updateCursor(event, ForceUpdate);
return false;
}
bool PDFPlugin::handleMouseLeaveEvent(const WebMouseEvent&)
{
mouseExitedContentArea();
return false;
}
bool PDFPlugin::handleContextMenuEvent(const WebMouseEvent& event)
{
NSMenu *nsMenu = [m_pdfLayerController.get() menuForEvent:nsEventForWebMouseEvent(event)];
FrameView* frameView = webFrame()->coreFrame()->view();
IntPoint point = frameView->contentsToScreen(IntRect(frameView->windowToContents(event.position()), IntSize())).location();
if (nsMenu) {
WKPopupContextMenu(nsMenu, point);
return true;
}
return false;
}
bool PDFPlugin::handleKeyboardEvent(const WebKeyboardEvent& event)
{
NSEventType eventType = eventTypeFromWebEvent(event);
NSUInteger modifierFlags = modifierFlagsFromWebEvent(event);
NSEvent *fakeEvent = [NSEvent keyEventWithType:eventType location:NSZeroPoint modifierFlags:modifierFlags timestamp:0 windowNumber:0 context:0 characters:event.text() charactersIgnoringModifiers:event.unmodifiedText() isARepeat:event.isAutoRepeat() keyCode:event.nativeVirtualKeyCode()];
switch (event.type()) {
case WebEvent::KeyDown:
return [m_pdfLayerController.get() keyDown:fakeEvent];
default:
return false;
}
return false;
}
bool PDFPlugin::handleEditingCommand(const String& commandName, const String& argument)
{
if (commandName == "copy")
[m_pdfLayerController.get() copySelection];
else if (commandName == "selectAll")
[m_pdfLayerController.get() selectAll];
return true;
}
bool PDFPlugin::isEditingCommandEnabled(const String& commandName)
{
if (commandName == "copy")
return [m_pdfLayerController.get() currentSelection];
if (commandName == "selectAll")
return true;
return false;
}
void PDFPlugin::setScrollOffset(const IntPoint& offset)
{
m_scrollOffset = IntSize(offset.x(), offset.y());
[CATransaction begin];
[m_pdfLayerController.get() setScrollPosition:offset];
if (m_activeAnnotation)
m_activeAnnotation->updateGeometry();
[CATransaction commit];
}
void PDFPlugin::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
{
if (scrollbar == horizontalScrollbar())
[m_horizontalScrollbarLayer.get() setNeedsDisplay];
else if (scrollbar == verticalScrollbar())
[m_verticalScrollbarLayer.get() setNeedsDisplay];
}
void PDFPlugin::invalidateScrollCornerRect(const IntRect& rect)
{
[m_scrollCornerLayer.get() setNeedsDisplay];
}
bool PDFPlugin::isFullFramePlugin()
{
// <object> or <embed> plugins will appear to be in their parent frame, so we have to
// check whether our frame's widget is exactly our PluginView.
Document* document = webFrame()->coreFrame()->document();
return document->isPluginDocument() && static_cast<PluginDocument*>(document)->pluginWidget() == pluginView();
}
bool PDFPlugin::handlesPageScaleFactor()
{
return webFrame()->isMainFrame() && isFullFramePlugin();
}
void PDFPlugin::clickedLink(NSURL *url)
{
Frame* frame = webFrame()->coreFrame();
RefPtr<Event> coreEvent;
if (m_lastMouseEvent.type() != WebEvent::NoType)
coreEvent = MouseEvent::create(eventNames().clickEvent, frame->document()->defaultView(), platform(m_lastMouseEvent), 0, 0);
frame->loader()->urlSelected(url, emptyString(), coreEvent.get(), false, false, MaybeSendReferrer);
}
void PDFPlugin::setActiveAnnotation(PDFAnnotation *annotation)
{
if (!supportsForms())
return;
if (m_activeAnnotation)
m_activeAnnotation->commit();
if (annotation) {
if ([annotation isKindOfClass:pdfAnnotationTextWidgetClass()] && static_cast<PDFAnnotationTextWidget *>(annotation).isReadOnly) {
m_activeAnnotation = 0;
return;
}
m_activeAnnotation = PDFPluginAnnotation::create(annotation, m_pdfLayerController.get(), this);
m_activeAnnotation->attach(m_annotationContainer.get());
} else
m_activeAnnotation = 0;
}
bool PDFPlugin::supportsForms()
{
// FIXME: We support forms for full-main-frame and <iframe> PDFs, but not <embed> or <object>, because those cases do not have their own Document into which to inject form elements.
return isFullFramePlugin();
}
void PDFPlugin::notifyContentScaleFactorChanged(CGFloat scaleFactor)
{
if (handlesPageScaleFactor())
pluginView()->setPageScaleFactor(scaleFactor, IntPoint());
calculateSizes();
updateScrollbars();
}
void PDFPlugin::notifyDisplayModeChanged(int)
{
calculateSizes();
updateScrollbars();
}
void PDFPlugin::saveToPDF()
{
// FIXME: We should probably notify the user that they can't save before the document is finished loading.
// PDFViewController does an NSBeep(), but that seems insufficient.
if (!pdfDocument())
return;
NSData *data = liveData();
webFrame()->page()->savePDFToFileInDownloadsFolder(suggestedFilename(), webFrame()->url(), static_cast<const unsigned char *>([data bytes]), [data length]);
}
void PDFPlugin::openWithNativeApplication()
{
if (!m_temporaryPDFUUID) {
// FIXME: We should probably notify the user that they can't save before the document is finished loading.
// PDFViewController does an NSBeep(), but that seems insufficient.
if (!pdfDocument())
return;
NSData *data = liveData();
m_temporaryPDFUUID = WebCore::createCanonicalUUIDString();
ASSERT(m_temporaryPDFUUID);
webFrame()->page()->savePDFToTemporaryFolderAndOpenWithNativeApplication(suggestedFilename(), webFrame()->url(), static_cast<const unsigned char *>([data bytes]), [data length], m_temporaryPDFUUID);
return;
}
webFrame()->page()->send(Messages::WebPageProxy::OpenPDFFromTemporaryFolderWithNativeApplication(m_temporaryPDFUUID));
}
void PDFPlugin::writeItemsToPasteboard(NSArray *items, NSArray *types)
{
Vector<String> pasteboardTypes;
for (NSString *type in types)
pasteboardTypes.append(type);
WebProcess::shared().connection()->send(Messages::WebContext::SetPasteboardTypes(NSGeneralPboard, pasteboardTypes), 0);
for (NSUInteger i = 0, count = items.count; i < count; ++i) {
NSString *type = [types objectAtIndex:i];
NSData *data = [items objectAtIndex:i];
// We don't expect the data for any items to be empty, but aren't completely sure.
// Avoid crashing in the SharedMemory constructor in release builds if we're wrong.
ASSERT(data.length);
if (!data.length)
continue;
if ([type isEqualToString:NSStringPboardType] || [type isEqualToString:NSPasteboardTypeString]) {
RetainPtr<NSString> plainTextString = adoptNS([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
WebProcess::shared().connection()->send(Messages::WebContext::SetPasteboardStringForType(NSGeneralPboard, type, plainTextString.get()), 0);
} else {
RefPtr<SharedBuffer> buffer = SharedBuffer::wrapNSData(data);
if (!buffer)
continue;
SharedMemory::Handle handle;
RefPtr<SharedMemory> sharedMemory = SharedMemory::create(buffer->size());
memcpy(sharedMemory->data(), buffer->data(), buffer->size());
sharedMemory->createHandle(handle, SharedMemory::ReadOnly);
WebProcess::shared().connection()->send(Messages::WebContext::SetPasteboardBufferForType(NSGeneralPboard, type, handle, buffer->size()), 0);
}
}
}
IntPoint PDFPlugin::convertFromPDFViewToRootView(const IntPoint& point) const
{
IntPoint pointInPluginCoordinates(point.x(), size().height() - point.y());
return m_rootViewToPluginTransform.inverse().mapPoint(pointInPluginCoordinates);
}
void PDFPlugin::showDefinitionForAttributedString(NSAttributedString *string, CGPoint point)
{
DictionaryPopupInfo dictionaryPopupInfo;
dictionaryPopupInfo.origin = convertFromPDFViewToRootView(IntPoint(point));
AttributedString attributedString;
attributedString.string = string;
webFrame()->page()->send(Messages::WebPageProxy::DidPerformDictionaryLookup(attributedString, dictionaryPopupInfo));
}
unsigned PDFPlugin::countFindMatches(const String& target, WebCore::FindOptions options, unsigned maxMatchCount)
{
if (!target.length())
return 0;
int nsOptions = (options & FindOptionsCaseInsensitive) ? NSCaseInsensitiveSearch : 0;
return [[pdfDocument().get() findString:target withOptions:nsOptions] count];
}
PDFSelection *PDFPlugin::nextMatchForString(const String& target, BOOL searchForward, BOOL caseSensitive, BOOL wrapSearch, PDFSelection *initialSelection, BOOL startInSelection)
{
if (!target.length())
return nil;
NSStringCompareOptions options = 0;
if (!searchForward)
options |= NSBackwardsSearch;
if (!caseSensitive)
options |= NSCaseInsensitiveSearch;
PDFDocument *document = pdfDocument().get();
PDFSelection *selectionForInitialSearch = [initialSelection copy];
if (startInSelection) {
// Initially we want to include the selected text in the search. So we must modify the starting search
// selection to fit PDFDocument's search requirements: selection must have a length >= 1, begin before
// the current selection (if searching forwards) or after (if searching backwards).
int initialSelectionLength = [[initialSelection string] length];
if (searchForward) {
[selectionForInitialSearch extendSelectionAtStart:1];
[selectionForInitialSearch extendSelectionAtEnd:-initialSelectionLength];
} else {
[selectionForInitialSearch extendSelectionAtEnd:1];
[selectionForInitialSearch extendSelectionAtStart:-initialSelectionLength];
}
}
PDFSelection *foundSelection = [document findString:target fromSelection:selectionForInitialSearch withOptions:options];
[selectionForInitialSearch release];
// If we first searched in the selection, and we found the selection, search again from just past the selection.
if (startInSelection && [foundSelection isEqual:initialSelection])
foundSelection = [document findString:target fromSelection:initialSelection withOptions:options];
if (!foundSelection && wrapSearch)
foundSelection = [document findString:target fromSelection:nil withOptions:options];
return foundSelection;
}
bool PDFPlugin::findString(const String& target, WebCore::FindOptions options, unsigned maxMatchCount)
{
BOOL searchForward = !(options & FindOptionsBackwards);
BOOL caseSensitive = !(options & FindOptionsCaseInsensitive);
BOOL wrapSearch = options & FindOptionsWrapAround;
unsigned matchCount;
if (!maxMatchCount) {
// If the max was zero, any result means we exceeded the max. We can skip computing the actual count.
matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
} else {
matchCount = countFindMatches(target, options, maxMatchCount);
if (matchCount > maxMatchCount)
matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
}
if (target.isEmpty()) {
PDFSelection* searchSelection = [m_pdfLayerController.get() searchSelection];
[m_pdfLayerController.get() findString:target caseSensitive:caseSensitive highlightMatches:YES];
[m_pdfLayerController.get() setSearchSelection:searchSelection];
m_lastFoundString = emptyString();
return false;
}
if (m_lastFoundString == target) {
PDFSelection *selection = nextMatchForString(target, searchForward, caseSensitive, wrapSearch, [m_pdfLayerController.get() searchSelection], NO);
if (!selection)
return false;
[m_pdfLayerController.get() setSearchSelection:selection];
[m_pdfLayerController.get() gotoSelection:selection];
} else {
[m_pdfLayerController.get() findString:target caseSensitive:caseSensitive highlightMatches:YES];
m_lastFoundString = target;
}
return matchCount > 0;
}
bool PDFPlugin::performDictionaryLookupAtLocation(const WebCore::FloatPoint& point)
{
PDFSelection* lookupSelection = [m_pdfLayerController.get() getSelectionForWordAtPoint:convertFromPluginToPDFView(roundedIntPoint(point))];
if ([[lookupSelection string] length])
[m_pdfLayerController.get() searchInDictionaryWithSelection:lookupSelection];
return true;
}
void PDFPlugin::focusNextAnnotation()
{
[m_pdfLayerController.get() activateNextAnnotation:false];
}
void PDFPlugin::focusPreviousAnnotation()
{
[m_pdfLayerController.get() activateNextAnnotation:true];
}
void PDFPlugin::notifySelectionChanged(PDFSelection *)
{
webFrame()->page()->didChangeSelection();
}
String PDFPlugin::getSelectionString() const
{
return [[m_pdfLayerController.get() currentSelection] string];
}
void PDFPlugin::performWebSearch(NSString *string)
{
webFrame()->page()->send(Messages::WebPageProxy::SearchTheWeb(string));
}
void PDFPlugin::performSpotlightSearch(NSString *string)
{
webFrame()->page()->send(Messages::WebPageProxy::SearchWithSpotlight(string));
}
bool PDFPlugin::handleWheelEvent(const WebWheelEvent& event)
{
PDFDisplayMode displayMode = [m_pdfLayerController.get() displayMode];
if (displayMode == kPDFDisplaySinglePageContinuous || displayMode == kPDFDisplayTwoUpContinuous)
return SimplePDFPlugin::handleWheelEvent(event);
NSUInteger currentPageIndex = [m_pdfLayerController.get() currentPageIndex];
bool inFirstPage = currentPageIndex == 0;
bool inLastPage = [m_pdfLayerController.get() lastPageIndex] == currentPageIndex;
bool atScrollTop = scrollPosition().y() == 0;
bool atScrollBottom = scrollPosition().y() == maximumScrollPosition().y();
bool inMomentumScroll = event.momentumPhase() != WebWheelEvent::PhaseNone;
int scrollMagnitudeThresholdForPageFlip = defaultScrollMagnitudeThresholdForPageFlip;
// Imprecise input devices should have a lower threshold so that "clicky" scroll wheels can flip pages.
if (!event.hasPreciseScrollingDeltas())
scrollMagnitudeThresholdForPageFlip = 0;
if (atScrollBottom && !inLastPage && event.delta().height() < 0) {
if (event.delta().height() <= -scrollMagnitudeThresholdForPageFlip && !inMomentumScroll)
[m_pdfLayerController.get() gotoNextPage];
return true;
} else if (atScrollTop && !inFirstPage && event.delta().height() > 0) {
if (event.delta().height() >= scrollMagnitudeThresholdForPageFlip && !inMomentumScroll) {
[CATransaction begin];
[m_pdfLayerController.get() gotoPreviousPage];
scrollToOffsetWithoutAnimation(maximumScrollPosition());
[CATransaction commit];
}
return true;
}
return SimplePDFPlugin::handleWheelEvent(event);
}
NSData *PDFPlugin::liveData() const
{
if (m_activeAnnotation)
m_activeAnnotation->commit();
return SimplePDFPlugin::liveData();
}
} // namespace WebKit
#endif // ENABLE(PDFKIT_PLUGIN)