blob: 9f43df3f872d2a21b50f6e45a0d0c7ecc13542fb [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 "third_party/blink/renderer/core/paint/box_paint_invalidator.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/object_paint_invalidator.h"
#include "third_party/blink/renderer/core/paint/paint_invalidator.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/platform/geometry/layout_rect.h"
namespace blink {
bool BoxPaintInvalidator::HasEffectiveBackground() {
// The view can paint background not from the style.
if (IsA<LayoutView>(box_))
return true;
return box_.StyleRef().HasBackground() && !box_.BackgroundTransfersToView();
}
// |width| is of the positioning area.
static bool ShouldFullyInvalidateFillLayersOnWidthChange(
const FillLayer& layer) {
// Nobody will use multiple layers without wanting fancy positioning.
if (layer.Next())
return true;
// The layer properties checked below apply only when there is a valid image.
const StyleImage* image = layer.GetImage();
if (!image || !image->CanRender())
return false;
if (layer.RepeatX() != EFillRepeat::kRepeatFill &&
layer.RepeatX() != EFillRepeat::kNoRepeatFill)
return true;
// TODO(alancutter): Make this work correctly for calc lengths.
if (layer.PositionX().IsPercentOrCalc() && !layer.PositionX().IsZero())
return true;
if (layer.BackgroundXOrigin() != BackgroundEdgeOrigin::kLeft)
return true;
EFillSizeType size_type = layer.SizeType();
if (size_type == EFillSizeType::kContain ||
size_type == EFillSizeType::kCover)
return true;
DCHECK_EQ(size_type, EFillSizeType::kSizeLength);
// TODO(alancutter): Make this work correctly for calc lengths.
const Length& width = layer.SizeLength().Width();
if (width.IsPercentOrCalc() && !width.IsZero())
return true;
if (width.IsAuto() && !image->HasIntrinsicSize())
return true;
return false;
}
// |height| is of the positioning area.
static bool ShouldFullyInvalidateFillLayersOnHeightChange(
const FillLayer& layer) {
// Nobody will use multiple layers without wanting fancy positioning.
if (layer.Next())
return true;
// The layer properties checked below apply only when there is a valid image.
const StyleImage* image = layer.GetImage();
if (!image || !image->CanRender())
return false;
if (layer.RepeatY() != EFillRepeat::kRepeatFill &&
layer.RepeatY() != EFillRepeat::kNoRepeatFill)
return true;
// TODO(alancutter): Make this work correctly for calc lengths.
if (layer.PositionY().IsPercentOrCalc() && !layer.PositionY().IsZero())
return true;
if (layer.BackgroundYOrigin() != BackgroundEdgeOrigin::kTop)
return true;
EFillSizeType size_type = layer.SizeType();
if (size_type == EFillSizeType::kContain ||
size_type == EFillSizeType::kCover)
return true;
DCHECK_EQ(size_type, EFillSizeType::kSizeLength);
// TODO(alancutter): Make this work correctly for calc lengths.
const Length& height = layer.SizeLength().Height();
if (height.IsPercentOrCalc() && !height.IsZero())
return true;
if (height.IsAuto() && !image->HasIntrinsicSize())
return true;
return false;
}
// old_size and new_size are the old and new sizes of the positioning area.
bool ShouldFullyInvalidateFillLayersOnSizeChange(const FillLayer& layer,
const PhysicalSize& old_size,
const PhysicalSize& new_size) {
return (old_size.width != new_size.width &&
ShouldFullyInvalidateFillLayersOnWidthChange(layer)) ||
(old_size.height != new_size.height &&
ShouldFullyInvalidateFillLayersOnHeightChange(layer));
}
PaintInvalidationReason BoxPaintInvalidator::ComputePaintInvalidationReason() {
PaintInvalidationReason reason =
ObjectPaintInvalidatorWithContext(box_, context_)
.ComputePaintInvalidationReason();
if (reason != PaintInvalidationReason::kIncremental)
return reason;
const ComputedStyle& style = box_.StyleRef();
if (style.MaskLayers().AnyLayerUsesContentBox() &&
box_.PreviousPhysicalContentBoxRect() != box_.PhysicalContentBoxRect())
return PaintInvalidationReason::kGeometry;
if (box_.PreviousSize() == box_.Size() &&
box_.PreviousPhysicalSelfVisualOverflowRect() ==
box_.PhysicalSelfVisualOverflowRect()) {
return box_.HasPartialInvalidationRect()
? PaintInvalidationReason::kRectangle
: PaintInvalidationReason::kNone;
}
// Incremental invalidation is not applicable if there is visual overflow.
if (box_.PreviousPhysicalSelfVisualOverflowRect().size !=
PhysicalSizeToBeNoop(box_.PreviousSize()) ||
box_.PhysicalSelfVisualOverflowRect().size !=
PhysicalSizeToBeNoop(box_.Size()))
return PaintInvalidationReason::kGeometry;
// Incremental invalidation is not applicable if paint offset or size has
// fraction.
if (context_.old_paint_offset.HasFraction() ||
context_.fragment_data->PaintOffset().HasFraction() ||
PhysicalSizeToBeNoop(box_.PreviousSize()).HasFraction() ||
PhysicalSizeToBeNoop(box_.Size()).HasFraction())
return PaintInvalidationReason::kGeometry;
// 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.HasEffectiveAppearance() ||
style.HasFilterInducingProperty() || style.HasMask() ||
style.HasClipPath())
return PaintInvalidationReason::kGeometry;
if (style.HasBorderRadius() || style.CanRenderBorderImage())
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;
// Background invalidation has been done during InvalidateBackground(), so
// we don't need to check background in this function.
return PaintInvalidationReason::kIncremental;
}
bool BoxPaintInvalidator::BackgroundGeometryDependsOnLayoutOverflowRect() {
return HasEffectiveBackground() &&
box_.StyleRef().BackgroundLayers().AnyLayerHasLocalAttachmentImage();
}
bool BoxPaintInvalidator::BackgroundPaintsOntoScrollingContentsLayer() {
if (!HasEffectiveBackground())
return false;
return box_.GetBackgroundPaintLocation() &
kBackgroundPaintInScrollingContents;
}
bool BoxPaintInvalidator::BackgroundPaintsOntoMainGraphicsLayer() {
if (!HasEffectiveBackground())
return false;
return box_.GetBackgroundPaintLocation() & kBackgroundPaintInGraphicsLayer;
}
bool BoxPaintInvalidator::ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(
const PhysicalRect& old_layout_overflow,
const PhysicalRect& new_layout_overflow) {
if (new_layout_overflow == old_layout_overflow)
return false;
if (!BackgroundGeometryDependsOnLayoutOverflowRect())
return false;
// The background should invalidate on most location changes.
if (new_layout_overflow.offset != old_layout_overflow.offset)
return true;
return ShouldFullyInvalidateFillLayersOnSizeChange(
box_.StyleRef().BackgroundLayers(), old_layout_overflow.size,
new_layout_overflow.size);
}
BoxPaintInvalidator::BackgroundInvalidationType
BoxPaintInvalidator::ComputeViewBackgroundInvalidation() {
DCHECK(IsA<LayoutView>(box_));
const auto& layout_view = To<LayoutView>(box_);
auto new_background_rect = layout_view.BackgroundRect();
auto old_background_rect = layout_view.PreviousBackgroundRect();
layout_view.SetPreviousBackgroundRect(new_background_rect);
// BackgroundRect is the positioning area of all fixed attachment backgrounds,
// including the LayoutView's and descendants'.
bool background_location_changed =
new_background_rect.offset != old_background_rect.offset;
bool background_size_changed =
new_background_rect.size != old_background_rect.size;
if (background_location_changed || background_size_changed) {
for (auto* object :
layout_view.GetFrameView()->BackgroundAttachmentFixedObjects())
object->SetBackgroundNeedsFullPaintInvalidation();
}
if (background_location_changed ||
layout_view.BackgroundNeedsFullPaintInvalidation() ||
(context_.subtree_flags &
PaintInvalidatorContext::kSubtreeFullInvalidation)) {
return BackgroundInvalidationType::kFull;
}
if (Element* root_element = box_.GetDocument().documentElement()) {
if (const auto* root_object = root_element->GetLayoutObject()) {
if (root_object->IsBox()) {
const auto* root_box = To<LayoutBox>(root_object);
// LayoutView's non-fixed-attachment background is positioned in the
// root element and needs to invalidate if the size changes.
// See: https://drafts.csswg.org/css-backgrounds-3/#root-background.
const auto& background_layers = box_.StyleRef().BackgroundLayers();
if (ShouldFullyInvalidateFillLayersOnSizeChange(
background_layers,
PhysicalSizeToBeNoop(root_box->PreviousSize()),
PhysicalSizeToBeNoop(root_box->Size()))) {
return BackgroundInvalidationType::kFull;
}
if (BackgroundGeometryDependsOnLayoutOverflowRect() &&
ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(
root_box->PreviousPhysicalLayoutOverflowRect(),
root_box->PhysicalLayoutOverflowRect())) {
return BackgroundInvalidationType::kFull;
}
// It also uses the root element's content box in case the background
// comes from the root element and positioned in content box.
if (background_layers.AnyLayerUsesContentBox() &&
root_box->PreviousPhysicalContentBoxRect() !=
root_box->PhysicalContentBoxRect()) {
return BackgroundInvalidationType::kFull;
}
}
// The view background paints with a transform but nevertheless extended
// onto an infinite canvas. In cases where it has a transform we can't
// apply incremental invalidation, because the visual rect is no longer
// axis-aligned to the LayoutView.
if (root_object->StyleRef().HasTransform())
return BackgroundInvalidationType::kFull;
}
}
return background_size_changed ? BackgroundInvalidationType::kIncremental
: BackgroundInvalidationType::kNone;
}
BoxPaintInvalidator::BackgroundInvalidationType
BoxPaintInvalidator::ComputeBackgroundInvalidation(
bool& should_invalidate_all_layers) {
// If background changed, we may paint the background on different graphics
// layer, so we need to fully invalidate the background on all layers.
if (box_.BackgroundNeedsFullPaintInvalidation() ||
(context_.subtree_flags &
PaintInvalidatorContext::kSubtreeFullInvalidation)) {
should_invalidate_all_layers = true;
return BackgroundInvalidationType::kFull;
}
if (!HasEffectiveBackground())
return BackgroundInvalidationType::kNone;
const auto& background_layers = box_.StyleRef().BackgroundLayers();
if (background_layers.AnyLayerHasDefaultAttachmentImage() &&
ShouldFullyInvalidateFillLayersOnSizeChange(
background_layers, PhysicalSizeToBeNoop(box_.PreviousSize()),
PhysicalSizeToBeNoop(box_.Size())))
return BackgroundInvalidationType::kFull;
if (background_layers.AnyLayerUsesContentBox() &&
box_.PreviousPhysicalContentBoxRect() != box_.PhysicalContentBoxRect())
return BackgroundInvalidationType::kFull;
bool layout_overflow_change_causes_invalidation =
(BackgroundGeometryDependsOnLayoutOverflowRect() ||
BackgroundPaintsOntoScrollingContentsLayer());
if (!layout_overflow_change_causes_invalidation)
return BackgroundInvalidationType::kNone;
const auto& old_layout_overflow = box_.PreviousPhysicalLayoutOverflowRect();
auto new_layout_overflow = box_.PhysicalLayoutOverflowRect();
if (ShouldFullyInvalidateBackgroundOnLayoutOverflowChange(
old_layout_overflow, new_layout_overflow))
return BackgroundInvalidationType::kFull;
if (new_layout_overflow != old_layout_overflow) {
// Do incremental invalidation if possible.
if (old_layout_overflow.offset == new_layout_overflow.offset)
return BackgroundInvalidationType::kIncremental;
return BackgroundInvalidationType::kFull;
}
return BackgroundInvalidationType::kNone;
}
void BoxPaintInvalidator::InvalidateBackground() {
bool should_invalidate_all_layers = false;
auto background_invalidation_type =
ComputeBackgroundInvalidation(should_invalidate_all_layers);
if (IsA<LayoutView>(box_)) {
background_invalidation_type = std::max(
background_invalidation_type, ComputeViewBackgroundInvalidation());
}
if (box_.GetScrollableArea()) {
if (should_invalidate_all_layers ||
(BackgroundPaintsOntoScrollingContentsLayer() &&
background_invalidation_type != BackgroundInvalidationType::kNone)) {
auto reason =
background_invalidation_type == BackgroundInvalidationType::kFull
? PaintInvalidationReason::kBackground
: PaintInvalidationReason::kIncremental;
context_.painting_layer->SetNeedsRepaint();
ObjectPaintInvalidator(box_).InvalidateDisplayItemClient(
box_.GetScrollableArea()->GetScrollingBackgroundDisplayItemClient(),
reason);
}
}
if (should_invalidate_all_layers ||
(BackgroundPaintsOntoMainGraphicsLayer() &&
background_invalidation_type == BackgroundInvalidationType::kFull)) {
box_.GetMutableForPainting()
.SetShouldDoFullPaintInvalidationWithoutGeometryChange(
PaintInvalidationReason::kBackground);
}
}
void BoxPaintInvalidator::InvalidatePaint() {
InvalidateBackground();
ObjectPaintInvalidatorWithContext(box_, context_)
.InvalidatePaintWithComputedReason(ComputePaintInvalidationReason());
if (PaintLayerScrollableArea* area = box_.GetScrollableArea())
area->InvalidatePaintOfScrollControlsIfNeeded(context_);
// This is for the next invalidatePaintIfNeeded so must be at the end.
SavePreviousBoxGeometriesIfNeeded();
}
bool BoxPaintInvalidator::NeedsToSavePreviousContentBoxRect() {
// Replaced elements are clipped to the content box thus we need to check
// for its size.
if (box_.IsLayoutReplaced())
return true;
const ComputedStyle& style = box_.StyleRef();
// Background and mask layers can depend on other boxes than border box. See
// crbug.com/490533
if ((style.BackgroundLayers().AnyLayerUsesContentBox() ||
style.MaskLayers().AnyLayerUsesContentBox()) &&
box_.ContentSize() != box_.Size())
return true;
return false;
}
bool BoxPaintInvalidator::NeedsToSavePreviousOverflowData() {
if (box_.HasVisualOverflow() || box_.HasLayoutOverflow())
return true;
// If we don't have layout overflow, the layout overflow rect is the padding
// box rect, and we need to save it if the background depends on it.
// We also need to save the rect for the document element because the
// LayoutView may depend on the document element's layout overflow rect
// (see: ComputeViewBackgroundInvalidation).
if ((BackgroundGeometryDependsOnLayoutOverflowRect() ||
BackgroundPaintsOntoScrollingContentsLayer() ||
box_.IsDocumentElement()) &&
box_.LayoutOverflowRect() != box_.BorderBoxRect())
return true;
return false;
}
void BoxPaintInvalidator::SavePreviousBoxGeometriesIfNeeded() {
auto mutable_box = box_.GetMutableForPainting();
mutable_box.SavePreviousSize();
if (NeedsToSavePreviousOverflowData())
mutable_box.SavePreviousOverflowData();
else
mutable_box.ClearPreviousOverflowData();
if (NeedsToSavePreviousContentBoxRect())
mutable_box.SavePreviousContentBoxRect();
else
mutable_box.ClearPreviousContentBoxRect();
}
} // namespace blink