1/*
2 * Copyright (C) 2012 Google 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 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#include "web/LinkHighlight.h"
29
30#include "SkMatrix44.h"
31#include "core/dom/Node.h"
32#include "core/frame/FrameView.h"
33#include "core/frame/LocalFrame.h"
34#include "core/rendering/RenderLayer.h"
35#include "core/rendering/RenderLayerModelObject.h"
36#include "core/rendering/RenderObject.h"
37#include "core/rendering/RenderPart.h"
38#include "core/rendering/RenderView.h"
39#include "core/rendering/compositing/CompositedLayerMapping.h"
40#include "core/rendering/style/ShadowData.h"
41#include "platform/graphics/Color.h"
42#include "public/platform/Platform.h"
43#include "public/platform/WebCompositorAnimationCurve.h"
44#include "public/platform/WebCompositorSupport.h"
45#include "public/platform/WebFloatAnimationCurve.h"
46#include "public/platform/WebFloatPoint.h"
47#include "public/platform/WebRect.h"
48#include "public/platform/WebSize.h"
49#include "public/web/WebKit.h"
50#include "web/WebLocalFrameImpl.h"
51#include "web/WebSettingsImpl.h"
52#include "web/WebViewImpl.h"
53#include "wtf/CurrentTime.h"
54
55namespace blink {
56
57class WebViewImpl;
58
59PassOwnPtr<LinkHighlight> LinkHighlight::create(Node* node, WebViewImpl* owningWebViewImpl)
60{
61    return adoptPtr(new LinkHighlight(node, owningWebViewImpl));
62}
63
64LinkHighlight::LinkHighlight(Node* node, WebViewImpl* owningWebViewImpl)
65    : m_node(node)
66    , m_owningWebViewImpl(owningWebViewImpl)
67    , m_currentGraphicsLayer(0)
68    , m_geometryNeedsUpdate(false)
69    , m_isAnimating(false)
70    , m_startTime(monotonicallyIncreasingTime())
71{
72    ASSERT(m_node);
73    ASSERT(owningWebViewImpl);
74    WebCompositorSupport* compositorSupport = Platform::current()->compositorSupport();
75    m_contentLayer = adoptPtr(compositorSupport->createContentLayer(this));
76    m_clipLayer = adoptPtr(compositorSupport->createLayer());
77    m_clipLayer->setTransformOrigin(WebFloatPoint3D());
78    m_clipLayer->addChild(m_contentLayer->layer());
79    m_contentLayer->layer()->setAnimationDelegate(this);
80    m_contentLayer->layer()->setDrawsContent(true);
81    m_contentLayer->layer()->setOpacity(1);
82    m_geometryNeedsUpdate = true;
83    updateGeometry();
84}
85
86LinkHighlight::~LinkHighlight()
87{
88    clearGraphicsLayerLinkHighlightPointer();
89    releaseResources();
90}
91
92WebContentLayer* LinkHighlight::contentLayer()
93{
94    return m_contentLayer.get();
95}
96
97WebLayer* LinkHighlight::clipLayer()
98{
99    return m_clipLayer.get();
100}
101
102void LinkHighlight::releaseResources()
103{
104    m_node.clear();
105}
106
107RenderLayer* LinkHighlight::computeEnclosingCompositingLayer()
108{
109    if (!m_node || !m_node->renderer())
110        return 0;
111
112    // Find the nearest enclosing composited layer and attach to it. We may need to cross frame boundaries
113    // to find a suitable layer.
114    RenderObject* renderer = m_node->renderer();
115    RenderLayer* renderLayer;
116    do {
117        renderLayer = renderer->enclosingLayer()->enclosingLayerForPaintInvalidation();
118        if (!renderLayer) {
119            renderer = renderer->frame()->ownerRenderer();
120            if (!renderer)
121                return 0;
122        }
123    } while (!renderLayer);
124
125    ASSERT(renderLayer->compositingState() != NotComposited);
126
127    GraphicsLayer* newGraphicsLayer = renderLayer->graphicsLayerBacking();
128    if (!newGraphicsLayer->drawsContent()) {
129        newGraphicsLayer = renderLayer->graphicsLayerBackingForScrolling();
130    }
131
132    m_clipLayer->setTransform(SkMatrix44(SkMatrix44::kIdentity_Constructor));
133
134    if (m_currentGraphicsLayer != newGraphicsLayer) {
135        if (m_currentGraphicsLayer)
136            clearGraphicsLayerLinkHighlightPointer();
137
138        m_currentGraphicsLayer = newGraphicsLayer;
139        m_currentGraphicsLayer->addLinkHighlight(this);
140    }
141
142    return renderLayer;
143}
144
145static void convertTargetSpaceQuadToCompositedLayer(const FloatQuad& targetSpaceQuad, RenderObject* targetRenderer, RenderObject* compositedRenderer, FloatQuad& compositedSpaceQuad)
146{
147    ASSERT(targetRenderer);
148    ASSERT(compositedRenderer);
149
150    for (unsigned i = 0; i < 4; ++i) {
151        IntPoint point;
152        switch (i) {
153        case 0: point = roundedIntPoint(targetSpaceQuad.p1()); break;
154        case 1: point = roundedIntPoint(targetSpaceQuad.p2()); break;
155        case 2: point = roundedIntPoint(targetSpaceQuad.p3()); break;
156        case 3: point = roundedIntPoint(targetSpaceQuad.p4()); break;
157        }
158
159        point = targetRenderer->frame()->view()->contentsToWindow(point);
160        point = compositedRenderer->frame()->view()->windowToContents(point);
161        FloatPoint floatPoint = compositedRenderer->absoluteToLocal(point, UseTransforms);
162
163        switch (i) {
164        case 0: compositedSpaceQuad.setP1(floatPoint); break;
165        case 1: compositedSpaceQuad.setP2(floatPoint); break;
166        case 2: compositedSpaceQuad.setP3(floatPoint); break;
167        case 3: compositedSpaceQuad.setP4(floatPoint); break;
168        }
169    }
170}
171
172static void addQuadToPath(const FloatQuad& quad, Path& path)
173{
174    // FIXME: Make this create rounded quad-paths, just like the axis-aligned case.
175    path.moveTo(quad.p1());
176    path.addLineTo(quad.p2());
177    path.addLineTo(quad.p3());
178    path.addLineTo(quad.p4());
179    path.closeSubpath();
180}
181
182void LinkHighlight::computeQuads(RenderObject& renderer, Vector<FloatQuad>& outQuads) const
183{
184    // For inline elements, absoluteQuads will return a line box based on the line-height
185    // and font metrics, which is technically incorrect as replaced elements like images
186    // should use their intristic height and expand the linebox  as needed. To get an
187    // appropriately sized highlight we descend into the children and have them add their
188    // boxes.
189    if (renderer.isRenderInline()) {
190        for (RenderObject* child = renderer.slowFirstChild(); child; child = child->nextSibling())
191            computeQuads(*child, outQuads);
192    } else {
193        renderer.absoluteQuads(outQuads);
194    }
195}
196
197bool LinkHighlight::computeHighlightLayerPathAndPosition(RenderLayer* compositingLayer)
198{
199    if (!m_node || !m_node->renderer() || !m_currentGraphicsLayer)
200        return false;
201
202    ASSERT(compositingLayer);
203
204    // Get quads for node in absolute coordinates.
205    Vector<FloatQuad> quads;
206    computeQuads(*m_node->renderer(), quads);
207    ASSERT(quads.size());
208
209    // Adjust for offset between target graphics layer and the node's renderer.
210    FloatPoint positionAdjust = IntPoint(m_currentGraphicsLayer->offsetFromRenderer());
211
212    Path newPath;
213    for (size_t quadIndex = 0; quadIndex < quads.size(); ++quadIndex) {
214        FloatQuad absoluteQuad = quads[quadIndex];
215        absoluteQuad.move(-positionAdjust.x(), -positionAdjust.y());
216
217        // Transform node quads in target absolute coords to local coordinates in the compositor layer.
218        FloatQuad transformedQuad;
219        convertTargetSpaceQuadToCompositedLayer(absoluteQuad, m_node->renderer(), compositingLayer->renderer(), transformedQuad);
220
221        // FIXME: for now, we'll only use rounded paths if we have a single node quad. The reason for this is that
222        // we may sometimes get a chain of adjacent boxes (e.g. for text nodes) which end up looking like sausage
223        // links: these should ideally be merged into a single rect before creating the path, but that's
224        // another CL.
225        if (quads.size() == 1 && transformedQuad.isRectilinear()
226            && !m_owningWebViewImpl->settingsImpl()->mockGestureTapHighlightsEnabled()) {
227            FloatSize rectRoundingRadii(3, 3);
228            newPath.addRoundedRect(transformedQuad.boundingBox(), rectRoundingRadii);
229        } else
230            addQuadToPath(transformedQuad, newPath);
231    }
232
233    FloatRect boundingRect = newPath.boundingRect();
234    newPath.translate(-toFloatSize(boundingRect.location()));
235
236    bool pathHasChanged = !(newPath == m_path);
237    if (pathHasChanged) {
238        m_path = newPath;
239        m_contentLayer->layer()->setBounds(enclosingIntRect(boundingRect).size());
240    }
241
242    m_contentLayer->layer()->setPosition(boundingRect.location());
243
244    return pathHasChanged;
245}
246
247void LinkHighlight::paintContents(WebCanvas* canvas, const WebRect& webClipRect, bool, WebContentLayerClient::GraphicsContextStatus contextStatus)
248{
249    if (!m_node || !m_node->renderer())
250        return;
251
252    GraphicsContext gc(canvas,
253        contextStatus == WebContentLayerClient::GraphicsContextEnabled ? GraphicsContext::NothingDisabled : GraphicsContext::FullyDisabled);
254    IntRect clipRect(IntPoint(webClipRect.x, webClipRect.y), IntSize(webClipRect.width, webClipRect.height));
255    gc.clip(clipRect);
256    gc.setFillColor(m_node->renderer()->style()->tapHighlightColor());
257    gc.fillPath(m_path);
258}
259
260void LinkHighlight::startHighlightAnimationIfNeeded()
261{
262    if (m_isAnimating)
263        return;
264
265    m_isAnimating = true;
266    const float startOpacity = 1;
267    // FIXME: Should duration be configurable?
268    const float fadeDuration = 0.1f;
269    const float minPreFadeDuration = 0.1f;
270
271    m_contentLayer->layer()->setOpacity(startOpacity);
272
273    WebCompositorSupport* compositorSupport = Platform::current()->compositorSupport();
274
275    OwnPtr<WebFloatAnimationCurve> curve = adoptPtr(compositorSupport->createFloatAnimationCurve());
276
277    curve->add(WebFloatKeyframe(0, startOpacity));
278    // Make sure we have displayed for at least minPreFadeDuration before starting to fade out.
279    float extraDurationRequired = std::max(0.f, minPreFadeDuration - static_cast<float>(monotonicallyIncreasingTime() - m_startTime));
280    if (extraDurationRequired)
281        curve->add(WebFloatKeyframe(extraDurationRequired, startOpacity));
282    // For layout tests we don't fade out.
283    curve->add(WebFloatKeyframe(fadeDuration + extraDurationRequired, layoutTestMode() ? startOpacity : 0));
284
285    OwnPtr<WebCompositorAnimation> animation = adoptPtr(compositorSupport->createAnimation(*curve, WebCompositorAnimation::TargetPropertyOpacity));
286
287    m_contentLayer->layer()->setDrawsContent(true);
288    m_contentLayer->layer()->addAnimation(animation.leakPtr());
289
290    invalidate();
291    m_owningWebViewImpl->scheduleAnimation();
292}
293
294void LinkHighlight::clearGraphicsLayerLinkHighlightPointer()
295{
296    if (m_currentGraphicsLayer) {
297        m_currentGraphicsLayer->removeLinkHighlight(this);
298        m_currentGraphicsLayer = 0;
299    }
300}
301
302void LinkHighlight::notifyAnimationStarted(double, WebCompositorAnimation::TargetProperty)
303{
304}
305
306void LinkHighlight::notifyAnimationFinished(double, WebCompositorAnimation::TargetProperty)
307{
308    // Since WebViewImpl may hang on to us for a while, make sure we
309    // release resources as soon as possible.
310    clearGraphicsLayerLinkHighlightPointer();
311    releaseResources();
312}
313
314void LinkHighlight::updateGeometry()
315{
316    // To avoid unnecessary updates (e.g. other entities have requested animations from our WebViewImpl),
317    // only proceed if we actually requested an update.
318    if (!m_geometryNeedsUpdate)
319        return;
320
321    m_geometryNeedsUpdate = false;
322
323    RenderLayer* compositingLayer = computeEnclosingCompositingLayer();
324    if (compositingLayer && computeHighlightLayerPathAndPosition(compositingLayer)) {
325        // We only need to invalidate the layer if the highlight size has changed, otherwise
326        // we can just re-position the layer without needing to repaint.
327        m_contentLayer->layer()->invalidate();
328
329        if (m_currentGraphicsLayer)
330            m_currentGraphicsLayer->addRepaintRect(FloatRect(layer()->position().x, layer()->position().y, layer()->bounds().width, layer()->bounds().height));
331    } else if (!m_node || !m_node->renderer()) {
332        clearGraphicsLayerLinkHighlightPointer();
333        releaseResources();
334    }
335}
336
337void LinkHighlight::clearCurrentGraphicsLayer()
338{
339    m_currentGraphicsLayer = 0;
340    m_geometryNeedsUpdate = true;
341}
342
343void LinkHighlight::invalidate()
344{
345    // Make sure we update geometry on the next callback from WebViewImpl::layout().
346    m_geometryNeedsUpdate = true;
347}
348
349WebLayer* LinkHighlight::layer()
350{
351    return clipLayer();
352}
353
354} // namespace blink
355