Home | History | Annotate | Download | only in web
      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