BuiltInPDFView.mm   [plain text]


/*
 * Copyright (C) 2009, 2011, 2012 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "config.h"
#import "BuiltInPDFView.h"

#import "PDFKitImports.h"
#import "PluginView.h"
#import "ShareableBitmap.h"
#import "WebEvent.h"
#import "WebEventConversion.h"
#import <JavaScriptCore/JSContextRef.h>
#import <JavaScriptCore/JSObjectRef.h>
#import <JavaScriptCore/JSStringRef.h>
#import <JavaScriptCore/JSStringRefCF.h>
#import <PDFKit/PDFKit.h>
#import <WebCore/ArchiveResource.h>
#import <WebCore/Chrome.h>
#import <WebCore/DocumentLoader.h>
#import <WebCore/FocusController.h>
#import <WebCore/Frame.h>
#import <WebCore/FrameView.h>
#import <WebCore/GraphicsContext.h>
#import <WebCore/HTTPHeaderMap.h>
#import <WebCore/LocalizedStrings.h>
#import <WebCore/Page.h>
#import <WebCore/PluginData.h>
#import <WebCore/RenderBoxModelObject.h>
#import <WebCore/ScrollAnimator.h>
#import <WebCore/ScrollbarTheme.h>

using namespace WebCore;
using namespace std;

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 {

const uint64_t pdfDocumentRequestID = 1; // PluginController supports loading multiple streams, but we only need one for PDF.

const int gutterHeight = 10;
const int shadowOffsetX = 0;
const int shadowOffsetY = -2;
const int shadowSize = 7;

PassRefPtr<BuiltInPDFView> BuiltInPDFView::create(WebFrame* frame)
{
    return adoptRef(new BuiltInPDFView(frame));
}

BuiltInPDFView::BuiltInPDFView(WebFrame* frame)
    : m_frame(frame)
{
}

BuiltInPDFView::~BuiltInPDFView()
{
}

PluginInfo BuiltInPDFView::pluginInfo()
{
    PluginInfo info;
    info.name = builtInPDFPluginName();

    MimeClassInfo mimeClassInfo;
    mimeClassInfo.type ="application/pdf";
    mimeClassInfo.desc = pdfDocumentTypeDescription();
    mimeClassInfo.extensions.append("pdf");

    info.mimes.append(mimeClassInfo);
    return info;
}

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

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

void BuiltInPDFView::updateScrollbars()
{
    bool hadScrollbars = m_horizontalScrollbar || m_verticalScrollbar;

    if (m_horizontalScrollbar) {
        if (m_pluginSize.width() >= m_pdfDocumentSize.width())
            destroyScrollbar(HorizontalScrollbar);
    } else if (m_pluginSize.width() < m_pdfDocumentSize.width())
        m_horizontalScrollbar = createScrollbar(HorizontalScrollbar);

    if (m_verticalScrollbar) {
        if (m_pluginSize.height() >= m_pdfDocumentSize.height())
            destroyScrollbar(VerticalScrollbar);
    } else if (m_pluginSize.height() < m_pdfDocumentSize.height())
        m_verticalScrollbar = createScrollbar(VerticalScrollbar);

    int horizontalScrollbarHeight = (m_horizontalScrollbar && !m_horizontalScrollbar->isOverlayScrollbar()) ? m_horizontalScrollbar->height() : 0;
    int verticalScrollbarWidth = (m_verticalScrollbar && !m_verticalScrollbar->isOverlayScrollbar()) ? m_verticalScrollbar->width() : 0;

    int pageStep = m_pageBoxes.isEmpty() ? 0 : m_pageBoxes[0].height();

    if (m_horizontalScrollbar) {
        m_horizontalScrollbar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep);
        m_horizontalScrollbar->setProportion(m_pluginSize.width() - verticalScrollbarWidth, m_pdfDocumentSize.width());
        IntRect scrollbarRect(pluginView()->x(), pluginView()->y() + m_pluginSize.height() - m_horizontalScrollbar->height(), m_pluginSize.width(), m_horizontalScrollbar->height());
        if (m_verticalScrollbar)
            scrollbarRect.contract(m_verticalScrollbar->width(), 0);
        m_horizontalScrollbar->setFrameRect(scrollbarRect);
    }
    if (m_verticalScrollbar) {
        m_verticalScrollbar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep);
        m_verticalScrollbar->setProportion(m_pluginSize.height() - horizontalScrollbarHeight, m_pdfDocumentSize.height());
        IntRect scrollbarRect(IntRect(pluginView()->x() + m_pluginSize.width() - m_verticalScrollbar->width(), pluginView()->y(), m_verticalScrollbar->width(), m_pluginSize.height()));
        if (m_horizontalScrollbar)
            scrollbarRect.contract(0, m_horizontalScrollbar->height());
        m_verticalScrollbar->setFrameRect(scrollbarRect);
    }
    
    FrameView* frameView = m_frame->coreFrame()->view();
    if (!frameView)
        return;

    bool hasScrollbars = m_horizontalScrollbar || m_verticalScrollbar;
    if (hadScrollbars != hasScrollbars) {
        if (hasScrollbars)
            frameView->addScrollableArea(this);
        else
            frameView->removeScrollableArea(this);

        frameView->setNeedsLayout();
    }
}

PassRefPtr<Scrollbar> BuiltInPDFView::createScrollbar(ScrollbarOrientation orientation)
{
    RefPtr<Scrollbar> widget = Scrollbar::createNativeScrollbar(this, orientation, RegularScrollbar);
    if (orientation == HorizontalScrollbar)
        didAddHorizontalScrollbar(widget.get());
    else 
        didAddVerticalScrollbar(widget.get());
    pluginView()->frame()->view()->addChild(widget.get());
    return widget.release();
}

void BuiltInPDFView::destroyScrollbar(ScrollbarOrientation orientation)
{
    RefPtr<Scrollbar>& scrollbar = orientation == HorizontalScrollbar ? m_horizontalScrollbar : m_verticalScrollbar;
    if (!scrollbar)
        return;

    if (orientation == HorizontalScrollbar)
        willRemoveHorizontalScrollbar(scrollbar.get());
    else
        willRemoveVerticalScrollbar(scrollbar.get());

    scrollbar->removeFromParent();
    scrollbar->disconnectFromScrollableArea();
    scrollbar = 0;
}

void BuiltInPDFView::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.
    ResourceResponse synthesizedResponse;
    synthesizedResponse.setSuggestedFilename(m_suggestedFilename);
    synthesizedResponse.setURL(m_sourceURL); // Needs to match the HitTestResult::absolutePDFURL.
    synthesizedResponse.setMimeType("application/pdf");

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

void BuiltInPDFView::pdfDocumentDidLoad()
{
    addArchiveResource();

    m_pdfDocument.adoptNS([[pdfDocumentClass() alloc] initWithData:(NSData *)m_dataBuffer.get()]);

    calculateSizes();
    updateScrollbars();

    controller()->invalidate(IntRect(0, 0, m_pluginSize.width(), m_pluginSize.height()));

    Vector<RetainPtr<CFStringRef> > scripts;
    getAllScriptsInPDFDocument([m_pdfDocument.get() 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 BuiltInPDFView::calculateSizes()
{
    size_t pageCount = CGPDFDocumentGetNumberOfPages([m_pdfDocument.get() documentRef]);
    for (size_t i = 0; i < pageCount; ++i) {
        CGPDFPageRef pdfPage = CGPDFDocumentGetPage([m_pdfDocument.get() documentRef], i + 1);
        ASSERT(pdfPage);

        CGRect box = CGPDFPageGetBoxRect(pdfPage, kCGPDFCropBox);
        if (CGRectIsEmpty(box))
            box = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);
        m_pageBoxes.append(IntRect(box));
        m_pdfDocumentSize.setWidth(max(m_pdfDocumentSize.width(), static_cast<int>(box.size.width)));
        m_pdfDocumentSize.expand(0, box.size.height);
    }
    m_pdfDocumentSize.expand(0, gutterHeight * (m_pageBoxes.size() - 1));
}

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

    return true;
}

void BuiltInPDFView::destroy()
{
    if (m_frame) {
        if (FrameView* frameView = m_frame->coreFrame()->view())
            frameView->removeScrollableArea(this);
    }

    destroyScrollbar(HorizontalScrollbar);
    destroyScrollbar(VerticalScrollbar);
}

void BuiltInPDFView::paint(GraphicsContext* graphicsContext, const IntRect& dirtyRect)
{
    contentAreaWillPaint();

    paintBackground(graphicsContext, dirtyRect);

    if (!m_pdfDocument) // FIXME: Draw loading progress.
        return;

    paintContent(graphicsContext, dirtyRect);
    paintControls(graphicsContext, dirtyRect);
}

void BuiltInPDFView::paintBackground(GraphicsContext* graphicsContext, const IntRect& dirtyRect)
{
    GraphicsContextStateSaver stateSaver(*graphicsContext);
    graphicsContext->setFillColor(Color::gray, ColorSpaceDeviceRGB);
    graphicsContext->fillRect(dirtyRect);
}

void BuiltInPDFView::paintContent(GraphicsContext* graphicsContext, const IntRect& dirtyRect)
{
    GraphicsContextStateSaver stateSaver(*graphicsContext);
    CGContextRef context = graphicsContext->platformContext();

    graphicsContext->setImageInterpolationQuality(InterpolationHigh);
    graphicsContext->setShouldAntialias(true);
    graphicsContext->setShouldSmoothFonts(true);
    graphicsContext->setFillColor(Color::white, ColorSpaceDeviceRGB);

    graphicsContext->clip(dirtyRect);
    IntRect contentRect(dirtyRect);
    contentRect.moveBy(IntPoint(m_scrollOffset));
    graphicsContext->translate(-m_scrollOffset.width(), -m_scrollOffset.height());

    CGContextScaleCTM(context, 1, -1);

    int pageTop = 0;
    for (size_t i = 0; i < m_pageBoxes.size(); ++i) {
        IntRect pageBox = m_pageBoxes[i];
        float extraOffsetForCenteringX = max(roundf((m_pluginSize.width() - pageBox.width()) / 2.0f), 0.0f);
        float extraOffsetForCenteringY = (m_pageBoxes.size() == 1) ? max(roundf((m_pluginSize.height() - pageBox.height() + shadowOffsetY) / 2.0f), 0.0f) : 0;

        if (pageTop > contentRect.maxY())
            break;
        if (pageTop + pageBox.height() + extraOffsetForCenteringY + gutterHeight >= contentRect.y()) {
            CGPDFPageRef pdfPage = CGPDFDocumentGetPage([m_pdfDocument.get() documentRef], i + 1);

            graphicsContext->save();
            graphicsContext->translate(extraOffsetForCenteringX - pageBox.x(), -extraOffsetForCenteringY - pageBox.y() - pageBox.height());

            graphicsContext->setShadow(FloatSize(shadowOffsetX, shadowOffsetY), shadowSize, Color::black, ColorSpaceDeviceRGB);
            graphicsContext->fillRect(pageBox);
            graphicsContext->clearShadow();

            graphicsContext->clip(pageBox);

            CGContextDrawPDFPage(context, pdfPage);
            graphicsContext->restore();
        }
        pageTop += pageBox.height() + gutterHeight;
        CGContextTranslateCTM(context, 0, -pageBox.height() - gutterHeight);
    }
}

void BuiltInPDFView::paintControls(GraphicsContext* graphicsContext, const IntRect& dirtyRect)
{
    {
        GraphicsContextStateSaver stateSaver(*graphicsContext);
        IntRect scrollbarDirtyRect = dirtyRect;
        scrollbarDirtyRect.moveBy(pluginView()->frameRect().location());
        graphicsContext->translate(-pluginView()->frameRect().x(), -pluginView()->frameRect().y());

        if (m_horizontalScrollbar)
            m_horizontalScrollbar->paint(graphicsContext, scrollbarDirtyRect);

        if (m_verticalScrollbar)
            m_verticalScrollbar->paint(graphicsContext, scrollbarDirtyRect);
    }

    IntRect dirtyCornerRect = intersection(scrollCornerRect(), dirtyRect);
    ScrollbarTheme::theme()->paintScrollCorner(0, graphicsContext, dirtyCornerRect);
}

void BuiltInPDFView::updateControlTints(GraphicsContext* graphicsContext)
{
    ASSERT(graphicsContext->updatingControlTints());

    if (m_horizontalScrollbar)
        m_horizontalScrollbar->invalidate();
    if (m_verticalScrollbar)
        m_verticalScrollbar->invalidate();
    invalidateScrollCorner(scrollCornerRect());
}

PassRefPtr<ShareableBitmap> BuiltInPDFView::snapshot()
{
    return 0;
}

#if PLATFORM(MAC)
PlatformLayer* BuiltInPDFView::pluginLayer()
{
    return 0;
}
#endif


bool BuiltInPDFView::isTransparent()
{
    // This should never be called from the web process.
    ASSERT_NOT_REACHED();
    return false;
}

bool BuiltInPDFView::wantsWheelEvents()
{
    return true;
}

void BuiltInPDFView::geometryDidChange(const IntSize& pluginSize, const IntRect& clipRect, const AffineTransform& pluginToRootViewTransform)
{
    if (m_pluginSize == pluginSize) {
        // Nothing to do.
        return;
    }

    m_pluginSize = pluginSize;
    updateScrollbars();
}

void BuiltInPDFView::visibilityDidChange()
{
}

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

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

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

void BuiltInPDFView::streamDidReceiveResponse(uint64_t streamID, const KURL&, uint32_t, uint32_t, const String&, const String&, const String& suggestedFilename)
{
    ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);

    m_suggestedFilename = suggestedFilename;
}
                                           
void BuiltInPDFView::streamDidReceiveData(uint64_t streamID, const char* bytes, int length)
{
    ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);

    if (!m_dataBuffer)
        m_dataBuffer.adoptCF(CFDataCreateMutable(0, 0));

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

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

    pdfDocumentDidLoad();
}

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

    m_dataBuffer.clear();
}

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

void BuiltInPDFView::manualStreamDidReceiveData(const char* bytes, int length)
{
    if (!m_dataBuffer)
        m_dataBuffer.adoptCF(CFDataCreateMutable(0, 0));

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

void BuiltInPDFView::manualStreamDidFinishLoading()
{
    pdfDocumentDidLoad();
}

void BuiltInPDFView::manualStreamDidFail(bool)
{
    m_dataBuffer.clear();
}

bool BuiltInPDFView::handleMouseEvent(const WebMouseEvent& event)
{
    switch (event.type()) {
    case WebEvent::MouseMove:
        mouseMovedInContentArea();
        // FIXME: Should also notify scrollbar to show hover effect. Should also send mouseExited to hide it.
        break;
    case WebEvent::MouseDown: {
        // Returning false as will make EventHandler unfocus the plug-in, which is appropriate when clicking scrollbars.
        // Ideally, we wouldn't change focus at all, but PluginView already did that for us.
        // When support for PDF forms is added, we'll need to actually focus the plug-in when clicking in a form.
        break;
    }
    case WebEvent::MouseUp: {
        PlatformMouseEvent platformEvent = platform(event);
        if (m_horizontalScrollbar)
            m_horizontalScrollbar->mouseUp(platformEvent);
        if (m_verticalScrollbar)
            m_verticalScrollbar->mouseUp(platformEvent);
        break;
    }
    default:
        break;
    }
        
    return false;
}

bool BuiltInPDFView::handleWheelEvent(const WebWheelEvent& event)
{
    PlatformWheelEvent platformEvent = platform(event);
    return ScrollableArea::handleWheelEvent(platformEvent);
}

bool BuiltInPDFView::handleMouseEnterEvent(const WebMouseEvent&)
{
    mouseEnteredContentArea();
    return false;
}

bool BuiltInPDFView::handleMouseLeaveEvent(const WebMouseEvent&)
{
    mouseExitedContentArea();
    return false;
}

bool BuiltInPDFView::handleContextMenuEvent(const WebMouseEvent&)
{
    // Use default WebKit context menu.
    return false;
}

bool BuiltInPDFView::handleKeyboardEvent(const WebKeyboardEvent&)
{
    return false;
}

void BuiltInPDFView::setFocus(bool hasFocus)
{
}

NPObject* BuiltInPDFView::pluginScriptableNPObject()
{
    return 0;
}

#if PLATFORM(MAC)

void BuiltInPDFView::windowFocusChanged(bool)
{
}

void BuiltInPDFView::windowAndViewFramesChanged(const WebCore::IntRect& windowFrameInScreenCoordinates, const WebCore::IntRect& viewFrameInWindowCoordinates)
{
}

void BuiltInPDFView::windowVisibilityChanged(bool)
{
}

void BuiltInPDFView::contentsScaleFactorChanged(float)
{
}

uint64_t BuiltInPDFView::pluginComplexTextInputIdentifier() const
{
    return 0;
}

void BuiltInPDFView::sendComplexTextInput(const String&)
{
}

void BuiltInPDFView::setLayerHostingMode(LayerHostingMode)
{
}

#endif

void BuiltInPDFView::privateBrowsingStateChanged(bool)
{
}

bool BuiltInPDFView::getFormValue(String&)
{
    return false;
}

bool BuiltInPDFView::handleScroll(ScrollDirection direction, ScrollGranularity granularity)
{
    return scroll(direction, granularity);
}

Scrollbar* BuiltInPDFView::horizontalScrollbar()
{
    return m_horizontalScrollbar.get();
}

Scrollbar* BuiltInPDFView::verticalScrollbar()
{
    return m_verticalScrollbar.get();
}

IntRect BuiltInPDFView::scrollCornerRect() const
{
    if (!m_horizontalScrollbar || !m_verticalScrollbar)
        return IntRect();
    if (m_horizontalScrollbar->isOverlayScrollbar()) {
        ASSERT(m_verticalScrollbar->isOverlayScrollbar());
        return IntRect();
    }
    return IntRect(pluginView()->width() - m_verticalScrollbar->width(), pluginView()->height() - m_horizontalScrollbar->height(), m_verticalScrollbar->width(), m_horizontalScrollbar->height());
}

ScrollableArea* BuiltInPDFView::enclosingScrollableArea() const
{
    // FIXME: Walk up the frame tree and look for a scrollable parent frame or RenderLayer.
    return 0;
}

IntRect BuiltInPDFView::scrollableAreaBoundingBox() const
{
    return pluginView()->frameRect();
}

void BuiltInPDFView::setScrollOffset(const IntPoint& offset)
{
    m_scrollOffset = IntSize(offset.x(), offset.y());
    // FIXME: It would be better for performance to blit parts that remain visible.
    controller()->invalidate(IntRect(0, 0, m_pluginSize.width(), m_pluginSize.height()));
}

int BuiltInPDFView::scrollSize(ScrollbarOrientation orientation) const
{
    Scrollbar* scrollbar = ((orientation == HorizontalScrollbar) ? m_horizontalScrollbar : m_verticalScrollbar).get();
    return scrollbar ? (scrollbar->totalSize() - scrollbar->visibleSize()) : 0;
}

bool BuiltInPDFView::isActive() const
{
    if (Frame* coreFrame = m_frame->coreFrame()) {
        if (Page* page = coreFrame->page())
            return page->focusController()->isActive();
    }

    return false;
}

void BuiltInPDFView::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
{
    IntRect dirtyRect = rect;
    dirtyRect.moveBy(scrollbar->location());
    dirtyRect.moveBy(-pluginView()->location());
    controller()->invalidate(dirtyRect);
}

void BuiltInPDFView::invalidateScrollCornerRect(const IntRect& rect)
{
    controller()->invalidate(rect);
}

bool BuiltInPDFView::isScrollCornerVisible() const
{
    return false;
}

int BuiltInPDFView::scrollPosition(Scrollbar* scrollbar) const
{
    if (scrollbar->orientation() == HorizontalScrollbar)
        return m_scrollOffset.width();
    if (scrollbar->orientation() == VerticalScrollbar)
        return m_scrollOffset.height();
    ASSERT_NOT_REACHED();
    return 0;
}

IntPoint BuiltInPDFView::scrollPosition() const
{
    return IntPoint(m_scrollOffset.width(), m_scrollOffset.height());
}

IntPoint BuiltInPDFView::minimumScrollPosition() const
{
    return IntPoint(0, 0);
}

IntPoint BuiltInPDFView::maximumScrollPosition() const
{
    int horizontalScrollbarHeight = (m_horizontalScrollbar && !m_horizontalScrollbar->isOverlayScrollbar()) ? m_horizontalScrollbar->height() : 0;
    int verticalScrollbarWidth = (m_verticalScrollbar && !m_verticalScrollbar->isOverlayScrollbar()) ? m_verticalScrollbar->width() : 0;

    IntPoint maximumOffset(m_pdfDocumentSize.width() - m_pluginSize.width() + verticalScrollbarWidth, m_pdfDocumentSize.height() - m_pluginSize.height() + horizontalScrollbarHeight);
    maximumOffset.clampNegativeToZero();
    return maximumOffset;
}

int BuiltInPDFView::visibleHeight() const
{
    return m_pluginSize.height();
}

int BuiltInPDFView::visibleWidth() const
{
    return m_pluginSize.width();
}

IntSize BuiltInPDFView::contentsSize() const
{
    return m_pdfDocumentSize;
}

bool BuiltInPDFView::isOnActivePage() const
{
    return !pluginView()->frame()->document()->inPageCache();
}

void BuiltInPDFView::scrollbarStyleChanged(int, bool forceUpdate)
{
    if (!forceUpdate)
        return;

    // 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.
    IntPoint newScrollOffset = IntPoint(m_scrollOffset).shrunkTo(maximumScrollPosition());
    setScrollOffset(newScrollOffset);

    // As size of the content area changes, scrollbars may need to appear or to disappear.
    updateScrollbars();

    ScrollableArea::contentsResized();
}

IntPoint BuiltInPDFView::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
{
    IntPoint point = pluginView()->frame()->view()->convertToRenderer(pluginView()->renderer(), parentPoint);
    point.move(pluginView()->location() - scrollbar->location());

    return point;
}

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

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

JSValueRef BuiltInPDFView::jsPDFDocPrint(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
    BuiltInPDFView* pdfView = static_cast<BuiltInPDFView*>(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 BuiltInPDFView::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);
}

} // namespace WebKit