blob: 5493a27944cb97c14560ce0faae5cec51ec3e539 [file] [log] [blame]
// 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);
}
bool BoxPaintInvalidator::incrementallyInvalidatePaint() {
bool result = ObjectPaintInvalidatorWithContext(m_box, m_context)
.incrementallyInvalidatePaint();
bool hasBoxDecorations = m_box.styleRef().hasBoxDecorations();
if (!m_box.styleRef().hasBackground() && !hasBoxDecorations)
return result;
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,
// ObjectPaintInvalidatorWithContext::incrementallyInvalidatePaint() is good.
if (oldBorderBoxSize == newBorderBoxSize)
return result;
// If size of the paint invalidation rect equals to size of border box,
// ObjectPaintInvalidatorWithContext::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 result;
// 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);
}
return true;
}
void BoxPaintInvalidator::invalidatePaintRectClippedByOldAndNewBounds(
const LayoutRect& rect) {
if (rect.isEmpty())
return;
ObjectPaintInvalidator objectPaintInvalidator(m_box);
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) {
objectPaintInvalidator.invalidatePaintUsingContainer(
*m_context.paintInvalidationContainer, rectClippedByOldBounds,
PaintInvalidationIncremental);
return;
}
// Invalidate the bigger one if one contains another. Otherwise invalidate
// both.
if (!rectClippedByNewBounds.contains(rectClippedByOldBounds))
objectPaintInvalidator.invalidatePaintUsingContainer(
*m_context.paintInvalidationContainer, rectClippedByOldBounds,
PaintInvalidationIncremental);
if (!rectClippedByOldBounds.contains(rectClippedByNewBounds))
objectPaintInvalidator.invalidatePaintUsingContainer(
*m_context.paintInvalidationContainer, rectClippedByNewBounds,
PaintInvalidationIncremental);
}
PaintInvalidationReason BoxPaintInvalidator::computePaintInvalidationReason() {
PaintInvalidationReason reason =
ObjectPaintInvalidatorWithContext(m_box, m_context)
.computePaintInvalidationReason();
if (isImmediateFullPaintInvalidationReason(reason) ||
reason == PaintInvalidationNone)
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() &&
!RuntimeEnabledFeatures::rootLayerScrollingEnabled())
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.oldBounds != m_context.newBounds &&
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_context.oldBounds != m_context.newBounds &&
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;
// 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;
return reason;
}
PaintInvalidationReason BoxPaintInvalidator::invalidatePaintIfNeeded() {
PaintInvalidationReason reason = computePaintInvalidationReason();
if (reason == PaintInvalidationIncremental) {
if (incrementallyInvalidatePaint()) {
m_context.paintingLayer->setNeedsRepaint();
m_box.invalidateDisplayItemClients(reason);
} else {
reason = PaintInvalidationNone;
}
// Though we have done our own version of incremental invalidation, we still
// need to call ObjectPaintInvalidator with PaintInvalidationNone to do any
// other required operations.
reason = std::max(
reason,
ObjectPaintInvalidatorWithContext(m_box, m_context)
.invalidatePaintIfNeededWithComputedReason(PaintInvalidationNone));
} else {
reason = ObjectPaintInvalidatorWithContext(m_box, m_context)
.invalidatePaintIfNeededWithComputedReason(reason);
}
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