blob: a6351c9d476b68eb10b8101842cc98687c2d58e9 [file] [log] [blame]
/*
* Copyright (C) 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.
*/
#include "config.h"
#include "RenderSnapshottedPlugIn.h"
#include "Chrome.h"
#include "ChromeClient.h"
#include "Cursor.h"
#include "FEGaussianBlur.h"
#include "Filter.h"
#include "FrameLoaderClient.h"
#include "FrameView.h"
#include "Gradient.h"
#include "HTMLPlugInImageElement.h"
#include "ImageBuffer.h"
#include "MouseEvent.h"
#include "Page.h"
#include "PaintInfo.h"
#include "Path.h"
#include "SourceGraphic.h"
namespace WebCore {
static const int autoStartPlugInSizeThresholdWidth = 1;
static const int autoStartPlugInSizeThresholdHeight = 1;
static const int startLabelPadding = 10; // Label should be 10px from edge of box.
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.
static const double showLabelAfterMouseOverDelay = 1;
static const double showLabelAutomaticallyDelay = 3;
static const int snapshotLabelBlurRadius = 5;
#if ENABLE(FILTERS)
class RenderSnapshottedPlugInBlurFilter : public Filter {
WTF_MAKE_FAST_ALLOCATED;
public:
static PassRefPtr<RenderSnapshottedPlugInBlurFilter> create(int radius)
{
return adoptRef(new RenderSnapshottedPlugInBlurFilter(radius));
}
void setSourceImageRect(const FloatRect& r)
{
m_sourceImageRect = r;
m_filterRegion = r;
m_sourceGraphic->setMaxEffectRect(r);
m_blur->setMaxEffectRect(r);
}
virtual FloatRect sourceImageRect() const { return m_sourceImageRect; }
virtual FloatRect filterRegion() const { return m_filterRegion; }
void apply();
ImageBuffer* output() const { return m_blur->asImageBuffer(); }
private:
RenderSnapshottedPlugInBlurFilter(int radius);
FloatRect m_sourceImageRect;
FloatRect m_filterRegion;
RefPtr<SourceGraphic> m_sourceGraphic;
RefPtr<FEGaussianBlur> m_blur;
};
RenderSnapshottedPlugInBlurFilter::RenderSnapshottedPlugInBlurFilter(int radius)
{
setFilterResolution(FloatSize(1, 1));
m_sourceGraphic = SourceGraphic::create(this);
m_blur = FEGaussianBlur::create(this, radius, radius);
m_blur->inputEffects().append(m_sourceGraphic);
}
void RenderSnapshottedPlugInBlurFilter::apply()
{
m_sourceGraphic->clearResult();
m_blur->clearResult();
m_blur->apply();
}
#endif
RenderSnapshottedPlugIn::RenderSnapshottedPlugIn(HTMLPlugInImageElement* element)
: RenderEmbeddedObject(element)
, m_snapshotResource(RenderImageResource::create())
, m_shouldShowLabel(false)
, m_shouldShowLabelAutomatically(false)
, m_showedLabelOnce(false)
, m_showReason(UserMousedOver)
, m_showLabelDelayTimer(this, &RenderSnapshottedPlugIn::showLabelDelayTimerFired)
, m_snapshotResourceForLabel(RenderImageResource::create())
{
m_snapshotResource->initialize(this);
m_snapshotResourceForLabel->initialize(this);
}
RenderSnapshottedPlugIn::~RenderSnapshottedPlugIn()
{
ASSERT(m_snapshotResource);
m_snapshotResource->shutdown();
ASSERT(m_snapshotResourceForLabel);
m_snapshotResourceForLabel->shutdown();
}
HTMLPlugInImageElement* RenderSnapshottedPlugIn::plugInImageElement() const
{
return static_cast<HTMLPlugInImageElement*>(node());
}
void RenderSnapshottedPlugIn::updateSnapshot(PassRefPtr<Image> image)
{
// Zero-size plugins will have no image.
if (!image)
return;
// We may have stored a version of this snapshot to use when showing the
// label. Invalidate it now and it will be regenerated later.
if (m_snapshotResourceForLabel->hasImage())
m_snapshotResourceForLabel->setCachedImage(0);
m_snapshotResource->setCachedImage(new CachedImage(image.get()));
repaint();
if (m_shouldShowLabelAutomatically)
resetDelayTimer(ShouldShowAutomatically);
}
void RenderSnapshottedPlugIn::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
if (plugInImageElement()->displayState() < HTMLPlugInElement::PlayingWithPendingMouseClick) {
RenderReplaced::paint(paintInfo, paintOffset);
return;
}
RenderEmbeddedObject::paint(paintInfo, paintOffset);
}
void RenderSnapshottedPlugIn::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
if (plugInImageElement()->displayState() < HTMLPlugInElement::PlayingWithPendingMouseClick) {
if (m_shouldShowLabel)
paintReplacedSnapshotWithLabel(paintInfo, paintOffset);
else
paintReplacedSnapshot(paintInfo, paintOffset);
return;
}
RenderEmbeddedObject::paintReplaced(paintInfo, paintOffset);
}
void RenderSnapshottedPlugIn::paintSnapshot(Image* image, PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
LayoutUnit cWidth = contentWidth();
LayoutUnit cHeight = contentHeight();
if (!cWidth || !cHeight)
return;
GraphicsContext* context = paintInfo.context;
#if PLATFORM(MAC)
if (style()->highlight() != nullAtom && !context->paintingDisabled())
paintCustomHighlight(toPoint(paintOffset - location()), style()->highlight(), true);
#endif
LayoutSize contentSize(cWidth, cHeight);
LayoutPoint contentLocation = paintOffset;
contentLocation.move(borderLeft() + paddingLeft(), borderTop() + paddingTop());
LayoutRect rect(contentLocation, contentSize);
IntRect alignedRect = pixelSnappedIntRect(rect);
if (alignedRect.width() <= 0 || alignedRect.height() <= 0)
return;
bool useLowQualityScaling = shouldPaintAtLowQuality(context, image, image, alignedRect.size());
context->drawImage(image, style()->colorSpace(), alignedRect, CompositeSourceOver, shouldRespectImageOrientation(), useLowQualityScaling);
}
void RenderSnapshottedPlugIn::paintReplacedSnapshot(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
RefPtr<Image> image = m_snapshotResource->image();
if (!image || image->isNull())
return;
paintSnapshot(image.get(), paintInfo, paintOffset);
}
Image* RenderSnapshottedPlugIn::startLabelImage(LabelSize size) const
{
static Image* labelImages[2] = { 0, 0 };
static bool initializedImages[2] = { false, false };
int arrayIndex = static_cast<int>(size);
if (labelImages[arrayIndex])
return labelImages[arrayIndex];
if (initializedImages[arrayIndex])
return 0;
if (document()->page()) {
labelImages[arrayIndex] = document()->page()->chrome()->client()->plugInStartLabelImage(size).leakRef();
initializedImages[arrayIndex] = true;
}
return labelImages[arrayIndex];
}
#if ENABLE(FILTERS)
static PassRefPtr<Image> snapshottedPluginImageForLabelDisplay(PassRefPtr<Image> snapshot, const LayoutRect& blurRegion)
{
OwnPtr<ImageBuffer> snapshotBuffer = ImageBuffer::create(snapshot->size());
snapshotBuffer->context()->drawImage(snapshot.get(), ColorSpaceDeviceRGB, IntPoint(0, 0));
OwnPtr<ImageBuffer> blurBuffer = ImageBuffer::create(roundedIntSize(blurRegion.size()));
blurBuffer->context()->drawImage(snapshot.get(), ColorSpaceDeviceRGB, IntPoint(-blurRegion.x(), -blurRegion.y()));
RefPtr<RenderSnapshottedPlugInBlurFilter> blurFilter = RenderSnapshottedPlugInBlurFilter::create(snapshotLabelBlurRadius);
blurFilter->setSourceImage(blurBuffer.release());
blurFilter->setSourceImageRect(FloatRect(FloatPoint(), blurRegion.size()));
blurFilter->apply();
snapshotBuffer->context()->drawImageBuffer(blurFilter->output(), ColorSpaceDeviceRGB, roundedIntPoint(blurRegion.location()));
return snapshotBuffer->copyImage();
}
#endif
void RenderSnapshottedPlugIn::paintReplacedSnapshotWithLabel(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
if (contentBoxRect().isEmpty())
return;
if (!plugInImageElement()->hovered() && m_showReason == UserMousedOver)
return;
m_showedLabelOnce = true;
LayoutRect rect = contentBoxRect();
LayoutRect labelRect = tryToFitStartLabel(LabelSizeLarge, rect);
LabelSize size = NoLabel;
if (!labelRect.isEmpty())
size = LabelSizeLarge;
else {
labelRect = tryToFitStartLabel(LabelSizeSmall, rect);
if (!labelRect.isEmpty())
size = LabelSizeSmall;
else
return;
}
Image* labelImage = startLabelImage(size);
if (!labelImage)
return;
RefPtr<Image> snapshotImage = m_snapshotResource->image();
if (!snapshotImage || snapshotImage->isNull())
return;
#if ENABLE(FILTERS)
RefPtr<Image> blurredSnapshotImage = m_snapshotResourceForLabel->image();
if (!blurredSnapshotImage || blurredSnapshotImage->isNull()) {
blurredSnapshotImage = snapshottedPluginImageForLabelDisplay(snapshotImage, labelRect);
m_snapshotResourceForLabel->setCachedImage(new CachedImage(blurredSnapshotImage.get()));
}
snapshotImage = blurredSnapshotImage;
#endif
paintSnapshot(snapshotImage.get(), paintInfo, paintOffset);
// Remember that the labelRect includes the label inset, so we need to adjust for it.
paintInfo.context->drawImage(labelImage, ColorSpaceDeviceRGB,
IntRect(roundedIntPoint(paintOffset + labelRect.location() - IntSize(startLabelInset, startLabelInset)),
roundedIntSize(labelRect.size() + IntSize(2 * startLabelInset, 2 * startLabelInset))),
labelImage->rect());
}
void RenderSnapshottedPlugIn::repaintLabel()
{
// FIXME: This is unfortunate. We should just repaint the label.
repaint();
}
void RenderSnapshottedPlugIn::showLabelDelayTimerFired(Timer<RenderSnapshottedPlugIn>*)
{
m_shouldShowLabel = true;
repaintLabel();
}
void RenderSnapshottedPlugIn::setShouldShowLabelAutomatically(bool show)
{
m_shouldShowLabelAutomatically = show;
}
CursorDirective RenderSnapshottedPlugIn::getCursor(const LayoutPoint& point, Cursor& overrideCursor) const
{
if (plugInImageElement()->displayState() < HTMLPlugInElement::PlayingWithPendingMouseClick) {
overrideCursor = handCursor();
return SetCursor;
}
return RenderEmbeddedObject::getCursor(point, overrideCursor);
}
void RenderSnapshottedPlugIn::handleEvent(Event* event)
{
if (!event->isMouseEvent())
return;
MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
if (event->type() == eventNames().clickEvent) {
if (mouseEvent->button() != LeftButton)
return;
plugInImageElement()->setDisplayState(HTMLPlugInElement::PlayingWithPendingMouseClick);
plugInImageElement()->userDidClickSnapshot(mouseEvent);
if (widget()) {
if (Frame* frame = document()->frame())
frame->loader()->client()->recreatePlugin(widget());
repaint();
}
event->setDefaultHandled();
} else if (event->type() == eventNames().mousedownEvent) {
if (mouseEvent->button() != LeftButton)
return;
if (m_showLabelDelayTimer.isActive())
m_showLabelDelayTimer.stop();
event->setDefaultHandled();
} else if (event->type() == eventNames().mouseoverEvent) {
if (!m_showedLabelOnce || m_showReason != ShouldShowAutomatically)
resetDelayTimer(UserMousedOver);
event->setDefaultHandled();
} else if (event->type() == eventNames().mouseoutEvent) {
if (m_showLabelDelayTimer.isActive())
m_showLabelDelayTimer.stop();
if (m_shouldShowLabel) {
if (m_showReason == UserMousedOver) {
m_shouldShowLabel = false;
repaintLabel();
}
} else if (m_shouldShowLabelAutomatically)
resetDelayTimer(ShouldShowAutomatically);
event->setDefaultHandled();
}
}
LayoutRect RenderSnapshottedPlugIn::tryToFitStartLabel(LabelSize size, const LayoutRect& contentBox) const
{
Image* labelImage = startLabelImage(size);
if (!labelImage)
return LayoutRect();
// Assume that the labelImage has been provided to match our device scale.
float scaleFactor = 1;
if (document()->page())
scaleFactor = document()->page()->deviceScaleFactor();
IntSize labelImageSize = labelImage->size();
labelImageSize.scale(1 / (scaleFactor ? scaleFactor : 1));
LayoutSize labelSize = labelImageSize - LayoutSize(2 * startLabelInset, 2 * startLabelInset);
LayoutRect candidateRect(contentBox.maxXMinYCorner() + LayoutSize(-startLabelPadding, startLabelPadding) + LayoutSize(-labelSize.width(), 0), labelSize);
// The minimum allowed content box size is the label image placed in the center of the box, surrounded by startLabelPadding.
if (candidateRect.x() < startLabelPadding || candidateRect.maxY() > contentBox.height() - startLabelPadding)
return LayoutRect();
return candidateRect;
}
void RenderSnapshottedPlugIn::resetDelayTimer(ShowReason reason)
{
m_showReason = reason;
m_showLabelDelayTimer.startOneShot(reason == UserMousedOver ? showLabelAfterMouseOverDelay : showLabelAutomaticallyDelay);
}
} // namespace WebCore