OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "core/paint/BoxPaintInvalidator.h" |
| 6 |
| 7 #include "core/frame/Settings.h" |
| 8 #include "core/layout/LayoutView.h" |
| 9 #include "core/paint/ObjectPaintInvalidator.h" |
| 10 #include "core/paint/PaintInvalidator.h" |
| 11 #include "core/paint/PaintLayer.h" |
| 12 #include "core/paint/PaintLayerScrollableArea.h" |
| 13 #include "platform/geometry/LayoutRect.h" |
| 14 |
| 15 namespace blink { |
| 16 |
| 17 struct PreviousBoxSizes { |
| 18 LayoutSize borderBoxSize; |
| 19 LayoutRect contentBoxRect; |
| 20 LayoutRect layoutOverflowRect; |
| 21 }; |
| 22 |
| 23 typedef HashMap<const LayoutBox*, PreviousBoxSizes> PreviousBoxSizesMap; |
| 24 static PreviousBoxSizesMap& previousBoxSizesMap() |
| 25 { |
| 26 DEFINE_STATIC_LOCAL(PreviousBoxSizesMap, map, ()); |
| 27 return map; |
| 28 } |
| 29 |
| 30 void BoxPaintInvalidator::boxWillBeDestroyed(const LayoutBox& box) |
| 31 { |
| 32 previousBoxSizesMap().remove(&box); |
| 33 } |
| 34 |
| 35 // This is called when ObjectPaintInvalidator already did incremental invalidati
on, |
| 36 // so this function just does what is additionally needed for the LayoutBox. |
| 37 void BoxPaintInvalidator::incrementallyInvalidatePaint() |
| 38 { |
| 39 bool hasBoxDecorations = m_box.styleRef().hasBoxDecorations(); |
| 40 if (!m_box.styleRef().hasBackground() && !hasBoxDecorations) |
| 41 return; |
| 42 |
| 43 const LayoutRect& oldBounds = m_context.oldBounds; |
| 44 const LayoutRect& newBounds = m_context.newBounds; |
| 45 |
| 46 LayoutSize oldBorderBoxSize = computePreviousBorderBoxSize(oldBounds.size())
; |
| 47 LayoutSize newBorderBoxSize = m_box.size(); |
| 48 |
| 49 // If border m_box size didn't change, LayoutObject's incrementallyInvalidat
ePaint() is good. |
| 50 if (oldBorderBoxSize == newBorderBoxSize) |
| 51 return; |
| 52 |
| 53 // If size of the paint invalidation rect equals to size of border box, Obje
ctPaintInvalidator::incrementallyInvalidatePaint() |
| 54 // is good for boxes having background without box decorations. |
| 55 DCHECK(oldBounds.location() == newBounds.location()); // Otherwise we won't
do incremental invalidation. |
| 56 if (!hasBoxDecorations |
| 57 && m_context.newLocation == newBounds.location() |
| 58 && oldBorderBoxSize == oldBounds.size() |
| 59 && newBorderBoxSize == newBounds.size()) |
| 60 return; |
| 61 |
| 62 // Invalidate the right delta part and the right border of the old or new m_
box which has smaller width. |
| 63 if (LayoutUnit deltaWidth = (oldBorderBoxSize.width() - newBorderBoxSize.wid
th()).abs()) { |
| 64 LayoutUnit smallerWidth = std::min(oldBorderBoxSize.width(), newBorderBo
xSize.width()); |
| 65 LayoutUnit borderTopRightRadiusWidth = valueForLength(m_box.styleRef().b
orderTopRightRadius().width(), smallerWidth); |
| 66 LayoutUnit borderBottomRightRadiusWidth = valueForLength(m_box.styleRef(
).borderBottomRightRadius().width(), smallerWidth); |
| 67 LayoutUnit borderWidth = std::max(LayoutUnit(m_box.borderRight()), std::
max(borderTopRightRadiusWidth, borderBottomRightRadiusWidth)); |
| 68 LayoutRect rightDeltaRect(m_context.newLocation.x() + smallerWidth - bor
derWidth, m_context.newLocation.y(), |
| 69 deltaWidth + borderWidth, std::max(oldBorderBoxSize.height(), newBor
derBoxSize.height())); |
| 70 invalidatePaintRectClippedByOldAndNewBounds(rightDeltaRect); |
| 71 } |
| 72 |
| 73 // Invalidate the bottom delta part and the bottom border of the old or new
m_box which has smaller height. |
| 74 if (LayoutUnit deltaHeight = (oldBorderBoxSize.height() - newBorderBoxSize.h
eight()).abs()) { |
| 75 LayoutUnit smallerHeight = std::min(oldBorderBoxSize.height(), newBorder
BoxSize.height()); |
| 76 LayoutUnit borderBottomLeftRadiusHeight = valueForLength(m_box.styleRef(
).borderBottomLeftRadius().height(), smallerHeight); |
| 77 LayoutUnit borderBottomRightRadiusHeight = valueForLength(m_box.styleRef
().borderBottomRightRadius().height(), smallerHeight); |
| 78 LayoutUnit borderHeight = std::max(LayoutUnit(m_box.borderBottom()), std
::max(borderBottomLeftRadiusHeight, borderBottomRightRadiusHeight)); |
| 79 LayoutRect bottomDeltaRect(m_context.newLocation.x(), m_context.newLocat
ion.y() + smallerHeight - borderHeight, |
| 80 std::max(oldBorderBoxSize.width(), newBorderBoxSize.width()), deltaH
eight + borderHeight); |
| 81 invalidatePaintRectClippedByOldAndNewBounds(bottomDeltaRect); |
| 82 } |
| 83 } |
| 84 |
| 85 void BoxPaintInvalidator::invalidatePaintRectClippedByOldAndNewBounds(const Layo
utRect& rect) |
| 86 { |
| 87 if (rect.isEmpty()) |
| 88 return; |
| 89 |
| 90 LayoutRect rectClippedByOldBounds = intersection(rect, m_context.oldBounds); |
| 91 LayoutRect rectClippedByNewBounds = intersection(rect, m_context.newBounds); |
| 92 // Invalidate only once if the clipped rects equal. |
| 93 if (rectClippedByOldBounds == rectClippedByNewBounds) { |
| 94 m_box.invalidatePaintUsingContainer(*m_context.paintInvalidationContaine
r, rectClippedByOldBounds, PaintInvalidationIncremental); |
| 95 return; |
| 96 } |
| 97 // Invalidate the bigger one if one contains another. Otherwise invalidate b
oth. |
| 98 if (!rectClippedByNewBounds.contains(rectClippedByOldBounds)) |
| 99 m_box.invalidatePaintUsingContainer(*m_context.paintInvalidationContaine
r, rectClippedByOldBounds, PaintInvalidationIncremental); |
| 100 if (!rectClippedByOldBounds.contains(rectClippedByNewBounds)) |
| 101 m_box.invalidatePaintUsingContainer(*m_context.paintInvalidationContaine
r, rectClippedByNewBounds, PaintInvalidationIncremental); |
| 102 } |
| 103 |
| 104 PaintInvalidationReason BoxPaintInvalidator::computePaintInvalidationReason() |
| 105 { |
| 106 PaintInvalidationReason reason = ObjectPaintInvalidator(m_box, m_context).co
mputePaintInvalidationReason(); |
| 107 |
| 108 if (reason != PaintInvalidationDelayedFull && isFullPaintInvalidationReason(
reason)) |
| 109 return reason; |
| 110 |
| 111 if (m_box.mayNeedPaintInvalidationAnimatedBackgroundImage() && !m_box.backgr
oundIsKnownToBeObscured()) |
| 112 reason = PaintInvalidationDelayedFull; |
| 113 |
| 114 // If the current paint invalidation reason is PaintInvalidationDelayedFull,
then this paint invalidation can delayed if the |
| 115 // LayoutBox in question is not on-screen. The logic to decide whether this
is appropriate exists at the site of the original |
| 116 // paint invalidation that chose PaintInvalidationDelayedFull. |
| 117 if (reason == PaintInvalidationDelayedFull) { |
| 118 // Do regular full paint invalidation if the object is onscreen. |
| 119 return m_box.intersectsVisibleViewport() ? PaintInvalidationFull : Paint
InvalidationDelayedFull; |
| 120 } |
| 121 |
| 122 if (m_box.isLayoutView()) { |
| 123 const LayoutView& layoutView = toLayoutView(m_box); |
| 124 // In normal compositing mode, root background doesn't need to be invali
dated for |
| 125 // box changes, because the background always covers the whole document
rect |
| 126 // and clipping is done by compositor()->m_containerLayer. Also the scro
llbars |
| 127 // are always composited. There are no other box decoration on the Layou
tView thus |
| 128 // we can safely exit here. |
| 129 if (layoutView.usesCompositing() && (!layoutView.document().settings() |
| !layoutView.document().settings()->rootLayerScrolls())) |
| 130 return reason; |
| 131 } |
| 132 |
| 133 // If the transform is not identity or translation, incremental invalidation
is not applicable |
| 134 // because the difference between oldBounds and newBounds doesn't cover all
area needing invalidation. |
| 135 // FIXME: Should also consider ancestor transforms since paintInvalidationCo
ntainer. crbug.com/426111. |
| 136 if (reason == PaintInvalidationIncremental |
| 137 && m_context.paintInvalidationContainer != m_box |
| 138 && m_box.hasLayer() && m_box.layer()->transform() |
| 139 && !m_box.layer()->transform()->isIdentityOrTranslation()) |
| 140 return PaintInvalidationBoundsChange; |
| 141 |
| 142 const ComputedStyle& style = m_box.styleRef(); |
| 143 if (style.backgroundLayers().thisOrNextLayersUseContentBox() || style.maskLa
yers().thisOrNextLayersUseContentBox() || style.boxSizing() == BoxSizingBorderBo
x) { |
| 144 if (previousBoxSizesMap().get(&m_box).contentBoxRect != m_box.contentBox
Rect()) |
| 145 return PaintInvalidationContentBoxChange; |
| 146 } |
| 147 |
| 148 if (!style.hasBackground() && !style.hasBoxDecorations()) { |
| 149 // We could let incremental invalidation cover non-composited scrollbars
, but just |
| 150 // do a full invalidation because incremental invalidation will go away
with slimming paint. |
| 151 if (reason == PaintInvalidationIncremental && m_box.hasNonCompositedScro
llbars()) |
| 152 return PaintInvalidationBorderBoxChange; |
| 153 return reason; |
| 154 } |
| 155 |
| 156 if (style.backgroundLayers().thisOrNextLayersHaveLocalAttachment()) { |
| 157 if (previousBoxSizesMap().get(&m_box).layoutOverflowRect != m_box.layout
OverflowRect()) |
| 158 return PaintInvalidationLayoutOverflowBoxChange; |
| 159 } |
| 160 |
| 161 LayoutSize oldBorderBoxSize = computePreviousBorderBoxSize(m_context.oldBoun
ds.size()); |
| 162 LayoutSize newBorderBoxSize = m_box.size(); |
| 163 |
| 164 if (oldBorderBoxSize == newBorderBoxSize) |
| 165 return reason; |
| 166 |
| 167 // LayoutBox::incrementallyInvalidatePaint() depends on positionFromPaintInv
alidationBacking |
| 168 // which is not available when slimmingPaintOffsetCachingEnabled. |
| 169 if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() && (style.has
BoxDecorations() || style.hasBackground())) |
| 170 return PaintInvalidationBorderBoxChange; |
| 171 |
| 172 // See another hasNonCompositedScrollbars() callsite above. |
| 173 if (m_box.hasNonCompositedScrollbars()) |
| 174 return PaintInvalidationBorderBoxChange; |
| 175 |
| 176 if (style.hasVisualOverflowingEffect() || style.hasAppearance() || style.has
FilterInducingProperty() || style.resize() != RESIZE_NONE) |
| 177 return PaintInvalidationBorderBoxChange; |
| 178 |
| 179 if (style.hasBorderRadius()) { |
| 180 // If a border-radius exists and width/height is smaller than radius wid
th/height, |
| 181 // we need to fully invalidate to cover the changed radius. |
| 182 FloatRoundedRect oldRoundedRect = style.getRoundedBorderFor(LayoutRect(L
ayoutPoint(0, 0), oldBorderBoxSize)); |
| 183 FloatRoundedRect newRoundedRect = style.getRoundedBorderFor(LayoutRect(L
ayoutPoint(0, 0), newBorderBoxSize)); |
| 184 if (oldRoundedRect.getRadii() != newRoundedRect.getRadii()) |
| 185 return PaintInvalidationBorderBoxChange; |
| 186 } |
| 187 |
| 188 if (oldBorderBoxSize.width() != newBorderBoxSize.width() && m_box.mustInvali
dateBackgroundOrBorderPaintOnWidthChange()) |
| 189 return PaintInvalidationBorderBoxChange; |
| 190 if (oldBorderBoxSize.height() != newBorderBoxSize.height() && m_box.mustInva
lidateBackgroundOrBorderPaintOnHeightChange()) |
| 191 return PaintInvalidationBorderBoxChange; |
| 192 |
| 193 if (reason == PaintInvalidationNone && (style.hasBackground() || style.hasBo
xDecorations())) |
| 194 reason = PaintInvalidationIncremental; |
| 195 |
| 196 return reason; |
| 197 } |
| 198 |
| 199 PaintInvalidationReason BoxPaintInvalidator::invalidatePaintIfNeeded() |
| 200 { |
| 201 PaintInvalidationReason reason = ObjectPaintInvalidator(m_box, m_context).in
validatePaintIfNeededWithComputedReason(computePaintInvalidationReason()); |
| 202 |
| 203 // For incremental invalidation, ObjectPaintInvalidator::incrementallyInvali
datePaint() is |
| 204 // not enough for LayoutBox in some cases, e.g. having border, border-radius
, etc. |
| 205 // The following will do additional incremental invalidation for LayoutBox i
f needed. |
| 206 if (reason == PaintInvalidationIncremental) |
| 207 incrementallyInvalidatePaint(); |
| 208 |
| 209 if (PaintLayerScrollableArea* area = m_box.getScrollableArea()) |
| 210 area->invalidatePaintOfScrollControlsIfNeeded(m_context); |
| 211 |
| 212 // This is for the next invalidatePaintIfNeeded so must be at the end. |
| 213 savePreviousBoxSizesIfNeeded(); |
| 214 |
| 215 return reason; |
| 216 } |
| 217 |
| 218 bool BoxPaintInvalidator::needsToSavePreviousBoxSizes() |
| 219 { |
| 220 LayoutSize paintInvalidationSize = m_context.newBounds.size(); |
| 221 // Don't save old box sizes if the paint rect is empty because we'll |
| 222 // full invalidate once the paint rect becomes non-empty. |
| 223 if (paintInvalidationSize.isEmpty()) |
| 224 return false; |
| 225 |
| 226 const ComputedStyle& style = m_box.styleRef(); |
| 227 |
| 228 // If we use border-box sizing we need to track changes in the size of the c
ontent box. |
| 229 if (style.boxSizing() == BoxSizingBorderBox) |
| 230 return true; |
| 231 |
| 232 // We need the old box sizes only when the box has background, decorations,
or masks. |
| 233 // Main LayoutView paints base background, thus interested in box size. |
| 234 if (!m_box.isLayoutView() && !style.hasBackground() && !style.hasBoxDecorati
ons() && !style.hasMask()) |
| 235 return false; |
| 236 |
| 237 // No need to save old border box size if we can use size of the old paint |
| 238 // rect as the old border box size in the next invalidation. |
| 239 if (paintInvalidationSize != m_box.size()) |
| 240 return true; |
| 241 |
| 242 // Background and mask layers can depend on other boxes than border box. See
crbug.com/490533 |
| 243 if (style.backgroundLayers().thisOrNextLayersUseContentBox() || style.backgr
oundLayers().thisOrNextLayersHaveLocalAttachment() |
| 244 || style.maskLayers().thisOrNextLayersUseContentBox()) |
| 245 return true; |
| 246 |
| 247 return false; |
| 248 } |
| 249 |
| 250 void BoxPaintInvalidator::savePreviousBoxSizesIfNeeded() |
| 251 { |
| 252 if (!needsToSavePreviousBoxSizes()) { |
| 253 previousBoxSizesMap().remove(&m_box); |
| 254 return; |
| 255 } |
| 256 |
| 257 PreviousBoxSizes sizes = { |
| 258 m_box.size(), |
| 259 m_box.contentBoxRect(), |
| 260 m_box.layoutOverflowRect() |
| 261 }; |
| 262 previousBoxSizesMap().set(&m_box, sizes); |
| 263 } |
| 264 |
| 265 LayoutSize BoxPaintInvalidator::computePreviousBorderBoxSize(const LayoutSize& p
reviousBoundsSize) |
| 266 { |
| 267 // PreviousBorderBoxSize is only valid when there is background or box decor
ations. |
| 268 DCHECK(m_box.styleRef().hasBackground() || m_box.styleRef().hasBoxDecoration
s()); |
| 269 |
| 270 auto it = previousBoxSizesMap().find(&m_box); |
| 271 if (it != previousBoxSizesMap().end()) |
| 272 return it->value.borderBoxSize; |
| 273 |
| 274 // We didn't save the old border box size because it was the same as the siz
e of oldBounds. |
| 275 return previousBoundsSize; |
| 276 } |
| 277 |
| 278 } // namespace blink |
OLD | NEW |