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
55 namespace blink {
56
57 class WebViewImpl;
58
create(Node * node,WebViewImpl * owningWebViewImpl)59 PassOwnPtr<LinkHighlight> LinkHighlight::create(Node* node, WebViewImpl* owningWebViewImpl)
60 {
61 return adoptPtr(new LinkHighlight(node, owningWebViewImpl));
62 }
63
LinkHighlight(Node * node,WebViewImpl * owningWebViewImpl)64 LinkHighlight::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
~LinkHighlight()86 LinkHighlight::~LinkHighlight()
87 {
88 clearGraphicsLayerLinkHighlightPointer();
89 releaseResources();
90 }
91
contentLayer()92 WebContentLayer* LinkHighlight::contentLayer()
93 {
94 return m_contentLayer.get();
95 }
96
clipLayer()97 WebLayer* LinkHighlight::clipLayer()
98 {
99 return m_clipLayer.get();
100 }
101
releaseResources()102 void LinkHighlight::releaseResources()
103 {
104 m_node.clear();
105 }
106
computeEnclosingCompositingLayer()107 RenderLayer* 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
convertTargetSpaceQuadToCompositedLayer(const FloatQuad & targetSpaceQuad,RenderObject * targetRenderer,RenderObject * compositedRenderer,FloatQuad & compositedSpaceQuad)145 static 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
addQuadToPath(const FloatQuad & quad,Path & path)172 static 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
computeQuads(RenderObject & renderer,Vector<FloatQuad> & outQuads) const182 void 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
computeHighlightLayerPathAndPosition(RenderLayer * compositingLayer)197 bool 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
paintContents(WebCanvas * canvas,const WebRect & webClipRect,bool,WebContentLayerClient::GraphicsContextStatus contextStatus)247 void 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
startHighlightAnimationIfNeeded()260 void 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
clearGraphicsLayerLinkHighlightPointer()294 void LinkHighlight::clearGraphicsLayerLinkHighlightPointer()
295 {
296 if (m_currentGraphicsLayer) {
297 m_currentGraphicsLayer->removeLinkHighlight(this);
298 m_currentGraphicsLayer = 0;
299 }
300 }
301
notifyAnimationStarted(double,WebCompositorAnimation::TargetProperty)302 void LinkHighlight::notifyAnimationStarted(double, WebCompositorAnimation::TargetProperty)
303 {
304 }
305
notifyAnimationFinished(double,WebCompositorAnimation::TargetProperty)306 void 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
updateGeometry()314 void 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
clearCurrentGraphicsLayer()337 void LinkHighlight::clearCurrentGraphicsLayer()
338 {
339 m_currentGraphicsLayer = 0;
340 m_geometryNeedsUpdate = true;
341 }
342
invalidate()343 void LinkHighlight::invalidate()
344 {
345 // Make sure we update geometry on the next callback from WebViewImpl::layout().
346 m_geometryNeedsUpdate = true;
347 }
348
layer()349 WebLayer* LinkHighlight::layer()
350 {
351 return clipLayer();
352 }
353
354 } // namespace blink
355