Chromium Code Reviews| Index: third_party/WebKit/Source/core/paint/BoxPaintInvalidator.cpp |
| diff --git a/third_party/WebKit/Source/core/paint/BoxPaintInvalidator.cpp b/third_party/WebKit/Source/core/paint/BoxPaintInvalidator.cpp |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..3c7e2c218e4ec98cd690c5659ca77de42e25c075 |
| --- /dev/null |
| +++ b/third_party/WebKit/Source/core/paint/BoxPaintInvalidator.cpp |
| @@ -0,0 +1,278 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "core/paint/BoxPaintInvalidator.h" |
| + |
| +#include "core/frame/Settings.h" |
| +#include "core/layout/LayoutView.h" |
| +#include "core/paint/ObjectPaintInvalidator.h" |
| +#include "core/paint/PaintInvalidator.h" |
| +#include "core/paint/PaintLayer.h" |
| +#include "core/paint/PaintLayerScrollableArea.h" |
| +#include "platform/geometry/LayoutRect.h" |
| + |
| +namespace blink { |
| + |
| +struct PreviousBoxSizes { |
| + LayoutSize borderBoxSize; |
| + LayoutRect contentBoxRect; |
| + LayoutRect layoutOverflowRect; |
| +}; |
| + |
| +typedef HashMap<const LayoutBox*, PreviousBoxSizes> PreviousBoxSizesMap; |
| +static PreviousBoxSizesMap& previousBoxSizesMap() |
| +{ |
| + DEFINE_STATIC_LOCAL(PreviousBoxSizesMap, map, ()); |
| + return map; |
| +} |
| + |
| +void BoxPaintInvalidator::boxWillBeDestroyed(const LayoutBox& box) |
| +{ |
| + previousBoxSizesMap().remove(&box); |
| +} |
| + |
| +// This is called when ObjectPaintInvalidator already did incremental invalidation, |
| +// so this function just does what is additionally needed for the LayoutBox. |
| +void BoxPaintInvalidator::incrementallyInvalidatePaint() |
| +{ |
| + bool hasBoxDecorations = m_box.styleRef().hasBoxDecorations(); |
|
chrishtr
2016/08/09 23:47:31
This used to delegate to LayoutObject first.
Xianzhu
2016/08/10 16:25:01
It doesn't delegate because ObjectPaintInvalidator
|
| + if (!m_box.styleRef().hasBackground() && !hasBoxDecorations) |
| + return; |
| + |
| + const LayoutRect& oldBounds = m_context.oldBounds; |
| + const LayoutRect& newBounds = m_context.newBounds; |
| + |
| + LayoutSize oldBorderBoxSize = computePreviousBorderBoxSize(oldBounds.size()); |
| + LayoutSize newBorderBoxSize = m_box.size(); |
| + |
| + // If border m_box size didn't change, LayoutObject's incrementallyInvalidatePaint() is good. |
| + if (oldBorderBoxSize == newBorderBoxSize) |
| + return; |
| + |
| + // If size of the paint invalidation rect equals to size of border box, ObjectPaintInvalidator::incrementallyInvalidatePaint() |
| + // is good for boxes having background without box decorations. |
| + DCHECK(oldBounds.location() == newBounds.location()); // Otherwise we won't do incremental invalidation. |
| + if (!hasBoxDecorations |
| + && m_context.newLocation == newBounds.location() |
| + && oldBorderBoxSize == oldBounds.size() |
| + && newBorderBoxSize == newBounds.size()) |
| + return; |
| + |
| + // Invalidate the right delta part and the right border of the old or new m_box which has smaller width. |
| + if (LayoutUnit deltaWidth = (oldBorderBoxSize.width() - newBorderBoxSize.width()).abs()) { |
| + LayoutUnit smallerWidth = std::min(oldBorderBoxSize.width(), newBorderBoxSize.width()); |
| + LayoutUnit borderTopRightRadiusWidth = valueForLength(m_box.styleRef().borderTopRightRadius().width(), smallerWidth); |
| + LayoutUnit borderBottomRightRadiusWidth = valueForLength(m_box.styleRef().borderBottomRightRadius().width(), smallerWidth); |
| + LayoutUnit borderWidth = std::max(LayoutUnit(m_box.borderRight()), std::max(borderTopRightRadiusWidth, borderBottomRightRadiusWidth)); |
| + LayoutRect rightDeltaRect(m_context.newLocation.x() + smallerWidth - borderWidth, m_context.newLocation.y(), |
| + deltaWidth + borderWidth, std::max(oldBorderBoxSize.height(), newBorderBoxSize.height())); |
| + invalidatePaintRectClippedByOldAndNewBounds(rightDeltaRect); |
| + } |
| + |
| + // Invalidate the bottom delta part and the bottom border of the old or new m_box which has smaller height. |
| + if (LayoutUnit deltaHeight = (oldBorderBoxSize.height() - newBorderBoxSize.height()).abs()) { |
| + LayoutUnit smallerHeight = std::min(oldBorderBoxSize.height(), newBorderBoxSize.height()); |
| + LayoutUnit borderBottomLeftRadiusHeight = valueForLength(m_box.styleRef().borderBottomLeftRadius().height(), smallerHeight); |
| + LayoutUnit borderBottomRightRadiusHeight = valueForLength(m_box.styleRef().borderBottomRightRadius().height(), smallerHeight); |
| + LayoutUnit borderHeight = std::max(LayoutUnit(m_box.borderBottom()), std::max(borderBottomLeftRadiusHeight, borderBottomRightRadiusHeight)); |
| + LayoutRect bottomDeltaRect(m_context.newLocation.x(), m_context.newLocation.y() + smallerHeight - borderHeight, |
| + std::max(oldBorderBoxSize.width(), newBorderBoxSize.width()), deltaHeight + borderHeight); |
| + invalidatePaintRectClippedByOldAndNewBounds(bottomDeltaRect); |
| + } |
| +} |
| + |
| +void BoxPaintInvalidator::invalidatePaintRectClippedByOldAndNewBounds(const LayoutRect& rect) |
| +{ |
| + if (rect.isEmpty()) |
| + return; |
| + |
| + LayoutRect rectClippedByOldBounds = intersection(rect, m_context.oldBounds); |
| + LayoutRect rectClippedByNewBounds = intersection(rect, m_context.newBounds); |
| + // Invalidate only once if the clipped rects equal. |
| + if (rectClippedByOldBounds == rectClippedByNewBounds) { |
| + m_box.invalidatePaintUsingContainer(*m_context.paintInvalidationContainer, rectClippedByOldBounds, PaintInvalidationIncremental); |
| + return; |
| + } |
| + // Invalidate the bigger one if one contains another. Otherwise invalidate both. |
| + if (!rectClippedByNewBounds.contains(rectClippedByOldBounds)) |
| + m_box.invalidatePaintUsingContainer(*m_context.paintInvalidationContainer, rectClippedByOldBounds, PaintInvalidationIncremental); |
| + if (!rectClippedByOldBounds.contains(rectClippedByNewBounds)) |
| + m_box.invalidatePaintUsingContainer(*m_context.paintInvalidationContainer, rectClippedByNewBounds, PaintInvalidationIncremental); |
| +} |
| + |
| +PaintInvalidationReason BoxPaintInvalidator::computePaintInvalidationReason() |
| +{ |
| + PaintInvalidationReason reason = ObjectPaintInvalidator(m_box, m_context).computePaintInvalidationReason(); |
| + |
| + // If the current paint invalidation reason is PaintInvalidationDelayedFull, then this paint invalidation |
| + // can be delayed if the LayoutBox in question is not on-screen. The logic to decide whether this is appropriate |
| + // exists at the site of the original paint invalidation that chose PaintInvalidationDelayedFull. |
| + if (reason == PaintInvalidationDelayedFull) { |
| + if (!m_box.intersectsVisibleViewport()) |
| + return PaintInvalidationDelayedFull; |
| + |
| + // Do regular full paint invalidation if the object is on screen. |
| + return PaintInvalidationFull; |
|
chrishtr
2016/08/09 23:47:31
Previously, in LayoutBox.cpp:1578, we called setSh
Xianzhu
2016/08/10 16:25:01
I have verified the tests and they still work.
Th
|
| + } |
| + |
| + if (isFullPaintInvalidationReason(reason)) |
| + return reason; |
| + |
| + if (m_box.isLayoutView()) { |
| + const LayoutView& layoutView = toLayoutView(m_box); |
| + // In normal compositing mode, root background doesn't need to be invalidated for |
| + // box changes, because the background always covers the whole document rect |
| + // and clipping is done by compositor()->m_containerLayer. Also the scrollbars |
| + // are always composited. There are no other box decoration on the LayoutView thus |
| + // we can safely exit here. |
| + if (layoutView.usesCompositing() && (!layoutView.document().settings() || !layoutView.document().settings()->rootLayerScrolls())) |
| + return reason; |
| + } |
| + |
| + // If the transform is not identity or translation, incremental invalidation is not applicable |
| + // because the difference between oldBounds and newBounds doesn't cover all area needing invalidation. |
| + // FIXME: Should also consider ancestor transforms since paintInvalidationContainer. crbug.com/426111. |
| + if (reason == PaintInvalidationIncremental |
| + && m_context.paintInvalidationContainer != m_box |
| + && m_box.hasLayer() && m_box.layer()->transform() |
| + && !m_box.layer()->transform()->isIdentityOrTranslation()) |
| + return PaintInvalidationBoundsChange; |
| + |
| + const ComputedStyle& style = m_box.styleRef(); |
| + if (style.backgroundLayers().thisOrNextLayersUseContentBox() || style.maskLayers().thisOrNextLayersUseContentBox() || style.boxSizing() == BoxSizingBorderBox) { |
| + if (previousBoxSizesMap().get(&m_box).contentBoxRect != m_box.contentBoxRect()) |
| + return PaintInvalidationContentBoxChange; |
| + } |
| + |
| + if (!style.hasBackground() && !style.hasBoxDecorations()) { |
| + // We could let incremental invalidation cover non-composited scrollbars, but just |
| + // do a full invalidation because incremental invalidation will go away with slimming paint. |
| + if (reason == PaintInvalidationIncremental && m_box.hasNonCompositedScrollbars()) |
| + return PaintInvalidationBorderBoxChange; |
| + return reason; |
| + } |
| + |
| + if (style.backgroundLayers().thisOrNextLayersHaveLocalAttachment()) { |
| + if (previousBoxSizesMap().get(&m_box).layoutOverflowRect != m_box.layoutOverflowRect()) |
| + return PaintInvalidationLayoutOverflowBoxChange; |
| + } |
| + |
| + LayoutSize oldBorderBoxSize = computePreviousBorderBoxSize(m_context.oldBounds.size()); |
| + LayoutSize newBorderBoxSize = m_box.size(); |
| + |
| + if (oldBorderBoxSize == newBorderBoxSize) |
| + return reason; |
| + |
| + // LayoutBox::incrementallyInvalidatePaint() depends on positionFromPaintInvalidationBacking |
| + // which is not available when slimmingPaintOffsetCachingEnabled. |
| + if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() && (style.hasBoxDecorations() || style.hasBackground())) |
| + return PaintInvalidationBorderBoxChange; |
| + |
| + // See another hasNonCompositedScrollbars() callsite above. |
| + if (m_box.hasNonCompositedScrollbars()) |
| + return PaintInvalidationBorderBoxChange; |
| + |
| + if (style.hasVisualOverflowingEffect() || style.hasAppearance() || style.hasFilterInducingProperty() || style.resize() != RESIZE_NONE) |
| + return PaintInvalidationBorderBoxChange; |
| + |
| + if (style.hasBorderRadius()) { |
| + // If a border-radius exists and width/height is smaller than radius width/height, |
| + // we need to fully invalidate to cover the changed radius. |
| + FloatRoundedRect oldRoundedRect = style.getRoundedBorderFor(LayoutRect(LayoutPoint(0, 0), oldBorderBoxSize)); |
| + FloatRoundedRect newRoundedRect = style.getRoundedBorderFor(LayoutRect(LayoutPoint(0, 0), newBorderBoxSize)); |
| + if (oldRoundedRect.getRadii() != newRoundedRect.getRadii()) |
| + return PaintInvalidationBorderBoxChange; |
| + } |
| + |
| + if (oldBorderBoxSize.width() != newBorderBoxSize.width() && m_box.mustInvalidateBackgroundOrBorderPaintOnWidthChange()) |
| + return PaintInvalidationBorderBoxChange; |
| + if (oldBorderBoxSize.height() != newBorderBoxSize.height() && m_box.mustInvalidateBackgroundOrBorderPaintOnHeightChange()) |
| + return PaintInvalidationBorderBoxChange; |
| + |
| + if (reason == PaintInvalidationNone && (style.hasBackground() || style.hasBoxDecorations())) |
|
chrishtr
2016/08/09 23:47:31
This is a little different than the old code, beca
Xianzhu
2016/08/10 16:25:01
This is caused by my careless merging of changes t
|
| + reason = PaintInvalidationIncremental; |
| + |
| + return reason; |
| +} |
| + |
| +PaintInvalidationReason BoxPaintInvalidator::invalidatePaintIfNeeded() |
| +{ |
| + PaintInvalidationReason reason = ObjectPaintInvalidator(m_box, m_context).invalidatePaintIfNeededWithComputedReason(computePaintInvalidationReason()); |
| + |
|
chrishtr
2016/08/09 23:47:31
Delayed paint invalidation used to early-out here
Xianzhu
2016/08/10 16:25:01
The old code also delays other paint invalidations
|
| + // For incremental invalidation, ObjectPaintInvalidator::incrementallyInvalidatePaint() is |
| + // not enough for LayoutBox in some cases, e.g. having border, border-radius, etc. |
| + // The following will do additional incremental invalidation for LayoutBox if needed. |
| + if (reason == PaintInvalidationIncremental) |
| + incrementallyInvalidatePaint(); |
| + |
| + if (PaintLayerScrollableArea* area = m_box.getScrollableArea()) |
| + area->invalidatePaintOfScrollControlsIfNeeded(m_context); |
| + |
| + // This is for the next invalidatePaintIfNeeded so must be at the end. |
| + savePreviousBoxSizesIfNeeded(); |
| + |
| + return reason; |
| +} |
| + |
| +bool BoxPaintInvalidator::needsToSavePreviousBoxSizes() |
| +{ |
| + LayoutSize paintInvalidationSize = m_context.newBounds.size(); |
| + // Don't save old box sizes if the paint rect is empty because we'll |
| + // full invalidate once the paint rect becomes non-empty. |
| + if (paintInvalidationSize.isEmpty()) |
| + return false; |
| + |
| + const ComputedStyle& style = m_box.styleRef(); |
| + |
| + // If we use border-box sizing we need to track changes in the size of the content box. |
| + if (style.boxSizing() == BoxSizingBorderBox) |
| + return true; |
| + |
| + // We need the old box sizes only when the box has background, decorations, or masks. |
| + // Main LayoutView paints base background, thus interested in box size. |
| + if (!m_box.isLayoutView() && !style.hasBackground() && !style.hasBoxDecorations() && !style.hasMask()) |
| + return false; |
| + |
| + // No need to save old border box size if we can use size of the old paint |
| + // rect as the old border box size in the next invalidation. |
| + if (paintInvalidationSize != m_box.size()) |
| + return true; |
| + |
| + // Background and mask layers can depend on other boxes than border box. See crbug.com/490533 |
| + if (style.backgroundLayers().thisOrNextLayersUseContentBox() || style.backgroundLayers().thisOrNextLayersHaveLocalAttachment() |
| + || style.maskLayers().thisOrNextLayersUseContentBox()) |
| + return true; |
| + |
| + return false; |
| +} |
| + |
| +void BoxPaintInvalidator::savePreviousBoxSizesIfNeeded() |
| +{ |
| + if (!needsToSavePreviousBoxSizes()) { |
| + previousBoxSizesMap().remove(&m_box); |
| + return; |
| + } |
| + |
| + PreviousBoxSizes sizes = { |
| + m_box.size(), |
| + m_box.contentBoxRect(), |
| + m_box.layoutOverflowRect() |
| + }; |
| + previousBoxSizesMap().set(&m_box, sizes); |
| +} |
| + |
| +LayoutSize BoxPaintInvalidator::computePreviousBorderBoxSize(const LayoutSize& previousBoundsSize) |
| +{ |
| + // PreviousBorderBoxSize is only valid when there is background or box decorations. |
| + DCHECK(m_box.styleRef().hasBackground() || m_box.styleRef().hasBoxDecorations()); |
| + |
| + auto it = previousBoxSizesMap().find(&m_box); |
| + if (it != previousBoxSizesMap().end()) |
| + return it->value.borderBoxSize; |
| + |
| + // We didn't save the old border box size because it was the same as the size of oldBounds. |
| + return previousBoundsSize; |
| +} |
| + |
| +} // namespace blink |