blob: 72fc292dcaa5f587aa0c0deea1a7c370c32f2cbe [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/layout/compositing/CompositedLayerMapping.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 {
static LayoutRect ComputeRightDelta(const LayoutPoint& location,
const LayoutSize& old_size,
const LayoutSize& new_size,
const LayoutUnit& extra_width) {
LayoutUnit delta = new_size.Width() - old_size.Width();
if (delta > 0) {
return LayoutRect(location.X() + old_size.Width() - extra_width,
location.Y(), delta + extra_width, new_size.Height());
}
if (delta < 0) {
return LayoutRect(location.X() + new_size.Width() - extra_width,
location.Y(), -delta + extra_width, old_size.Height());
}
return LayoutRect();
}
static LayoutRect ComputeBottomDelta(const LayoutPoint& location,
const LayoutSize& old_size,
const LayoutSize& new_size,
const LayoutUnit& extra_height) {
LayoutUnit delta = new_size.Height() - old_size.Height();
if (delta > 0) {
return LayoutRect(location.X(),
location.Y() + old_size.Height() - extra_height,
new_size.Width(), delta + extra_height);
}
if (delta < 0) {
return LayoutRect(location.X(),
location.Y() + new_size.Height() - extra_height,
old_size.Width(), -delta + extra_height);
}
return LayoutRect();
}
bool BoxPaintInvalidator::IncrementallyInvalidatePaint(
PaintInvalidationReason reason,
const LayoutRect& old_rect,
const LayoutRect& new_rect) {
DCHECK(old_rect.Location() == new_rect.Location());
LayoutRect right_delta = ComputeRightDelta(
new_rect.Location(), old_rect.Size(), new_rect.Size(),
reason == PaintInvalidationReason::kIncremental ? box_.BorderRight()
: LayoutUnit());
LayoutRect bottom_delta = ComputeBottomDelta(
new_rect.Location(), old_rect.Size(), new_rect.Size(),
reason == PaintInvalidationReason::kIncremental ? box_.BorderBottom()
: LayoutUnit());
if (right_delta.IsEmpty() && bottom_delta.IsEmpty())
return false;
ObjectPaintInvalidatorWithContext object_paint_invalidator(box_, context_);
object_paint_invalidator.InvalidatePaintRectangleWithContext(right_delta,
reason);
object_paint_invalidator.InvalidatePaintRectangleWithContext(bottom_delta,
reason);
return true;
}
PaintInvalidationReason BoxPaintInvalidator::ComputePaintInvalidationReason() {
PaintInvalidationReason reason =
ObjectPaintInvalidatorWithContext(box_, context_)
.ComputePaintInvalidationReason();
if (reason != PaintInvalidationReason::kIncremental)
return reason;
if (box_.IsLayoutView()) {
const LayoutView& layout_view = ToLayoutView(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 (layout_view.UsesCompositing() &&
!RuntimeEnabledFeatures::rootLayerScrollingEnabled())
return reason;
}
const ComputedStyle& style = box_.StyleRef();
if ((style.BackgroundLayers().ThisOrNextLayersUseContentBox() ||
style.MaskLayers().ThisOrNextLayersUseContentBox()) &&
box_.PreviousContentBoxSize() != box_.ContentBoxRect().Size())
return PaintInvalidationReason::kGeometry;
LayoutSize old_border_box_size = box_.PreviousSize();
LayoutSize new_border_box_size = box_.Size();
bool border_box_changed = old_border_box_size != new_border_box_size;
if (!border_box_changed && context_.old_visual_rect == box_.VisualRect())
return PaintInvalidationReason::kNone;
// If either border box changed or bounds changed, and old or new border box
// doesn't equal old or new bounds, incremental invalidation is not
// applicable. This captures the following cases:
// - pixel snapping of paint invalidation bounds,
// - scale, rotate, skew etc. transforms,
// - visual overflows.
if (context_.old_visual_rect !=
LayoutRect(context_.old_location, old_border_box_size) ||
box_.VisualRect() !=
LayoutRect(context_.new_location, new_border_box_size)) {
return PaintInvalidationReason::kGeometry;
}
DCHECK(border_box_changed);
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
// Incremental invalidation is not applicable if there is border in the
// direction of border box size change because we don't know the border
// width when issuing incremental raster invalidations.
if (box_.BorderRight() || box_.BorderBottom())
return PaintInvalidationReason::kGeometry;
}
if (style.HasVisualOverflowingEffect() || style.HasAppearance() ||
style.HasFilterInducingProperty() || style.HasMask())
return PaintInvalidationReason::kGeometry;
if (style.HasBorderRadius())
return PaintInvalidationReason::kGeometry;
if (old_border_box_size.Width() != new_border_box_size.Width() &&
box_.MustInvalidateBackgroundOrBorderPaintOnWidthChange())
return PaintInvalidationReason::kGeometry;
if (old_border_box_size.Height() != new_border_box_size.Height() &&
box_.MustInvalidateBackgroundOrBorderPaintOnHeightChange())
return PaintInvalidationReason::kGeometry;
// Needs to repaint frame boundaries.
if (box_.IsFrameSet())
return PaintInvalidationReason::kGeometry;
// Needs to repaint column rules.
if (box_.IsLayoutMultiColumnSet())
return PaintInvalidationReason::kGeometry;
return PaintInvalidationReason::kIncremental;
}
bool BoxPaintInvalidator::BackgroundGeometryDependsOnLayoutOverflowRect() {
return !box_.IsDocumentElement() && !box_.BackgroundStolenForBeingBody() &&
box_.StyleRef()
.BackgroundLayers()
.ThisOrNextLayersHaveLocalAttachment();
}
// Background positioning in layout overflow rect doesn't mean it will
// paint onto the scrolling contents layer because some conditions prevent
// it from that. We may also treat non-local solid color backgrounds as local
// and paint onto the scrolling contents layer.
// See PaintLayer::canPaintBackgroundOntoScrollingContentsLayer().
bool BoxPaintInvalidator::BackgroundPaintsOntoScrollingContentsLayer() {
if (box_.IsDocumentElement() || box_.BackgroundStolenForBeingBody())
return false;
if (!box_.HasLayer())
return false;
if (auto* mapping = box_.Layer()->GetCompositedLayerMapping())
return mapping->BackgroundPaintsOntoScrollingContentsLayer();
return false;
}
bool BoxPaintInvalidator::ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(
const LayoutRect& old_layout_overflow,
const LayoutRect& new_layout_overflow) {
DCHECK(old_layout_overflow != new_layout_overflow);
if (new_layout_overflow.IsEmpty() || old_layout_overflow.IsEmpty())
return true;
if (new_layout_overflow.Location() != old_layout_overflow.Location())
return true;
if (new_layout_overflow.Width() != old_layout_overflow.Width() &&
box_.MustInvalidateFillLayersPaintOnHeightChange(
box_.StyleRef().BackgroundLayers()))
return true;
if (new_layout_overflow.Height() != old_layout_overflow.Height() &&
box_.MustInvalidateFillLayersPaintOnHeightChange(
box_.StyleRef().BackgroundLayers()))
return true;
return false;
}
void BoxPaintInvalidator::InvalidateScrollingContentsBackgroundIfNeeded() {
bool paints_onto_scrolling_contents_layer =
BackgroundPaintsOntoScrollingContentsLayer();
if (!paints_onto_scrolling_contents_layer &&
!BackgroundGeometryDependsOnLayoutOverflowRect())
return;
const LayoutRect& old_layout_overflow = box_.PreviousLayoutOverflowRect();
LayoutRect new_layout_overflow = box_.LayoutOverflowRect();
bool should_fully_invalidate_on_scrolling_contents_layer = false;
if (box_.BackgroundChangedSinceLastPaintInvalidation()) {
if (!paints_onto_scrolling_contents_layer) {
// The box should have been set needing full invalidation on style change.
DCHECK(box_.ShouldDoFullPaintInvalidation());
return;
}
should_fully_invalidate_on_scrolling_contents_layer = true;
} else {
// Check change of layout overflow for full or incremental invalidation.
if (new_layout_overflow == old_layout_overflow)
return;
bool should_fully_invalidate =
ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(
old_layout_overflow, new_layout_overflow);
if (!paints_onto_scrolling_contents_layer) {
if (should_fully_invalidate) {
box_.GetMutableForPainting()
.SetShouldDoFullPaintInvalidationWithoutGeometryChange(
PaintInvalidationReason::kBackground);
}
return;
}
should_fully_invalidate_on_scrolling_contents_layer =
should_fully_invalidate;
}
if (should_fully_invalidate_on_scrolling_contents_layer) {
ObjectPaintInvalidatorWithContext(box_, context_)
.FullyInvalidatePaint(
PaintInvalidationReason::kBackgroundOnScrollingContentsLayer,
old_layout_overflow, new_layout_overflow);
} else {
IncrementallyInvalidatePaint(
PaintInvalidationReason::kBackgroundOnScrollingContentsLayer,
old_layout_overflow, new_layout_overflow);
}
context_.painting_layer->SetNeedsRepaint();
// Currently we use CompositedLayerMapping as the DisplayItemClient to paint
// background on the scrolling contents layer.
ObjectPaintInvalidator(box_).InvalidateDisplayItemClient(
*box_.Layer()->GetCompositedLayerMapping()->ScrollingContentsLayer(),
PaintInvalidationReason::kBackgroundOnScrollingContentsLayer);
}
PaintInvalidationReason BoxPaintInvalidator::InvalidatePaint() {
InvalidateScrollingContentsBackgroundIfNeeded();
PaintInvalidationReason reason = ComputePaintInvalidationReason();
if (reason == PaintInvalidationReason::kIncremental) {
bool invalidated;
if (box_.IsLayoutView() &&
!RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
invalidated = IncrementallyInvalidatePaint(
reason, context_.old_visual_rect, box_.VisualRect());
} else {
invalidated = IncrementallyInvalidatePaint(
reason, LayoutRect(context_.old_location, box_.PreviousSize()),
LayoutRect(context_.new_location, box_.Size()));
}
if (invalidated) {
context_.painting_layer->SetNeedsRepaint();
box_.InvalidateDisplayItemClients(reason);
} else {
reason = PaintInvalidationReason::kNone;
}
// Though we have done incremental invalidation, we still need to call
// ObjectPaintInvalidator with PaintInvalidationNone to do any other
// required operations.
reason = std::max(reason, ObjectPaintInvalidatorWithContext(box_, context_)
.InvalidatePaintWithComputedReason(
PaintInvalidationReason::kNone));
} else {
reason = ObjectPaintInvalidatorWithContext(box_, context_)
.InvalidatePaintWithComputedReason(reason);
}
if (PaintLayerScrollableArea* area = box_.GetScrollableArea())
area->InvalidatePaintOfScrollControlsIfNeeded(context_);
// This is for the next invalidatePaintIfNeeded so must be at the end.
SavePreviousBoxGeometriesIfNeeded();
return reason;
}
bool BoxPaintInvalidator::
NeedsToSavePreviousContentBoxSizeOrLayoutOverflowRect() {
// Don't save old box geometries if the paint rect is empty because we'll
// fully invalidate once the paint rect becomes non-empty.
if (box_.VisualRect().IsEmpty())
return false;
if (box_.PaintedOutputOfObjectHasNoEffectRegardlessOfSize())
return false;
const ComputedStyle& style = box_.StyleRef();
// Background and mask layers can depend on other boxes than border box. See
// crbug.com/490533
if ((style.BackgroundLayers().ThisOrNextLayersUseContentBox() ||
style.MaskLayers().ThisOrNextLayersUseContentBox()) &&
box_.ContentBoxRect().Size() != box_.Size())
return true;
if ((BackgroundGeometryDependsOnLayoutOverflowRect() ||
BackgroundPaintsOntoScrollingContentsLayer()) &&
box_.LayoutOverflowRect() != box_.BorderBoxRect())
return true;
return false;
}
void BoxPaintInvalidator::SavePreviousBoxGeometriesIfNeeded() {
box_.GetMutableForPainting().SavePreviousSize();
if (NeedsToSavePreviousContentBoxSizeOrLayoutOverflowRect()) {
box_.GetMutableForPainting()
.SavePreviousContentBoxSizeAndLayoutOverflowRect();
} else {
box_.GetMutableForPainting()
.ClearPreviousContentBoxSizeAndLayoutOverflowRect();
}
}
} // namespace blink