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