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 "LinkHighlight.h"
29
30#include "SkMatrix44.h"
31#include "WebFrameImpl.h"
32#include "WebKit.h"
33#include "WebViewImpl.h"
34#include "core/dom/Node.h"
35#include "core/frame/Frame.h"
36#include "core/frame/FrameView.h"
37#include "core/rendering/CompositedLayerMapping.h"
38#include "core/rendering/RenderLayer.h"
39#include "core/rendering/RenderLayerModelObject.h"
40#include "core/rendering/RenderObject.h"
41#include "core/rendering/RenderView.h"
42#include "core/rendering/style/ShadowData.h"
43#include "platform/graphics/Color.h"
44#include "public/platform/Platform.h"
45#include "public/platform/WebAnimationCurve.h"
46#include "public/platform/WebCompositorSupport.h"
47#include "public/platform/WebFloatAnimationCurve.h"
48#include "public/platform/WebFloatPoint.h"
49#include "public/platform/WebRect.h"
50#include "public/platform/WebSize.h"
51#include "wtf/CurrentTime.h"
52
53using namespace WebCore;
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->setAnchorPoint(WebFloatPoint());
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    RenderLayerModelObject* repaintContainer;
116    do {
117        repaintContainer = renderer->containerForRepaint();
118        if (!repaintContainer) {
119            renderer = renderer->frame()->ownerRenderer();
120            if (!renderer)
121                return 0;
122        }
123    } while (!repaintContainer);
124    RenderLayer* renderLayer = repaintContainer->layer();
125
126    if (!renderLayer || renderLayer->compositingState() == NotComposited)
127        return 0;
128
129    GraphicsLayer* newGraphicsLayer = renderLayer->compositedLayerMapping()->mainGraphicsLayer();
130    m_clipLayer->setSublayerTransform(SkMatrix44());
131
132    if (!newGraphicsLayer->drawsContent()) {
133        if (renderLayer->scrollableArea() && renderLayer->scrollableArea()->usesCompositedScrolling()) {
134            ASSERT(renderLayer->hasCompositedLayerMapping() && renderLayer->compositedLayerMapping()->scrollingContentsLayer());
135            newGraphicsLayer = renderLayer->compositedLayerMapping()->scrollingContentsLayer();
136        }
137    }
138
139    if (m_currentGraphicsLayer != newGraphicsLayer) {
140        if (m_currentGraphicsLayer)
141            clearGraphicsLayerLinkHighlightPointer();
142
143        m_currentGraphicsLayer = newGraphicsLayer;
144        m_currentGraphicsLayer->addLinkHighlight(this);
145    }
146
147    return renderLayer;
148}
149
150static void convertTargetSpaceQuadToCompositedLayer(const FloatQuad& targetSpaceQuad, RenderObject* targetRenderer, RenderObject* compositedRenderer, FloatQuad& compositedSpaceQuad)
151{
152    ASSERT(targetRenderer);
153    ASSERT(compositedRenderer);
154
155    for (unsigned i = 0; i < 4; ++i) {
156        IntPoint point;
157        switch (i) {
158        case 0: point = roundedIntPoint(targetSpaceQuad.p1()); break;
159        case 1: point = roundedIntPoint(targetSpaceQuad.p2()); break;
160        case 2: point = roundedIntPoint(targetSpaceQuad.p3()); break;
161        case 3: point = roundedIntPoint(targetSpaceQuad.p4()); break;
162        }
163
164        point = targetRenderer->frame()->view()->contentsToWindow(point);
165        point = compositedRenderer->frame()->view()->windowToContents(point);
166        FloatPoint floatPoint = compositedRenderer->absoluteToLocal(point, UseTransforms);
167
168        switch (i) {
169        case 0: compositedSpaceQuad.setP1(floatPoint); break;
170        case 1: compositedSpaceQuad.setP2(floatPoint); break;
171        case 2: compositedSpaceQuad.setP3(floatPoint); break;
172        case 3: compositedSpaceQuad.setP4(floatPoint); break;
173        }
174    }
175}
176
177static void addQuadToPath(const FloatQuad& quad, Path& path)
178{
179    // FIXME: Make this create rounded quad-paths, just like the axis-aligned case.
180    path.moveTo(quad.p1());
181    path.addLineTo(quad.p2());
182    path.addLineTo(quad.p3());
183    path.addLineTo(quad.p4());
184    path.closeSubpath();
185}
186
187bool LinkHighlight::computeHighlightLayerPathAndPosition(RenderLayer* compositingLayer)
188{
189    if (!m_node || !m_node->renderer() || !m_currentGraphicsLayer)
190        return false;
191
192    ASSERT(compositingLayer);
193
194    // Get quads for node in absolute coordinates.
195    Vector<FloatQuad> quads;
196    m_node->renderer()->absoluteQuads(quads);
197    ASSERT(quads.size());
198
199    // Adjust for offset between target graphics layer and the node's renderer.
200    FloatPoint positionAdjust = IntPoint(m_currentGraphicsLayer->offsetFromRenderer());
201
202    Path newPath;
203    for (unsigned quadIndex = 0; quadIndex < quads.size(); ++quadIndex) {
204        FloatQuad absoluteQuad = quads[quadIndex];
205        absoluteQuad.move(-positionAdjust.x(), -positionAdjust.y());
206
207        // Transform node quads in target absolute coords to local coordinates in the compositor layer.
208        FloatQuad transformedQuad;
209        convertTargetSpaceQuadToCompositedLayer(absoluteQuad, m_node->renderer(), compositingLayer->renderer(), transformedQuad);
210
211        // FIXME: for now, we'll only use rounded paths if we have a single node quad. The reason for this is that
212        // we may sometimes get a chain of adjacent boxes (e.g. for text nodes) which end up looking like sausage
213        // links: these should ideally be merged into a single rect before creating the path, but that's
214        // another CL.
215        if (quads.size() == 1 && transformedQuad.isRectilinear()) {
216            FloatSize rectRoundingRadii(3, 3);
217            newPath.addRoundedRect(transformedQuad.boundingBox(), rectRoundingRadii);
218        } else
219            addQuadToPath(transformedQuad, newPath);
220    }
221
222    FloatRect boundingRect = newPath.boundingRect();
223    newPath.translate(-toFloatSize(boundingRect.location()));
224
225    bool pathHasChanged = !(newPath == m_path);
226    if (pathHasChanged) {
227        m_path = newPath;
228        m_contentLayer->layer()->setBounds(enclosingIntRect(boundingRect).size());
229    }
230
231    m_contentLayer->layer()->setPosition(boundingRect.location());
232
233    return pathHasChanged;
234}
235
236void LinkHighlight::paintContents(WebCanvas* canvas, const WebRect& webClipRect, bool, WebFloatRect&)
237{
238    if (!m_node || !m_node->renderer())
239        return;
240
241    GraphicsContext gc(canvas);
242    IntRect clipRect(IntPoint(webClipRect.x, webClipRect.y), IntSize(webClipRect.width, webClipRect.height));
243    gc.clip(clipRect);
244    gc.setFillColor(m_node->renderer()->style()->tapHighlightColor());
245    gc.fillPath(m_path);
246}
247
248void LinkHighlight::startHighlightAnimationIfNeeded()
249{
250    if (m_isAnimating)
251        return;
252
253    m_isAnimating = true;
254    const float startOpacity = 1;
255    // FIXME: Should duration be configurable?
256    const float fadeDuration = 0.1f;
257    const float minPreFadeDuration = 0.1f;
258
259    m_contentLayer->layer()->setOpacity(startOpacity);
260
261    WebCompositorSupport* compositorSupport = Platform::current()->compositorSupport();
262
263    OwnPtr<WebFloatAnimationCurve> curve = adoptPtr(compositorSupport->createFloatAnimationCurve());
264
265    curve->add(WebFloatKeyframe(0, startOpacity));
266    // Make sure we have displayed for at least minPreFadeDuration before starting to fade out.
267    float extraDurationRequired = std::max(0.f, minPreFadeDuration - static_cast<float>(monotonicallyIncreasingTime() - m_startTime));
268    if (extraDurationRequired)
269        curve->add(WebFloatKeyframe(extraDurationRequired, startOpacity));
270    // For layout tests we don't fade out.
271    curve->add(WebFloatKeyframe(fadeDuration + extraDurationRequired, blink::layoutTestMode() ? startOpacity : 0));
272
273    OwnPtr<WebAnimation> animation = adoptPtr(compositorSupport->createAnimation(*curve, WebAnimation::TargetPropertyOpacity));
274
275    m_contentLayer->layer()->setDrawsContent(true);
276    m_contentLayer->layer()->addAnimation(animation.leakPtr());
277
278    invalidate();
279    m_owningWebViewImpl->scheduleAnimation();
280}
281
282void LinkHighlight::clearGraphicsLayerLinkHighlightPointer()
283{
284    if (m_currentGraphicsLayer) {
285        m_currentGraphicsLayer->removeLinkHighlight(this);
286        m_currentGraphicsLayer = 0;
287    }
288}
289
290void LinkHighlight::notifyAnimationStarted(double, double, blink::WebAnimation::TargetProperty)
291{
292}
293
294void LinkHighlight::notifyAnimationFinished(double, double, blink::WebAnimation::TargetProperty)
295{
296    // Since WebViewImpl may hang on to us for a while, make sure we
297    // release resources as soon as possible.
298    clearGraphicsLayerLinkHighlightPointer();
299    releaseResources();
300}
301
302void LinkHighlight::updateGeometry()
303{
304    // To avoid unnecessary updates (e.g. other entities have requested animations from our WebViewImpl),
305    // only proceed if we actually requested an update.
306    if (!m_geometryNeedsUpdate)
307        return;
308
309    m_geometryNeedsUpdate = false;
310
311    RenderLayer* compositingLayer = computeEnclosingCompositingLayer();
312    if (compositingLayer && computeHighlightLayerPathAndPosition(compositingLayer)) {
313        // We only need to invalidate the layer if the highlight size has changed, otherwise
314        // we can just re-position the layer without needing to repaint.
315        m_contentLayer->layer()->invalidate();
316
317        if (m_currentGraphicsLayer)
318            m_currentGraphicsLayer->addRepaintRect(FloatRect(layer()->position().x, layer()->position().y, layer()->bounds().width, layer()->bounds().height));
319    } else if (!m_node || !m_node->renderer()) {
320        clearGraphicsLayerLinkHighlightPointer();
321        releaseResources();
322    }
323}
324
325void LinkHighlight::clearCurrentGraphicsLayer()
326{
327    m_currentGraphicsLayer = 0;
328    m_geometryNeedsUpdate = true;
329}
330
331void LinkHighlight::invalidate()
332{
333    // Make sure we update geometry on the next callback from WebViewImpl::layout().
334    m_geometryNeedsUpdate = true;
335}
336
337WebLayer* LinkHighlight::layer()
338{
339    return clipLayer();
340}
341
342} // namespace WeKit
343