Unreviewed, rolling out r142343.
[WebKit-https.git] / Source / WebCore / rendering / RenderSnapshottedPlugIn.cpp
1 /*
2  * Copyright (C) 2012 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 "RenderSnapshottedPlugIn.h"
28
29 #include "Chrome.h"
30 #include "ChromeClient.h"
31 #include "Cursor.h"
32 #include "FEGaussianBlur.h"
33 #include "Filter.h"
34 #include "FrameLoaderClient.h"
35 #include "FrameView.h"
36 #include "Gradient.h"
37 #include "HTMLPlugInImageElement.h"
38 #include "ImageBuffer.h"
39 #include "MouseEvent.h"
40 #include "Page.h"
41 #include "PaintInfo.h"
42 #include "Path.h"
43 #include "SourceGraphic.h"
44
45 namespace WebCore {
46
47 static const int autoStartPlugInSizeThresholdWidth = 1;
48 static const int autoStartPlugInSizeThresholdHeight = 1;
49 static const int startLabelPadding = 10; // Label should be 10px from edge of box.
50 static const int startLabelInset = 20; // But the label is inset from its box also. FIXME: This will be removed when we go to a ShadowDOM approach.
51 static const double showLabelAfterMouseOverDelay = 1;
52 static const double showLabelAutomaticallyDelay = 3;
53 static const int snapshotLabelBlurRadius = 5;
54
55 #if ENABLE(FILTERS)
56 class RenderSnapshottedPlugInBlurFilter : public Filter {
57     WTF_MAKE_FAST_ALLOCATED;
58 public:
59     static PassRefPtr<RenderSnapshottedPlugInBlurFilter> create(int radius)
60     {
61         return adoptRef(new RenderSnapshottedPlugInBlurFilter(radius));
62     }
63
64     void setSourceImageRect(const FloatRect& r)
65     {
66         m_sourceImageRect = r;
67         m_filterRegion = r;
68         m_sourceGraphic->setMaxEffectRect(r);
69         m_blur->setMaxEffectRect(r);
70     }
71     virtual FloatRect sourceImageRect() const { return m_sourceImageRect; }
72     virtual FloatRect filterRegion() const { return m_filterRegion; }
73
74     void apply();
75     ImageBuffer* output() const { return m_blur->asImageBuffer(); }
76
77 private:
78     RenderSnapshottedPlugInBlurFilter(int radius);
79
80     FloatRect m_sourceImageRect;
81     FloatRect m_filterRegion;
82     RefPtr<SourceGraphic> m_sourceGraphic;
83     RefPtr<FEGaussianBlur> m_blur;
84 };
85
86 RenderSnapshottedPlugInBlurFilter::RenderSnapshottedPlugInBlurFilter(int radius)
87 {
88     setFilterResolution(FloatSize(1, 1));
89     m_sourceGraphic = SourceGraphic::create(this);
90     m_blur = FEGaussianBlur::create(this, radius, radius);
91     m_blur->inputEffects().append(m_sourceGraphic);
92 }
93
94 void RenderSnapshottedPlugInBlurFilter::apply()
95 {
96     m_sourceGraphic->clearResult();
97     m_blur->clearResult();
98     m_blur->apply();
99 }
100 #endif
101
102 RenderSnapshottedPlugIn::RenderSnapshottedPlugIn(HTMLPlugInImageElement* element)
103     : RenderEmbeddedObject(element)
104     , m_snapshotResource(RenderImageResource::create())
105     , m_shouldShowLabel(false)
106     , m_shouldShowLabelAutomatically(false)
107     , m_showedLabelOnce(false)
108     , m_showReason(UserMousedOver)
109     , m_showLabelDelayTimer(this, &RenderSnapshottedPlugIn::showLabelDelayTimerFired)
110     , m_snapshotResourceForLabel(RenderImageResource::create())
111 {
112     m_snapshotResource->initialize(this);
113     m_snapshotResourceForLabel->initialize(this);
114 }
115
116 RenderSnapshottedPlugIn::~RenderSnapshottedPlugIn()
117 {
118     ASSERT(m_snapshotResource);
119     m_snapshotResource->shutdown();
120     ASSERT(m_snapshotResourceForLabel);
121     m_snapshotResourceForLabel->shutdown();
122 }
123
124 HTMLPlugInImageElement* RenderSnapshottedPlugIn::plugInImageElement() const
125 {
126     return static_cast<HTMLPlugInImageElement*>(node());
127 }
128
129 void RenderSnapshottedPlugIn::updateSnapshot(PassRefPtr<Image> image)
130 {
131     // Zero-size plugins will have no image.
132     if (!image)
133         return;
134
135     // We may have stored a version of this snapshot to use when showing the
136     // label. Invalidate it now and it will be regenerated later.
137     if (m_snapshotResourceForLabel->hasImage())
138         m_snapshotResourceForLabel->setCachedImage(0);
139
140     m_snapshotResource->setCachedImage(new CachedImage(image.get()));
141     repaint();
142     if (m_shouldShowLabelAutomatically)
143         resetDelayTimer(ShouldShowAutomatically);
144 }
145
146 void RenderSnapshottedPlugIn::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
147 {
148     if (plugInImageElement()->displayState() < HTMLPlugInElement::PlayingWithPendingMouseClick) {
149         RenderReplaced::paint(paintInfo, paintOffset);
150         return;
151     }
152
153     RenderEmbeddedObject::paint(paintInfo, paintOffset);
154 }
155
156 void RenderSnapshottedPlugIn::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
157 {
158     if (plugInImageElement()->displayState() < HTMLPlugInElement::PlayingWithPendingMouseClick) {
159         if (m_shouldShowLabel)
160             paintReplacedSnapshotWithLabel(paintInfo, paintOffset);
161         else
162             paintReplacedSnapshot(paintInfo, paintOffset);
163         return;
164     }
165
166     RenderEmbeddedObject::paintReplaced(paintInfo, paintOffset);
167 }
168
169 void RenderSnapshottedPlugIn::paintSnapshot(Image* image, PaintInfo& paintInfo, const LayoutPoint& paintOffset)
170 {
171     LayoutUnit cWidth = contentWidth();
172     LayoutUnit cHeight = contentHeight();
173     if (!cWidth || !cHeight)
174         return;
175
176     GraphicsContext* context = paintInfo.context;
177 #if PLATFORM(MAC)
178     if (style()->highlight() != nullAtom && !context->paintingDisabled())
179         paintCustomHighlight(toPoint(paintOffset - location()), style()->highlight(), true);
180 #endif
181
182     LayoutSize contentSize(cWidth, cHeight);
183     LayoutPoint contentLocation = paintOffset;
184     contentLocation.move(borderLeft() + paddingLeft(), borderTop() + paddingTop());
185
186     LayoutRect rect(contentLocation, contentSize);
187     IntRect alignedRect = pixelSnappedIntRect(rect);
188     if (alignedRect.width() <= 0 || alignedRect.height() <= 0)
189         return;
190
191     bool useLowQualityScaling = shouldPaintAtLowQuality(context, image, image, alignedRect.size());
192     context->drawImage(image, style()->colorSpace(), alignedRect, CompositeSourceOver, shouldRespectImageOrientation(), useLowQualityScaling);
193 }
194
195 void RenderSnapshottedPlugIn::paintReplacedSnapshot(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
196 {
197     RefPtr<Image> image = m_snapshotResource->image();
198     if (!image || image->isNull())
199         return;
200
201     paintSnapshot(image.get(), paintInfo, paintOffset);
202 }
203
204 Image* RenderSnapshottedPlugIn::startLabelImage(LabelSize size) const
205 {
206     static Image* labelImages[2] = { 0, 0 };
207     static bool initializedImages[2] = { false, false };
208
209     int arrayIndex = static_cast<int>(size);
210     if (labelImages[arrayIndex])
211         return labelImages[arrayIndex];
212     if (initializedImages[arrayIndex])
213         return 0;
214
215     if (document()->page()) {
216         labelImages[arrayIndex] = document()->page()->chrome()->client()->plugInStartLabelImage(size).leakRef();
217         initializedImages[arrayIndex] = true;
218     }
219     return labelImages[arrayIndex];
220 }
221
222 #if ENABLE(FILTERS)
223 static PassRefPtr<Image> snapshottedPluginImageForLabelDisplay(PassRefPtr<Image> snapshot, const LayoutRect& blurRegion)
224 {
225     OwnPtr<ImageBuffer> snapshotBuffer = ImageBuffer::create(snapshot->size());
226     snapshotBuffer->context()->drawImage(snapshot.get(), ColorSpaceDeviceRGB, IntPoint(0, 0));
227
228     OwnPtr<ImageBuffer> blurBuffer = ImageBuffer::create(roundedIntSize(blurRegion.size()));
229     blurBuffer->context()->drawImage(snapshot.get(), ColorSpaceDeviceRGB, IntPoint(-blurRegion.x(), -blurRegion.y()));
230
231     RefPtr<RenderSnapshottedPlugInBlurFilter> blurFilter = RenderSnapshottedPlugInBlurFilter::create(snapshotLabelBlurRadius);
232     blurFilter->setSourceImage(blurBuffer.release());
233     blurFilter->setSourceImageRect(FloatRect(FloatPoint(), blurRegion.size()));
234     blurFilter->apply();
235
236     snapshotBuffer->context()->drawImageBuffer(blurFilter->output(), ColorSpaceDeviceRGB, roundedIntPoint(blurRegion.location()));
237     return snapshotBuffer->copyImage();
238 }
239 #endif
240
241 void RenderSnapshottedPlugIn::paintReplacedSnapshotWithLabel(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
242 {
243     if (contentBoxRect().isEmpty())
244         return;
245
246     if (!plugInImageElement()->hovered() && m_showReason == UserMousedOver)
247         return;
248
249     m_showedLabelOnce = true;
250     LayoutRect rect = contentBoxRect();
251     LayoutRect labelRect = tryToFitStartLabel(LabelSizeLarge, rect);
252     LabelSize size = NoLabel;
253     if (!labelRect.isEmpty())
254         size = LabelSizeLarge;
255     else {
256         labelRect = tryToFitStartLabel(LabelSizeSmall, rect);
257         if (!labelRect.isEmpty())
258             size = LabelSizeSmall;
259         else
260             return;
261     }
262
263     Image* labelImage = startLabelImage(size);
264     if (!labelImage)
265         return;
266
267     RefPtr<Image> snapshotImage = m_snapshotResource->image();
268     if (!snapshotImage || snapshotImage->isNull())
269         return;
270
271 #if ENABLE(FILTERS)
272     RefPtr<Image> blurredSnapshotImage = m_snapshotResourceForLabel->image();
273     if (!blurredSnapshotImage || blurredSnapshotImage->isNull()) {
274         blurredSnapshotImage = snapshottedPluginImageForLabelDisplay(snapshotImage, labelRect);
275         m_snapshotResourceForLabel->setCachedImage(new CachedImage(blurredSnapshotImage.get()));
276     }
277     snapshotImage = blurredSnapshotImage;
278 #endif
279
280     paintSnapshot(snapshotImage.get(), paintInfo, paintOffset);
281
282     // Remember that the labelRect includes the label inset, so we need to adjust for it.
283     paintInfo.context->drawImage(labelImage, ColorSpaceDeviceRGB,
284                                  IntRect(roundedIntPoint(paintOffset + labelRect.location() - IntSize(startLabelInset, startLabelInset)),
285                                          roundedIntSize(labelRect.size() + IntSize(2 * startLabelInset, 2 * startLabelInset))),
286                                  labelImage->rect());
287 }
288
289 void RenderSnapshottedPlugIn::repaintLabel()
290 {
291     // FIXME: This is unfortunate. We should just repaint the label.
292     repaint();
293 }
294
295 void RenderSnapshottedPlugIn::showLabelDelayTimerFired(Timer<RenderSnapshottedPlugIn>*)
296 {
297     m_shouldShowLabel = true;
298     repaintLabel();
299 }
300
301 void RenderSnapshottedPlugIn::setShouldShowLabelAutomatically(bool show)
302 {
303     m_shouldShowLabelAutomatically = show;
304 }
305
306 CursorDirective RenderSnapshottedPlugIn::getCursor(const LayoutPoint& point, Cursor& overrideCursor) const
307 {
308     if (plugInImageElement()->displayState() < HTMLPlugInElement::PlayingWithPendingMouseClick) {
309         overrideCursor = handCursor();
310         return SetCursor;
311     }
312     return RenderEmbeddedObject::getCursor(point, overrideCursor);
313 }
314
315 void RenderSnapshottedPlugIn::handleEvent(Event* event)
316 {
317     if (!event->isMouseEvent())
318         return;
319
320     MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
321
322     if (event->type() == eventNames().clickEvent) {
323         if (mouseEvent->button() != LeftButton)
324             return;
325
326         plugInImageElement()->setDisplayState(HTMLPlugInElement::PlayingWithPendingMouseClick);
327         plugInImageElement()->userDidClickSnapshot(mouseEvent);
328
329         if (widget()) {
330             if (Frame* frame = document()->frame())
331                 frame->loader()->client()->recreatePlugin(widget());
332             repaint();
333         }
334         event->setDefaultHandled();
335     } else if (event->type() == eventNames().mousedownEvent) {
336         if (mouseEvent->button() != LeftButton)
337             return;
338
339         if (m_showLabelDelayTimer.isActive())
340             m_showLabelDelayTimer.stop();
341
342         event->setDefaultHandled();
343     } else if (event->type() == eventNames().mouseoverEvent) {
344         if (!m_showedLabelOnce || m_showReason != ShouldShowAutomatically)
345             resetDelayTimer(UserMousedOver);
346         event->setDefaultHandled();
347     } else if (event->type() == eventNames().mouseoutEvent) {
348         if (m_showLabelDelayTimer.isActive())
349             m_showLabelDelayTimer.stop();
350         if (m_shouldShowLabel) {
351             if (m_showReason == UserMousedOver) {
352                 m_shouldShowLabel = false;
353                 repaintLabel();
354             }
355         } else if (m_shouldShowLabelAutomatically)
356             resetDelayTimer(ShouldShowAutomatically);
357         event->setDefaultHandled();
358     }
359 }
360
361 LayoutRect RenderSnapshottedPlugIn::tryToFitStartLabel(LabelSize size, const LayoutRect& contentBox) const
362 {
363     Image* labelImage = startLabelImage(size);
364     if (!labelImage)
365         return LayoutRect();
366
367     // Assume that the labelImage has been provided to match our device scale.
368     float scaleFactor = 1;
369     if (document()->page())
370         scaleFactor = document()->page()->deviceScaleFactor();
371     IntSize labelImageSize = labelImage->size();
372     labelImageSize.scale(1 / (scaleFactor ? scaleFactor : 1));
373
374     LayoutSize labelSize = labelImageSize - LayoutSize(2 * startLabelInset, 2 * startLabelInset);
375     LayoutRect candidateRect(contentBox.maxXMinYCorner() + LayoutSize(-startLabelPadding, startLabelPadding) + LayoutSize(-labelSize.width(), 0), labelSize);
376     // The minimum allowed content box size is the label image placed in the center of the box, surrounded by startLabelPadding.
377     if (candidateRect.x() < startLabelPadding || candidateRect.maxY() > contentBox.height() - startLabelPadding)
378         return LayoutRect();
379     return candidateRect;
380 }
381
382 void RenderSnapshottedPlugIn::resetDelayTimer(ShowReason reason)
383 {
384     m_showReason = reason;
385     m_showLabelDelayTimer.startOneShot(reason == UserMousedOver ? showLabelAfterMouseOverDelay : showLabelAutomaticallyDelay);
386 }
387
388 } // namespace WebCore