2 * Copyright (C) 2009, 2011, 2012, 2015 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
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
29 #if ENABLE(PDFKIT_PLUGIN)
31 #import "ArgumentCoders.h"
32 #import "AttributedString.h"
33 #import "DataReference.h"
34 #import "DictionaryPopupInfo.h"
35 #import "PDFAnnotationTextWidgetDetails.h"
36 #import "PDFKitImports.h"
37 #import "PDFLayerControllerDetails.h"
38 #import "PDFPluginAnnotation.h"
39 #import "PDFPluginPasswordField.h"
40 #import "PluginView.h"
41 #import "WKAccessibilityWebPageObjectMac.h"
42 #import "WKPageFindMatchesClient.h"
43 #import "WebCoreArgumentCoders.h"
45 #import "WebEventConversion.h"
47 #import "WebPageProxyMessages.h"
48 #import "WebPasteboardProxyMessages.h"
49 #import "WebProcess.h"
50 #import <JavaScriptCore/JSContextRef.h>
51 #import <JavaScriptCore/JSObjectRef.h>
52 #import <JavaScriptCore/JSStringRef.h>
53 #import <JavaScriptCore/JSStringRefCF.h>
54 #import <PDFKit/PDFKit.h>
55 #import <QuartzCore/QuartzCore.h>
56 #import <WebCore/ArchiveResource.h>
57 #import <WebCore/Chrome.h>
58 #import <WebCore/Cursor.h>
59 #import <WebCore/DocumentLoader.h>
60 #import <WebCore/FocusController.h>
61 #import <WebCore/FormState.h>
62 #import <WebCore/Frame.h>
63 #import <WebCore/FrameLoader.h>
64 #import <WebCore/FrameView.h>
65 #import <WebCore/GraphicsContext.h>
66 #import <WebCore/HTMLElement.h>
67 #import <WebCore/HTMLFormElement.h>
68 #import <WebCore/LocalizedStrings.h>
69 #import <WebCore/MouseEvent.h>
70 #import <WebCore/Page.h>
71 #import <WebCore/Pasteboard.h>
72 #import <WebCore/PluginData.h>
73 #import <WebCore/PluginDocument.h>
74 #import <WebCore/RenderBoxModelObject.h>
75 #import <WebCore/ScrollbarTheme.h>
76 #import <WebCore/Settings.h>
77 #import <WebCore/UUID.h>
78 #import <WebKitSystemInterface.h>
79 #import <wtf/CurrentTime.h>
81 using namespace WebCore;
83 // Set overflow: hidden on the annotation container so <input> elements scrolled out of view don't show
84 // scrollbars on the body. We can't add annotations directly to the body, because overflow: hidden on the body
85 // will break rubber-banding.
86 static const char* annotationStyle =
87 "#annotationContainer {"
89 " position: absolute; "
90 " pointer-events: none; "
95 " display: -webkit-box; "
96 " -webkit-box-align: center; "
97 " -webkit-box-pack: center; "
100 " position: absolute; "
101 " pointer-events: auto; "
103 "textarea.annotation { "
106 "input.annotation[type='password'] { "
107 " position: static; "
109 " margin-top: 100px; "
112 // In non-continuous modes, a single scroll event with a magnitude of >= 20px
113 // will jump to the next or previous page, to match PDFKit behavior.
114 static const int defaultScrollMagnitudeThresholdForPageFlip = 20;
116 @interface WKPDFPluginAccessibilityObject : NSObject {
117 PDFLayerController *_pdfLayerController;
119 WebKit::PDFPlugin* _pdfPlugin;
122 @property (assign) PDFLayerController *pdfLayerController;
123 @property (assign) NSObject *parent;
124 @property (assign) WebKit::PDFPlugin* pdfPlugin;
126 - (id)initWithPDFPlugin:(WebKit::PDFPlugin *)plugin;
130 @implementation WKPDFPluginAccessibilityObject
132 @synthesize pdfLayerController=_pdfLayerController;
133 @synthesize parent=_parent;
134 @synthesize pdfPlugin=_pdfPlugin;
136 - (id)initWithPDFPlugin:(WebKit::PDFPlugin *)plugin
138 if (!(self = [super init]))
146 - (BOOL)accessibilityIsIgnored
151 - (id)accessibilityAttributeValue:(NSString *)attribute
153 if ([attribute isEqualToString:NSAccessibilityParentAttribute])
155 if ([attribute isEqualToString:NSAccessibilityValueAttribute])
156 return [_pdfLayerController accessibilityValueAttribute];
157 if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute])
158 return [_pdfLayerController accessibilitySelectedTextAttribute];
159 if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute])
160 return [_pdfLayerController accessibilitySelectedTextRangeAttribute];
161 if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute])
162 return [_pdfLayerController accessibilityNumberOfCharactersAttribute];
163 if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute])
164 return [_pdfLayerController accessibilityVisibleCharacterRangeAttribute];
165 if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute])
166 return [_parent accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute];
167 if ([attribute isEqualToString:NSAccessibilityRoleAttribute])
168 return [_pdfLayerController accessibilityRoleAttribute];
169 if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
170 return [_pdfLayerController accessibilityRoleDescriptionAttribute];
171 if ([attribute isEqualToString:NSAccessibilityWindowAttribute])
172 return [_parent accessibilityAttributeValue:NSAccessibilityWindowAttribute];
173 if ([attribute isEqualToString:NSAccessibilitySizeAttribute])
174 return [NSValue valueWithSize:_pdfPlugin->boundsOnScreen().size()];
175 if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
176 return [_parent accessibilityAttributeValue:NSAccessibilityFocusedAttribute];
177 if ([attribute isEqualToString:NSAccessibilityEnabledAttribute])
178 return [_parent accessibilityAttributeValue:NSAccessibilityEnabledAttribute];
179 if ([attribute isEqualToString:NSAccessibilityPositionAttribute])
180 return [NSValue valueWithPoint:_pdfPlugin->boundsOnScreen().location()];
185 - (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter
187 if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) {
188 NSRect boundsInPDFViewCoordinates = [[_pdfLayerController accessibilityBoundsForRangeAttributeForParameter:parameter] rectValue];
189 NSRect boundsInScreenCoordinates = _pdfPlugin->convertFromPDFViewToScreen(boundsInPDFViewCoordinates);
190 return [NSValue valueWithRect:boundsInScreenCoordinates];
193 if ([attribute isEqualToString:NSAccessibilityLineForIndexParameterizedAttribute])
194 return [_pdfLayerController accessibilityLineForIndexAttributeForParameter:parameter];
195 if ([attribute isEqualToString:NSAccessibilityRangeForLineParameterizedAttribute])
196 return [_pdfLayerController accessibilityRangeForLineAttributeForParameter:parameter];
197 if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute])
198 return [_pdfLayerController accessibilityStringForRangeAttributeForParameter:parameter];
203 - (CPReadingModel *)readingModel
205 return [_pdfLayerController readingModel];
208 - (NSArray *)accessibilityAttributeNames
210 static NSArray *attributeNames = 0;
212 if (!attributeNames) {
213 attributeNames = @[NSAccessibilityValueAttribute,
214 NSAccessibilitySelectedTextAttribute,
215 NSAccessibilitySelectedTextRangeAttribute,
216 NSAccessibilityNumberOfCharactersAttribute,
217 NSAccessibilityVisibleCharacterRangeAttribute,
218 NSAccessibilityParentAttribute,
219 NSAccessibilityRoleAttribute,
220 NSAccessibilityWindowAttribute,
221 NSAccessibilityTopLevelUIElementAttribute,
222 NSAccessibilityRoleDescriptionAttribute,
223 NSAccessibilitySizeAttribute,
224 NSAccessibilityFocusedAttribute,
225 NSAccessibilityEnabledAttribute,
226 NSAccessibilityPositionAttribute];
227 [attributeNames retain];
230 return attributeNames;
233 - (NSArray *)accessibilityActionNames
235 static NSArray *actionNames = 0;
238 actionNames = [[NSArray arrayWithObject:NSAccessibilityShowMenuAction] retain];
243 - (void)accessibilityPerformAction:(NSString *)action
245 if ([action isEqualToString:NSAccessibilityShowMenuAction])
246 _pdfPlugin->showContextMenuAtPoint(IntRect(IntPoint(), _pdfPlugin->size()).center());
249 - (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute
251 return [_pdfLayerController accessibilityIsAttributeSettable:attribute];
254 - (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute
256 return [_pdfLayerController accessibilitySetValue:value forAttribute:attribute];
259 - (NSArray *)accessibilityParameterizedAttributeNames
261 return [_pdfLayerController accessibilityParameterizedAttributeNames];
264 - (id)accessibilityFocusedUIElement
269 - (id)accessibilityHitTest:(NSPoint)point
277 @interface WKPDFPluginScrollbarLayer : CALayer {
278 WebKit::PDFPlugin* _pdfPlugin;
281 @property (assign) WebKit::PDFPlugin* pdfPlugin;
285 @implementation WKPDFPluginScrollbarLayer
287 @synthesize pdfPlugin=_pdfPlugin;
289 - (id)initWithPDFPlugin:(WebKit::PDFPlugin *)plugin
291 if (!(self = [super init]))
299 - (id<CAAction>)actionForKey:(NSString *)key
304 - (void)drawInContext:(CGContextRef)ctx
306 _pdfPlugin->paintControlForLayerInContext(self, ctx);
311 @interface WKPDFLayerControllerDelegate : NSObject<PDFLayerControllerDelegate> {
312 WebKit::PDFPlugin* _pdfPlugin;
315 @property (assign) WebKit::PDFPlugin* pdfPlugin;
319 @implementation WKPDFLayerControllerDelegate
321 @synthesize pdfPlugin=_pdfPlugin;
323 - (id)initWithPDFPlugin:(WebKit::PDFPlugin *)plugin
325 if (!(self = [super init]))
333 - (void)updateScrollPosition:(CGPoint)newPosition
335 _pdfPlugin->notifyScrollPositionChanged(IntPoint(newPosition));
338 - (void)writeItemsToPasteboard:(NSArray *)items withTypes:(NSArray *)types
340 _pdfPlugin->writeItemsToPasteboard(NSGeneralPboard, items, types);
343 - (void)showDefinitionForAttributedString:(NSAttributedString *)string atPoint:(CGPoint)point
345 _pdfPlugin->showDefinitionForAttributedString(string, point);
348 - (void)performWebSearch:(NSString *)string
350 _pdfPlugin->performWebSearch(string);
353 - (void)performSpotlightSearch:(NSString *)string
355 _pdfPlugin->performSpotlightSearch(string);
358 - (void)openWithNativeApplication
360 _pdfPlugin->openWithNativeApplication();
365 _pdfPlugin->saveToPDF();
368 - (void)pdfLayerController:(PDFLayerController *)pdfLayerController clickedLinkWithURL:(NSURL *)url
370 _pdfPlugin->clickedLink(url);
373 - (void)pdfLayerController:(PDFLayerController *)pdfLayerController didChangeActiveAnnotation:(PDFAnnotation *)annotation
375 _pdfPlugin->setActiveAnnotation(annotation);
378 - (void)pdfLayerController:(PDFLayerController *)pdfLayerController didChangeContentScaleFactor:(CGFloat)scaleFactor
380 _pdfPlugin->notifyContentScaleFactorChanged(scaleFactor);
383 - (void)pdfLayerController:(PDFLayerController *)pdfLayerController didChangeDisplayMode:(int)mode
385 _pdfPlugin->notifyDisplayModeChanged(mode);
388 - (void)pdfLayerController:(PDFLayerController *)pdfLayerController didChangeSelection:(PDFSelection *)selection
390 _pdfPlugin->notifySelectionChanged(selection);
395 static const char* postScriptMIMEType = "application/postscript";
396 const uint64_t pdfDocumentRequestID = 1; // PluginController supports loading multiple streams, but we only need one for PDF.
398 static void appendValuesInPDFNameSubtreeToVector(CGPDFDictionaryRef subtree, Vector<CGPDFObjectRef>& values)
401 if (CGPDFDictionaryGetArray(subtree, "Names", &names)) {
402 size_t nameCount = CGPDFArrayGetCount(names) / 2;
403 for (size_t i = 0; i < nameCount; ++i) {
404 CGPDFObjectRef object;
405 CGPDFArrayGetObject(names, 2 * i + 1, &object);
406 values.append(object);
412 if (!CGPDFDictionaryGetArray(subtree, "Kids", &kids))
415 size_t kidCount = CGPDFArrayGetCount(kids);
416 for (size_t i = 0; i < kidCount; ++i) {
417 CGPDFDictionaryRef kid;
418 if (!CGPDFArrayGetDictionary(kids, i, &kid))
420 appendValuesInPDFNameSubtreeToVector(kid, values);
424 static void getAllValuesInPDFNameTree(CGPDFDictionaryRef tree, Vector<CGPDFObjectRef>& allValues)
426 appendValuesInPDFNameSubtreeToVector(tree, allValues);
429 static void getAllScriptsInPDFDocument(CGPDFDocumentRef pdfDocument, Vector<RetainPtr<CFStringRef>>& scripts)
434 CGPDFDictionaryRef pdfCatalog = CGPDFDocumentGetCatalog(pdfDocument);
438 // Get the dictionary of all document-level name trees.
439 CGPDFDictionaryRef namesDictionary;
440 if (!CGPDFDictionaryGetDictionary(pdfCatalog, "Names", &namesDictionary))
443 // Get the document-level "JavaScript" name tree.
444 CGPDFDictionaryRef javaScriptNameTree;
445 if (!CGPDFDictionaryGetDictionary(namesDictionary, "JavaScript", &javaScriptNameTree))
448 // The names are arbitrary. We are only interested in the values.
449 Vector<CGPDFObjectRef> objects;
450 getAllValuesInPDFNameTree(javaScriptNameTree, objects);
451 size_t objectCount = objects.size();
453 for (size_t i = 0; i < objectCount; ++i) {
454 CGPDFDictionaryRef javaScriptAction;
455 if (!CGPDFObjectGetValue(reinterpret_cast<CGPDFObjectRef>(objects[i]), kCGPDFObjectTypeDictionary, &javaScriptAction))
458 // A JavaScript action must have an action type of "JavaScript".
459 const char* actionType;
460 if (!CGPDFDictionaryGetName(javaScriptAction, "S", &actionType) || strcmp(actionType, "JavaScript"))
463 const UInt8* bytes = 0;
465 CGPDFStreamRef stream;
466 CGPDFStringRef string;
467 RetainPtr<CFDataRef> data;
468 if (CGPDFDictionaryGetStream(javaScriptAction, "JS", &stream)) {
469 CGPDFDataFormat format;
470 data = adoptCF(CGPDFStreamCopyData(stream, &format));
473 bytes = CFDataGetBytePtr(data.get());
474 length = CFDataGetLength(data.get());
475 } else if (CGPDFDictionaryGetString(javaScriptAction, "JS", &string)) {
476 bytes = CGPDFStringGetBytePtr(string);
477 length = CGPDFStringGetLength(string);
482 CFStringEncoding encoding = (length > 1 && bytes[0] == 0xFE && bytes[1] == 0xFF) ? kCFStringEncodingUnicode : kCFStringEncodingUTF8;
483 RetainPtr<CFStringRef> script = adoptCF(CFStringCreateWithBytes(kCFAllocatorDefault, bytes, length, encoding, true));
487 scripts.append(script);
493 using namespace HTMLNames;
495 PassRefPtr<PDFPlugin> PDFPlugin::create(WebFrame* frame)
497 return adoptRef(new PDFPlugin(frame));
500 PDFPlugin::PDFPlugin(WebFrame* frame)
502 , m_isPostScript(false)
503 , m_pdfDocumentWasMutated(false)
504 , m_containerLayer(adoptNS([[CALayer alloc] init]))
505 , m_contentLayer(adoptNS([[CALayer alloc] init]))
506 , m_scrollCornerLayer(adoptNS([[WKPDFPluginScrollbarLayer alloc] initWithPDFPlugin:this]))
507 , m_pdfLayerController(adoptNS([[pdfLayerControllerClass() alloc] init]))
508 , m_pdfLayerControllerDelegate(adoptNS([[WKPDFLayerControllerDelegate alloc] initWithPDFPlugin:this]))
510 m_pdfLayerController.get().delegate = m_pdfLayerControllerDelegate.get();
511 m_pdfLayerController.get().parentLayer = m_contentLayer.get();
513 if (supportsForms()) {
514 Document* document = webFrame()->coreFrame()->document();
515 m_annotationContainer = document->createElement(divTag, false);
516 m_annotationContainer->setAttribute(idAttr, "annotationContainer");
518 RefPtr<Element> m_annotationStyle = document->createElement(styleTag, false);
519 m_annotationStyle->setTextContent(annotationStyle, ASSERT_NO_EXCEPTION);
521 m_annotationContainer->appendChild(m_annotationStyle.get());
522 document->bodyOrFrameset()->appendChild(m_annotationContainer.get());
525 m_accessibilityObject = adoptNS([[WKPDFPluginAccessibilityObject alloc] initWithPDFPlugin:this]);
526 m_accessibilityObject.get().pdfLayerController = m_pdfLayerController.get();
527 m_accessibilityObject.get().parent = webFrame()->page()->accessibilityRemoteObject();
529 [m_containerLayer addSublayer:m_contentLayer.get()];
530 [m_containerLayer addSublayer:m_scrollCornerLayer.get()];
533 PDFPlugin::~PDFPlugin()
537 PluginInfo PDFPlugin::pluginInfo()
540 info.name = builtInPDFPluginName();
541 info.isApplicationPlugin = true;
543 MimeClassInfo pdfMimeClassInfo;
544 pdfMimeClassInfo.type = "application/pdf";
545 pdfMimeClassInfo.desc = pdfDocumentTypeDescription();
546 pdfMimeClassInfo.extensions.append("pdf");
547 info.mimes.append(pdfMimeClassInfo);
549 MimeClassInfo textPDFMimeClassInfo;
550 textPDFMimeClassInfo.type = "text/pdf";
551 textPDFMimeClassInfo.desc = pdfDocumentTypeDescription();
552 textPDFMimeClassInfo.extensions.append("pdf");
553 info.mimes.append(textPDFMimeClassInfo);
555 MimeClassInfo postScriptMimeClassInfo;
556 postScriptMimeClassInfo.type = postScriptMIMEType;
557 postScriptMimeClassInfo.desc = postScriptDocumentTypeDescription();
558 postScriptMimeClassInfo.extensions.append("ps");
559 info.mimes.append(postScriptMimeClassInfo);
564 void PDFPlugin::updateScrollbars()
566 bool hadScrollbars = m_horizontalScrollbar || m_verticalScrollbar;
568 if (m_horizontalScrollbar) {
569 if (m_size.width() >= m_pdfDocumentSize.width())
570 destroyScrollbar(HorizontalScrollbar);
571 } else if (m_size.width() < m_pdfDocumentSize.width())
572 m_horizontalScrollbar = createScrollbar(HorizontalScrollbar);
574 if (m_verticalScrollbar) {
575 if (m_size.height() >= m_pdfDocumentSize.height())
576 destroyScrollbar(VerticalScrollbar);
577 } else if (m_size.height() < m_pdfDocumentSize.height())
578 m_verticalScrollbar = createScrollbar(VerticalScrollbar);
580 int horizontalScrollbarHeight = (m_horizontalScrollbar && !m_horizontalScrollbar->isOverlayScrollbar()) ? m_horizontalScrollbar->height() : 0;
581 int verticalScrollbarWidth = (m_verticalScrollbar && !m_verticalScrollbar->isOverlayScrollbar()) ? m_verticalScrollbar->width() : 0;
583 int pageStep = m_pageBoxes.isEmpty() ? 0 : m_pageBoxes[0].height();
585 if (m_horizontalScrollbar) {
586 m_horizontalScrollbar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep);
587 m_horizontalScrollbar->setProportion(m_size.width() - verticalScrollbarWidth, m_pdfDocumentSize.width());
588 IntRect scrollbarRect(pluginView()->x(), pluginView()->y() + m_size.height() - m_horizontalScrollbar->height(), m_size.width(), m_horizontalScrollbar->height());
589 if (m_verticalScrollbar)
590 scrollbarRect.contract(m_verticalScrollbar->width(), 0);
591 m_horizontalScrollbar->setFrameRect(scrollbarRect);
593 if (m_verticalScrollbar) {
594 m_verticalScrollbar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep);
595 m_verticalScrollbar->setProportion(m_size.height() - horizontalScrollbarHeight, m_pdfDocumentSize.height());
596 IntRect scrollbarRect(IntRect(pluginView()->x() + m_size.width() - m_verticalScrollbar->width(), pluginView()->y(), m_verticalScrollbar->width(), m_size.height()));
597 if (m_horizontalScrollbar)
598 scrollbarRect.contract(0, m_horizontalScrollbar->height());
599 m_verticalScrollbar->setFrameRect(scrollbarRect);
602 FrameView* frameView = m_frame->coreFrame()->view();
606 bool hasScrollbars = m_horizontalScrollbar || m_verticalScrollbar;
607 if (hadScrollbars != hasScrollbars) {
609 frameView->addScrollableArea(this);
611 frameView->removeScrollableArea(this);
613 frameView->setNeedsLayout();
616 if (m_verticalScrollbarLayer) {
617 m_verticalScrollbarLayer.get().frame = verticalScrollbar()->frameRect();
618 [m_verticalScrollbarLayer setNeedsDisplay];
621 if (m_horizontalScrollbarLayer) {
622 m_horizontalScrollbarLayer.get().frame = horizontalScrollbar()->frameRect();
623 [m_horizontalScrollbarLayer setNeedsDisplay];
626 if (m_scrollCornerLayer) {
627 m_scrollCornerLayer.get().frame = scrollCornerRect();
628 [m_scrollCornerLayer setNeedsDisplay];
632 PluginView* PDFPlugin::pluginView()
634 return static_cast<PluginView*>(controller());
637 const PluginView* PDFPlugin::pluginView() const
639 return static_cast<const PluginView*>(controller());
642 PassRefPtr<Scrollbar> PDFPlugin::createScrollbar(ScrollbarOrientation orientation)
644 RefPtr<Scrollbar> widget = Scrollbar::createNativeScrollbar(*this, orientation, RegularScrollbar);
645 if (orientation == HorizontalScrollbar) {
646 m_horizontalScrollbarLayer = adoptNS([[WKPDFPluginScrollbarLayer alloc] initWithPDFPlugin:this]);
647 [m_containerLayer addSublayer:m_horizontalScrollbarLayer.get()];
649 m_verticalScrollbarLayer = adoptNS([[WKPDFPluginScrollbarLayer alloc] initWithPDFPlugin:this]);
650 [m_containerLayer addSublayer:m_verticalScrollbarLayer.get()];
652 didAddScrollbar(widget.get(), orientation);
653 pluginView()->frame()->view()->addChild(widget.get());
654 return widget.release();
657 void PDFPlugin::destroyScrollbar(ScrollbarOrientation orientation)
659 RefPtr<Scrollbar>& scrollbar = orientation == HorizontalScrollbar ? m_horizontalScrollbar : m_verticalScrollbar;
663 willRemoveScrollbar(scrollbar.get(), orientation);
664 scrollbar->removeFromParent();
667 if (orientation == HorizontalScrollbar) {
668 [m_horizontalScrollbarLayer removeFromSuperlayer];
669 m_horizontalScrollbarLayer = 0;
671 [m_verticalScrollbarLayer removeFromSuperlayer];
672 m_verticalScrollbarLayer = 0;
676 IntRect PDFPlugin::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const
678 IntRect rect = scrollbarRect;
679 rect.move(scrollbar->location() - pluginView()->location());
681 return pluginView()->frame()->view()->convertFromRendererToContainingView(pluginView()->renderer(), rect);
684 IntRect PDFPlugin::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
686 IntRect rect = pluginView()->frame()->view()->convertFromContainingViewToRenderer(pluginView()->renderer(), parentRect);
687 rect.move(pluginView()->location() - scrollbar->location());
692 IntPoint PDFPlugin::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const
694 IntPoint point = scrollbarPoint;
695 point.move(scrollbar->location() - pluginView()->location());
697 return pluginView()->frame()->view()->convertFromRendererToContainingView(pluginView()->renderer(), point);
700 IntPoint PDFPlugin::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
702 IntPoint point = pluginView()->frame()->view()->convertFromContainingViewToRenderer(pluginView()->renderer(), parentPoint);
703 point.move(pluginView()->location() - scrollbar->location());
708 bool PDFPlugin::handleScroll(ScrollDirection direction, ScrollGranularity granularity)
710 return scroll(direction, granularity);
713 IntRect PDFPlugin::scrollCornerRect() const
715 if (!m_horizontalScrollbar || !m_verticalScrollbar)
717 if (m_horizontalScrollbar->isOverlayScrollbar()) {
718 ASSERT(m_verticalScrollbar->isOverlayScrollbar());
721 return IntRect(pluginView()->width() - m_verticalScrollbar->width(), pluginView()->height() - m_horizontalScrollbar->height(), m_verticalScrollbar->width(), m_horizontalScrollbar->height());
724 ScrollableArea* PDFPlugin::enclosingScrollableArea() const
726 // FIXME: Walk up the frame tree and look for a scrollable parent frame or RenderLayer.
730 IntRect PDFPlugin::scrollableAreaBoundingBox() const
732 return pluginView()->frameRect();
735 int PDFPlugin::scrollSize(ScrollbarOrientation orientation) const
737 Scrollbar* scrollbar = ((orientation == HorizontalScrollbar) ? m_horizontalScrollbar : m_verticalScrollbar).get();
738 return scrollbar ? (scrollbar->totalSize() - scrollbar->visibleSize()) : 0;
741 bool PDFPlugin::isActive() const
743 if (Frame* coreFrame = m_frame->coreFrame()) {
744 if (Page* page = coreFrame->page())
745 return page->focusController().isActive();
751 bool PDFPlugin::forceUpdateScrollbarsOnMainThreadForPerformanceTesting() const
753 if (Frame* coreFrame = m_frame->coreFrame()) {
754 if (Page* page = coreFrame->page())
755 return page->settings().forceUpdateScrollbarsOnMainThreadForPerformanceTesting();
761 int PDFPlugin::scrollPosition(Scrollbar* scrollbar) const
763 if (scrollbar->orientation() == HorizontalScrollbar)
764 return m_scrollOffset.width();
765 if (scrollbar->orientation() == VerticalScrollbar)
766 return m_scrollOffset.height();
767 ASSERT_NOT_REACHED();
771 IntPoint PDFPlugin::scrollPosition() const
773 return IntPoint(m_scrollOffset.width(), m_scrollOffset.height());
776 IntPoint PDFPlugin::minimumScrollPosition() const
781 IntPoint PDFPlugin::maximumScrollPosition() const
783 int horizontalScrollbarHeight = (m_horizontalScrollbar && !m_horizontalScrollbar->isOverlayScrollbar()) ? m_horizontalScrollbar->height() : 0;
784 int verticalScrollbarWidth = (m_verticalScrollbar && !m_verticalScrollbar->isOverlayScrollbar()) ? m_verticalScrollbar->width() : 0;
786 IntPoint maximumOffset(m_pdfDocumentSize.width() - m_size.width() + verticalScrollbarWidth, m_pdfDocumentSize.height() - m_size.height() + horizontalScrollbarHeight);
787 maximumOffset.clampNegativeToZero();
788 return maximumOffset;
791 void PDFPlugin::scrollbarStyleChanged(ScrollbarStyle style, bool forceUpdate)
796 // If the PDF was scrolled all the way to bottom right and scrollbars change to overlay style, we don't want to display white rectangles where scrollbars were.
797 IntPoint newScrollOffset = IntPoint(m_scrollOffset).shrunkTo(maximumScrollPosition());
798 setScrollOffset(newScrollOffset);
800 ScrollableArea::scrollbarStyleChanged(style, forceUpdate);
801 // As size of the content area changes, scrollbars may need to appear or to disappear.
805 void PDFPlugin::addArchiveResource()
807 // FIXME: It's a hack to force add a resource to DocumentLoader. PDF documents should just be fetched as CachedResources.
809 // Add just enough data for context menu handling and web archives to work.
810 NSDictionary* headers = @{ @"Content-Disposition": (NSString *)m_suggestedFilename, @"Content-Type" : @"application/pdf" };
811 RetainPtr<NSURLResponse> response = adoptNS([[NSHTTPURLResponse alloc] initWithURL:m_sourceURL statusCode:200 HTTPVersion:(NSString*)kCFHTTPVersion1_1 headerFields:headers]);
812 ResourceResponse synthesizedResponse(response.get());
814 RefPtr<ArchiveResource> resource = ArchiveResource::create(SharedBuffer::wrapCFData(m_data.get()), m_sourceURL, "application/pdf", String(), String(), synthesizedResponse);
815 pluginView()->frame()->document()->loader()->addArchiveResource(resource.release());
818 static void jsPDFDocInitialize(JSContextRef ctx, JSObjectRef object)
820 PDFPlugin* pdfView = static_cast<PDFPlugin*>(JSObjectGetPrivate(object));
824 static void jsPDFDocFinalize(JSObjectRef object)
826 PDFPlugin* pdfView = static_cast<PDFPlugin*>(JSObjectGetPrivate(object));
830 JSValueRef PDFPlugin::jsPDFDocPrint(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
832 PDFPlugin* pdfView = static_cast<PDFPlugin*>(JSObjectGetPrivate(thisObject));
834 WebFrame* frame = pdfView->m_frame;
836 return JSValueMakeUndefined(ctx);
838 Frame* coreFrame = frame->coreFrame();
840 return JSValueMakeUndefined(ctx);
842 Page* page = coreFrame->page();
844 return JSValueMakeUndefined(ctx);
846 page->chrome().print(coreFrame);
848 return JSValueMakeUndefined(ctx);
851 JSObjectRef PDFPlugin::makeJSPDFDoc(JSContextRef ctx)
853 static JSStaticFunction jsPDFDocStaticFunctions[] = {
854 { "print", jsPDFDocPrint, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
858 static JSClassDefinition jsPDFDocClassDefinition = {
860 kJSClassAttributeNone,
864 jsPDFDocStaticFunctions,
865 jsPDFDocInitialize, jsPDFDocFinalize, 0, 0, 0, 0, 0, 0, 0, 0, 0
868 static JSClassRef jsPDFDocClass = JSClassCreate(&jsPDFDocClassDefinition);
870 return JSObjectMake(ctx, jsPDFDocClass, this);
873 static RetainPtr<CFMutableDataRef> convertPostScriptDataToPDF(RetainPtr<CFDataRef> postScriptData)
875 // Convert PostScript to PDF using the Quartz 2D API.
876 // http://developer.apple.com/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_ps_convert/chapter_16_section_1.html
878 CGPSConverterCallbacks callbacks = { 0, 0, 0, 0, 0, 0, 0, 0 };
879 RetainPtr<CGPSConverterRef> converter = adoptCF(CGPSConverterCreate(0, &callbacks, 0));
880 RetainPtr<CGDataProviderRef> provider = adoptCF(CGDataProviderCreateWithCFData(postScriptData.get()));
881 RetainPtr<CFMutableDataRef> pdfData = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
882 RetainPtr<CGDataConsumerRef> consumer = adoptCF(CGDataConsumerCreateWithCFData(pdfData.get()));
884 CGPSConverterConvert(converter.get(), provider.get(), consumer.get(), 0);
889 void PDFPlugin::convertPostScriptDataIfNeeded()
894 m_suggestedFilename = String(m_suggestedFilename + ".pdf");
895 m_data = convertPostScriptDataToPDF(m_data);
898 void PDFPlugin::pdfDocumentDidLoad()
900 addArchiveResource();
902 RetainPtr<PDFDocument> document = adoptNS([[pdfDocumentClass() alloc] initWithData:rawData()]);
904 setPDFDocument(document);
906 updatePageAndDeviceScaleFactors();
908 [m_pdfLayerController setFrameSize:size()];
909 m_pdfLayerController.get().document = document.get();
911 if (handlesPageScaleFactor())
912 pluginView()->setPageScaleFactor([m_pdfLayerController contentScaleFactor], IntPoint());
914 notifyScrollPositionChanged(IntPoint([m_pdfLayerController scrollPosition]));
919 runScriptsInPDFDocument();
921 if ([document isLocked])
922 createPasswordEntryForm();
925 void PDFPlugin::streamDidReceiveResponse(uint64_t streamID, const URL&, uint32_t, uint32_t, const String& mimeType, const String&, const String& suggestedFilename)
927 ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);
929 m_suggestedFilename = suggestedFilename;
931 if (equalIgnoringCase(mimeType, postScriptMIMEType))
932 m_isPostScript = true;
935 void PDFPlugin::streamDidReceiveData(uint64_t streamID, const char* bytes, int length)
937 ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);
940 m_data = adoptCF(CFDataCreateMutable(0, 0));
942 CFDataAppendBytes(m_data.get(), reinterpret_cast<const UInt8*>(bytes), length);
945 void PDFPlugin::streamDidFinishLoading(uint64_t streamID)
947 ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);
949 convertPostScriptDataIfNeeded();
950 pdfDocumentDidLoad();
953 void PDFPlugin::streamDidFail(uint64_t streamID, bool wasCancelled)
955 ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);
960 void PDFPlugin::manualStreamDidReceiveResponse(const URL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers, const String& suggestedFilename)
962 m_suggestedFilename = suggestedFilename;
964 if (equalIgnoringCase(mimeType, postScriptMIMEType))
965 m_isPostScript = true;
968 void PDFPlugin::manualStreamDidReceiveData(const char* bytes, int length)
971 m_data = adoptCF(CFDataCreateMutable(0, 0));
973 CFDataAppendBytes(m_data.get(), reinterpret_cast<const UInt8*>(bytes), length);
976 void PDFPlugin::manualStreamDidFinishLoading()
978 convertPostScriptDataIfNeeded();
979 pdfDocumentDidLoad();
982 void PDFPlugin::manualStreamDidFail(bool)
987 void PDFPlugin::runScriptsInPDFDocument()
989 Vector<RetainPtr<CFStringRef>> scripts;
990 getAllScriptsInPDFDocument([m_pdfDocument documentRef], scripts);
992 size_t scriptCount = scripts.size();
996 JSGlobalContextRef ctx = JSGlobalContextCreate(0);
997 JSObjectRef jsPDFDoc = makeJSPDFDoc(ctx);
999 for (size_t i = 0; i < scriptCount; ++i) {
1000 JSStringRef script = JSStringCreateWithCFString(scripts[i].get());
1001 JSEvaluateScript(ctx, script, jsPDFDoc, 0, 0, 0);
1002 JSStringRelease(script);
1005 JSGlobalContextRelease(ctx);
1008 void PDFPlugin::createPasswordEntryForm()
1010 m_passwordField = PDFPluginPasswordField::create(m_pdfLayerController.get(), this);
1011 m_passwordField->attach(m_annotationContainer.get());
1014 void PDFPlugin::attemptToUnlockPDF(const String& password)
1016 [m_pdfLayerController attemptToUnlockWithPassword:password];
1018 if (![pdfDocument() isLocked]) {
1019 m_passwordField = nullptr;
1026 void PDFPlugin::updatePageAndDeviceScaleFactors()
1028 double newScaleFactor = controller()->contentsScaleFactor();
1029 if (!handlesPageScaleFactor())
1030 newScaleFactor *= webFrame()->page()->pageScaleFactor();
1032 [m_pdfLayerController setDeviceScaleFactor:newScaleFactor];
1035 void PDFPlugin::contentsScaleFactorChanged(float)
1037 updatePageAndDeviceScaleFactors();
1040 void PDFPlugin::computePageBoxes()
1042 size_t pageCount = CGPDFDocumentGetNumberOfPages([m_pdfDocument documentRef]);
1043 for (size_t i = 0; i < pageCount; ++i) {
1044 CGPDFPageRef pdfPage = CGPDFDocumentGetPage([m_pdfDocument documentRef], i + 1);
1047 CGRect box = CGPDFPageGetBoxRect(pdfPage, kCGPDFCropBox);
1048 if (CGRectIsEmpty(box))
1049 box = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);
1050 m_pageBoxes.append(IntRect(box));
1054 void PDFPlugin::calculateSizes()
1056 if ([pdfDocument() isLocked]) {
1057 setPDFDocumentSize(IntSize(0, 0));
1061 // FIXME: This should come straight from PDFKit.
1064 setPDFDocumentSize(IntSize([m_pdfLayerController contentSizeRespectingZoom]));
1067 bool PDFPlugin::initialize(const Parameters& parameters)
1069 m_sourceURL = parameters.url;
1070 if (!parameters.shouldUseManualLoader && !parameters.url.isEmpty())
1071 controller()->loadURL(pdfDocumentRequestID, "GET", parameters.url.string(), String(), HTTPHeaderMap(), Vector<uint8_t>(), false);
1073 controller()->didInitializePlugin();
1077 void PDFPlugin::destroy()
1079 m_pdfLayerController.get().delegate = 0;
1082 if (FrameView* frameView = webFrame()->coreFrame()->view())
1083 frameView->removeScrollableArea(this);
1086 m_activeAnnotation = 0;
1087 m_annotationContainer = 0;
1089 destroyScrollbar(HorizontalScrollbar);
1090 destroyScrollbar(VerticalScrollbar);
1092 [m_scrollCornerLayer removeFromSuperlayer];
1093 [m_contentLayer removeFromSuperlayer];
1096 void PDFPlugin::updateControlTints(GraphicsContext* graphicsContext)
1098 ASSERT(graphicsContext->updatingControlTints());
1100 if (m_horizontalScrollbar)
1101 m_horizontalScrollbar->invalidate();
1102 if (m_verticalScrollbar)
1103 m_verticalScrollbar->invalidate();
1104 invalidateScrollCorner(scrollCornerRect());
1107 void PDFPlugin::paintControlForLayerInContext(CALayer *layer, CGContextRef context)
1109 GraphicsContext graphicsContext(context);
1110 GraphicsContextStateSaver stateSaver(graphicsContext);
1112 graphicsContext.setIsCALayerContext(true);
1114 if (layer == m_scrollCornerLayer) {
1115 IntRect scrollCornerRect = this->scrollCornerRect();
1116 graphicsContext.translate(-scrollCornerRect.x(), -scrollCornerRect.y());
1117 ScrollbarTheme::theme()->paintScrollCorner(0, &graphicsContext, scrollCornerRect);
1121 Scrollbar* scrollbar = nullptr;
1123 if (layer == m_verticalScrollbarLayer)
1124 scrollbar = verticalScrollbar();
1125 else if (layer == m_horizontalScrollbarLayer)
1126 scrollbar = horizontalScrollbar();
1131 graphicsContext.translate(-scrollbar->x(), -scrollbar->y());
1132 scrollbar->paint(&graphicsContext, scrollbar->frameRect());
1135 PassRefPtr<ShareableBitmap> PDFPlugin::snapshot()
1137 if (size().isEmpty())
1140 float contentsScaleFactor = controller()->contentsScaleFactor();
1141 IntSize backingStoreSize = size();
1142 backingStoreSize.scale(contentsScaleFactor);
1144 RefPtr<ShareableBitmap> bitmap = ShareableBitmap::createShareable(backingStoreSize, ShareableBitmap::SupportsAlpha);
1145 auto context = bitmap->createGraphicsContext();
1147 context->scale(FloatSize(contentsScaleFactor, -contentsScaleFactor));
1148 context->translate(-m_scrollOffset.width(), -m_pdfDocumentSize.height() + m_scrollOffset.height());
1150 [m_pdfLayerController snapshotInContext:context->platformContext()];
1152 return bitmap.release();
1155 PlatformLayer* PDFPlugin::pluginLayer()
1157 return m_containerLayer.get();
1160 IntPoint PDFPlugin::convertFromPluginToPDFView(const IntPoint& point) const
1162 return IntPoint(point.x(), size().height() - point.y());
1165 IntPoint PDFPlugin::convertFromRootViewToPlugin(const IntPoint& point) const
1167 return m_rootViewToPluginTransform.mapPoint(point);
1170 IntPoint PDFPlugin::convertFromPDFViewToRootView(const IntPoint& point) const
1172 IntPoint pointInPluginCoordinates(point.x(), size().height() - point.y());
1173 return m_rootViewToPluginTransform.inverse().mapPoint(pointInPluginCoordinates);
1176 FloatRect PDFPlugin::convertFromPDFViewToScreen(const FloatRect& rect) const
1178 FrameView* frameView = webFrame()->coreFrame()->view();
1183 FloatPoint originInPluginCoordinates(rect.x(), size().height() - rect.y() - rect.height());
1184 FloatRect rectInRootViewCoordinates = m_rootViewToPluginTransform.inverse().mapRect(FloatRect(originInPluginCoordinates, rect.size()));
1186 return frameView->contentsToScreen(enclosingIntRect(rectInRootViewCoordinates));
1189 IntRect PDFPlugin::boundsOnScreen() const
1191 FrameView* frameView = webFrame()->coreFrame()->view();
1196 FloatRect bounds = FloatRect(FloatPoint(), size());
1197 FloatRect rectInRootViewCoordinates = m_rootViewToPluginTransform.inverse().mapRect(bounds);
1198 return frameView->contentsToScreen(enclosingIntRect(rectInRootViewCoordinates));
1201 void PDFPlugin::geometryDidChange(const IntSize& pluginSize, const IntRect&, const AffineTransform& pluginToRootViewTransform)
1203 if (size() == pluginSize && pluginView()->pageScaleFactor() == [m_pdfLayerController contentScaleFactor])
1206 m_size = pluginSize;
1207 m_rootViewToPluginTransform = pluginToRootViewTransform.inverse();
1208 [m_pdfLayerController setFrameSize:pluginSize];
1210 [CATransaction begin];
1211 [CATransaction setDisableActions:YES];
1212 CATransform3D transform = CATransform3DMakeScale(1, -1, 1);
1213 transform = CATransform3DTranslate(transform, 0, -pluginSize.height(), 0);
1215 if (handlesPageScaleFactor()) {
1216 CGFloat magnification = pluginView()->pageScaleFactor() - [m_pdfLayerController contentScaleFactor];
1218 // FIXME: Instead of m_lastMousePositionInPluginCoordinates, we should use the zoom origin from PluginView::setPageScaleFactor.
1220 [m_pdfLayerController magnifyWithMagnification:magnification atPoint:convertFromPluginToPDFView(m_lastMousePositionInPluginCoordinates) immediately:NO];
1222 // If we don't handle page scale ourselves, we need to respect our parent page's
1223 // scale, which may have changed.
1224 updatePageAndDeviceScaleFactors();
1230 if (m_activeAnnotation)
1231 m_activeAnnotation->updateGeometry();
1233 [m_contentLayer setSublayerTransform:transform];
1234 [CATransaction commit];
1237 void PDFPlugin::frameDidFinishLoading(uint64_t)
1239 ASSERT_NOT_REACHED();
1242 void PDFPlugin::frameDidFail(uint64_t, bool)
1244 ASSERT_NOT_REACHED();
1247 void PDFPlugin::didEvaluateJavaScript(uint64_t, const WTF::String&)
1249 ASSERT_NOT_REACHED();
1253 static NSUInteger modifierFlagsFromWebEvent(const WebEvent& event)
1255 return (event.shiftKey() ? NSShiftKeyMask : 0)
1256 | (event.controlKey() ? NSControlKeyMask : 0)
1257 | (event.altKey() ? NSAlternateKeyMask : 0)
1258 | (event.metaKey() ? NSCommandKeyMask : 0);
1261 static bool getEventTypeFromWebEvent(const WebEvent& event, NSEventType& eventType)
1263 switch (event.type()) {
1264 case WebEvent::KeyDown:
1265 eventType = NSKeyDown;
1267 case WebEvent::KeyUp:
1268 eventType = NSKeyUp;
1270 case WebEvent::MouseDown:
1271 switch (static_cast<const WebMouseEvent&>(event).button()) {
1272 case WebMouseEvent::LeftButton:
1273 eventType = NSLeftMouseDown;
1275 case WebMouseEvent::RightButton:
1276 eventType = NSRightMouseDown;
1281 case WebEvent::MouseUp:
1282 switch (static_cast<const WebMouseEvent&>(event).button()) {
1283 case WebMouseEvent::LeftButton:
1284 eventType = NSLeftMouseUp;
1286 case WebMouseEvent::RightButton:
1287 eventType = NSRightMouseUp;
1292 case WebEvent::MouseMove:
1293 switch (static_cast<const WebMouseEvent&>(event).button()) {
1294 case WebMouseEvent::LeftButton:
1295 eventType = NSLeftMouseDragged;
1297 case WebMouseEvent::RightButton:
1298 eventType = NSRightMouseDragged;
1300 case WebMouseEvent::NoButton:
1301 eventType = NSMouseMoved;
1311 NSEvent *PDFPlugin::nsEventForWebMouseEvent(const WebMouseEvent& event)
1313 m_lastMousePositionInPluginCoordinates = convertFromRootViewToPlugin(event.position());
1315 IntPoint positionInPDFViewCoordinates(convertFromPluginToPDFView(m_lastMousePositionInPluginCoordinates));
1317 NSEventType eventType;
1319 if (!getEventTypeFromWebEvent(event, eventType))
1322 NSUInteger modifierFlags = modifierFlagsFromWebEvent(event);
1324 return [NSEvent mouseEventWithType:eventType location:positionInPDFViewCoordinates modifierFlags:modifierFlags timestamp:0 windowNumber:0 context:nil eventNumber:0 clickCount:event.clickCount() pressure:0];
1327 void PDFPlugin::updateCursor(const WebMouseEvent& event, UpdateCursorMode mode)
1329 HitTestResult hitTestResult = None;
1331 PDFSelection *selectionUnderMouse = [m_pdfLayerController getSelectionForWordAtPoint:convertFromPluginToPDFView(event.position())];
1332 if (selectionUnderMouse && [[selectionUnderMouse string] length])
1333 hitTestResult = Text;
1335 if (hitTestResult == m_lastHitTestResult && mode == UpdateIfNeeded)
1338 webFrame()->page()->send(Messages::WebPageProxy::SetCursor(hitTestResult == Text ? iBeamCursor() : pointerCursor()));
1339 m_lastHitTestResult = hitTestResult;
1342 bool PDFPlugin::handleMouseEvent(const WebMouseEvent& event)
1344 PlatformMouseEvent platformEvent = platform(event);
1345 IntPoint mousePosition = convertFromRootViewToPlugin(event.position());
1347 m_lastMouseEvent = event;
1349 RefPtr<Scrollbar> targetScrollbar;
1350 RefPtr<Scrollbar> targetScrollbarForLastMousePosition;
1352 if (m_verticalScrollbarLayer) {
1353 IntRect verticalScrollbarFrame(m_verticalScrollbarLayer.get().frame);
1354 if (verticalScrollbarFrame.contains(mousePosition))
1355 targetScrollbar = verticalScrollbar();
1356 if (verticalScrollbarFrame.contains(m_lastMousePositionInPluginCoordinates))
1357 targetScrollbarForLastMousePosition = verticalScrollbar();
1360 if (m_horizontalScrollbarLayer) {
1361 IntRect horizontalScrollbarFrame(m_horizontalScrollbarLayer.get().frame);
1362 if (horizontalScrollbarFrame.contains(mousePosition))
1363 targetScrollbar = horizontalScrollbar();
1364 if (horizontalScrollbarFrame.contains(m_lastMousePositionInPluginCoordinates))
1365 targetScrollbarForLastMousePosition = horizontalScrollbar();
1368 if (m_scrollCornerLayer && IntRect(m_scrollCornerLayer.get().frame).contains(mousePosition))
1371 if ([pdfDocument() isLocked])
1374 // Right-clicks and Control-clicks always call handleContextMenuEvent as well.
1375 if (event.button() == WebMouseEvent::RightButton || (event.button() == WebMouseEvent::LeftButton && event.controlKey()))
1378 NSEvent *nsEvent = nsEventForWebMouseEvent(event);
1380 switch (event.type()) {
1381 case WebEvent::MouseMove:
1382 mouseMovedInContentArea();
1383 updateCursor(event);
1385 if (targetScrollbar) {
1386 if (!targetScrollbarForLastMousePosition) {
1387 targetScrollbar->mouseEntered();
1390 return targetScrollbar->mouseMoved(platformEvent);
1393 if (!targetScrollbar && targetScrollbarForLastMousePosition)
1394 targetScrollbarForLastMousePosition->mouseExited();
1396 switch (event.button()) {
1397 case WebMouseEvent::LeftButton:
1398 [m_pdfLayerController mouseDragged:nsEvent];
1400 case WebMouseEvent::RightButton:
1401 case WebMouseEvent::MiddleButton:
1403 case WebMouseEvent::NoButton:
1404 [m_pdfLayerController mouseMoved:nsEvent];
1407 case WebEvent::MouseDown:
1408 switch (event.button()) {
1409 case WebMouseEvent::LeftButton:
1410 if (targetScrollbar)
1411 return targetScrollbar->mouseDown(platformEvent);
1413 [m_pdfLayerController mouseDown:nsEvent];
1415 case WebMouseEvent::RightButton:
1416 [m_pdfLayerController rightMouseDown:nsEvent];
1418 case WebMouseEvent::MiddleButton:
1419 case WebMouseEvent::NoButton:
1422 case WebEvent::MouseUp:
1423 switch (event.button()) {
1424 case WebMouseEvent::LeftButton:
1425 if (targetScrollbar)
1426 return targetScrollbar->mouseUp(platformEvent);
1428 [m_pdfLayerController mouseUp:nsEvent];
1430 case WebMouseEvent::RightButton:
1431 case WebMouseEvent::MiddleButton:
1432 case WebMouseEvent::NoButton:
1442 bool PDFPlugin::handleMouseEnterEvent(const WebMouseEvent& event)
1444 mouseEnteredContentArea();
1445 updateCursor(event, ForceUpdate);
1449 bool PDFPlugin::handleMouseLeaveEvent(const WebMouseEvent&)
1451 mouseExitedContentArea();
1455 bool PDFPlugin::showContextMenuAtPoint(const IntPoint& point)
1457 FrameView* frameView = webFrame()->coreFrame()->view();
1458 IntPoint contentsPoint = frameView->contentsToRootView(point);
1459 WebMouseEvent event(WebEvent::MouseDown, WebMouseEvent::RightButton, contentsPoint, contentsPoint, 0, 0, 0, 1, static_cast<WebEvent::Modifiers>(0), monotonicallyIncreasingTime());
1460 return handleContextMenuEvent(event);
1463 bool PDFPlugin::handleContextMenuEvent(const WebMouseEvent& event)
1465 FrameView* frameView = webFrame()->coreFrame()->view();
1466 IntPoint point = frameView->contentsToScreen(IntRect(frameView->windowToContents(event.position()), IntSize())).location();
1468 if (NSMenu *nsMenu = [m_pdfLayerController menuForEvent:nsEventForWebMouseEvent(event)]) {
1469 WKPopupContextMenu(nsMenu, point);
1476 bool PDFPlugin::handleKeyboardEvent(const WebKeyboardEvent& event)
1478 NSEventType eventType;
1480 if (!getEventTypeFromWebEvent(event, eventType))
1483 NSUInteger modifierFlags = modifierFlagsFromWebEvent(event);
1485 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()];
1487 switch (event.type()) {
1488 case WebEvent::KeyDown:
1489 return [m_pdfLayerController keyDown:fakeEvent];
1497 bool PDFPlugin::handleEditingCommand(const String& commandName, const String& argument)
1499 if (commandName == "copy")
1500 [m_pdfLayerController copySelection];
1501 else if (commandName == "selectAll")
1502 [m_pdfLayerController selectAll];
1503 else if (commandName == "takeFindStringFromSelection") {
1504 NSString *string = [m_pdfLayerController currentSelection].string;
1506 writeItemsToPasteboard(NSFindPboard, @[ [string dataUsingEncoding:NSUTF8StringEncoding] ], @[ NSPasteboardTypeString ]);
1512 bool PDFPlugin::isEditingCommandEnabled(const String& commandName)
1514 if (commandName == "copy" || commandName == "takeFindStringFromSelection")
1515 return [m_pdfLayerController currentSelection];
1517 if (commandName == "selectAll")
1523 void PDFPlugin::setScrollOffset(const IntPoint& offset)
1525 m_scrollOffset = IntSize(offset.x(), offset.y());
1527 [CATransaction begin];
1528 [m_pdfLayerController setScrollPosition:offset];
1530 if (m_activeAnnotation)
1531 m_activeAnnotation->updateGeometry();
1533 [CATransaction commit];
1536 void PDFPlugin::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
1538 if (scrollbar == horizontalScrollbar())
1539 [m_horizontalScrollbarLayer setNeedsDisplay];
1540 else if (scrollbar == verticalScrollbar())
1541 [m_verticalScrollbarLayer setNeedsDisplay];
1544 void PDFPlugin::invalidateScrollCornerRect(const IntRect& rect)
1546 [m_scrollCornerLayer setNeedsDisplay];
1549 bool PDFPlugin::isFullFramePlugin()
1551 // <object> or <embed> plugins will appear to be in their parent frame, so we have to
1552 // check whether our frame's widget is exactly our PluginView.
1553 Document* document = webFrame()->coreFrame()->document();
1554 return document->isPluginDocument() && static_cast<PluginDocument*>(document)->pluginWidget() == pluginView();
1557 bool PDFPlugin::handlesPageScaleFactor()
1559 return webFrame()->isMainFrame() && isFullFramePlugin();
1562 void PDFPlugin::clickedLink(NSURL *url)
1564 Frame* frame = webFrame()->coreFrame();
1566 RefPtr<Event> coreEvent;
1567 if (m_lastMouseEvent.type() != WebEvent::NoType)
1568 coreEvent = MouseEvent::create(eventNames().clickEvent, frame->document()->defaultView(), platform(m_lastMouseEvent), 0, 0);
1570 frame->loader().urlSelected(url, emptyString(), coreEvent.get(), LockHistory::No, LockBackForwardList::No, MaybeSendReferrer);
1573 void PDFPlugin::setActiveAnnotation(PDFAnnotation *annotation)
1575 if (!supportsForms())
1578 if (m_activeAnnotation)
1579 m_activeAnnotation->commit();
1582 if ([annotation isKindOfClass:pdfAnnotationTextWidgetClass()] && static_cast<PDFAnnotationTextWidget *>(annotation).isReadOnly) {
1583 m_activeAnnotation = 0;
1587 m_activeAnnotation = PDFPluginAnnotation::create(annotation, m_pdfLayerController.get(), this);
1588 m_activeAnnotation->attach(m_annotationContainer.get());
1590 m_activeAnnotation = 0;
1593 bool PDFPlugin::supportsForms()
1595 // 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.
1596 return isFullFramePlugin();
1599 void PDFPlugin::notifyContentScaleFactorChanged(CGFloat scaleFactor)
1601 if (handlesPageScaleFactor())
1602 pluginView()->setPageScaleFactor(scaleFactor, IntPoint());
1608 void PDFPlugin::notifyDisplayModeChanged(int)
1614 PassRefPtr<SharedBuffer> PDFPlugin::liveResourceData() const
1616 NSData *pdfData = liveData();
1621 return SharedBuffer::wrapNSData(pdfData);
1624 void PDFPlugin::saveToPDF()
1626 // FIXME: We should probably notify the user that they can't save before the document is finished loading.
1627 // PDFViewController does an NSBeep(), but that seems insufficient.
1631 NSData *data = liveData();
1632 webFrame()->page()->savePDFToFileInDownloadsFolder(m_suggestedFilename, webFrame()->url(), static_cast<const unsigned char *>([data bytes]), [data length]);
1635 void PDFPlugin::openWithNativeApplication()
1637 if (!m_temporaryPDFUUID) {
1638 // FIXME: We should probably notify the user that they can't save before the document is finished loading.
1639 // PDFViewController does an NSBeep(), but that seems insufficient.
1643 NSData *data = liveData();
1645 m_temporaryPDFUUID = WebCore::createCanonicalUUIDString();
1646 ASSERT(m_temporaryPDFUUID);
1648 webFrame()->page()->savePDFToTemporaryFolderAndOpenWithNativeApplication(m_suggestedFilename, webFrame()->url(), static_cast<const unsigned char *>([data bytes]), [data length], m_temporaryPDFUUID);
1652 webFrame()->page()->send(Messages::WebPageProxy::OpenPDFFromTemporaryFolderWithNativeApplication(m_temporaryPDFUUID));
1655 void PDFPlugin::writeItemsToPasteboard(NSString *pasteboardName, NSArray *items, NSArray *types)
1657 Vector<String> pasteboardTypes;
1659 for (NSString *type in types)
1660 pasteboardTypes.append(type);
1662 uint64_t newChangeCount;
1663 auto& webProcess = WebProcess::singleton();
1664 webProcess.parentProcessConnection()->sendSync(Messages::WebPasteboardProxy::SetPasteboardTypes(pasteboardName, pasteboardTypes),
1665 Messages::WebPasteboardProxy::SetPasteboardTypes::Reply(newChangeCount), 0);
1667 for (NSUInteger i = 0, count = items.count; i < count; ++i) {
1668 NSString *type = [types objectAtIndex:i];
1669 NSData *data = [items objectAtIndex:i];
1671 // We don't expect the data for any items to be empty, but aren't completely sure.
1672 // Avoid crashing in the SharedMemory constructor in release builds if we're wrong.
1673 ASSERT(data.length);
1677 if ([type isEqualToString:NSStringPboardType] || [type isEqualToString:NSPasteboardTypeString]) {
1678 RetainPtr<NSString> plainTextString = adoptNS([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
1679 webProcess.parentProcessConnection()->sendSync(Messages::WebPasteboardProxy::SetPasteboardStringForType(pasteboardName, type, plainTextString.get()), Messages::WebPasteboardProxy::SetPasteboardStringForType::Reply(newChangeCount), 0);
1681 RefPtr<SharedBuffer> buffer = SharedBuffer::wrapNSData(data);
1686 SharedMemory::Handle handle;
1687 RefPtr<SharedMemory> sharedMemory = SharedMemory::create(buffer->size());
1688 memcpy(sharedMemory->data(), buffer->data(), buffer->size());
1689 sharedMemory->createHandle(handle, SharedMemory::ReadOnly);
1690 webProcess.parentProcessConnection()->sendSync(Messages::WebPasteboardProxy::SetPasteboardBufferForType(pasteboardName, type, handle, buffer->size()), Messages::WebPasteboardProxy::SetPasteboardBufferForType::Reply(newChangeCount), 0);
1695 void PDFPlugin::showDefinitionForAttributedString(NSAttributedString *string, CGPoint point)
1697 DictionaryPopupInfo dictionaryPopupInfo;
1698 dictionaryPopupInfo.origin = convertFromPDFViewToRootView(IntPoint(point));
1699 dictionaryPopupInfo.attributedString.string = string;
1701 webFrame()->page()->send(Messages::WebPageProxy::DidPerformDictionaryLookup(dictionaryPopupInfo));
1704 unsigned PDFPlugin::countFindMatches(const String& target, WebCore::FindOptions options, unsigned maxMatchCount)
1706 if (!target.length())
1709 int nsOptions = (options & FindOptionsCaseInsensitive) ? NSCaseInsensitiveSearch : 0;
1711 return [[pdfDocument() findString:target withOptions:nsOptions] count];
1714 PDFSelection *PDFPlugin::nextMatchForString(const String& target, BOOL searchForward, BOOL caseSensitive, BOOL wrapSearch, PDFSelection *initialSelection, BOOL startInSelection)
1716 if (!target.length())
1719 NSStringCompareOptions options = 0;
1721 options |= NSBackwardsSearch;
1724 options |= NSCaseInsensitiveSearch;
1726 PDFDocument *document = pdfDocument().get();
1728 PDFSelection *selectionForInitialSearch = [initialSelection copy];
1729 if (startInSelection) {
1730 // Initially we want to include the selected text in the search. So we must modify the starting search
1731 // selection to fit PDFDocument's search requirements: selection must have a length >= 1, begin before
1732 // the current selection (if searching forwards) or after (if searching backwards).
1733 int initialSelectionLength = [[initialSelection string] length];
1734 if (searchForward) {
1735 [selectionForInitialSearch extendSelectionAtStart:1];
1736 [selectionForInitialSearch extendSelectionAtEnd:-initialSelectionLength];
1738 [selectionForInitialSearch extendSelectionAtEnd:1];
1739 [selectionForInitialSearch extendSelectionAtStart:-initialSelectionLength];
1743 PDFSelection *foundSelection = [document findString:target fromSelection:selectionForInitialSearch withOptions:options];
1744 [selectionForInitialSearch release];
1746 // If we first searched in the selection, and we found the selection, search again from just past the selection.
1747 if (startInSelection && [foundSelection isEqual:initialSelection])
1748 foundSelection = [document findString:target fromSelection:initialSelection withOptions:options];
1750 if (!foundSelection && wrapSearch)
1751 foundSelection = [document findString:target fromSelection:nil withOptions:options];
1753 return foundSelection;
1756 bool PDFPlugin::findString(const String& target, WebCore::FindOptions options, unsigned maxMatchCount)
1758 BOOL searchForward = !(options & FindOptionsBackwards);
1759 BOOL caseSensitive = !(options & FindOptionsCaseInsensitive);
1760 BOOL wrapSearch = options & FindOptionsWrapAround;
1762 unsigned matchCount;
1763 if (!maxMatchCount) {
1764 // If the max was zero, any result means we exceeded the max. We can skip computing the actual count.
1765 matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
1767 matchCount = countFindMatches(target, options, maxMatchCount);
1768 if (matchCount > maxMatchCount)
1769 matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
1772 if (target.isEmpty()) {
1773 PDFSelection* searchSelection = [m_pdfLayerController searchSelection];
1774 [m_pdfLayerController findString:target caseSensitive:caseSensitive highlightMatches:YES];
1775 [m_pdfLayerController setSearchSelection:searchSelection];
1776 m_lastFoundString = emptyString();
1780 if (m_lastFoundString == target) {
1781 PDFSelection *selection = nextMatchForString(target, searchForward, caseSensitive, wrapSearch, [m_pdfLayerController searchSelection], NO);
1785 [m_pdfLayerController setSearchSelection:selection];
1786 [m_pdfLayerController gotoSelection:selection];
1788 [m_pdfLayerController findString:target caseSensitive:caseSensitive highlightMatches:YES];
1789 m_lastFoundString = target;
1792 return matchCount > 0;
1795 bool PDFPlugin::performDictionaryLookupAtLocation(const WebCore::FloatPoint& point)
1797 IntPoint localPoint = convertFromRootViewToPlugin(roundedIntPoint(point));
1798 PDFSelection* lookupSelection = [m_pdfLayerController getSelectionForWordAtPoint:convertFromPluginToPDFView(localPoint)];
1800 if ([[lookupSelection string] length])
1801 [m_pdfLayerController searchInDictionaryWithSelection:lookupSelection];
1806 void PDFPlugin::focusNextAnnotation()
1808 [m_pdfLayerController activateNextAnnotation:false];
1811 void PDFPlugin::focusPreviousAnnotation()
1813 [m_pdfLayerController activateNextAnnotation:true];
1816 void PDFPlugin::notifySelectionChanged(PDFSelection *)
1818 webFrame()->page()->didChangeSelection();
1821 String PDFPlugin::getSelectionString() const
1823 return [[m_pdfLayerController currentSelection] string];
1826 void PDFPlugin::performWebSearch(NSString *string)
1828 webFrame()->page()->send(Messages::WebPageProxy::SearchTheWeb(string));
1831 void PDFPlugin::performSpotlightSearch(NSString *string)
1833 webFrame()->page()->send(Messages::WebPageProxy::SearchWithSpotlight(string));
1836 bool PDFPlugin::handleWheelEvent(const WebWheelEvent& event)
1838 PDFDisplayMode displayMode = [m_pdfLayerController displayMode];
1840 if (displayMode == kPDFDisplaySinglePageContinuous || displayMode == kPDFDisplayTwoUpContinuous)
1841 return ScrollableArea::handleWheelEvent(platform(event));
1843 NSUInteger currentPageIndex = [m_pdfLayerController currentPageIndex];
1844 bool inFirstPage = !currentPageIndex;
1845 bool inLastPage = [m_pdfLayerController lastPageIndex] == currentPageIndex;
1847 bool atScrollTop = !scrollPosition().y();
1848 bool atScrollBottom = scrollPosition().y() == maximumScrollPosition().y();
1850 bool inMomentumScroll = event.momentumPhase() != WebWheelEvent::PhaseNone;
1852 int scrollMagnitudeThresholdForPageFlip = defaultScrollMagnitudeThresholdForPageFlip;
1854 // Imprecise input devices should have a lower threshold so that "clicky" scroll wheels can flip pages.
1855 if (!event.hasPreciseScrollingDeltas())
1856 scrollMagnitudeThresholdForPageFlip = 0;
1858 if (atScrollBottom && !inLastPage && event.delta().height() < 0) {
1859 if (event.delta().height() <= -scrollMagnitudeThresholdForPageFlip && !inMomentumScroll)
1860 [m_pdfLayerController gotoNextPage];
1864 if (atScrollTop && !inFirstPage && event.delta().height() > 0) {
1865 if (event.delta().height() >= scrollMagnitudeThresholdForPageFlip && !inMomentumScroll) {
1866 [CATransaction begin];
1867 [m_pdfLayerController gotoPreviousPage];
1868 scrollToOffsetWithoutAnimation(maximumScrollPosition());
1869 [CATransaction commit];
1874 return ScrollableArea::handleWheelEvent(platform(event));
1877 NSData *PDFPlugin::liveData() const
1879 if (m_activeAnnotation)
1880 m_activeAnnotation->commit();
1882 // Save data straight from the resource instead of PDFKit if the document is
1883 // untouched by the user, so that PDFs which PDFKit can't display will still be downloadable.
1884 if (m_pdfDocumentWasMutated)
1885 return [m_pdfDocument dataRepresentation];
1890 NSObject *PDFPlugin::accessibilityObject() const
1892 return m_accessibilityObject.get();
1895 } // namespace WebKit
1897 #endif // ENABLE(PDFKIT_PLUGIN)