PDFPlugin.mm   [plain text]


/*
 * Copyright (C) 2009, 2011, 2012, 2015 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) && !USE(DEPRECATED_PDF_PLUGIN)

#import "ArgumentCoders.h"
#import "DataReference.h"
#import "PDFAnnotationTextWidgetDetails.h"
#import "PDFLayerControllerSPI.h"
#import "PDFPluginAnnotation.h"
#import "PDFPluginPasswordField.h"
#import "PluginView.h"
#import "WKAccessibilityWebPageObjectMac.h"
#import "WKPageFindMatchesClient.h"
#import "WebCoreArgumentCoders.h"
#import "WebEvent.h"
#import "WebEventConversion.h"
#import "WebPage.h"
#import "WebPageProxyMessages.h"
#import "WebPasteboardProxyMessages.h"
#import "WebProcess.h"
#import <JavaScriptCore/JSContextRef.h>
#import <JavaScriptCore/JSObjectRef.h>
#import <JavaScriptCore/JSStringRef.h>
#import <JavaScriptCore/JSStringRefCF.h>
#import <PDFKit/PDFKit.h>
#import <QuartzCore/QuartzCore.h>
#import <WebCore/ArchiveResource.h>
#import <WebCore/Chrome.h>
#import <WebCore/Cursor.h>
#import <WebCore/DictionaryLookup.h>
#import <WebCore/DocumentLoader.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/GraphicsLayerCA.h>
#import <WebCore/HTMLElement.h>
#import <WebCore/HTMLFormElement.h>
#import <WebCore/HTMLPlugInElement.h>
#import <WebCore/LocalizedStrings.h>
#import <WebCore/MainFrame.h>
#import <WebCore/MouseEvent.h>
#import <WebCore/Page.h>
#import <WebCore/PageOverlayController.h>
#import <WebCore/Pasteboard.h>
#import <WebCore/PlatformCAAnimationCocoa.h>
#import <WebCore/PluginData.h>
#import <WebCore/PluginDocument.h>
#import <WebCore/RenderBoxModelObject.h>
#import <WebCore/Settings.h>
#import <WebCore/UUID.h>
#import <WebKitSystemInterface.h>
#import <wtf/CurrentTime.h>

using namespace WebCore;

static const char* annotationStyle =
"body { "
"    background-color: rgb(146, 146, 146) !important;"
"} "
"#passwordContainer {"
"    display: -webkit-box; "
"    -webkit-box-align: center; "
"    -webkit-box-pack: center; "
"    position: fixed; "
"    top: 0; "
"    left: 0; "
"    right: 0; "
"    bottom: 0; "
"} "
".annotation { "
"    position: absolute; "
"    pointer-events: auto; "
"} "
"textarea.annotation { "
"    resize: none; "
"} "
"input.annotation[type='password'] { "
"    position: static; "
"    width: 200px; "
"    margin-top: 100px; "
"} ";

const double zoomButtonScaleMultiplier = 1.18920;

@interface WKPDFPluginAccessibilityObject : NSObject {
    PDFLayerController *_pdfLayerController;
    NSObject *_parent;
    WebKit::PDFPlugin* _pdfPlugin;
}

@property (assign) PDFLayerController *pdfLayerController;
@property (assign) NSObject *parent;
@property (assign) WebKit::PDFPlugin* pdfPlugin;

- (id)initWithPDFPlugin:(WebKit::PDFPlugin *)plugin;

@end

@implementation WKPDFPluginAccessibilityObject

@synthesize pdfLayerController=_pdfLayerController;
@synthesize parent=_parent;
@synthesize pdfPlugin=_pdfPlugin;

- (id)initWithPDFPlugin:(WebKit::PDFPlugin *)plugin
{
    if (!(self = [super init]))
        return nil;

    _pdfPlugin = plugin;

    return self;
}

- (BOOL)accessibilityIsIgnored
{
    return NO;
}

- (id)accessibilityAttributeValue:(NSString *)attribute
{
    if ([attribute isEqualToString:NSAccessibilityParentAttribute])
        return _parent;
    if ([attribute isEqualToString:NSAccessibilityValueAttribute])
        return [_pdfLayerController accessibilityValueAttribute];
    if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute])
        return [_pdfLayerController accessibilitySelectedTextAttribute];
    if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute])
        return [_pdfLayerController accessibilitySelectedTextRangeAttribute];
    if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute])
        return [_pdfLayerController accessibilityNumberOfCharactersAttribute];
    if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute])
        return [_pdfLayerController accessibilityVisibleCharacterRangeAttribute];
    if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute])
        return [_parent accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute];
    if ([attribute isEqualToString:NSAccessibilityRoleAttribute])
        return [_pdfLayerController accessibilityRoleAttribute];
    if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
        return [_pdfLayerController accessibilityRoleDescriptionAttribute];
    if ([attribute isEqualToString:NSAccessibilityWindowAttribute])
        return [_parent accessibilityAttributeValue:NSAccessibilityWindowAttribute];
    if ([attribute isEqualToString:NSAccessibilitySizeAttribute])
        return [NSValue valueWithSize:_pdfPlugin->boundsOnScreen().size()];
    if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
        return [_parent accessibilityAttributeValue:NSAccessibilityFocusedAttribute];
    if ([attribute isEqualToString:NSAccessibilityEnabledAttribute])
        return [_parent accessibilityAttributeValue:NSAccessibilityEnabledAttribute];
    if ([attribute isEqualToString:NSAccessibilityPositionAttribute])
        return [NSValue valueWithPoint:_pdfPlugin->boundsOnScreen().location()];

    return 0;
}

- (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter
{
    if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) {
        NSRect boundsInPDFViewCoordinates = [[_pdfLayerController accessibilityBoundsForRangeAttributeForParameter:parameter] rectValue];
        NSRect boundsInScreenCoordinates = _pdfPlugin->convertFromPDFViewToScreen(boundsInPDFViewCoordinates);
        return [NSValue valueWithRect:boundsInScreenCoordinates];
    }

    if ([attribute isEqualToString:NSAccessibilityLineForIndexParameterizedAttribute])
        return [_pdfLayerController accessibilityLineForIndexAttributeForParameter:parameter];
    if ([attribute isEqualToString:NSAccessibilityRangeForLineParameterizedAttribute])
        return [_pdfLayerController accessibilityRangeForLineAttributeForParameter:parameter];
    if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute])
        return [_pdfLayerController accessibilityStringForRangeAttributeForParameter:parameter];

    return 0;
}

- (CPReadingModel *)readingModel
{
    return [_pdfLayerController readingModel];
}

- (NSArray *)accessibilityAttributeNames
{
    static NSArray *attributeNames = 0;

    if (!attributeNames) {
        attributeNames = @[NSAccessibilityValueAttribute,
            NSAccessibilitySelectedTextAttribute,
            NSAccessibilitySelectedTextRangeAttribute,
            NSAccessibilityNumberOfCharactersAttribute,
            NSAccessibilityVisibleCharacterRangeAttribute,
            NSAccessibilityParentAttribute,
            NSAccessibilityRoleAttribute,
            NSAccessibilityWindowAttribute,
            NSAccessibilityTopLevelUIElementAttribute,
            NSAccessibilityRoleDescriptionAttribute,
            NSAccessibilitySizeAttribute,
            NSAccessibilityFocusedAttribute,
            NSAccessibilityEnabledAttribute,
            NSAccessibilityPositionAttribute];
        [attributeNames retain];
    }

    return attributeNames;
}

- (NSArray *)accessibilityActionNames
{
    static NSArray *actionNames = 0;
    
    if (!actionNames)
        actionNames = [[NSArray arrayWithObject:NSAccessibilityShowMenuAction] retain];
    
    return actionNames;
}

- (void)accessibilityPerformAction:(NSString *)action
{
    if ([action isEqualToString:NSAccessibilityShowMenuAction])
        _pdfPlugin->showContextMenuAtPoint(IntRect(IntPoint(), _pdfPlugin->size()).center());
}

- (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute
{
    return [_pdfLayerController accessibilityIsAttributeSettable:attribute];
}

- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute
{
    return [_pdfLayerController accessibilitySetValue:value forAttribute:attribute];
}

- (NSArray *)accessibilityParameterizedAttributeNames
{
    return [_pdfLayerController accessibilityParameterizedAttributeNames];
}

- (id)accessibilityFocusedUIElement
{
    return self;
}

- (id)accessibilityHitTest:(NSPoint)point
{
    return self;
}

@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)pdfLayerController:(PDFLayerController *)pdfLayerController scrollToPoint:(CGPoint)newPosition
{
    _pdfPlugin->scrollToPoint(IntPoint(newPosition));
}

- (void)pdfLayerController:(PDFLayerController *)pdfLayerController copyItems:(NSArray *)items withTypes:(NSArray *)types
{
    _pdfPlugin->writeItemsToPasteboard(NSGeneralPboard, items, types);
}

- (void)pdfLayerController:(PDFLayerController *)pdfLayerController showDefinitionForAttributedString:(NSAttributedString *)string atPoint:(CGPoint)point
{
    _pdfPlugin->showDefinitionForAttributedString(string, point);
}

- (void)pdfLayerController:(PDFLayerController *)pdfLayerController performWebSearchForString:(NSString *)string
{
    _pdfPlugin->performWebSearch(string);
}

- (void)pdfLayerController:(PDFLayerController *)pdfLayerController performSpotlightSearchForString:(NSString *)string
{
    _pdfPlugin->performSpotlightSearch(string);
}

- (void)pdfLayerControllerOpenWithNativeApplication:(PDFLayerController *)pdfLayerController
{
    _pdfPlugin->openWithNativeApplication();
}

- (void)pdfLayerControllerSaveToPDF:(PDFLayerController *)pdfLayerController
{
    _pdfPlugin->saveToPDF();
}

- (void)pdfLayerController:(PDFLayerController *)pdfLayerController didClickLinkWithURL:(NSURL *)url
{
    _pdfPlugin->clickedLink(url);
}

- (void)pdfLayerController:(PDFLayerController *)pdfLayerController didChangeActiveAnnotation:(PDFAnnotation *)annotation
{
    _pdfPlugin->setActiveAnnotation(annotation);
}

- (void)pdfLayerController:(PDFLayerController *)pdfLayerController didChangeDisplayMode:(int)mode
{
    _pdfPlugin->notifyDisplayModeChanged(mode);
}

- (void)pdfLayerController:(PDFLayerController *)pdfLayerController didChangeSelection:(PDFSelection *)selection
{
    _pdfPlugin->notifySelectionChanged(selection);
}

- (void)pdfLayerController:(PDFLayerController *)pdfLayerController invalidateRect:(CGRect)rect
{
    _pdfPlugin->invalidatePDFRect(enclosingIntRect(rect));
}

- (void)pdfLayerControllerInvalidateHUD:(PDFLayerController *)pdfLayerController
{
    _pdfPlugin->invalidateHUD();
}

- (void)pdfLayerControllerZoomIn:(PDFLayerController *)pdfLayerController
{
    _pdfPlugin->zoomIn();
}

- (void)pdfLayerControllerZoomOut:(PDFLayerController *)pdfLayerController
{
    _pdfPlugin->zoomOut();
}

@end

@interface WKPDFHUDAnimationDelegate : NSObject {
    std::function<void (bool)> _completionHandler;
}
@end

@implementation WKPDFHUDAnimationDelegate

- (id)initWithAnimationCompletionHandler:(std::function<void (bool)>)completionHandler
{
    if (!(self = [super init]))
        return nil;

    _completionHandler = WTFMove(completionHandler);

    return self;
}

- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
    _completionHandler(flag);
}

@end

@interface PDFViewLayout
- (NSPoint)convertPoint:(NSPoint)point toPage:(PDFPage *)page forScaleFactor:(CGFloat)scaleFactor;
- (NSRect)convertRect:(NSRect)rect fromPage:(PDFPage *) page forScaleFactor:(CGFloat) scaleFactor;
- (PDFPage *)pageNearestPoint:(NSPoint)point currentPage:(PDFPage *)currentPage;
@end

static const char* postScriptMIMEType = "application/postscript";
const uint64_t pdfDocumentRequestID = 1; // PluginController supports loading multiple streams, but we only need one for PDF.

static void appendValuesInPDFNameSubtreeToVector(CGPDFDictionaryRef subtree, Vector<CGPDFObjectRef>& values)
{
    CGPDFArrayRef names;
    if (CGPDFDictionaryGetArray(subtree, "Names", &names)) {
        size_t nameCount = CGPDFArrayGetCount(names) / 2;
        for (size_t i = 0; i < nameCount; ++i) {
            CGPDFObjectRef object;
            CGPDFArrayGetObject(names, 2 * i + 1, &object);
            values.append(object);
        }
        return;
    }

    CGPDFArrayRef kids;
    if (!CGPDFDictionaryGetArray(subtree, "Kids", &kids))
        return;

    size_t kidCount = CGPDFArrayGetCount(kids);
    for (size_t i = 0; i < kidCount; ++i) {
        CGPDFDictionaryRef kid;
        if (!CGPDFArrayGetDictionary(kids, i, &kid))
            continue;
        appendValuesInPDFNameSubtreeToVector(kid, values);
    }
}

static void getAllValuesInPDFNameTree(CGPDFDictionaryRef tree, Vector<CGPDFObjectRef>& allValues)
{
    appendValuesInPDFNameSubtreeToVector(tree, allValues);
}

static void getAllScriptsInPDFDocument(CGPDFDocumentRef pdfDocument, Vector<RetainPtr<CFStringRef>>& scripts)
{
    if (!pdfDocument)
        return;

    CGPDFDictionaryRef pdfCatalog = CGPDFDocumentGetCatalog(pdfDocument);
    if (!pdfCatalog)
        return;

    // Get the dictionary of all document-level name trees.
    CGPDFDictionaryRef namesDictionary;
    if (!CGPDFDictionaryGetDictionary(pdfCatalog, "Names", &namesDictionary))
        return;

    // Get the document-level "JavaScript" name tree.
    CGPDFDictionaryRef javaScriptNameTree;
    if (!CGPDFDictionaryGetDictionary(namesDictionary, "JavaScript", &javaScriptNameTree))
        return;

    // The names are arbitrary. We are only interested in the values.
    Vector<CGPDFObjectRef> objects;
    getAllValuesInPDFNameTree(javaScriptNameTree, objects);
    size_t objectCount = objects.size();

    for (size_t i = 0; i < objectCount; ++i) {
        CGPDFDictionaryRef javaScriptAction;
        if (!CGPDFObjectGetValue(reinterpret_cast<CGPDFObjectRef>(objects[i]), kCGPDFObjectTypeDictionary, &javaScriptAction))
            continue;

        // A JavaScript action must have an action type of "JavaScript".
        const char* actionType;
        if (!CGPDFDictionaryGetName(javaScriptAction, "S", &actionType) || strcmp(actionType, "JavaScript"))
            continue;

        const UInt8* bytes = 0;
        CFIndex length;
        CGPDFStreamRef stream;
        CGPDFStringRef string;
        RetainPtr<CFDataRef> data;
        if (CGPDFDictionaryGetStream(javaScriptAction, "JS", &stream)) {
            CGPDFDataFormat format;
            data = adoptCF(CGPDFStreamCopyData(stream, &format));
            if (!data)
                continue;
            bytes = CFDataGetBytePtr(data.get());
            length = CFDataGetLength(data.get());
        } else if (CGPDFDictionaryGetString(javaScriptAction, "JS", &string)) {
            bytes = CGPDFStringGetBytePtr(string);
            length = CGPDFStringGetLength(string);
        }
        if (!bytes)
            continue;

        CFStringEncoding encoding = (length > 1 && bytes[0] == 0xFE && bytes[1] == 0xFF) ? kCFStringEncodingUnicode : kCFStringEncodingUTF8;
        RetainPtr<CFStringRef> script = adoptCF(CFStringCreateWithBytes(kCFAllocatorDefault, bytes, length, encoding, true));
        if (!script)
            continue;
        
        scripts.append(script);
    }
}

namespace WebKit {

using namespace HTMLNames;

Ref<PDFPlugin> PDFPlugin::create(WebFrame* frame)
{
    return adoptRef(*new PDFPlugin(frame));
}

PDFPlugin::PDFPlugin(WebFrame* frame)
    : Plugin(PDFPluginType)
    , m_frame(frame)
    , m_pdfLayerController(adoptNS([[pdfLayerControllerClass() alloc] init]))
    , m_pdfLayerControllerDelegate(adoptNS([[WKPDFLayerControllerDelegate alloc] initWithPDFPlugin:this]))
    , m_HUD(*this)
{
    [m_pdfLayerController setDelegate:m_pdfLayerControllerDelegate.get()];

    if (supportsForms()) {
        Document* document = webFrame()->coreFrame()->document();

        Ref<Element> annotationStyleElement = document->createElement(styleTag, false);
        annotationStyleElement->setTextContent(annotationStyle, ASSERT_NO_EXCEPTION);

        document->bodyOrFrameset()->appendChild(annotationStyleElement.get());
    }

    m_accessibilityObject = adoptNS([[WKPDFPluginAccessibilityObject alloc] initWithPDFPlugin:this]);
    [m_accessibilityObject setPdfLayerController:m_pdfLayerController.get()];
    [m_accessibilityObject setParent:webFrame()->page()->accessibilityRemoteObject()];
}

PDFPlugin::~PDFPlugin()
{
}

PluginInfo PDFPlugin::pluginInfo()
{
    PluginInfo info;
    info.name = builtInPDFPluginName();
    info.isApplicationPlugin = true;
    info.clientLoadPolicy = PluginLoadClientPolicyUndefined;

    MimeClassInfo pdfMimeClassInfo;
    pdfMimeClassInfo.type = "application/pdf";
    pdfMimeClassInfo.desc = pdfDocumentTypeDescription();
    pdfMimeClassInfo.extensions.append("pdf");
    info.mimes.append(pdfMimeClassInfo);

    MimeClassInfo textPDFMimeClassInfo;
    textPDFMimeClassInfo.type = "text/pdf";
    textPDFMimeClassInfo.desc = pdfDocumentTypeDescription();
    textPDFMimeClassInfo.extensions.append("pdf");
    info.mimes.append(textPDFMimeClassInfo);

    MimeClassInfo postScriptMimeClassInfo;
    postScriptMimeClassInfo.type = postScriptMIMEType;
    postScriptMimeClassInfo.desc = postScriptDocumentTypeDescription();
    postScriptMimeClassInfo.extensions.append("ps");
    info.mimes.append(postScriptMimeClassInfo);
    
    return info;
}

PluginView* PDFPlugin::pluginView()
{
    return static_cast<PluginView*>(controller());
}

const PluginView* PDFPlugin::pluginView() const
{
    return static_cast<const PluginView*>(controller());
}

void PDFPlugin::addArchiveResource()
{
    // FIXME: It's a hack to force add a resource to DocumentLoader. PDF documents should just be fetched as CachedResources.

    // Add just enough data for context menu handling and web archives to work.
    NSDictionary* headers = @{ @"Content-Disposition": (NSString *)m_suggestedFilename, @"Content-Type" : @"application/pdf" };
    RetainPtr<NSURLResponse> response = adoptNS([[NSHTTPURLResponse alloc] initWithURL:m_sourceURL statusCode:200 HTTPVersion:(NSString*)kCFHTTPVersion1_1 headerFields:headers]);
    ResourceResponse synthesizedResponse(response.get());

    RefPtr<ArchiveResource> resource = ArchiveResource::create(SharedBuffer::wrapCFData(m_data.get()), m_sourceURL, "application/pdf", String(), String(), synthesizedResponse);
    pluginView()->frame()->document()->loader()->addArchiveResource(resource.release());
}

static void jsPDFDocInitialize(JSContextRef ctx, JSObjectRef object)
{
    PDFPlugin* pdfView = static_cast<PDFPlugin*>(JSObjectGetPrivate(object));
    pdfView->ref();
}

static void jsPDFDocFinalize(JSObjectRef object)
{
    PDFPlugin* pdfView = static_cast<PDFPlugin*>(JSObjectGetPrivate(object));
    pdfView->deref();
}

JSValueRef PDFPlugin::jsPDFDocPrint(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
    PDFPlugin* pdfView = static_cast<PDFPlugin*>(JSObjectGetPrivate(thisObject));

    WebFrame* frame = pdfView->m_frame;
    if (!frame)
        return JSValueMakeUndefined(ctx);

    Frame* coreFrame = frame->coreFrame();
    if (!coreFrame)
        return JSValueMakeUndefined(ctx);

    Page* page = coreFrame->page();
    if (!page)
        return JSValueMakeUndefined(ctx);

    page->chrome().print(coreFrame);

    return JSValueMakeUndefined(ctx);
}

JSObjectRef PDFPlugin::makeJSPDFDoc(JSContextRef ctx)
{
    static JSStaticFunction jsPDFDocStaticFunctions[] = {
        { "print", jsPDFDocPrint, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
        { 0, 0, 0 },
    };

    static JSClassDefinition jsPDFDocClassDefinition = {
        0,
        kJSClassAttributeNone,
        "Doc",
        0,
        0,
        jsPDFDocStaticFunctions,
        jsPDFDocInitialize, jsPDFDocFinalize, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };

    static JSClassRef jsPDFDocClass = JSClassCreate(&jsPDFDocClassDefinition);

    return JSObjectMake(ctx, jsPDFDocClass, this);
}

static RetainPtr<CFMutableDataRef> convertPostScriptDataToPDF(RetainPtr<CFDataRef> postScriptData)
{
    // Convert PostScript to PDF using the Quartz 2D API.
    // http://developer.apple.com/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_ps_convert/chapter_16_section_1.html

    CGPSConverterCallbacks callbacks = { 0, 0, 0, 0, 0, 0, 0, 0 };
    RetainPtr<CGPSConverterRef> converter = adoptCF(CGPSConverterCreate(0, &callbacks, 0));
    RetainPtr<CGDataProviderRef> provider = adoptCF(CGDataProviderCreateWithCFData(postScriptData.get()));
    RetainPtr<CFMutableDataRef> pdfData = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
    RetainPtr<CGDataConsumerRef> consumer = adoptCF(CGDataConsumerCreateWithCFData(pdfData.get()));
    
    CGPSConverterConvert(converter.get(), provider.get(), consumer.get(), 0);
    
    return pdfData;
}

void PDFPlugin::convertPostScriptDataIfNeeded()
{
    if (!m_isPostScript)
        return;

    m_suggestedFilename = String(m_suggestedFilename + ".pdf");
    m_data = convertPostScriptDataToPDF(m_data);
}

void PDFPlugin::pdfDocumentDidLoad()
{
    addArchiveResource();
    
    RetainPtr<PDFDocument> document = adoptNS([[pdfDocumentClass() alloc] initWithData:rawData()]);

    setPDFDocument(document);

    [m_pdfLayerController setFrameSize:webFrame()->coreFrame()->view()->visibleContentRect().size()]; /// ???
    [m_pdfLayerController setDocument:document.get()];

    calculateSizes();

    runScriptsInPDFDocument();

    if (isLocked())
        createPasswordEntryForm();

    m_HUD.setVisible(!isLocked(), HUD::AnimateVisibilityTransition::No);
}

bool PDFPlugin::isLocked() const
{
    return [m_pdfDocument isLocked];
}

void PDFPlugin::streamDidReceiveResponse(uint64_t streamID, const URL&, uint32_t, uint32_t, const String& mimeType, const String&, const String& suggestedFilename)
{
    ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);

    m_suggestedFilename = suggestedFilename;

    if (equalIgnoringASCIICase(mimeType, postScriptMIMEType))
        m_isPostScript = true;
}

void PDFPlugin::streamDidReceiveData(uint64_t streamID, const char* bytes, int length)
{
    ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);

    if (!m_data)
        m_data = adoptCF(CFDataCreateMutable(0, 0));

    CFDataAppendBytes(m_data.get(), reinterpret_cast<const UInt8*>(bytes), length);
}

void PDFPlugin::streamDidFinishLoading(uint64_t streamID)
{
    ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);

    convertPostScriptDataIfNeeded();
    pdfDocumentDidLoad();
}

void PDFPlugin::streamDidFail(uint64_t streamID, bool wasCancelled)
{
    ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);

    m_data.clear();
}

void PDFPlugin::manualStreamDidReceiveResponse(const URL& responseURL, uint32_t streamLength,  uint32_t lastModifiedTime, const String& mimeType, const String& headers, const String& suggestedFilename)
{
    m_suggestedFilename = suggestedFilename;

    if (equalIgnoringASCIICase(mimeType, postScriptMIMEType))
        m_isPostScript = true;
}

void PDFPlugin::manualStreamDidReceiveData(const char* bytes, int length)
{
    if (!m_data)
        m_data = adoptCF(CFDataCreateMutable(0, 0));

    CFDataAppendBytes(m_data.get(), reinterpret_cast<const UInt8*>(bytes), length);
}

void PDFPlugin::manualStreamDidFinishLoading()
{
    convertPostScriptDataIfNeeded();
    pdfDocumentDidLoad();
}

void PDFPlugin::manualStreamDidFail(bool)
{
    m_data.clear();
}

void PDFPlugin::runScriptsInPDFDocument()
{
    Vector<RetainPtr<CFStringRef>> scripts;
    getAllScriptsInPDFDocument([m_pdfDocument documentRef], scripts);

    size_t scriptCount = scripts.size();
    if (!scriptCount)
        return;

    JSGlobalContextRef ctx = JSGlobalContextCreate(0);
    JSObjectRef jsPDFDoc = makeJSPDFDoc(ctx);

    for (size_t i = 0; i < scriptCount; ++i) {
        JSStringRef script = JSStringCreateWithCFString(scripts[i].get());
        JSEvaluateScript(ctx, script, jsPDFDoc, 0, 0, 0);
        JSStringRelease(script);
    }
    
    JSGlobalContextRelease(ctx);
}

void PDFPlugin::invalidatePDFRect(IntRect rect)
{
    Widget* widget = pluginView();

    // FIXME: One of the conversion functions should do this (with the flipping).
    IntRect invalidRect = rect;
    invalidRect.setY(m_pdfDocumentSize.height() - invalidRect.y() - invalidRect.height());

    widget->invalidateRect(invalidRect);
}

void PDFPlugin::invalidateHUD()
{
    m_HUD.invalidate();
}

void PDFPlugin::createPasswordEntryForm()
{
    if (!supportsForms())
        return;

    Document* document = webFrame()->coreFrame()->document();
    m_passwordContainer = document->createElement(divTag, false);
    m_passwordContainer->setAttributeWithoutSynchronization(idAttr, AtomicString("passwordContainer", AtomicString::ConstructFromLiteral));

    m_passwordField = PDFPluginPasswordField::create(m_pdfLayerController.get(), this);
    m_passwordField->attach(m_passwordContainer.get());
    document->bodyOrFrameset()->appendChild(*m_passwordContainer);
}

void PDFPlugin::attemptToUnlockPDF(const String& password)
{
    [m_pdfLayerController attemptToUnlockWithPassword:password];

    if (!isLocked()) {
        m_passwordContainer = nullptr;
        m_passwordField = nullptr;

        calculateSizes();

        m_HUD.setVisible(true, HUD::AnimateVisibilityTransition::Yes);
    }
}

void PDFPlugin::calculateSizes()
{
    setPDFDocumentSize(isLocked() ? IntSize() : IntSize([m_pdfLayerController contentSizeRespectingZoom]));

    // We have to asynchronously update styles because we could be inside layout.
    RefPtr<PDFPlugin> reffedThis = this;
    dispatch_async(dispatch_get_main_queue(), [reffedThis] {
        reffedThis->didCalculateSizes();
    });
}

void PDFPlugin::didCalculateSizes()
{
    HTMLPlugInElement* pluginElement = downcast<PluginDocument>(*webFrame()->coreFrame()->document()).pluginElement();

    if (isLocked()) {
        pluginElement->setInlineStyleProperty(CSSPropertyWidth, 100, CSSPrimitiveValue::CSS_PERCENTAGE);
        pluginElement->setInlineStyleProperty(CSSPropertyHeight, 100, CSSPrimitiveValue::CSS_PERCENTAGE);
        return;
    }

    pluginElement->setInlineStyleProperty(CSSPropertyWidth, m_pdfDocumentSize.width(), CSSPrimitiveValue::CSS_PX);
    pluginElement->setInlineStyleProperty(CSSPropertyHeight, m_pdfDocumentSize.height(), CSSPrimitiveValue::CSS_PX);

    // FIXME: Can't do this in the overflow/subframe case. Where does scroll-snap-type go?
    if (m_usingContinuousMode || !m_frame->isMainFrame() || !isFullFramePlugin())
        return;

    RetainPtr<NSArray> pageRects = [m_pdfLayerController pageRects];

    StringBuilder coordinates;
    for (NSValue *rect in pageRects.get()) {
        // FIXME: Why 4?
        coordinates.appendNumber((long)[rect rectValue].origin.y / 4);
        coordinates.appendLiteral("px ");
    }

    pluginElement->setInlineStyleProperty(CSSPropertyWebkitScrollSnapCoordinate, coordinates.toString());

    Document* document = webFrame()->coreFrame()->document();
    document->bodyOrFrameset()->setInlineStyleProperty(CSSPropertyWebkitScrollSnapType, "mandatory");
}

bool PDFPlugin::initialize(const Parameters& parameters)
{
    m_sourceURL = parameters.url;
    if (!parameters.shouldUseManualLoader && !parameters.url.isEmpty())
        controller()->loadURL(pdfDocumentRequestID, "GET", parameters.url.string(), String(), HTTPHeaderMap(), Vector<uint8_t>(), false);

    controller()->didInitializePlugin();
    return true;
}

void PDFPlugin::destroy()
{
    [m_pdfLayerController setDelegate:nil];

    m_activeAnnotation = nullptr;
}

void PDFPlugin::paint(GraphicsContext& context, const IntRect& dirtyRectInWindowCoordinates)
{
    context.scale(FloatSize(1, -1));
    context.translate(0, -m_pdfDocumentSize.height());
    [m_pdfLayerController drawInContext:context.platformContext()];
}

RefPtr<ShareableBitmap> PDFPlugin::snapshot()
{
    return nullptr;
}

IntPoint PDFPlugin::convertFromPluginToPDFView(const IntPoint& point) const
{
    return IntPoint(point.x(), m_size.height() - point.y());
}

IntPoint PDFPlugin::convertFromRootViewToPlugin(const IntPoint& point) const
{
    return m_rootViewToPluginTransform.mapPoint(point);
}

IntPoint PDFPlugin::convertFromPDFViewToRootView(const IntPoint& point) const
{
    IntPoint pointInPluginCoordinates(point.x(), m_size.height() - point.y());
    return m_pluginToRootViewTransform.mapPoint(pointInPluginCoordinates);
}

FloatRect PDFPlugin::convertFromPDFViewToScreen(const FloatRect& rect) const
{
    FrameView* frameView = webFrame()->coreFrame()->view();

    if (!frameView)
        return FloatRect();

    FloatPoint originInPluginCoordinates(rect.x(), m_size.height() - rect.y() - rect.height());
    FloatRect rectInRootViewCoordinates = m_pluginToRootViewTransform.mapRect(FloatRect(originInPluginCoordinates, rect.size()));

    return frameView->contentsToScreen(enclosingIntRect(rectInRootViewCoordinates));
}

IntRect PDFPlugin::boundsOnScreen() const
{
    FrameView* frameView = webFrame()->coreFrame()->view();

    if (!frameView)
        return IntRect();

    FloatRect bounds = FloatRect(FloatPoint(), size());
    FloatRect rectInRootViewCoordinates = m_pluginToRootViewTransform.mapRect(bounds);
    return frameView->contentsToScreen(enclosingIntRect(rectInRootViewCoordinates));
}

void PDFPlugin::geometryDidChange(const IntSize& pluginSize, const IntRect&, const AffineTransform& pluginToRootViewTransform)
{
    m_pluginToRootViewTransform = pluginToRootViewTransform;
    m_rootViewToPluginTransform = pluginToRootViewTransform.inverse().valueOr(AffineTransform());
    m_size = pluginSize;

    FrameView* frameView = webFrame()->coreFrame()->view();

    [m_pdfLayerController setFrameSize:frameView->frameRect().size()];
    [m_pdfLayerController setVisibleRect:frameView->visibleContentRect()];

    calculateSizes();

    if (m_activeAnnotation)
        m_activeAnnotation->updateGeometry();
}

void PDFPlugin::frameDidFinishLoading(uint64_t)
{
    ASSERT_NOT_REACHED();
}

void PDFPlugin::frameDidFail(uint64_t, bool)
{
    ASSERT_NOT_REACHED();
}

void PDFPlugin::didEvaluateJavaScript(uint64_t, const WTF::String&)
{
    ASSERT_NOT_REACHED();
}

static NSUInteger modifierFlagsFromWebEvent(const WebEvent& event)
{
    return (event.shiftKey() ? NSShiftKeyMask : 0)
        | (event.controlKey() ? NSControlKeyMask : 0)
        | (event.altKey() ? NSAlternateKeyMask : 0)
        | (event.metaKey() ? NSCommandKeyMask : 0);
}
    
static bool getEventTypeFromWebEvent(const WebEvent& event, NSEventType& eventType)
{
    switch (event.type()) {
    case WebEvent::MouseDown:
        switch (static_cast<const WebMouseEvent&>(event).button()) {
        case WebMouseEvent::LeftButton:
            eventType = NSLeftMouseDown;
            return true;
        case WebMouseEvent::RightButton:
            eventType = NSRightMouseDown;
            return true;
        default:
            return false;
        }
    case WebEvent::MouseUp:
        switch (static_cast<const WebMouseEvent&>(event).button()) {
        case WebMouseEvent::LeftButton:
            eventType = NSLeftMouseUp;
            return true;
        case WebMouseEvent::RightButton:
            eventType = NSRightMouseUp;
            return true;
        default:
            return false;
        }
    case WebEvent::MouseMove:
        switch (static_cast<const WebMouseEvent&>(event).button()) {
        case WebMouseEvent::LeftButton:
            eventType = NSLeftMouseDragged;
            return true;
        case WebMouseEvent::RightButton:
            eventType = NSRightMouseDragged;
            return true;
        case WebMouseEvent::NoButton:
            eventType = NSMouseMoved;
            return true;
        default:
            return false;
        }
    default:
        return false;
    }
}
    
NSEvent *PDFPlugin::nsEventForWebMouseEvent(const WebMouseEvent& event)
{
    m_lastMousePositionInPluginCoordinates = convertFromRootViewToPlugin(event.position());
    IntPoint positionInPDFViewCoordinates(convertFromPluginToPDFView(m_lastMousePositionInPluginCoordinates));

    NSEventType eventType;

    if (!getEventTypeFromWebEvent(event, eventType))
        return nullptr;

    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, UpdateCursor mode)
{
    // FIXME: Should have a hand for links, and something special for annotations.

    HitTestResult hitTestResult = HitTestResult::None;

    IntPoint positionInPDFViewCoordinates(convertFromPluginToPDFView(m_lastMousePositionInPluginCoordinates));
    if (m_HUD.containsPointInRootView(event.position()))
        hitTestResult = HitTestResult::HUD;
    else {
        PDFSelection *selectionUnderMouse = [m_pdfLayerController getSelectionForWordAtPoint:positionInPDFViewCoordinates];
        if (selectionUnderMouse && [[selectionUnderMouse string] length])
            hitTestResult = HitTestResult::Text;
    }

    if (hitTestResult == m_lastHitTestResult && mode == UpdateCursor::IfNeeded)
        return;

    const Cursor& cursor = [hitTestResult] {
        switch (hitTestResult) {
        case HitTestResult::None:
            return pointerCursor();
        case HitTestResult::Text:
            return iBeamCursor();
        case HitTestResult::HUD:
            return pointerCursor();
        };
    }();

    webFrame()->page()->send(Messages::WebPageProxy::SetCursor(cursor));
    m_lastHitTestResult = hitTestResult;
}

bool PDFPlugin::handleMouseEvent(const WebMouseEvent& event)
{
    PlatformMouseEvent platformEvent = platform(event);

    m_lastMouseEvent = event;

    if (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;

    if (event.button() != WebMouseEvent::LeftButton)
        return false;

    NSEvent *nsEvent = nsEventForWebMouseEvent(event);

    switch (event.type()) {
    case WebEvent::MouseMove:
        updateCursor(event);
        [m_pdfLayerController mouseDragged:nsEvent];
        return true;
    case WebEvent::MouseDown:
        [m_pdfLayerController mouseDown:nsEvent];
        return true;
    case WebEvent::MouseUp:
        [m_pdfLayerController mouseUp:nsEvent];
        return true;
    default:
        break;
    }

    return false;
}

bool PDFPlugin::handleMouseEnterEvent(const WebMouseEvent& event)
{
    updateCursor(event, UpdateCursor::Force);
    return false;
}

bool PDFPlugin::handleMouseLeaveEvent(const WebMouseEvent&)
{
    return false;
}
    
bool PDFPlugin::showContextMenuAtPoint(const IntPoint& point)
{
    FrameView* frameView = webFrame()->coreFrame()->view();
    IntPoint contentsPoint = frameView->contentsToRootView(point);
    WebMouseEvent event(WebEvent::MouseDown, WebMouseEvent::RightButton, contentsPoint, contentsPoint, 0, 0, 0, 1, static_cast<WebEvent::Modifiers>(0), monotonicallyIncreasingTime(), ForceAtClick);
    return handleContextMenuEvent(event);
}

bool PDFPlugin::handleContextMenuEvent(const WebMouseEvent& event)
{
    FrameView* frameView = webFrame()->coreFrame()->view();
    IntPoint point = frameView->contentsToScreen(IntRect(frameView->windowToContents(event.position()), IntSize())).location();
    
    if (NSMenu *nsMenu = [m_pdfLayerController menuForEvent:nsEventForWebMouseEvent(event)]) {
        WKPopupContextMenu(nsMenu, point);
        return true;
    }
    
    return false;
}
    
bool PDFPlugin::handleEditingCommand(const String& commandName, const String& argument)
{
    if (commandName == "copy")
        [m_pdfLayerController copySelection];
    else if (commandName == "selectAll")
        [m_pdfLayerController selectAll];
    else if (commandName == "takeFindStringFromSelection") {
        NSString *string = [m_pdfLayerController currentSelection].string;
        if (string.length)
            writeItemsToPasteboard(NSFindPboard, @[ [string dataUsingEncoding:NSUTF8StringEncoding] ], @[ NSPasteboardTypeString ]);
    }

    return true;
}

bool PDFPlugin::isEditingCommandEnabled(const String& commandName)
{
    if (commandName == "copy" || commandName == "takeFindStringFromSelection")
        return [m_pdfLayerController currentSelection];

    if (commandName == "selectAll")
        return true;

    return false;
}

bool PDFPlugin::isFullFramePlugin() const
{
    // <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();
}

void PDFPlugin::clickedLink(NSURL *url)
{
    URL coreURL = url;
    if (protocolIsJavaScript(coreURL))
        return;

    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(coreURL, emptyString(), coreEvent.get(), LockHistory::No, LockBackForwardList::No, MaybeSendReferrer, ShouldOpenExternalURLsPolicy::ShouldAllow);
}

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 = nullptr;
            return;
        }

        m_activeAnnotation = PDFPluginAnnotation::create(annotation, m_pdfLayerController.get(), this);
        Document* document = webFrame()->coreFrame()->document();
        m_activeAnnotation->attach(document->bodyOrFrameset());
    } else
        m_activeAnnotation = nullptr;
}

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::notifyDisplayModeChanged(int mode)
{
    m_usingContinuousMode = (mode == kPDFDisplaySinglePageContinuous || mode == kPDFDisplayTwoUpContinuous);
    calculateSizes();
}

RefPtr<SharedBuffer> PDFPlugin::liveResourceData() const
{
    NSData *pdfData = liveData();

    if (!pdfData)
        return nullptr;

    return SharedBuffer::wrapNSData(pdfData);
}

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(m_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 = createCanonicalUUIDString();
        ASSERT(m_temporaryPDFUUID);

        webFrame()->page()->savePDFToTemporaryFolderAndOpenWithNativeApplication(m_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(NSString *pasteboardName, NSArray *items, NSArray *types)
{
    Vector<String> pasteboardTypes;

    for (NSString *type in types)
        pasteboardTypes.append(type);

    uint64_t newChangeCount;
    auto& webProcess = WebProcess::singleton();
    webProcess.parentProcessConnection()->sendSync(Messages::WebPasteboardProxy::SetPasteboardTypes(pasteboardName, pasteboardTypes),
        Messages::WebPasteboardProxy::SetPasteboardTypes::Reply(newChangeCount), 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.parentProcessConnection()->sendSync(Messages::WebPasteboardProxy::SetPasteboardStringForType(pasteboardName, type, plainTextString.get()), Messages::WebPasteboardProxy::SetPasteboardStringForType::Reply(newChangeCount), 0);
        } else {
            RefPtr<SharedBuffer> buffer = SharedBuffer::wrapNSData(data);

            if (!buffer)
                continue;

            SharedMemory::Handle handle;
            RefPtr<SharedMemory> sharedMemory = SharedMemory::allocate(buffer->size());
            memcpy(sharedMemory->data(), buffer->data(), buffer->size());
            sharedMemory->createHandle(handle, SharedMemory::Protection::ReadOnly);
            webProcess.parentProcessConnection()->sendSync(Messages::WebPasteboardProxy::SetPasteboardBufferForType(pasteboardName, type, handle, buffer->size()), Messages::WebPasteboardProxy::SetPasteboardBufferForType::Reply(newChangeCount), 0);
        }
    }
}

void PDFPlugin::showDefinitionForAttributedString(NSAttributedString *string, CGPoint point)
{
    DictionaryPopupInfo dictionaryPopupInfo;
    dictionaryPopupInfo.origin = convertFromPDFViewToRootView(IntPoint(point));
    dictionaryPopupInfo.attributedString = string;

    webFrame()->page()->send(Messages::WebPageProxy::DidPerformDictionaryLookup(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() 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 searchSelection];
        [m_pdfLayerController findString:target caseSensitive:caseSensitive highlightMatches:YES];
        [m_pdfLayerController setSearchSelection:searchSelection];
        m_lastFoundString = emptyString();
        return false;
    }

    if (m_lastFoundString == target) {
        PDFSelection *selection = nextMatchForString(target, searchForward, caseSensitive, wrapSearch, [m_pdfLayerController searchSelection], NO);
        if (!selection)
            return false;

        [m_pdfLayerController setSearchSelection:selection];
        [m_pdfLayerController gotoSelection:selection];
    } else {
        [m_pdfLayerController findString:target caseSensitive:caseSensitive highlightMatches:YES];
        m_lastFoundString = target;
    }

    return matchCount > 0;
}

bool PDFPlugin::performDictionaryLookupAtLocation(const FloatPoint& point)
{
    IntPoint localPoint = convertFromRootViewToPlugin(roundedIntPoint(point));
    PDFSelection* lookupSelection = [m_pdfLayerController getSelectionForWordAtPoint:convertFromPluginToPDFView(localPoint)];

    if ([[lookupSelection string] length])
        [m_pdfLayerController searchInDictionaryWithSelection:lookupSelection];

    return true;
}

void PDFPlugin::focusNextAnnotation()
{
    [m_pdfLayerController activateNextAnnotation:false];
}

void PDFPlugin::focusPreviousAnnotation()
{
    [m_pdfLayerController activateNextAnnotation:true];
}

void PDFPlugin::notifySelectionChanged(PDFSelection *selection)
{
    webFrame()->page()->didChangeSelection();
}

String PDFPlugin::getSelectionString() const
{
    return [[m_pdfLayerController currentSelection] string];
}

String PDFPlugin::getSelectionForWordAtPoint(const FloatPoint& point) const
{
    IntPoint pointInView = convertFromPluginToPDFView(convertFromRootViewToPlugin(roundedIntPoint(point)));
    PDFSelection *selectionForWord = [m_pdfLayerController getSelectionForWordAtPoint:pointInView];
    [m_pdfLayerController setCurrentSelection:selectionForWord];
    
    return [selectionForWord string];
}

bool PDFPlugin::existingSelectionContainsPoint(const FloatPoint& locationInViewCoordinates) const
{
    PDFSelection *currentSelection = [m_pdfLayerController currentSelection];
    if (!currentSelection)
        return false;
    
    IntPoint pointInPDFView = convertFromPluginToPDFView(convertFromRootViewToPlugin(roundedIntPoint(locationInViewCoordinates)));
    PDFSelection *selectionForWord = [m_pdfLayerController getSelectionForWordAtPoint:pointInPDFView];

    NSUInteger currentPageIndex = [m_pdfLayerController currentPageIndex];
    
    NSArray *selectionRects = [m_pdfLayerController rectsForSelectionInLayoutSpace:currentSelection];
    if (!selectionRects || !selectionRects.count)
        return false;
    
    if (currentPageIndex >= selectionRects.count)
        currentPageIndex = selectionRects.count - 1;

    NSArray *wordSelectionRects = [m_pdfLayerController rectsForSelectionInLayoutSpace:selectionForWord];
    if (!wordSelectionRects || !wordSelectionRects.count)
        return false;

    NSValue *selectionBounds = [selectionRects objectAtIndex:currentPageIndex];
    NSValue *wordSelectionBounds = [wordSelectionRects objectAtIndex:0];

    NSRect selectionBoundsRect = selectionBounds.rectValue;
    NSRect wordSelectionBoundsRect = wordSelectionBounds.rectValue;
    return NSIntersectsRect(wordSelectionBoundsRect, selectionBoundsRect);
}

static NSPoint pointInLayoutSpaceForPointInWindowSpace(PDFLayerController* pdfLayerController, NSPoint pointInView)
{
    CGPoint point = NSPointToCGPoint(pointInView);

    CGPoint scrollOffset = CGPointZero;
    CGFloat scaleFactor = [pdfLayerController contentScaleFactor];

    scrollOffset.y = [pdfLayerController contentSizeRespectingZoom].height - NSRectToCGRect([pdfLayerController frame]).size.height - scrollOffset.y;
    
    CGPoint newPoint = CGPointMake(scrollOffset.x + point.x, scrollOffset.y + point.y);
    newPoint.x /= scaleFactor;
    newPoint.y /= scaleFactor;
    return NSPointFromCGPoint(newPoint);
}

String PDFPlugin::lookupTextAtLocation(const FloatPoint& locationInViewCoordinates, WebHitTestResultData& data, PDFSelection **selectionPtr, NSDictionary **options) const
{
    PDFSelection*& selection = *selectionPtr;

    selection = [m_pdfLayerController currentSelection];
    if (existingSelectionContainsPoint(locationInViewCoordinates))
        return selection.string;
        
    IntPoint pointInPDFView = convertFromPluginToPDFView(convertFromRootViewToPlugin(roundedIntPoint(locationInViewCoordinates)));
    selection = [m_pdfLayerController getSelectionForWordAtPoint:pointInPDFView];
    if (!selection)
        return "";

    NSPoint pointInLayoutSpace = pointInLayoutSpaceForPointInWindowSpace(m_pdfLayerController.get(), pointInPDFView);

    PDFPage *currentPage = [[m_pdfLayerController layout] pageNearestPoint:pointInLayoutSpace currentPage:[m_pdfLayerController currentPage]];
    
    NSPoint pointInPageSpace = [[m_pdfLayerController layout] convertPoint:pointInLayoutSpace toPage:currentPage forScaleFactor:1.0];
    
    for (PDFAnnotation *annotation in currentPage.annotations) {
        if (![annotation isKindOfClass:pdfAnnotationLinkClass()])
            continue;
    
        NSRect bounds = annotation.bounds;
        if (!NSPointInRect(pointInPageSpace, bounds))
            continue;
        
        PDFAnnotationLink *linkAnnotation = (PDFAnnotationLink *)annotation;
        NSURL *url = linkAnnotation.URL;
        if (!url)
            continue;
        
        data.absoluteLinkURL = url.absoluteString;
        data.linkLabel = selection.string;
        return selection.string;
    }
    
    NSString *lookupText = DictionaryLookup::stringForPDFSelection(selection, options);
    if (!lookupText || !lookupText.length)
        return "";

    [m_pdfLayerController setCurrentSelection:selection];
    return lookupText;
}

static NSRect rectInViewSpaceForRectInLayoutSpace(PDFLayerController* pdfLayerController, NSRect layoutSpaceRect)
{
    CGRect newRect = NSRectToCGRect(layoutSpaceRect);
    CGFloat scaleFactor = pdfLayerController.contentScaleFactor;
    CGPoint scrollOffset = CGPointZero;

    scrollOffset.y = pdfLayerController.contentSizeRespectingZoom.height - NSRectToCGRect(pdfLayerController.frame).size.height - scrollOffset.y;

    newRect.origin.x *= scaleFactor;
    newRect.origin.y *= scaleFactor;
    newRect.size.width *= scaleFactor;
    newRect.size.height *= scaleFactor;

    newRect.origin.x -= scrollOffset.x;
    newRect.origin.y -= scrollOffset.y;

    return NSRectFromCGRect(newRect);
}

FloatRect PDFPlugin::rectForSelectionInRootView(PDFSelection *selection) const
{
    PDFPage *currentPage = nil;
    NSArray* pages = selection.pages;
    if (pages.count)
        currentPage = (PDFPage *)[pages objectAtIndex:0];

    if (!currentPage)
        currentPage = [m_pdfLayerController currentPage];
    
    NSRect rectInPageSpace = [selection boundsForPage:currentPage];
    NSRect rectInLayoutSpace = [[m_pdfLayerController layout] convertRect:rectInPageSpace fromPage:currentPage forScaleFactor:1.0];
    NSRect rectInView = rectInViewSpaceForRectInLayoutSpace(m_pdfLayerController.get(), rectInLayoutSpace);

    rectInView.origin = convertFromPDFViewToRootView(IntPoint(rectInView.origin));

    return FloatRect(rectInView);
}

void PDFPlugin::performWebSearch(NSString *string)
{
    webFrame()->page()->send(Messages::WebPageProxy::SearchTheWeb(string));
}

void PDFPlugin::performSpotlightSearch(NSString *string)
{
    webFrame()->page()->send(Messages::WebPageProxy::SearchWithSpotlight(string));
}

NSData *PDFPlugin::liveData() const
{
    if (m_activeAnnotation)
        m_activeAnnotation->commit();

    // Save data straight from the resource instead of PDFKit if the document is
    // untouched by the user, so that PDFs which PDFKit can't display will still be downloadable.
    if (m_pdfDocumentWasMutated)
        return [m_pdfDocument dataRepresentation];
    
    return rawData();
}

NSObject *PDFPlugin::accessibilityObject() const
{
    return m_accessibilityObject.get();
}

void PDFPlugin::scrollToPoint(IntPoint scrollPoint)
{
    Frame* frame = pluginView()->frame();
    float scale = frame->page()->pageScaleFactor();
    scrollPoint.scale(scale, scale);
    frame->view()->scrollToOffsetWithoutAnimation(scrollPoint);
}

float PDFPlugin::scaleFactor() const
{
    Frame* frame = pluginView()->frame();
    return frame->page()->pageScaleFactor();
}

void PDFPlugin::zoomIn()
{
    if (webFrame()->isMainFrame() && isFullFramePlugin()) {
        WebPage* page = webFrame()->page();
        page->scalePage(page->pageScaleFactor() * zoomButtonScaleMultiplier, IntPoint());
    } else {
        [m_pdfLayerController setContentScaleFactor:[m_pdfLayerController contentScaleFactor] * zoomButtonScaleMultiplier];
        calculateSizes();
    }
}

void PDFPlugin::zoomOut()
{
    if (webFrame()->isMainFrame() && isFullFramePlugin()) {
        WebPage* page = webFrame()->page();
        page->scalePage(page->pageScaleFactor() / zoomButtonScaleMultiplier, IntPoint());
    } else {
        [m_pdfLayerController setContentScaleFactor:[m_pdfLayerController contentScaleFactor] / zoomButtonScaleMultiplier];
        calculateSizes();
    }
}

PDFPlugin::HUD::HUD(PDFPlugin& plugin)
    : m_overlay(PageOverlay::create(*this))
    , m_plugin(plugin)
{
    WebFrame* webFrame = plugin.webFrame();

    // In order to avoid the overlay lagging behind main-frame scrolling, we need
    // to force synchronous scrolling for the whole page if we have a HUD in a subframe PDF.
    m_overlay->setNeedsSynchronousScrolling(!webFrame->isMainFrame());

    MainFrame& mainFrame = webFrame->coreFrame()->mainFrame();
    mainFrame.pageOverlayController().installPageOverlay(m_overlay.ptr(), PageOverlay::FadeMode::DoNotFade);
    m_overlay->setNeedsDisplay();

    RefPtr<PlatformCALayer> platformCALayer = downcast<GraphicsLayerCA>(m_overlay->layer()).platformCALayer();
    platformCALayer->setOpacity(0);
}

PDFPlugin::HUD::~HUD()
{
    MainFrame& mainFrame = m_plugin.webFrame()->coreFrame()->mainFrame();
    mainFrame.pageOverlayController().uninstallPageOverlay(m_overlay.ptr(), PageOverlay::FadeMode::DoNotFade);
}

void PDFPlugin::HUD::willMoveToPage(PageOverlay&, Page* page)
{
}

void PDFPlugin::HUD::didMoveToPage(PageOverlay&, Page*)
{
}

IntRect PDFPlugin::HUD::frameInRootView() const
{
    // FIXME: These should come from PDFKit.
    const int HUDWidth = 236;
    const int HUDHeight = 72;
    const int HUDYOffset = 50;

    FrameView* frameView = m_plugin.webFrame()->coreFrame()->view();
    IntRect frameRectInRootView = frameView->convertToRootView(frameView->boundsRect());
    FloatRect HUDRect(frameRectInRootView.x() + (frameRectInRootView.width() / 2 - (HUDWidth / 2)), frameRectInRootView.y() + (frameRectInRootView.height() - HUDYOffset - HUDHeight), HUDWidth, HUDHeight);
    return enclosingIntRect(HUDRect);
}

bool PDFPlugin::HUD::containsPointInRootView(IntPoint point)
{
    if (m_plugin.isLocked())
        return false;

    return frameInRootView().contains(point);
}

void PDFPlugin::HUD::drawRect(PageOverlay&, GraphicsContext& context, const IntRect& dirtyRect)
{
    IntRect frameInRootView = this->frameInRootView();

    context.translate(IntSize(frameInRootView.x(), frameInRootView.y() + frameInRootView.height()));
    context.scale(FloatSize(1, -1));

    IntRect localRect(IntPoint(), frameInRootView.size());
    context.clip(localRect);

    [m_plugin.pdfLayerController() drawHUDInContext:context.platformContext()];
}

void PDFPlugin::HUD::invalidate()
{
    m_overlay->setNeedsDisplay();
}

void PDFPlugin::HUD::setVisible(bool visible, AnimateVisibilityTransition animateTransition)
{
    if (visible == m_visible)
        return;

    m_visible = visible;

    float toValue = m_visible ? 0.75 : 0;
    RefPtr<PlatformCALayer> platformCALayer = downcast<GraphicsLayerCA>(m_overlay->layer()).platformCALayer();

    if (animateTransition == AnimateVisibilityTransition::No) {
        platformCALayer->removeAnimationForKey("HUDFade");
        platformCALayer->setOpacity(toValue);
        return;
    }

    float duration = m_visible ? 0.25 : 0.5;

    RetainPtr<CABasicAnimation> fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    [fadeAnimation setDuration:duration];
    [fadeAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    [fadeAnimation setDelegate:[[[WKPDFHUDAnimationDelegate alloc] initWithAnimationCompletionHandler:[platformCALayer, toValue] (bool completed) {
        if (completed)
            platformCALayer->setOpacity(toValue);

    }] autorelease]];
    [fadeAnimation setFromValue:@(((CALayer *)platformCALayer->platformLayer().presentationLayer).opacity)];
    [fadeAnimation setToValue:@(toValue)];

    RefPtr<PlatformCAAnimation> animation = PlatformCAAnimationCocoa::create(fadeAnimation.get());
    platformCALayer->removeAnimationForKey("HUDFade");
    platformCALayer->addAnimationForKey("HUDFade", *animation);
}

bool PDFPlugin::HUD::mouseEvent(PageOverlay&, const PlatformMouseEvent& event)
{
    if (m_plugin.isLocked())
        return false;

    // We don't want the HUD to appear when dragging past it, or disappear when dragging from a button,
    // so don't update visibility while any mouse buttons are pressed.
    if (event.button() == MouseButton::NoButton)
        setVisible(containsPointInRootView(event.position()), AnimateVisibilityTransition::Yes);

    if (!m_visible)
        return false;

    if (event.button() != MouseButton::LeftButton)
        return false;

    IntPoint positionInRootViewCoordinates(event.position());
    IntRect HUDRectInRootViewCoordinates = frameInRootView();

    auto eventWithType = [&](NSEventType eventType) -> NSEvent * {
        return [NSEvent mouseEventWithType:eventType location:positionInRootViewCoordinates modifierFlags:0 timestamp:0 windowNumber:0 context:nil eventNumber:0 clickCount:event.clickCount() pressure:0];
    };

    auto pdfLayerController = m_plugin.pdfLayerController();

    switch (event.type()) {
    case PlatformEvent::MouseMoved:
        return [pdfLayerController mouseDragged:eventWithType(NSLeftMouseDragged) inHUDWithBounds:HUDRectInRootViewCoordinates];
    case PlatformEvent::MousePressed:
        return [pdfLayerController mouseDown:eventWithType(NSLeftMouseDown) inHUDWithBounds:HUDRectInRootViewCoordinates];
    case PlatformEvent::MouseReleased:
        return [pdfLayerController mouseUp:eventWithType(NSLeftMouseUp) inHUDWithBounds:HUDRectInRootViewCoordinates];
    default:
        break;
    }

    return false;
}

} // namespace WebKit

#endif // ENABLE(PDFKIT_PLUGIN)