2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "web/LinkHighlightImpl.h"
29 #include "core/dom/LayoutTreeBuilderTraversal.h"
30 #include "core/dom/Node.h"
31 #include "core/frame/FrameView.h"
32 #include "core/frame/LocalFrame.h"
33 #include "core/layout/LayoutBoxModelObject.h"
34 #include "core/layout/LayoutObject.h"
35 #include "core/layout/LayoutView.h"
36 #include "core/layout/compositing/CompositedDeprecatedPaintLayerMapping.h"
37 #include "core/paint/DeprecatedPaintLayer.h"
38 #include "platform/RuntimeEnabledFeatures.h"
39 #include "platform/graphics/Color.h"
40 #include "platform/graphics/GraphicsLayer.h"
41 #include "platform/graphics/paint/DrawingRecorder.h"
42 #include "public/platform/Platform.h"
43 #include "public/platform/WebCompositorAnimationCurve.h"
44 #include "public/platform/WebCompositorSupport.h"
45 #include "public/platform/WebContentLayer.h"
46 #include "public/platform/WebDisplayItemList.h"
47 #include "public/platform/WebFloatAnimationCurve.h"
48 #include "public/platform/WebFloatPoint.h"
49 #include "public/platform/WebLayer.h"
50 #include "public/platform/WebRect.h"
51 #include "public/platform/WebSize.h"
52 #include "public/web/WebKit.h"
53 #include "third_party/skia/include/core/SkCanvas.h"
54 #include "third_party/skia/include/core/SkPictureRecorder.h"
55 #include "third_party/skia/include/utils/SkMatrix44.h"
56 #include "web/WebLocalFrameImpl.h"
57 #include "web/WebSettingsImpl.h"
58 #include "web/WebViewImpl.h"
59 #include "wtf/CurrentTime.h"
60 #include "wtf/Vector.h"
64 PassOwnPtr
<LinkHighlightImpl
> LinkHighlightImpl::create(Node
* node
, WebViewImpl
* owningWebViewImpl
)
66 return adoptPtr(new LinkHighlightImpl(node
, owningWebViewImpl
));
69 LinkHighlightImpl::LinkHighlightImpl(Node
* node
, WebViewImpl
* owningWebViewImpl
)
71 , m_owningWebViewImpl(owningWebViewImpl
)
72 , m_currentGraphicsLayer(0)
73 , m_geometryNeedsUpdate(false)
74 , m_isAnimating(false)
75 , m_startTime(monotonicallyIncreasingTime())
78 ASSERT(owningWebViewImpl
);
79 WebCompositorSupport
* compositorSupport
= Platform::current()->compositorSupport();
80 ASSERT(compositorSupport
);
81 m_contentLayer
= adoptPtr(compositorSupport
->createContentLayer(this));
82 m_clipLayer
= adoptPtr(compositorSupport
->createLayer());
83 m_clipLayer
->setTransformOrigin(WebFloatPoint3D());
84 m_clipLayer
->addChild(m_contentLayer
->layer());
85 if (RuntimeEnabledFeatures::compositorAnimationTimelinesEnabled() && Platform::current()->isThreadedAnimationEnabled()) {
86 m_compositorPlayer
= adoptPtr(compositorSupport
->createAnimationPlayer());
87 ASSERT(m_compositorPlayer
);
88 m_compositorPlayer
->setAnimationDelegate(this);
89 m_owningWebViewImpl
->linkHighlightsTimeline()->playerAttached(*this);
90 m_compositorPlayer
->attachLayer(m_contentLayer
->layer());
92 owningWebViewImpl
->registerForAnimations(m_contentLayer
->layer());
93 m_contentLayer
->layer()->setAnimationDelegate(this);
95 m_contentLayer
->layer()->setDrawsContent(true);
96 m_contentLayer
->layer()->setOpacity(1);
97 m_geometryNeedsUpdate
= true;
101 LinkHighlightImpl::~LinkHighlightImpl()
103 if (m_compositorPlayer
) {
104 m_compositorPlayer
->detachLayer();
105 m_owningWebViewImpl
->linkHighlightsTimeline()->playerDestroyed(*this);
106 m_compositorPlayer
->setAnimationDelegate(nullptr);
108 m_compositorPlayer
.clear();
110 clearGraphicsLayerLinkHighlightPointer();
114 WebContentLayer
* LinkHighlightImpl::contentLayer()
116 return m_contentLayer
.get();
119 WebLayer
* LinkHighlightImpl::clipLayer()
121 return m_clipLayer
.get();
124 void LinkHighlightImpl::releaseResources()
129 void LinkHighlightImpl::attachLinkHighlightToCompositingLayer(const LayoutBoxModelObject
* paintInvalidationContainer
)
131 GraphicsLayer
* newGraphicsLayer
= paintInvalidationContainer
->layer()->graphicsLayerBacking();
132 // FIXME: There should always be a GraphicsLayer. See crbug.com/431961.
133 if (newGraphicsLayer
&& !newGraphicsLayer
->drawsContent())
134 newGraphicsLayer
= paintInvalidationContainer
->layer()->graphicsLayerBackingForScrolling();
135 if (!newGraphicsLayer
)
138 m_clipLayer
->setTransform(SkMatrix44(SkMatrix44::kIdentity_Constructor
));
140 if (m_currentGraphicsLayer
!= newGraphicsLayer
) {
141 if (m_currentGraphicsLayer
)
142 clearGraphicsLayerLinkHighlightPointer();
144 m_currentGraphicsLayer
= newGraphicsLayer
;
145 m_currentGraphicsLayer
->addLinkHighlight(this);
149 static void convertTargetSpaceQuadToCompositedLayer(const FloatQuad
& targetSpaceQuad
, LayoutObject
* targetLayoutObject
, const LayoutBoxModelObject
* paintInvalidationContainer
, FloatQuad
& compositedSpaceQuad
)
151 ASSERT(targetLayoutObject
);
152 ASSERT(paintInvalidationContainer
);
153 for (unsigned i
= 0; i
< 4; ++i
) {
156 case 0: point
= roundedIntPoint(targetSpaceQuad
.p1()); break;
157 case 1: point
= roundedIntPoint(targetSpaceQuad
.p2()); break;
158 case 2: point
= roundedIntPoint(targetSpaceQuad
.p3()); break;
159 case 3: point
= roundedIntPoint(targetSpaceQuad
.p4()); break;
162 // FIXME: this does not need to be absolute, just in the paint invalidation container's space.
163 point
= targetLayoutObject
->frame()->view()->contentsToRootFrame(point
);
164 point
= paintInvalidationContainer
->frame()->view()->rootFrameToContents(point
);
165 FloatPoint floatPoint
= paintInvalidationContainer
->absoluteToLocal(point
, UseTransforms
);
166 DeprecatedPaintLayer::mapPointToPaintBackingCoordinates(paintInvalidationContainer
, floatPoint
);
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;
177 static void addQuadToPath(const FloatQuad
& quad
, Path
& path
)
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());
187 void LinkHighlightImpl::computeQuads(const Node
& node
, Vector
<FloatQuad
>& outQuads
) const
189 if (!node
.layoutObject())
192 LayoutObject
* layoutObject
= node
.layoutObject();
194 // For inline elements, absoluteQuads will return a line box based on the line-height
195 // and font metrics, which is technically incorrect as replaced elements like images
196 // should use their intristic height and expand the linebox as needed. To get an
197 // appropriately sized highlight we descend into the children and have them add their
199 if (layoutObject
->isLayoutInline()) {
200 for (Node
* child
= LayoutTreeBuilderTraversal::firstChild(node
); child
; child
= LayoutTreeBuilderTraversal::nextSibling(*child
))
201 computeQuads(*child
, outQuads
);
203 // FIXME: this does not need to be absolute, just in the paint invalidation container's space.
204 layoutObject
->absoluteQuads(outQuads
);
208 bool LinkHighlightImpl::computeHighlightLayerPathAndPosition(const LayoutBoxModelObject
* paintInvalidationContainer
)
210 if (!m_node
|| !m_node
->layoutObject() || !m_currentGraphicsLayer
)
212 ASSERT(paintInvalidationContainer
);
214 // FIXME: This is defensive code to avoid crashes such as those described in
215 // crbug.com/440887. This should be cleaned up once we fix the root cause of
216 // of the paint invalidation container not being composited.
217 if (!paintInvalidationContainer
->layer()->compositedDeprecatedPaintLayerMapping() && !paintInvalidationContainer
->layer()->groupedMapping())
220 // Get quads for node in absolute coordinates.
221 Vector
<FloatQuad
> quads
;
222 computeQuads(*m_node
, quads
);
223 ASSERT(quads
.size());
226 for (size_t quadIndex
= 0; quadIndex
< quads
.size(); ++quadIndex
) {
227 FloatQuad absoluteQuad
= quads
[quadIndex
];
229 // Transform node quads in target absolute coords to local coordinates in the compositor layer.
230 FloatQuad transformedQuad
;
231 convertTargetSpaceQuadToCompositedLayer(absoluteQuad
, m_node
->layoutObject(), paintInvalidationContainer
, transformedQuad
);
233 // FIXME: for now, we'll only use rounded paths if we have a single node quad. The reason for this is that
234 // we may sometimes get a chain of adjacent boxes (e.g. for text nodes) which end up looking like sausage
235 // links: these should ideally be merged into a single rect before creating the path, but that's
237 if (quads
.size() == 1 && transformedQuad
.isRectilinear()
238 && !m_owningWebViewImpl
->settingsImpl()->mockGestureTapHighlightsEnabled()) {
239 FloatSize
rectRoundingRadii(3, 3);
240 newPath
.addRoundedRect(transformedQuad
.boundingBox(), rectRoundingRadii
);
242 addQuadToPath(transformedQuad
, newPath
);
246 FloatRect boundingRect
= newPath
.boundingRect();
247 newPath
.translate(-toFloatSize(boundingRect
.location()));
249 bool pathHasChanged
= !(newPath
== m_path
);
250 if (pathHasChanged
) {
252 m_contentLayer
->layer()->setBounds(enclosingIntRect(boundingRect
).size());
255 m_contentLayer
->layer()->setPosition(boundingRect
.location());
257 return pathHasChanged
;
260 void LinkHighlightImpl::paintContents(WebCanvas
* canvas
, const WebRect
&, WebContentLayerClient::PaintingControlSetting paintingControl
)
262 if (!m_node
|| !m_node
->layoutObject())
266 paint
.setStyle(SkPaint::kFill_Style
);
267 paint
.setFlags(SkPaint::kAntiAlias_Flag
);
268 paint
.setColor(m_node
->layoutObject()->style()->tapHighlightColor().rgb());
269 canvas
->drawPath(m_path
.skPath(), paint
);
272 void LinkHighlightImpl::paintContents(WebDisplayItemList
* webDisplayItemList
, const WebRect
& webClipRect
, WebContentLayerClient::PaintingControlSetting paintingControl
)
274 if (!m_node
|| !m_node
->layoutObject())
277 SkPictureRecorder recorder
;
278 SkCanvas
* canvas
= recorder
.beginRecording(webClipRect
.width
, webClipRect
.height
);
279 canvas
->translate(-webClipRect
.x
, -webClipRect
.y
);
280 paintContents(canvas
, webClipRect
, paintingControl
);
281 RefPtr
<const SkPicture
> picture
= adoptRef(recorder
.endRecording());
282 webDisplayItemList
->appendDrawingItem(picture
.get());
285 void LinkHighlightImpl::startHighlightAnimationIfNeeded()
290 m_isAnimating
= true;
291 const float startOpacity
= 1;
292 // FIXME: Should duration be configurable?
293 const float fadeDuration
= 0.1f
;
294 const float minPreFadeDuration
= 0.1f
;
296 m_contentLayer
->layer()->setOpacity(startOpacity
);
298 WebCompositorSupport
* compositorSupport
= Platform::current()->compositorSupport();
300 OwnPtr
<WebFloatAnimationCurve
> curve
= adoptPtr(compositorSupport
->createFloatAnimationCurve());
302 curve
->add(WebFloatKeyframe(0, startOpacity
));
303 // Make sure we have displayed for at least minPreFadeDuration before starting to fade out.
304 float extraDurationRequired
= std::max(0.f
, minPreFadeDuration
- static_cast<float>(monotonicallyIncreasingTime() - m_startTime
));
305 if (extraDurationRequired
)
306 curve
->add(WebFloatKeyframe(extraDurationRequired
, startOpacity
));
307 // For layout tests we don't fade out.
308 curve
->add(WebFloatKeyframe(fadeDuration
+ extraDurationRequired
, layoutTestMode() ? startOpacity
: 0));
310 OwnPtr
<WebCompositorAnimation
> animation
= adoptPtr(compositorSupport
->createAnimation(*curve
, WebCompositorAnimation::TargetPropertyOpacity
));
312 m_contentLayer
->layer()->setDrawsContent(true);
313 if (RuntimeEnabledFeatures::compositorAnimationTimelinesEnabled())
314 m_compositorPlayer
->addAnimation(animation
.leakPtr());
316 m_contentLayer
->layer()->addAnimation(animation
.leakPtr());
319 m_owningWebViewImpl
->scheduleAnimation();
322 void LinkHighlightImpl::clearGraphicsLayerLinkHighlightPointer()
324 if (m_currentGraphicsLayer
) {
325 m_currentGraphicsLayer
->removeLinkHighlight(this);
326 m_currentGraphicsLayer
= 0;
330 void LinkHighlightImpl::notifyAnimationStarted(double, int)
334 void LinkHighlightImpl::notifyAnimationFinished(double, int)
336 // Since WebViewImpl may hang on to us for a while, make sure we
337 // release resources as soon as possible.
338 clearGraphicsLayerLinkHighlightPointer();
342 void LinkHighlightImpl::updateGeometry()
344 // To avoid unnecessary updates (e.g. other entities have requested animations from our WebViewImpl),
345 // only proceed if we actually requested an update.
346 if (!m_geometryNeedsUpdate
)
349 m_geometryNeedsUpdate
= false;
351 bool hasLayoutObject
= m_node
&& m_node
->layoutObject();
352 const LayoutBoxModelObject
* paintInvalidationContainer
= hasLayoutObject
? m_node
->layoutObject()->containerForPaintInvalidation() : 0;
353 if (paintInvalidationContainer
)
354 attachLinkHighlightToCompositingLayer(paintInvalidationContainer
);
355 if (paintInvalidationContainer
&& computeHighlightLayerPathAndPosition(paintInvalidationContainer
)) {
356 // We only need to invalidate the layer if the highlight size has changed, otherwise
357 // we can just re-position the layer without needing to repaint.
358 m_contentLayer
->layer()->invalidate();
360 if (m_currentGraphicsLayer
&& m_currentGraphicsLayer
->isTrackingPaintInvalidations())
361 m_currentGraphicsLayer
->trackPaintInvalidationRect(FloatRect(layer()->position().x
, layer()->position().y
, layer()->bounds().width
, layer()->bounds().height
));
362 } else if (!hasLayoutObject
) {
363 clearGraphicsLayerLinkHighlightPointer();
368 void LinkHighlightImpl::clearCurrentGraphicsLayer()
370 m_currentGraphicsLayer
= 0;
371 m_geometryNeedsUpdate
= true;
374 void LinkHighlightImpl::invalidate()
376 // Make sure we update geometry on the next callback from WebViewImpl::layout().
377 m_geometryNeedsUpdate
= true;
380 WebLayer
* LinkHighlightImpl::layer()
385 WebCompositorAnimationPlayer
* LinkHighlightImpl::compositorPlayer() const
387 return m_compositorPlayer
.get();