blob: ac13837b0b55bcb10d288097502582401bc77fd8 [file] [log] [blame]
/*
* Copyright (C) 2012 Google 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 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 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 "web/LinkHighlightImpl.h"
#include "core/dom/DOMNodeIds.h"
#include "core/dom/LayoutTreeBuilderTraversal.h"
#include "core/dom/Node.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/layout/LayoutBoxModelObject.h"
#include "core/layout/LayoutObject.h"
#include "core/layout/compositing/CompositedLayerMapping.h"
#include "core/paint/PaintLayer.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/animation/CompositorAnimation.h"
#include "platform/animation/CompositorAnimationCurve.h"
#include "platform/animation/CompositorFloatAnimationCurve.h"
#include "platform/animation/CompositorTargetProperty.h"
#include "platform/animation/TimingFunction.h"
#include "platform/graphics/Color.h"
#include "platform/graphics/CompositorElementId.h"
#include "platform/graphics/CompositorMutableProperties.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/graphics/paint/DrawingRecorder.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCompositorSupport.h"
#include "public/platform/WebContentLayer.h"
#include "public/platform/WebDisplayItemList.h"
#include "public/platform/WebFloatPoint.h"
#include "public/platform/WebLayer.h"
#include "public/platform/WebRect.h"
#include "public/platform/WebSize.h"
#include "public/web/WebKit.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkMatrix44.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "ui/gfx/geometry/rect.h"
#include "web/WebLocalFrameImpl.h"
#include "web/WebSettingsImpl.h"
#include "web/WebViewImpl.h"
#include "wtf/CurrentTime.h"
#include "wtf/PtrUtil.h"
#include "wtf/Vector.h"
#include <memory>
namespace blink {
std::unique_ptr<LinkHighlightImpl> LinkHighlightImpl::create(Node* node, WebViewImpl* owningWebViewImpl)
{
return wrapUnique(new LinkHighlightImpl(node, owningWebViewImpl));
}
LinkHighlightImpl::LinkHighlightImpl(Node* node, WebViewImpl* owningWebViewImpl)
: m_node(node)
, m_owningWebViewImpl(owningWebViewImpl)
, m_currentGraphicsLayer(0)
, m_isScrollingGraphicsLayer(false)
, m_geometryNeedsUpdate(false)
, m_isAnimating(false)
, m_startTime(monotonicallyIncreasingTime())
{
DCHECK(m_node);
DCHECK(owningWebViewImpl);
WebCompositorSupport* compositorSupport = Platform::current()->compositorSupport();
DCHECK(compositorSupport);
m_contentLayer = wrapUnique(compositorSupport->createContentLayer(this));
m_clipLayer = wrapUnique(compositorSupport->createLayer());
m_clipLayer->setTransformOrigin(WebFloatPoint3D());
m_clipLayer->addChild(m_contentLayer->layer());
m_compositorPlayer = CompositorAnimationPlayer::create();
DCHECK(m_compositorPlayer);
m_compositorPlayer->setAnimationDelegate(this);
if (m_owningWebViewImpl->linkHighlightsTimeline())
m_owningWebViewImpl->linkHighlightsTimeline()->playerAttached(*this);
CompositorElementId elementId = createCompositorElementId(DOMNodeIds::idForNode(node), CompositorSubElementId::LinkHighlight);
m_compositorPlayer->attachElement(elementId);
m_contentLayer->layer()->setDrawsContent(true);
m_contentLayer->layer()->setOpacity(1);
m_contentLayer->layer()->setElementId(elementId);
m_geometryNeedsUpdate = true;
}
LinkHighlightImpl::~LinkHighlightImpl()
{
if (m_compositorPlayer->isElementAttached())
m_compositorPlayer->detachElement();
if (m_owningWebViewImpl->linkHighlightsTimeline())
m_owningWebViewImpl->linkHighlightsTimeline()->playerDestroyed(*this);
m_compositorPlayer->setAnimationDelegate(nullptr);
m_compositorPlayer.reset();
clearGraphicsLayerLinkHighlightPointer();
releaseResources();
}
WebContentLayer* LinkHighlightImpl::contentLayer()
{
return m_contentLayer.get();
}
WebLayer* LinkHighlightImpl::clipLayer()
{
return m_clipLayer.get();
}
void LinkHighlightImpl::releaseResources()
{
m_node.clear();
}
void LinkHighlightImpl::attachLinkHighlightToCompositingLayer(const LayoutBoxModelObject& paintInvalidationContainer)
{
GraphicsLayer* newGraphicsLayer = paintInvalidationContainer.layer()->graphicsLayerBacking();
m_isScrollingGraphicsLayer = false;
// FIXME: There should always be a GraphicsLayer. See crbug.com/431961.
if (paintInvalidationContainer.layer()->needsCompositedScrolling() && m_node->layoutObject() != &paintInvalidationContainer) {
newGraphicsLayer = paintInvalidationContainer.layer()->graphicsLayerBackingForScrolling();
m_isScrollingGraphicsLayer = true;
}
if (!newGraphicsLayer)
return;
m_clipLayer->setTransform(SkMatrix44(SkMatrix44::kIdentity_Constructor));
if (m_currentGraphicsLayer != newGraphicsLayer) {
if (m_currentGraphicsLayer)
clearGraphicsLayerLinkHighlightPointer();
m_currentGraphicsLayer = newGraphicsLayer;
m_currentGraphicsLayer->addLinkHighlight(this);
}
}
static void convertTargetSpaceQuadToCompositedLayer(const FloatQuad& targetSpaceQuad, LayoutObject* targetLayoutObject, const LayoutBoxModelObject& paintInvalidationContainer, FloatQuad& compositedSpaceQuad)
{
DCHECK(targetLayoutObject);
for (unsigned i = 0; i < 4; ++i) {
IntPoint point;
switch (i) {
case 0: point = roundedIntPoint(targetSpaceQuad.p1()); break;
case 1: point = roundedIntPoint(targetSpaceQuad.p2()); break;
case 2: point = roundedIntPoint(targetSpaceQuad.p3()); break;
case 3: point = roundedIntPoint(targetSpaceQuad.p4()); break;
}
// FIXME: this does not need to be absolute, just in the paint invalidation container's space.
point = targetLayoutObject->frame()->view()->contentsToRootFrame(point);
point = paintInvalidationContainer.frame()->view()->rootFrameToContents(point);
FloatPoint floatPoint = paintInvalidationContainer.absoluteToLocal(point, UseTransforms);
PaintLayer::mapPointInPaintInvalidationContainerToBacking(paintInvalidationContainer, floatPoint);
switch (i) {
case 0: compositedSpaceQuad.setP1(floatPoint); break;
case 1: compositedSpaceQuad.setP2(floatPoint); break;
case 2: compositedSpaceQuad.setP3(floatPoint); break;
case 3: compositedSpaceQuad.setP4(floatPoint); break;
}
}
}
static void addQuadToPath(const FloatQuad& quad, Path& path)
{
// FIXME: Make this create rounded quad-paths, just like the axis-aligned case.
path.moveTo(quad.p1());
path.addLineTo(quad.p2());
path.addLineTo(quad.p3());
path.addLineTo(quad.p4());
path.closeSubpath();
}
void LinkHighlightImpl::computeQuads(const Node& node, Vector<FloatQuad>& outQuads) const
{
if (!node.layoutObject())
return;
LayoutObject* layoutObject = node.layoutObject();
// For inline elements, absoluteQuads will return a line box based on the line-height
// and font metrics, which is technically incorrect as replaced elements like images
// should use their intristic height and expand the linebox as needed. To get an
// appropriately sized highlight we descend into the children and have them add their
// boxes.
if (layoutObject->isLayoutInline()) {
for (Node* child = LayoutTreeBuilderTraversal::firstChild(node); child; child = LayoutTreeBuilderTraversal::nextSibling(*child))
computeQuads(*child, outQuads);
} else {
// FIXME: this does not need to be absolute, just in the paint invalidation container's space.
layoutObject->absoluteQuads(outQuads);
}
}
bool LinkHighlightImpl::computeHighlightLayerPathAndPosition(const LayoutBoxModelObject& paintInvalidationContainer)
{
if (!m_node || !m_node->layoutObject() || !m_currentGraphicsLayer)
return false;
// FIXME: This is defensive code to avoid crashes such as those described in
// crbug.com/440887. This should be cleaned up once we fix the root cause of
// of the paint invalidation container not being composited.
if (!paintInvalidationContainer.layer()->compositedLayerMapping() && !paintInvalidationContainer.layer()->groupedMapping())
return false;
// Get quads for node in absolute coordinates.
Vector<FloatQuad> quads;
computeQuads(*m_node, quads);
DCHECK(quads.size());
Path newPath;
for (size_t quadIndex = 0; quadIndex < quads.size(); ++quadIndex) {
FloatQuad absoluteQuad = quads[quadIndex];
// Scrolling content layers have the same offset from layout object as the non-scrolling layers. Thus we need
// to adjust for their scroll offset.
if (m_isScrollingGraphicsLayer) {
DoubleSize adjustedScrollOffset = paintInvalidationContainer.layer()->getScrollableArea()->adjustedScrollOffset();
absoluteQuad.move(adjustedScrollOffset.width(), adjustedScrollOffset.height());
}
// Transform node quads in target absolute coords to local coordinates in the compositor layer.
FloatQuad transformedQuad;
convertTargetSpaceQuadToCompositedLayer(absoluteQuad, m_node->layoutObject(), paintInvalidationContainer, transformedQuad);
// FIXME: for now, we'll only use rounded paths if we have a single node quad. The reason for this is that
// we may sometimes get a chain of adjacent boxes (e.g. for text nodes) which end up looking like sausage
// links: these should ideally be merged into a single rect before creating the path, but that's
// another CL.
if (quads.size() == 1 && transformedQuad.isRectilinear()
&& !m_owningWebViewImpl->settingsImpl()->mockGestureTapHighlightsEnabled()) {
FloatSize rectRoundingRadii(3, 3);
newPath.addRoundedRect(transformedQuad.boundingBox(), rectRoundingRadii);
} else {
addQuadToPath(transformedQuad, newPath);
}
}
FloatRect boundingRect = newPath.boundingRect();
newPath.translate(-toFloatSize(boundingRect.location()));
bool pathHasChanged = !(newPath == m_path);
if (pathHasChanged) {
m_path = newPath;
m_contentLayer->layer()->setBounds(enclosingIntRect(boundingRect).size());
}
m_contentLayer->layer()->setPosition(boundingRect.location());
return pathHasChanged;
}
gfx::Rect LinkHighlightImpl::paintableRegion()
{
return gfx::Rect(0, 0, contentLayer()->layer()->bounds().width, contentLayer()->layer()->bounds().height);
}
void LinkHighlightImpl::paintContents(WebDisplayItemList* webDisplayItemList, WebContentLayerClient::PaintingControlSetting paintingControl)
{
if (!m_node || !m_node->layoutObject())
return;
SkPictureRecorder recorder;
gfx::Rect visualRect = paintableRegion();
SkCanvas* canvas = recorder.beginRecording(visualRect.width(), visualRect.height());
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setFlags(SkPaint::kAntiAlias_Flag);
paint.setColor(m_node->layoutObject()->style()->tapHighlightColor().rgb());
canvas->drawPath(m_path.getSkPath(), paint);
webDisplayItemList->appendDrawingItem(WebRect(visualRect.x(), visualRect.y(),
visualRect.width(), visualRect.height()), recorder.finishRecordingAsPicture());
}
void LinkHighlightImpl::startHighlightAnimationIfNeeded()
{
if (m_isAnimating)
return;
m_isAnimating = true;
const float startOpacity = 1;
// FIXME: Should duration be configurable?
const float fadeDuration = 0.1f;
const float minPreFadeDuration = 0.1f;
m_contentLayer->layer()->setOpacity(startOpacity);
std::unique_ptr<CompositorFloatAnimationCurve> curve = CompositorFloatAnimationCurve::create();
const auto& timingFunction = *CubicBezierTimingFunction::preset(CubicBezierTimingFunction::EaseType::EASE);
curve->addCubicBezierKeyframe(CompositorFloatKeyframe(0, startOpacity), timingFunction);
// Make sure we have displayed for at least minPreFadeDuration before starting to fade out.
float extraDurationRequired = std::max(0.f, minPreFadeDuration - static_cast<float>(monotonicallyIncreasingTime() - m_startTime));
if (extraDurationRequired)
curve->addCubicBezierKeyframe(CompositorFloatKeyframe(extraDurationRequired, startOpacity), timingFunction);
// For layout tests we don't fade out.
curve->addCubicBezierKeyframe(CompositorFloatKeyframe(fadeDuration + extraDurationRequired, layoutTestMode() ? startOpacity : 0), timingFunction);
std::unique_ptr<CompositorAnimation> animation = CompositorAnimation::create(*curve, CompositorTargetProperty::OPACITY, 0, 0);
m_contentLayer->layer()->setDrawsContent(true);
m_compositorPlayer->addAnimation(animation.release());
invalidate();
m_owningWebViewImpl->mainFrameImpl()->frameWidget()->scheduleAnimation();
}
void LinkHighlightImpl::clearGraphicsLayerLinkHighlightPointer()
{
if (m_currentGraphicsLayer) {
m_currentGraphicsLayer->removeLinkHighlight(this);
m_currentGraphicsLayer = 0;
}
}
void LinkHighlightImpl::notifyAnimationStarted(double, int)
{
}
void LinkHighlightImpl::notifyAnimationFinished(double, int)
{
// Since WebViewImpl may hang on to us for a while, make sure we
// release resources as soon as possible.
clearGraphicsLayerLinkHighlightPointer();
releaseResources();
}
class LinkHighlightDisplayItemClientForTracking : public DisplayItemClient {
String debugName() const final { return "LinkHighlight"; }
LayoutRect visualRect() const final { return LayoutRect(); }
};
void LinkHighlightImpl::updateGeometry()
{
// To avoid unnecessary updates (e.g. other entities have requested animations from our WebViewImpl),
// only proceed if we actually requested an update.
if (!m_geometryNeedsUpdate)
return;
m_geometryNeedsUpdate = false;
bool hasLayoutObject = m_node && m_node->layoutObject();
if (hasLayoutObject) {
const LayoutBoxModelObject& paintInvalidationContainer = m_node->layoutObject()->containerForPaintInvalidation();
attachLinkHighlightToCompositingLayer(paintInvalidationContainer);
if (computeHighlightLayerPathAndPosition(paintInvalidationContainer)) {
// We only need to invalidate the layer if the highlight size has changed, otherwise
// we can just re-position the layer without needing to repaint.
m_contentLayer->layer()->invalidate();
if (m_currentGraphicsLayer)
m_currentGraphicsLayer->trackPaintInvalidation(LinkHighlightDisplayItemClientForTracking(), enclosingIntRect(FloatRect(layer()->position().x, layer()->position().y, layer()->bounds().width, layer()->bounds().height)), PaintInvalidationFull);
}
} else {
clearGraphicsLayerLinkHighlightPointer();
releaseResources();
}
}
void LinkHighlightImpl::clearCurrentGraphicsLayer()
{
m_currentGraphicsLayer = 0;
m_geometryNeedsUpdate = true;
}
void LinkHighlightImpl::invalidate()
{
// Make sure we update geometry on the next callback from WebViewImpl::layout().
m_geometryNeedsUpdate = true;
}
WebLayer* LinkHighlightImpl::layer()
{
return clipLayer();
}
CompositorAnimationPlayer* LinkHighlightImpl::compositorPlayer() const
{
return m_compositorPlayer.get();
}
} // namespace blink