| 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..a1625c3af3e9e00c5a9f149d6868482d0e95d497
|
| --- /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();
|
| + 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 (reason != PaintInvalidationDelayedFull && isFullPaintInvalidationReason(reason))
|
| + return reason;
|
| +
|
| + if (m_box.mayNeedPaintInvalidationAnimatedBackgroundImage() && !m_box.backgroundIsKnownToBeObscured())
|
| + reason = PaintInvalidationDelayedFull;
|
| +
|
| + // If the current paint invalidation reason is PaintInvalidationDelayedFull, then this paint invalidation can 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) {
|
| + // Do regular full paint invalidation if the object is onscreen.
|
| + return m_box.intersectsVisibleViewport() ? PaintInvalidationFull : PaintInvalidationDelayedFull;
|
| + }
|
| +
|
| + 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()))
|
| + reason = PaintInvalidationIncremental;
|
| +
|
| + return reason;
|
| +}
|
| +
|
| +PaintInvalidationReason BoxPaintInvalidator::invalidatePaintIfNeeded()
|
| +{
|
| + PaintInvalidationReason reason = ObjectPaintInvalidator(m_box, m_context).invalidatePaintIfNeededWithComputedReason(computePaintInvalidationReason());
|
| +
|
| + // 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
|
|
|