[Mac] Should be able to scroll embedded PDFs using mouse
[WebKit-https.git] / Source / WebKit2 / WebProcess / Plugins / PDF / BuiltInPDFView.cpp
1 /*
2  * Copyright (C) 2011 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "BuiltInPDFView.h"
28
29 #include "PluginView.h"
30 #include "ShareableBitmap.h"
31 #include "WebEvent.h"
32 #include "WebEventConversion.h"
33 #include <WebCore/FocusController.h>
34 #include <WebCore/Frame.h>
35 #include <WebCore/FrameView.h>
36 #include <WebCore/GraphicsContext.h>
37 #include <WebCore/HTTPHeaderMap.h>
38 #include <WebCore/LocalizedStrings.h>
39 #include <WebCore/Page.h>
40 #include <WebCore/PluginData.h>
41 #include <WebCore/ScrollAnimator.h>
42 #include <WebCore/ScrollbarTheme.h>
43
44 using namespace WebCore;
45 using namespace std;
46
47 namespace WebKit {
48
49 const uint64_t pdfDocumentRequestID = 1; // PluginController supports loading multiple streams, but we only need one for PDF.
50
51 PassRefPtr<BuiltInPDFView> BuiltInPDFView::create(Page* page)
52 {
53     return adoptRef(new BuiltInPDFView(page));
54 }
55
56 BuiltInPDFView::BuiltInPDFView(Page* page)
57     : m_page(page)
58 {
59     m_page->addScrollableArea(this);
60 }
61
62 BuiltInPDFView::~BuiltInPDFView()
63 {
64     if (m_page)
65         m_page->removeScrollableArea(this);
66
67     if (m_horizontalScrollbar)
68         willRemoveHorizontalScrollbar(m_horizontalScrollbar.get());
69     if (m_verticalScrollbar)
70         willRemoveVerticalScrollbar(m_verticalScrollbar.get());
71 }
72
73 PluginInfo BuiltInPDFView::pluginInfo()
74 {
75     PluginInfo info;
76     info.name = builtInPDFPluginName();
77
78     MimeClassInfo mimeClassInfo;
79     mimeClassInfo.type ="application/pdf";
80     mimeClassInfo.desc = pdfDocumentTypeDescription();
81     mimeClassInfo.extensions.append("pdf");
82
83     info.mimes.append(mimeClassInfo);
84     return info;
85 }
86
87 PluginView* BuiltInPDFView::pluginView()
88 {
89     return static_cast<PluginView*>(controller());
90 }
91
92 const PluginView* BuiltInPDFView::pluginView() const
93 {
94     return static_cast<const PluginView*>(controller());
95 }
96
97 void BuiltInPDFView::calculateDocumentSize()
98 {
99     CGPDFPageRef pdfPage = CGPDFDocumentGetPage(m_pdfDocument.get(), 1); // FIXME: Draw all pages of a document.
100     if (!pdfPage)
101         return;
102
103     CGRect box = CGPDFPageGetBoxRect(pdfPage, kCGPDFCropBox);
104     if (CGRectIsEmpty(box))
105         box = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);
106     m_pdfDocumentSize = IntSize(box.size);
107 }
108
109 void BuiltInPDFView::updateScrollbars()
110 {
111     if (m_horizontalScrollbar) {
112         if (m_frameRect.width() >= m_pdfDocumentSize.width())
113             destroyScrollbar(HorizontalScrollbar);
114     } else if (m_frameRect.width() < m_pdfDocumentSize.width())
115         m_horizontalScrollbar = createScrollbar(HorizontalScrollbar);
116
117     if (m_verticalScrollbar) {
118         if (m_frameRect.height() >= m_pdfDocumentSize.height())
119             destroyScrollbar(VerticalScrollbar);
120     } else if (m_frameRect.height() < m_pdfDocumentSize.height())
121         m_verticalScrollbar = createScrollbar(VerticalScrollbar);
122
123     int horizontalScrollbarHeight = (m_horizontalScrollbar && !m_horizontalScrollbar->isOverlayScrollbar()) ? m_horizontalScrollbar->height() : 0;
124     int verticalScrollbarWidth = (m_verticalScrollbar && !m_verticalScrollbar->isOverlayScrollbar()) ? m_verticalScrollbar->width() : 0;
125
126     // FIXME: Use document page size for PageDown step.
127     int clientHeight = m_pdfDocumentSize.height();
128     int pageStep = max(max<int>(clientHeight * Scrollbar::minFractionToStepWhenPaging(), clientHeight - Scrollbar::maxOverlapBetweenPages()), 1);
129
130     if (m_horizontalScrollbar) {
131         m_horizontalScrollbar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep);
132         m_horizontalScrollbar->setProportion(m_frameRect.width() - verticalScrollbarWidth, m_pdfDocumentSize.width());
133         IntRect scrollbarRect(pluginView()->x(), pluginView()->y() + m_frameRect.height() - m_horizontalScrollbar->height(), m_frameRect.width(), m_horizontalScrollbar->height());
134         if (m_verticalScrollbar)
135             scrollbarRect.contract(m_verticalScrollbar->width(), 0);
136         m_horizontalScrollbar->setFrameRect(scrollbarRect);
137     }
138     if (m_verticalScrollbar) {
139         m_verticalScrollbar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep);
140         m_verticalScrollbar->setProportion(m_frameRect.height() - horizontalScrollbarHeight, m_pdfDocumentSize.height());
141         IntRect scrollbarRect(IntRect(pluginView()->x() + m_frameRect.width() - m_verticalScrollbar->width(), pluginView()->y(), m_verticalScrollbar->width(), m_frameRect.height()));
142         if (m_horizontalScrollbar)
143             scrollbarRect.contract(0, m_horizontalScrollbar->height());
144         m_verticalScrollbar->setFrameRect(scrollbarRect);
145     }
146 }
147
148 void BuiltInPDFView::didAddHorizontalScrollbar(Scrollbar* scrollbar)
149 {
150     pluginView()->frame()->document()->didAddWheelEventHandler();
151     ScrollableArea::didAddHorizontalScrollbar(scrollbar);
152 }
153
154 void BuiltInPDFView::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
155 {
156     ScrollableArea::willRemoveHorizontalScrollbar(scrollbar);
157     // FIXME: Maybe need a separate ScrollableArea::didRemoveHorizontalScrollbar callback?
158     if (PluginView* pluginView = this->pluginView())
159         pluginView->frame()->document()->didRemoveWheelEventHandler();
160 }
161
162 PassRefPtr<Scrollbar> BuiltInPDFView::createScrollbar(ScrollbarOrientation orientation)
163 {
164     // FIXME: Support custom scrollbar styles.
165     RefPtr<Scrollbar> widget = Scrollbar::createNativeScrollbar(this, orientation, RegularScrollbar);
166     if (orientation == HorizontalScrollbar)
167         scrollAnimator()->didAddHorizontalScrollbar(widget.get());
168     else 
169         scrollAnimator()->didAddVerticalScrollbar(widget.get());
170     pluginView()->frame()->view()->addChild(widget.get());
171     return widget.release();
172 }
173
174 void BuiltInPDFView::destroyScrollbar(ScrollbarOrientation orientation)
175 {
176     RefPtr<Scrollbar>& scrollbar = orientation == HorizontalScrollbar ? m_horizontalScrollbar : m_verticalScrollbar;
177     if (!scrollbar)
178         return;
179
180     if (orientation == HorizontalScrollbar)
181         scrollAnimator()->willRemoveHorizontalScrollbar(scrollbar.get());
182     else
183         scrollAnimator()->willRemoveVerticalScrollbar(scrollbar.get());
184
185     scrollbar->removeFromParent();
186     scrollbar->disconnectFromScrollableArea();
187     scrollbar = 0;
188 }
189
190 void BuiltInPDFView::pdfDocumentDidLoad()
191 {
192     calculateDocumentSize();
193     updateScrollbars();
194
195     controller()->invalidate(IntRect(0, 0, m_frameRect.width(), m_frameRect.height()));
196 }
197
198 bool BuiltInPDFView::initialize(const Parameters& parameters)
199 {
200     // Load the src URL if needed.
201     if (!parameters.loadManually && !parameters.url.isEmpty())
202         controller()->loadURL(pdfDocumentRequestID, "GET", parameters.url.string(), String(), HTTPHeaderMap(), Vector<uint8_t>(), false);
203
204     return true;
205 }
206
207 void BuiltInPDFView::destroy()
208 {
209 }
210
211 void BuiltInPDFView::paint(GraphicsContext* graphicsContext, const IntRect& dirtyRectInWindowCoordinates)
212 {
213     if (!m_pdfDocument) // FIXME: Draw background and loading progress.
214         return;
215
216     // FIXME: This function just draws the fist page of a document at top left corner.
217     // We should show the whole document, centering small ones.
218     CGPDFPageRef pdfPage = CGPDFDocumentGetPage(m_pdfDocument.get(), 1);
219     if (!pdfPage)
220         return;
221
222     scrollAnimator()->contentAreaWillPaint();
223
224     CGContextRef context = graphicsContext->platformContext();
225     GraphicsContextStateSaver stateSaver(*graphicsContext);
226     graphicsContext->clip(dirtyRectInWindowCoordinates);
227     graphicsContext->setImageInterpolationQuality(InterpolationHigh);
228     graphicsContext->setShouldAntialias(true);
229     graphicsContext->setShouldSmoothFonts(true);
230
231     CGRect pageBox = CGPDFPageGetBoxRect(pdfPage, kCGPDFCropBox);
232     if (CGRectIsEmpty(pageBox))
233         pageBox = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);
234
235     CGContextClipToRect(context, CGRectMake(m_frameRect.x(), m_frameRect.y(), m_pdfDocumentSize.width() - m_scrollOffset.width(), m_pdfDocumentSize.height() - m_scrollOffset.height()));
236     CGContextTranslateCTM(context, m_frameRect.x() - pageBox.origin.x - m_scrollOffset.width(), m_frameRect.y() + pageBox.origin.y + m_pdfDocumentSize.height() - m_scrollOffset.height());
237
238     CGContextScaleCTM(context, 1, -1);
239     CGContextDrawPDFPage(context, pdfPage);
240
241     stateSaver.restore();
242
243     stateSaver.save();
244     // Undo translation to window coordinates performed by PluginView::paint().
245     IntRect dirtyRect = pluginView()->parent()->windowToContents(dirtyRectInWindowCoordinates);
246     IntPoint documentOriginInWindowCoordinates = pluginView()->parent()->windowToContents(IntPoint());
247     graphicsContext->translate(-documentOriginInWindowCoordinates.x(), -documentOriginInWindowCoordinates.y());
248     if (m_horizontalScrollbar)
249         m_horizontalScrollbar->paint(graphicsContext, dirtyRect);
250     if (m_verticalScrollbar)
251         m_verticalScrollbar->paint(graphicsContext, dirtyRect);
252
253     IntRect dirtyCornerRect = intersection(scrollCornerRect(), dirtyRect);
254     ScrollbarTheme::nativeTheme()->paintScrollCorner(0, graphicsContext, dirtyCornerRect);
255 }
256
257 void BuiltInPDFView::updateControlTints(GraphicsContext* graphicsContext)
258 {
259     ASSERT(graphicsContext->updatingControlTints());
260
261     if (m_horizontalScrollbar)
262         m_horizontalScrollbar->invalidate();
263     if (m_verticalScrollbar)
264         m_verticalScrollbar->invalidate();
265     invalidateScrollCorner(scrollCornerRect());
266 }
267
268 PassRefPtr<ShareableBitmap> BuiltInPDFView::snapshot()
269 {
270     return 0;
271 }
272
273 #if PLATFORM(MAC)
274 PlatformLayer* BuiltInPDFView::pluginLayer()
275 {
276     return 0;
277 }
278 #endif
279
280
281 bool BuiltInPDFView::isTransparent()
282 {
283     // This should never be called from the web process.
284     ASSERT_NOT_REACHED();
285     return false;
286 }
287
288 void BuiltInPDFView::geometryDidChange(const IntRect& frameRect, const IntRect& clipRect)
289 {
290     if (m_frameRect == frameRect) {
291         // Nothing to do.
292         return;
293     }
294
295     m_frameRect = frameRect;
296     updateScrollbars();
297 }
298
299 void BuiltInPDFView::visibilityDidChange()
300 {
301 }
302
303 void BuiltInPDFView::frameDidFinishLoading(uint64_t)
304 {
305     ASSERT_NOT_REACHED();
306 }
307
308 void BuiltInPDFView::frameDidFail(uint64_t, bool)
309 {
310     ASSERT_NOT_REACHED();
311 }
312
313 void BuiltInPDFView::didEvaluateJavaScript(uint64_t, const WTF::String&)
314 {
315     ASSERT_NOT_REACHED();
316 }
317
318 void BuiltInPDFView::streamDidReceiveResponse(uint64_t streamID, const KURL&, uint32_t, uint32_t, const WTF::String&, const WTF::String&)
319 {
320     ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);
321 }
322                                            
323 void BuiltInPDFView::streamDidReceiveData(uint64_t streamID, const char* bytes, int length)
324 {
325     ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);
326
327     if (!m_dataBuffer)
328         m_dataBuffer.adoptCF(CFDataCreateMutable(0, 0));
329
330     CFDataAppendBytes(m_dataBuffer.get(), reinterpret_cast<const UInt8*>(bytes), length);
331 }
332
333 void BuiltInPDFView::streamDidFinishLoading(uint64_t streamID)
334 {
335     ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);
336
337     RetainPtr<CGDataProviderRef> pdfDataProvider(AdoptCF, CGDataProviderCreateWithCFData(m_dataBuffer.get()));
338     m_pdfDocument.adoptCF(CGPDFDocumentCreateWithProvider(pdfDataProvider.get()));
339
340     pdfDocumentDidLoad();
341 }
342
343 void BuiltInPDFView::streamDidFail(uint64_t streamID, bool wasCancelled)
344 {
345     ASSERT_UNUSED(streamID, streamID == pdfDocumentRequestID);
346
347     m_dataBuffer.clear();
348 }
349
350 void BuiltInPDFView::manualStreamDidReceiveResponse(const KURL& responseURL, uint32_t streamLength,  uint32_t lastModifiedTime, const WTF::String& mimeType, const WTF::String& headers)
351 {
352 }
353
354 void BuiltInPDFView::manualStreamDidReceiveData(const char* bytes, int length)
355 {
356     if (!m_dataBuffer)
357         m_dataBuffer.adoptCF(CFDataCreateMutable(0, 0));
358
359     CFDataAppendBytes(m_dataBuffer.get(), reinterpret_cast<const UInt8*>(bytes), length);
360 }
361
362 void BuiltInPDFView::manualStreamDidFinishLoading()
363 {
364     RetainPtr<CGDataProviderRef> pdfDataProvider(AdoptCF, CGDataProviderCreateWithCFData(m_dataBuffer.get()));
365     m_pdfDocument.adoptCF(CGPDFDocumentCreateWithProvider(pdfDataProvider.get()));
366
367     pdfDocumentDidLoad();
368 }
369
370 void BuiltInPDFView::manualStreamDidFail(bool)
371 {
372     m_dataBuffer.clear();
373 }
374
375 bool BuiltInPDFView::handleMouseEvent(const WebMouseEvent& event)
376 {
377     switch (event.type()) {
378     case WebEvent::MouseMove:
379         scrollAnimator()->mouseMovedInContentArea();
380         // FIXME: Should also notify scrollbar to show hover effect. Should also send mouseExited to hide it.
381         break;
382     case WebEvent::MouseDown: {
383         // Returning false as will make EventHandler unfocus the plug-in, which is appropriate when clicking scrollbars.
384         // Ideally, we wouldn't change focus at all, but PluginView already did that for us.
385         // When support for PDF forms is added, we'll need to actually focus the plug-in when clicking in a form.
386         break;
387     }
388     case WebEvent::MouseUp:
389         if (m_horizontalScrollbar)
390             m_horizontalScrollbar->mouseUp();
391         if (m_verticalScrollbar)
392             m_verticalScrollbar->mouseUp();
393         break;
394     default:
395         break;
396     }
397         
398     return false;
399 }
400
401 bool BuiltInPDFView::handleWheelEvent(const WebWheelEvent& event)
402 {
403     PlatformWheelEvent platformEvent = platform(event);
404     return ScrollableArea::handleWheelEvent(platformEvent);
405 }
406
407 bool BuiltInPDFView::handleMouseEnterEvent(const WebMouseEvent&)
408 {
409     scrollAnimator()->mouseEnteredContentArea();
410     return false;
411 }
412
413 bool BuiltInPDFView::handleMouseLeaveEvent(const WebMouseEvent&)
414 {
415     scrollAnimator()->mouseExitedContentArea();
416     return false;
417 }
418
419 bool BuiltInPDFView::handleKeyboardEvent(const WebKeyboardEvent&)
420 {
421     return false;
422 }
423
424 void BuiltInPDFView::setFocus(bool hasFocus)
425 {
426 }
427
428 NPObject* BuiltInPDFView::pluginScriptableNPObject()
429 {
430     return 0;
431 }
432
433 #if PLATFORM(MAC)
434
435 void BuiltInPDFView::windowFocusChanged(bool)
436 {
437 }
438
439 void BuiltInPDFView::windowAndViewFramesChanged(const WebCore::IntRect& windowFrameInScreenCoordinates, const WebCore::IntRect& viewFrameInWindowCoordinates)
440 {
441 }
442
443 void BuiltInPDFView::windowVisibilityChanged(bool)
444 {
445 }
446
447 uint64_t BuiltInPDFView::pluginComplexTextInputIdentifier() const
448 {
449     return 0;
450 }
451
452 void BuiltInPDFView::sendComplexTextInput(const String&)
453 {
454 }
455
456 #endif
457
458 void BuiltInPDFView::privateBrowsingStateChanged(bool)
459 {
460 }
461
462 bool BuiltInPDFView::getFormValue(String&)
463 {
464     return false;
465 }
466
467 bool BuiltInPDFView::handleScroll(ScrollDirection direction, ScrollGranularity granularity)
468 {
469     return scroll(direction, granularity);
470 }
471
472 Scrollbar* BuiltInPDFView::horizontalScrollbar()
473 {
474     return m_horizontalScrollbar.get();
475 }
476
477 Scrollbar* BuiltInPDFView::verticalScrollbar()
478 {
479     return m_verticalScrollbar.get();
480 }
481
482 IntRect BuiltInPDFView::scrollCornerRect() const
483 {
484     if (!m_horizontalScrollbar || !m_verticalScrollbar)
485         return IntRect();
486     if (m_horizontalScrollbar->isOverlayScrollbar()) {
487         ASSERT(m_verticalScrollbar->isOverlayScrollbar());
488         return IntRect();
489     }
490     return IntRect(pluginView()->width() - m_verticalScrollbar->width(), pluginView()->height() - m_horizontalScrollbar->height(), m_verticalScrollbar->width(), m_horizontalScrollbar->height());
491 }
492
493 ScrollableArea* BuiltInPDFView::enclosingScrollableArea() const
494 {
495     // FIXME: Walk up the frame tree and look for a scrollable parent frame or RenderLayer.
496     return 0;
497 }
498
499 void BuiltInPDFView::setScrollOffset(const IntPoint& offset)
500 {
501     m_scrollOffset = IntSize(offset.x(), offset.y());
502     // FIXME: It would be better for performance to blit parts that remain visible.
503     controller()->invalidate(IntRect(0, 0, m_frameRect.width(), m_frameRect.height()));
504 }
505
506 int BuiltInPDFView::scrollSize(ScrollbarOrientation orientation) const
507 {
508     Scrollbar* scrollbar = ((orientation == HorizontalScrollbar) ? m_horizontalScrollbar : m_verticalScrollbar).get();
509     return scrollbar ? (scrollbar->totalSize() - scrollbar->visibleSize()) : 0;
510 }
511
512 bool BuiltInPDFView::isActive() const
513 {
514     return m_page->focusController()->isActive();
515 }
516
517 void BuiltInPDFView::invalidateScrollbarRect(Scrollbar* scrollbar, const LayoutRect& rect)
518 {
519     LayoutRect dirtyRect = rect;
520     dirtyRect.moveBy(scrollbar->location());
521     dirtyRect.moveBy(-pluginView()->location());
522     controller()->invalidate(dirtyRect);
523 }
524
525 void BuiltInPDFView::invalidateScrollCornerRect(const IntRect& rect)
526 {
527     controller()->invalidate(rect);
528 }
529
530 bool BuiltInPDFView::isScrollCornerVisible() const
531 {
532     return false;
533 }
534
535 int BuiltInPDFView::scrollPosition(Scrollbar* scrollbar) const
536 {
537     if (scrollbar->orientation() == HorizontalScrollbar)
538         return m_scrollOffset.width();
539     if (scrollbar->orientation() == VerticalScrollbar)
540         return m_scrollOffset.height();
541     ASSERT_NOT_REACHED();
542     return 0;
543 }
544
545 IntPoint BuiltInPDFView::scrollPosition() const
546 {
547     return IntPoint(m_scrollOffset.width(), m_scrollOffset.height());
548 }
549
550 IntPoint BuiltInPDFView::minimumScrollPosition() const
551 {
552     return IntPoint(0, 0);
553 }
554
555 IntPoint BuiltInPDFView::maximumScrollPosition() const
556 {
557     int horizontalScrollbarHeight = (m_horizontalScrollbar && !m_horizontalScrollbar->isOverlayScrollbar()) ? m_horizontalScrollbar->height() : 0;
558     int verticalScrollbarWidth = (m_verticalScrollbar && !m_verticalScrollbar->isOverlayScrollbar()) ? m_verticalScrollbar->width() : 0;
559
560     IntPoint maximumOffset(m_pdfDocumentSize.width() - m_frameRect.width() + verticalScrollbarWidth, m_pdfDocumentSize.height() - m_frameRect.height() + horizontalScrollbarHeight);
561     maximumOffset.clampNegativeToZero();
562     return maximumOffset;
563 }
564
565 LayoutUnit BuiltInPDFView::visibleHeight() const
566 {
567     return m_frameRect.height();
568 }
569
570 LayoutUnit BuiltInPDFView::visibleWidth() const
571 {
572     return m_frameRect.width();
573 }
574
575 IntSize BuiltInPDFView::contentsSize() const
576 {
577     return m_pdfDocumentSize;
578 }
579
580 bool BuiltInPDFView::isOnActivePage() const
581 {
582     return pluginView()->frame()->document()->inPageCache();
583 }
584
585 } // namespace WebKit