SimplePDFPlugin.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 "SimplePDFPlugin.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;
static const char* postScriptMIMEType = "application/postscript";
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<SimplePDFPlugin> SimplePDFPlugin::create(WebFrame* frame)
{
return adoptRef(new SimplePDFPlugin(frame));
}
SimplePDFPlugin::SimplePDFPlugin(WebFrame* frame)
: m_frame(frame)
, m_isPostScript(false)
, m_pdfDocumentWasMutated(false)
{
}
SimplePDFPlugin::~SimplePDFPlugin()
{
}
PluginInfo SimplePDFPlugin::pluginInfo()
{
PluginInfo info;
info.name = builtInPDFPluginName();
info.isApplicationPlugin = true;
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* SimplePDFPlugin::pluginView()
{
return static_cast<PluginView*>(controller());
}
const PluginView* SimplePDFPlugin::pluginView() const
{
return static_cast<const PluginView*>(controller());
}
void SimplePDFPlugin::updateScrollbars()
{
bool hadScrollbars = m_horizontalScrollbar || m_verticalScrollbar;
if (m_horizontalScrollbar) {
if (m_size.width() >= m_pdfDocumentSize.width())
destroyScrollbar(HorizontalScrollbar);
} else if (m_size.width() < m_pdfDocumentSize.width())
m_horizontalScrollbar = createScrollbar(HorizontalScrollbar);
if (m_verticalScrollbar) {
if (m_size.height() >= m_pdfDocumentSize.height())
destroyScrollbar(VerticalScrollbar);
} else if (m_size.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_size.width() - verticalScrollbarWidth, m_pdfDocumentSize.width());
IntRect scrollbarRect(pluginView()->x(), pluginView()->y() + m_size.height() - m_horizontalScrollbar->height(), m_size.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_size.height() - horizontalScrollbarHeight, m_pdfDocumentSize.height());
IntRect scrollbarRect(IntRect(pluginView()->x() + m_size.width() - m_verticalScrollbar->width(), pluginView()->y(), m_verticalScrollbar->width(), m_size.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> SimplePDFPlugin::createScrollbar(ScrollbarOrientation orientation)
{
RefPtr<Scrollbar> widget = Scrollbar::createNativeScrollbar(this, orientation, RegularScrollbar);
didAddScrollbar(widget.get(), orientation);
pluginView()->frame()->view()->addChild(widget.get());
return widget.release();
}
void SimplePDFPlugin::destroyScrollbar(ScrollbarOrientation orientation)
{
RefPtr<Scrollbar>& scrollbar = orientation == HorizontalScrollbar ? m_horizontalScrollbar : m_verticalScrollbar;
if (!scrollbar)
return;
willRemoveScrollbar(scrollbar.get(), orientation);
scrollbar->removeFromParent();
scrollbar->disconnectFromScrollableArea();
scrollbar = 0;
}
void SimplePDFPlugin::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_data.get()), m_sourceURL, "application/pdf", String(), String(), synthesizedResponse);
pluginView()->frame()->document()->loader()->addArchiveResource(resource.release());
}
static void jsPDFDocInitialize(JSContextRef ctx, JSObjectRef object)
{
SimplePDFPlugin* pdfView = static_cast<SimplePDFPlugin*>(JSObjectGetPrivate(object));
pdfView->ref();
}
static void jsPDFDocFinalize(JSObjectRef object)
{
SimplePDFPlugin* pdfView = static_cast<SimplePDFPlugin*>(JSObjectGetPrivate(object));
pdfView->deref();
}
JSValueRef SimplePDFPlugin::jsPDFDocPrint(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
SimplePDFPlugin* pdfView = static_cast<SimplePDFPlugin*>(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 SimplePDFPlugin::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 SimplePDFPlugin::pdfDocumentDidLoad()
{
addArchiveResource();
m_pdfDocument = adoptNS([[pdfDocumentClass() alloc] initWithData:(NSData *)m_data.get()]);
calculateSizes();
updateScrollbars();
controller()->invalidate(IntRect(0, 0, m_size.width(), m_size.height()));
runScriptsInPDFDocument();
}
void SimplePDFPlugin::runScriptsInPDFDocument()
{
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 SimplePDFPlugin::computePageBoxes()
{
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));
}
}
void SimplePDFPlugin::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 SimplePDFPlugin::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);
controller()->didInitializePlugin();
return true;
}
void SimplePDFPlugin::destroy()
{
if (m_frame) {
if (FrameView* frameView = m_frame->coreFrame()->view())
frameView->removeScrollableArea(this);
}
destroyScrollbar(HorizontalScrollbar);
destroyScrollbar(VerticalScrollbar);
}
void SimplePDFPlugin::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 SimplePDFPlugin::paintBackground(GraphicsContext* graphicsContext, const IntRect& dirtyRect)
{
GraphicsContextStateSaver stateSaver(*graphicsContext);
graphicsContext->setFillColor(Color::gray, ColorSpaceDeviceRGB);
graphicsContext->fillRect(dirtyRect);
}
void SimplePDFPlugin::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_size.width() - pageBox.width()) / 2.0f), 0.0f);
float extraOffsetForCenteringY = (m_pageBoxes.size() == 1) ? max(roundf((m_size.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 SimplePDFPlugin::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 SimplePDFPlugin::updateControlTints(GraphicsContext* graphicsContext)
{
ASSERT(graphicsContext->updatingControlTints());
if (m_horizontalScrollbar)
m_horizontalScrollbar->invalidate();
if (m_verticalScrollbar)
m_verticalScrollbar->invalidate();
invalidateScrollCorner(scrollCornerRect());
}
PassRefPtr<ShareableBitmap> SimplePDFPlugin::snapshot()
{
return 0;
}
#if PLATFORM(MAC)
PlatformLayer* SimplePDFPlugin::pluginLayer()
{
return 0;
}
#endif
bool SimplePDFPlugin::isTransparent()
{
// This should never be called from the web process.
ASSERT_NOT_REACHED();
return false;
}
bool SimplePDFPlugin::wantsWheelEvents()
{
return true;
}
void SimplePDFPlugin::geometryDidChange(const IntSize& size, const IntRect& clipRect, const AffineTransform& pluginToRootViewTransform)
{
if (m_size == size) {
// Nothing to do.
return;
}
m_size = size;
updateScrollbars();
}
void SimplePDFPlugin::visibilityDidChange()
{
}
void SimplePDFPlugin::frameDidFinishLoading(uint64_t)
{
ASSERT_NOT_REACHED();
}
void SimplePDFPlugin::frameDidFail(uint64_t, bool)
{
ASSERT_NOT_REACHED();
}
void SimplePDFPlugin::didEvaluateJavaScript(uint64_t, const WTF::String&)
{
ASSERT_NOT_REACHED();
}
void SimplePDFPlugin::convertPostScriptDataIfNeeded()
{
if (!m_isPostScript)
return;
m_suggestedFilename = String(m_suggestedFilename + ".pdf");
m_data = convertPostScriptDataToPDF(m_data);
}
void SimplePDFPlugin::streamDidReceiveResponse(uint64_t streamID, const KURL&, uint32_t, uint32_t, const String& mimeType, const String&, const String& suggestedFilename)
{
ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);
m_suggestedFilename = suggestedFilename;
if (equalIgnoringCase(mimeType, postScriptMIMEType))
m_isPostScript = true;
}
void SimplePDFPlugin::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 SimplePDFPlugin::streamDidFinishLoading(uint64_t streamID)
{
ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);
convertPostScriptDataIfNeeded();
pdfDocumentDidLoad();
}
void SimplePDFPlugin::streamDidFail(uint64_t streamID, bool wasCancelled)
{
ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);
m_data.clear();
}
void SimplePDFPlugin::manualStreamDidReceiveResponse(const KURL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers, const String& suggestedFilename)
{
m_suggestedFilename = suggestedFilename;
if (equalIgnoringCase(mimeType, postScriptMIMEType))
m_isPostScript = true;
}
void SimplePDFPlugin::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 SimplePDFPlugin::manualStreamDidFinishLoading()
{
convertPostScriptDataIfNeeded();
pdfDocumentDidLoad();
}
NSData *SimplePDFPlugin::liveData() const
{
// 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 (pdfDocumentWasMutated())
return [m_pdfDocument.get() dataRepresentation];
else
return rawData();
}
PassRefPtr<SharedBuffer> SimplePDFPlugin::liveResourceData() const
{
NSData *pdfData = liveData();
if (!pdfData)
return 0;
return SharedBuffer::wrapNSData(pdfData);
}
void SimplePDFPlugin::manualStreamDidFail(bool)
{
m_data.clear();
}
bool SimplePDFPlugin::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 SimplePDFPlugin::handleWheelEvent(const WebWheelEvent& event)
{
PlatformWheelEvent platformEvent = platform(event);
return ScrollableArea::handleWheelEvent(platformEvent);
}
bool SimplePDFPlugin::handleMouseEnterEvent(const WebMouseEvent&)
{
mouseEnteredContentArea();
return false;
}
bool SimplePDFPlugin::handleMouseLeaveEvent(const WebMouseEvent&)
{
mouseExitedContentArea();
return false;
}
bool SimplePDFPlugin::handleContextMenuEvent(const WebMouseEvent&)
{
// Use default WebKit context menu.
return false;
}
bool SimplePDFPlugin::handleKeyboardEvent(const WebKeyboardEvent&)
{
return false;
}
void SimplePDFPlugin::setFocus(bool hasFocus)
{
}
NPObject* SimplePDFPlugin::pluginScriptableNPObject()
{
return 0;
}
#if PLATFORM(MAC)
void SimplePDFPlugin::windowFocusChanged(bool)
{
}
void SimplePDFPlugin::windowAndViewFramesChanged(const WebCore::IntRect& windowFrameInScreenCoordinates, const WebCore::IntRect& viewFrameInWindowCoordinates)
{
}
void SimplePDFPlugin::windowVisibilityChanged(bool)
{
}
void SimplePDFPlugin::contentsScaleFactorChanged(float)
{
}
uint64_t SimplePDFPlugin::pluginComplexTextInputIdentifier() const
{
return 0;
}
void SimplePDFPlugin::sendComplexTextInput(const String&)
{
}
void SimplePDFPlugin::setLayerHostingMode(LayerHostingMode)
{
}
#endif
void SimplePDFPlugin::storageBlockingStateChanged(bool)
{
}
void SimplePDFPlugin::privateBrowsingStateChanged(bool)
{
}
bool SimplePDFPlugin::getFormValue(String&)
{
return false;
}
bool SimplePDFPlugin::handleScroll(ScrollDirection direction, ScrollGranularity granularity)
{
return scroll(direction, granularity);
}
Scrollbar* SimplePDFPlugin::horizontalScrollbar()
{
return m_horizontalScrollbar.get();
}
Scrollbar* SimplePDFPlugin::verticalScrollbar()
{
return m_verticalScrollbar.get();
}
IntRect SimplePDFPlugin::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* SimplePDFPlugin::enclosingScrollableArea() const
{
// FIXME: Walk up the frame tree and look for a scrollable parent frame or RenderLayer.
return 0;
}
IntRect SimplePDFPlugin::scrollableAreaBoundingBox() const
{
return pluginView()->frameRect();
}
void SimplePDFPlugin::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_size.width(), m_size.height()));
}
int SimplePDFPlugin::scrollSize(ScrollbarOrientation orientation) const
{
Scrollbar* scrollbar = ((orientation == HorizontalScrollbar) ? m_horizontalScrollbar : m_verticalScrollbar).get();
return scrollbar ? (scrollbar->totalSize() - scrollbar->visibleSize()) : 0;
}
bool SimplePDFPlugin::isActive() const
{
if (Frame* coreFrame = m_frame->coreFrame()) {
if (Page* page = coreFrame->page())
return page->focusController()->isActive();
}
return false;
}
void SimplePDFPlugin::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
{
IntRect dirtyRect = rect;
dirtyRect.moveBy(scrollbar->location());
dirtyRect.moveBy(-pluginView()->location());
controller()->invalidate(dirtyRect);
}
void SimplePDFPlugin::invalidateScrollCornerRect(const IntRect& rect)
{
controller()->invalidate(rect);
}
bool SimplePDFPlugin::isScrollCornerVisible() const
{
return false;
}
int SimplePDFPlugin::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 SimplePDFPlugin::scrollPosition() const
{
return IntPoint(m_scrollOffset.width(), m_scrollOffset.height());
}
IntPoint SimplePDFPlugin::minimumScrollPosition() const
{
return IntPoint(0, 0);
}
IntPoint SimplePDFPlugin::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_size.width() + verticalScrollbarWidth, m_pdfDocumentSize.height() - m_size.height() + horizontalScrollbarHeight);
maximumOffset.clampNegativeToZero();
return maximumOffset;
}
int SimplePDFPlugin::visibleHeight() const
{
return m_size.height();
}
int SimplePDFPlugin::visibleWidth() const
{
return m_size.width();
}
IntSize SimplePDFPlugin::contentsSize() const
{
return m_pdfDocumentSize;
}
bool SimplePDFPlugin::scrollbarsCanBeActive() const
{
return !pluginView()->frame()->document()->inPageCache();
}
void SimplePDFPlugin::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();
}
IntRect SimplePDFPlugin::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const
{
IntRect rect = scrollbarRect;
rect.move(scrollbar->location() - pluginView()->location());
return pluginView()->frame()->view()->convertFromRenderer(pluginView()->renderer(), rect);
}
IntRect SimplePDFPlugin::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
{
IntRect rect = pluginView()->frame()->view()->convertToRenderer(pluginView()->renderer(), parentRect);
rect.move(pluginView()->location() - scrollbar->location());
return rect;
}
IntPoint SimplePDFPlugin::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const
{
IntPoint point = scrollbarPoint;
point.move(scrollbar->location() - pluginView()->location());
return pluginView()->frame()->view()->convertFromRenderer(pluginView()->renderer(), point);
}
IntPoint SimplePDFPlugin::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;
}
bool SimplePDFPlugin::isEditingCommandEnabled(const String&)
{
return false;
}
bool SimplePDFPlugin::handleEditingCommand(const String&, const String&)
{
return false;
}
bool SimplePDFPlugin::handlesPageScaleFactor()
{
return false;
}
} // namespace WebKit