Home | History | Annotate | Download | only in rendering
      1 /*
      2  * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved.
      3  *
      4  * Portions are Copyright (C) 1998 Netscape Communications Corporation.
      5  *
      6  * Other contributors:
      7  *   Robert O'Callahan <roc+@cs.cmu.edu>
      8  *   David Baron <dbaron (at) fas.harvard.edu>
      9  *   Christian Biesinger <cbiesinger (at) web.de>
     10  *   Randall Jesup <rjesup (at) wgate.com>
     11  *   Roland Mainz <roland.mainz (at) informatik.med.uni-giessen.de>
     12  *   Josh Soref <timeless (at) mac.com>
     13  *   Boris Zbarsky <bzbarsky (at) mit.edu>
     14  *
     15  * This library is free software; you can redistribute it and/or
     16  * modify it under the terms of the GNU Lesser General Public
     17  * License as published by the Free Software Foundation; either
     18  * version 2.1 of the License, or (at your option) any later version.
     19  *
     20  * This library is distributed in the hope that it will be useful,
     21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     23  * Lesser General Public License for more details.
     24  *
     25  * You should have received a copy of the GNU Lesser General Public
     26  * License along with this library; if not, write to the Free Software
     27  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
     28  *
     29  * Alternatively, the contents of this file may be used under the terms
     30  * of either the Mozilla Public License Version 1.1, found at
     31  * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public
     32  * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html
     33  * (the "GPL"), in which case the provisions of the MPL or the GPL are
     34  * applicable instead of those above.  If you wish to allow use of your
     35  * version of this file only under the terms of one of those two
     36  * licenses (the MPL or the GPL) and not to allow others to use your
     37  * version of this file under the LGPL, indicate your decision by
     38  * deletingthe provisions above and replace them with the notice and
     39  * other provisions required by the MPL or the GPL, as the case may be.
     40  * If you do not delete the provisions above, a recipient may use your
     41  * version of this file under any of the LGPL, the MPL or the GPL.
     42  */
     43 
     44 #include "config.h"
     45 #include "core/rendering/RenderLayerClipper.h"
     46 
     47 #include "core/rendering/RenderLayer.h"
     48 #include "core/rendering/RenderView.h"
     49 
     50 namespace blink {
     51 
     52 static void adjustClipRectsForChildren(const RenderObject& renderer, ClipRects& clipRects)
     53 {
     54     EPosition position = renderer.style()->position();
     55     // A fixed object is essentially the root of its containing block hierarchy, so when
     56     // we encounter such an object, we reset our clip rects to the fixedClipRect.
     57     if (position == FixedPosition) {
     58         clipRects.setPosClipRect(clipRects.fixedClipRect());
     59         clipRects.setOverflowClipRect(clipRects.fixedClipRect());
     60         clipRects.setFixed(true);
     61     } else if (position == RelativePosition) {
     62         clipRects.setPosClipRect(clipRects.overflowClipRect());
     63     } else if (position == AbsolutePosition) {
     64         clipRects.setOverflowClipRect(clipRects.posClipRect());
     65     }
     66 }
     67 
     68 static void applyClipRects(const ClipRectsContext& context, RenderObject& renderer, LayoutPoint offset, ClipRects& clipRects)
     69 {
     70     ASSERT(renderer.hasOverflowClip() || renderer.hasClip());
     71 
     72     RenderView* view = renderer.view();
     73     ASSERT(view);
     74     if (clipRects.fixed() && context.rootLayer->renderer() == view)
     75         offset -= view->frameView()->scrollOffsetForFixedPosition();
     76 
     77     if (renderer.hasOverflowClip()) {
     78         ClipRect newOverflowClip = toRenderBox(renderer).overflowClipRect(offset, context.scrollbarRelevancy);
     79         newOverflowClip.setHasRadius(renderer.style()->hasBorderRadius());
     80         clipRects.setOverflowClipRect(intersection(newOverflowClip, clipRects.overflowClipRect()));
     81         if (renderer.isPositioned())
     82             clipRects.setPosClipRect(intersection(newOverflowClip, clipRects.posClipRect()));
     83     }
     84 
     85     if (renderer.hasClip()) {
     86         LayoutRect newClip = toRenderBox(renderer).clipRect(offset);
     87         clipRects.setPosClipRect(intersection(newClip, clipRects.posClipRect()));
     88         clipRects.setOverflowClipRect(intersection(newClip, clipRects.overflowClipRect()));
     89         clipRects.setFixedClipRect(intersection(newClip, clipRects.fixedClipRect()));
     90     }
     91 }
     92 
     93 RenderLayerClipper::RenderLayerClipper(RenderLayerModelObject& renderer)
     94     : m_renderer(renderer)
     95 {
     96 }
     97 
     98 ClipRects* RenderLayerClipper::clipRectsIfCached(const ClipRectsContext& context) const
     99 {
    100     ASSERT(context.usesCache());
    101     if (!m_cache)
    102         return 0;
    103     ClipRectsCache::Entry& entry = m_cache->get(context.cacheSlot);
    104     // FIXME: We used to ASSERT that we always got a consistent root layer.
    105     // We should add a test that has an inconsistent root. See
    106     // http://crbug.com/366118 for an example.
    107     if (context.rootLayer != entry.root)
    108         return 0;
    109     ASSERT(entry.scrollbarRelevancy == context.scrollbarRelevancy);
    110 
    111 #ifdef CHECK_CACHED_CLIP_RECTS
    112     // This code is useful to check cached clip rects, but is too expensive to leave enabled in debug builds by default.
    113     ClipRectsContext tempContext(context);
    114     tempContext.cacheSlot = UncachedClipRects;
    115     ClipRects clipRects;
    116     calculateClipRects(tempContext, clipRects);
    117     ASSERT(clipRects == *entry.clipRects);
    118 #endif
    119 
    120     return entry.clipRects.get();
    121 }
    122 
    123 ClipRects* RenderLayerClipper::storeClipRectsInCache(const ClipRectsContext& context, ClipRects* parentClipRects, const ClipRects& clipRects) const
    124 {
    125     ClipRectsCache::Entry& entry = cache().get(context.cacheSlot);
    126     entry.root = context.rootLayer;
    127 #if ENABLE(ASSERT)
    128     entry.scrollbarRelevancy = context.scrollbarRelevancy;
    129 #endif
    130 
    131     if (parentClipRects) {
    132         // If our clip rects match the clip rects of our parent, we share storage.
    133         if (clipRects == *parentClipRects) {
    134             entry.clipRects = parentClipRects;
    135             return parentClipRects;
    136         }
    137     }
    138 
    139     entry.clipRects = ClipRects::create(clipRects);
    140     return entry.clipRects.get();
    141 }
    142 
    143 ClipRects* RenderLayerClipper::getClipRects(const ClipRectsContext& context) const
    144 {
    145     if (ClipRects* result = clipRectsIfCached(context))
    146         return result;
    147 
    148     // Note that it's important that we call getClipRects on our parent
    149     // before we call calculateClipRects so that calculateClipRects will hit
    150     // the cache.
    151     ClipRects* parentClipRects = 0;
    152     if (context.rootLayer != m_renderer.layer() && m_renderer.layer()->parent())
    153         parentClipRects = m_renderer.layer()->parent()->clipper().getClipRects(context);
    154 
    155     ClipRects clipRects;
    156     calculateClipRects(context, clipRects);
    157     return storeClipRectsInCache(context, parentClipRects, clipRects);
    158 }
    159 
    160 void RenderLayerClipper::clearClipRectsIncludingDescendants()
    161 {
    162     m_cache = nullptr;
    163 
    164     for (RenderLayer* layer = m_renderer.layer()->firstChild(); layer; layer = layer->nextSibling())
    165         layer->clipper().clearClipRectsIncludingDescendants();
    166 }
    167 
    168 void RenderLayerClipper::clearClipRectsIncludingDescendants(ClipRectsCacheSlot cacheSlot)
    169 {
    170     if (m_cache)
    171         m_cache->clear(cacheSlot);
    172 
    173     for (RenderLayer* layer = m_renderer.layer()->firstChild(); layer; layer = layer->nextSibling())
    174         layer->clipper().clearClipRectsIncludingDescendants(cacheSlot);
    175 }
    176 
    177 LayoutRect RenderLayerClipper::childrenClipRect() const
    178 {
    179     // FIXME: border-radius not accounted for.
    180     // FIXME: Regions not accounted for.
    181     RenderLayer* clippingRootLayer = clippingRootForPainting();
    182     LayoutRect layerBounds;
    183     ClipRect backgroundRect, foregroundRect, outlineRect;
    184     // Need to use uncached clip rects, because the value of 'dontClipToOverflow' may be different from the painting path (<rdar://problem/11844909>).
    185     ClipRectsContext context(clippingRootLayer, UncachedClipRects);
    186     calculateRects(context, m_renderer.view()->unscaledDocumentRect(), layerBounds, backgroundRect, foregroundRect, outlineRect);
    187     return clippingRootLayer->renderer()->localToAbsoluteQuad(FloatQuad(foregroundRect.rect())).enclosingBoundingBox();
    188 }
    189 
    190 LayoutRect RenderLayerClipper::localClipRect() const
    191 {
    192     // FIXME: border-radius not accounted for.
    193     RenderLayer* clippingRootLayer = clippingRootForPainting();
    194     LayoutRect layerBounds;
    195     ClipRect backgroundRect, foregroundRect, outlineRect;
    196     ClipRectsContext context(clippingRootLayer, PaintingClipRects);
    197     calculateRects(context, PaintInfo::infiniteRect(), layerBounds, backgroundRect, foregroundRect, outlineRect);
    198 
    199     LayoutRect clipRect = backgroundRect.rect();
    200     if (clipRect == PaintInfo::infiniteRect())
    201         return clipRect;
    202 
    203     LayoutPoint clippingRootOffset;
    204     m_renderer.layer()->convertToLayerCoords(clippingRootLayer, clippingRootOffset);
    205     clipRect.moveBy(-clippingRootOffset);
    206 
    207     return clipRect;
    208 }
    209 
    210 void RenderLayerClipper::calculateRects(const ClipRectsContext& context, const LayoutRect& paintDirtyRect, LayoutRect& layerBounds,
    211     ClipRect& backgroundRect, ClipRect& foregroundRect, ClipRect& outlineRect, const LayoutPoint* offsetFromRoot) const
    212 {
    213     bool isClippingRoot = m_renderer.layer() == context.rootLayer;
    214 
    215     if (!isClippingRoot && m_renderer.layer()->parent()) {
    216         backgroundRect = backgroundClipRect(context);
    217         backgroundRect.move(roundedIntSize(context.subPixelAccumulation));
    218         backgroundRect.intersect(paintDirtyRect);
    219     } else {
    220         backgroundRect = paintDirtyRect;
    221     }
    222 
    223     foregroundRect = backgroundRect;
    224     outlineRect = backgroundRect;
    225 
    226     LayoutPoint offset;
    227     if (offsetFromRoot)
    228         offset = *offsetFromRoot;
    229     else
    230         m_renderer.layer()->convertToLayerCoords(context.rootLayer, offset);
    231     layerBounds = LayoutRect(offset, m_renderer.layer()->size());
    232 
    233     // Update the clip rects that will be passed to child layers.
    234     if (m_renderer.hasOverflowClip()) {
    235         // This layer establishes a clip of some kind.
    236         if (!isClippingRoot || context.respectOverflowClip == RespectOverflowClip) {
    237             foregroundRect.intersect(toRenderBox(m_renderer).overflowClipRect(offset, context.scrollbarRelevancy));
    238             if (m_renderer.style()->hasBorderRadius())
    239                 foregroundRect.setHasRadius(true);
    240         }
    241 
    242         // If we establish an overflow clip at all, then go ahead and make sure our background
    243         // rect is intersected with our layer's bounds including our visual overflow,
    244         // since any visual overflow like box-shadow or border-outset is not clipped by overflow:auto/hidden.
    245         if (toRenderBox(m_renderer).hasVisualOverflow()) {
    246             // FIXME: Perhaps we should be propagating the borderbox as the clip rect for children, even though
    247             //        we may need to inflate our clip specifically for shadows or outsets.
    248             // FIXME: Does not do the right thing with CSS regions yet, since we don't yet factor in the
    249             // individual region boxes as overflow.
    250             LayoutRect layerBoundsWithVisualOverflow = toRenderBox(m_renderer).visualOverflowRect();
    251             toRenderBox(m_renderer).flipForWritingMode(layerBoundsWithVisualOverflow); // Layers are in physical coordinates, so the overflow has to be flipped.
    252             layerBoundsWithVisualOverflow.moveBy(offset);
    253             if (!isClippingRoot || context.respectOverflowClip == RespectOverflowClip)
    254                 backgroundRect.intersect(layerBoundsWithVisualOverflow);
    255         } else {
    256             LayoutRect bounds = toRenderBox(m_renderer).borderBoxRect();
    257             bounds.moveBy(offset);
    258             if (!isClippingRoot || context.respectOverflowClip == RespectOverflowClip)
    259                 backgroundRect.intersect(bounds);
    260         }
    261     }
    262 
    263     // CSS clip (different than clipping due to overflow) can clip to any box, even if it falls outside of the border box.
    264     if (m_renderer.hasClip()) {
    265         // Clip applies to *us* as well, so go ahead and update the damageRect.
    266         LayoutRect newPosClip = toRenderBox(m_renderer).clipRect(offset);
    267         backgroundRect.intersect(newPosClip);
    268         foregroundRect.intersect(newPosClip);
    269         outlineRect.intersect(newPosClip);
    270     }
    271 }
    272 
    273 void RenderLayerClipper::calculateClipRects(const ClipRectsContext& context, ClipRects& clipRects) const
    274 {
    275     if (!m_renderer.layer()->parent()) {
    276         // The root layer's clip rect is always infinite.
    277         clipRects.reset(PaintInfo::infiniteRect());
    278         return;
    279     }
    280 
    281     bool isClippingRoot = m_renderer.layer() == context.rootLayer;
    282 
    283     // For transformed layers, the root layer was shifted to be us, so there is no need to
    284     // examine the parent. We want to cache clip rects with us as the root.
    285     RenderLayer* parentLayer = !isClippingRoot ? m_renderer.layer()->parent() : 0;
    286 
    287     // Ensure that our parent's clip has been calculated so that we can examine the values.
    288     if (parentLayer) {
    289         // FIXME: Why don't we just call getClipRects here?
    290         if (context.usesCache() && parentLayer->clipper().cachedClipRects(context)) {
    291             clipRects = *parentLayer->clipper().cachedClipRects(context);
    292         } else {
    293             parentLayer->clipper().calculateClipRects(context, clipRects);
    294         }
    295     } else {
    296         clipRects.reset(PaintInfo::infiniteRect());
    297     }
    298 
    299     adjustClipRectsForChildren(m_renderer, clipRects);
    300 
    301     // FIXME: This logic looks wrong. We'll apply overflow clip rects even if we were told to IgnoreOverflowClip if m_renderer.hasClip().
    302     if ((m_renderer.hasOverflowClip() && (context.respectOverflowClip == RespectOverflowClip || !isClippingRoot)) || m_renderer.hasClip()) {
    303         // This offset cannot use convertToLayerCoords, because sometimes our rootLayer may be across
    304         // some transformed layer boundary, for example, in the RenderLayerCompositor overlapMap, where
    305         // clipRects are needed in view space.
    306         applyClipRects(context, m_renderer, roundedLayoutPoint(m_renderer.localToContainerPoint(FloatPoint(), context.rootLayer->renderer())), clipRects);
    307     }
    308 }
    309 
    310 static ClipRect backgroundClipRectForPosition(const ClipRects& parentRects, EPosition position)
    311 {
    312     if (position == FixedPosition)
    313         return parentRects.fixedClipRect();
    314 
    315     if (position == AbsolutePosition)
    316         return parentRects.posClipRect();
    317 
    318     return parentRects.overflowClipRect();
    319 }
    320 
    321 ClipRect RenderLayerClipper::backgroundClipRect(const ClipRectsContext& context) const
    322 {
    323     ASSERT(m_renderer.layer()->parent());
    324     ASSERT(m_renderer.view());
    325 
    326     ClipRects parentClipRects;
    327     if (m_renderer.layer() == context.rootLayer)
    328         parentClipRects.reset(PaintInfo::infiniteRect());
    329     else
    330         m_renderer.layer()->parent()->clipper().getOrCalculateClipRects(context, parentClipRects);
    331 
    332     ClipRect result = backgroundClipRectForPosition(parentClipRects, m_renderer.style()->position());
    333 
    334     // Note: infinite clipRects should not be scrolled here, otherwise they will accidentally no longer be considered infinite.
    335     if (parentClipRects.fixed() && context.rootLayer->renderer() == m_renderer.view() && result != PaintInfo::infiniteRect())
    336         result.move(m_renderer.view()->frameView()->scrollOffsetForFixedPosition());
    337 
    338     return result;
    339 }
    340 
    341 void RenderLayerClipper::getOrCalculateClipRects(const ClipRectsContext& context, ClipRects& clipRects) const
    342 {
    343     if (context.usesCache())
    344         clipRects = *getClipRects(context);
    345     else
    346         calculateClipRects(context, clipRects);
    347 }
    348 
    349 RenderLayer* RenderLayerClipper::clippingRootForPainting() const
    350 {
    351     const RenderLayer* current = m_renderer.layer();
    352     // FIXME: getting rid of current->hasCompositedLayerMapping() here breaks the
    353     // compositing/backing/no-backing-for-clip.html layout test, because there is a
    354     // "composited but paints into ancestor" layer involved. However, it doesn't make sense that
    355     // that check would be appropriate here but not inside the while loop below.
    356     if (current->isPaintInvalidationContainer() || current->hasCompositedLayerMapping())
    357         return const_cast<RenderLayer*>(current);
    358 
    359     while (current) {
    360         if (current->isRootLayer())
    361             return const_cast<RenderLayer*>(current);
    362 
    363         current = current->compositingContainer();
    364         ASSERT(current);
    365         if (current->transform() || current->isPaintInvalidationContainer())
    366             return const_cast<RenderLayer*>(current);
    367     }
    368 
    369     ASSERT_NOT_REACHED();
    370     return 0;
    371 }
    372 
    373 } // namespace blink
    374