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