blob: 3d229397e8972f5e4962ad6cdafa982e2d3d3284 [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/paint_invalidator.h"
#include "base/optional.h"
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/core/layout/layout_shift_tracker.h"
#include "third_party/blink/renderer/core/layout/layout_table.h"
#include "third_party/blink/renderer/core/layout/layout_table_section.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h"
#include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h"
#include "third_party/blink/renderer/core/page/link_highlight.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/clip_path_clipper.h"
#include "third_party/blink/renderer/core/paint/object_paint_properties.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/core/paint/pre_paint_tree_walk.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
namespace blink {
const PaintInvalidatorContext*
PaintInvalidatorContext::ParentContextAccessor::ParentContext() const {
return tree_walk_ ? &tree_walk_->ContextAt(parent_context_index_)
.paint_invalidator_context
: nullptr;
}
void PaintInvalidator::UpdatePaintingLayer(const LayoutObject& object,
PaintInvalidatorContext& context,
bool is_ng_painting) {
if (object.HasLayer() &&
To<LayoutBoxModelObject>(object).HasSelfPaintingLayer()) {
context.painting_layer = To<LayoutBoxModelObject>(object).Layer();
} else if (!is_ng_painting &&
(object.IsColumnSpanAll() ||
object.IsFloatingWithNonContainingBlockParent())) {
// See |LayoutObject::PaintingLayer| for the special-cases of floating under
// inline and multicolumn.
// Post LayoutNG the |LayoutObject::IsFloatingWithNonContainingBlockParent|
// check can be removed as floats will be painted by the correct layer.
context.painting_layer = object.PaintingLayer();
}
auto* layout_block_flow = DynamicTo<LayoutBlockFlow>(object);
if (layout_block_flow && !object.IsLayoutNGBlockFlow() &&
layout_block_flow->ContainsFloats())
context.painting_layer->SetNeedsPaintPhaseFloat();
if (object.IsFloating() &&
(object.IsInLayoutNGInlineFormattingContext() ||
IsLayoutNGContainingBlock(object.ContainingBlock())))
context.painting_layer->SetNeedsPaintPhaseFloat();
if (!context.painting_layer->NeedsPaintPhaseDescendantOutlines() &&
((object != context.painting_layer->GetLayoutObject() &&
object.StyleRef().HasOutline()) ||
// If this is a block-in-inline, it may need to paint outline.
// See |StyleForContinuationOutline|.
(layout_block_flow && layout_block_flow->StyleForContinuationOutline())))
context.painting_layer->SetNeedsPaintPhaseDescendantOutlines();
}
void PaintInvalidator::UpdateDirectlyCompositedContainer(
const LayoutObject& object,
PaintInvalidatorContext& context,
bool is_ng_painting) {
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
if (object.CanBeCompositedForDirectReasons()) {
context.directly_composited_container = To<LayoutBoxModelObject>(&object);
if (object.IsStackingContext() || object.IsSVGRoot()) {
context.directly_composited_container_for_stacked_contents =
To<LayoutBoxModelObject>(&object);
}
} else if (IsA<LayoutView>(object)) {
// paint_invalidation_container_for_stacked_contents is only for stacked
// descendants in its own frame, because it doesn't establish stacking
// context for stacked contents in sub-frames.
// Contents stacked in the root stacking context in this frame should use
// this frame's PaintInvalidationContainer.
context.directly_composited_container_for_stacked_contents =
context.directly_composited_container =
&object.DirectlyCompositableContainer();
} else if (!is_ng_painting &&
(object.IsColumnSpanAll() ||
object.IsFloatingWithNonContainingBlockParent())) {
// In these cases, the object may belong to an ancestor of the current
// paint invalidation container, in paint order.
// Post LayoutNG the |LayoutObject::IsFloatingWithNonContainingBlockParent|
// check can be removed as floats will be painted by the correct layer.
context.directly_composited_container =
&object.DirectlyCompositableContainer();
} else if (object.IsStacked() &&
// This is to exclude some objects (e.g. LayoutText) inheriting
// stacked style from parent but aren't actually stacked.
object.HasLayer() &&
context.directly_composited_container !=
context.directly_composited_container_for_stacked_contents) {
if (To<LayoutBoxModelObject>(object)
.Layer()
->IsReplacedNormalFlowStacking()) {
DCHECK(object.IsStackingContext());
// A ReplacedNormalFlowStacking object doesn't stack into parent stacking
// context, while the stacked descendants are stacked into it and inherit
// its direct_composited_container.
context.directly_composited_container_for_stacked_contents =
context.directly_composited_container;
} else {
// The current object is stacked, so we should use
// directly_composited_container_for_stacked_contents as its paint
// invalidation container on which the current object is painted.
context.directly_composited_container =
context.directly_composited_container_for_stacked_contents;
if (context.subtree_flags &
PaintInvalidatorContext::kSubtreeFullInvalidationForStackedContents) {
context.subtree_flags |=
PaintInvalidatorContext::kSubtreeFullInvalidation;
}
}
}
if (object == context.directly_composited_container) {
// When we hit a new directly composited container, we don't need to
// continue forcing a check for paint invalidation, since we're
// descending into a different invalidation container. (For instance if
// our parents were moved, the entire container will just move.)
if (object != context.directly_composited_container_for_stacked_contents) {
// However, we need to keep kSubtreeFullInvalidationForStackedContents
// if the current object isn't the direct composited container of stacked
// contents.
context.subtree_flags &=
PaintInvalidatorContext::kSubtreeFullInvalidationForStackedContents;
} else {
context.subtree_flags = 0;
}
}
DCHECK_EQ(context.directly_composited_container,
object.DirectlyCompositableContainer())
<< object;
DCHECK_EQ(context.painting_layer, object.PaintingLayer()) << object;
}
void PaintInvalidator::UpdateFromTreeBuilderContext(
const PaintPropertyTreeBuilderFragmentContext& tree_builder_context,
PaintInvalidatorContext& context) {
DCHECK_EQ(tree_builder_context.current.paint_offset,
context.fragment_data->PaintOffset());
// For performance, we ignore subpixel movement of composited layers for paint
// invalidation. This will result in imperfect pixel-snapped painting.
// See crbug.com/833083 for details.
if (!RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
tree_builder_context.current
.directly_composited_container_paint_offset_subpixel_delta ==
tree_builder_context.current.paint_offset -
tree_builder_context.old_paint_offset) {
context.old_paint_offset = tree_builder_context.current.paint_offset;
} else {
context.old_paint_offset = tree_builder_context.old_paint_offset;
}
context.transform_ = tree_builder_context.current.transform;
}
void PaintInvalidator::UpdateLayoutShiftTracking(
const LayoutObject& object,
const PaintPropertyTreeBuilderFragmentContext& tree_builder_context,
PaintInvalidatorContext& context) {
if (!object.ShouldCheckGeometryForPaintInvalidation())
return;
if (tree_builder_context.this_or_ancestor_opacity_is_zero) {
object.GetMutableForPainting().SetShouldSkipNextLayoutShiftTracking(true);
return;
}
auto& layout_shift_tracker = object.GetFrameView()->GetLayoutShiftTracker();
if (!layout_shift_tracker.NeedsToTrack(object)) {
object.GetMutableForPainting().SetShouldSkipNextLayoutShiftTracking(true);
return;
}
PropertyTreeStateOrAlias property_tree_state(
*tree_builder_context.current.transform,
*tree_builder_context.current.clip, *tree_builder_context.current_effect);
// Adjust old_paint_offset so that LayoutShiftTracker will see the change of
// offset caused by change of paint offset translations and scroll offset
// below the layout shift root. For more details, see
// renderer/core/layout/layout-shift-tracker-old-paint-offset.md.
PhysicalOffset adjusted_old_paint_offset =
context.old_paint_offset -
tree_builder_context.current
.additional_offset_to_layout_shift_root_delta -
PhysicalOffset::FromFloatSizeRound(
tree_builder_context.translation_2d_to_layout_shift_root_delta +
tree_builder_context.current
.scroll_offset_to_layout_shift_root_delta);
PhysicalOffset new_paint_offset = tree_builder_context.current.paint_offset;
if (object.IsText()) {
const auto& text = To<LayoutText>(object);
LogicalOffset new_starting_point;
LayoutUnit logical_height;
text.LogicalStartingPointAndHeight(new_starting_point, logical_height);
LogicalOffset old_starting_point = text.PreviousLogicalStartingPoint();
if (new_starting_point == old_starting_point)
return;
text.SetPreviousLogicalStartingPoint(new_starting_point);
if (old_starting_point == LayoutText::UninitializedLogicalStartingPoint())
return;
// If the layout shift root has changed, LayoutShiftTracker can't use the
// current paint property tree to map the old rect.
if (tree_builder_context.current.layout_shift_root_changed)
return;
layout_shift_tracker.NotifyTextPrePaint(
text, property_tree_state, old_starting_point, new_starting_point,
adjusted_old_paint_offset,
tree_builder_context.translation_2d_to_layout_shift_root_delta,
tree_builder_context.current.scroll_offset_to_layout_shift_root_delta,
new_paint_offset, logical_height);
return;
}
DCHECK(object.IsBox());
const auto& box = To<LayoutBox>(object);
PhysicalRect new_rect = box.PhysicalVisualOverflowRect();
new_rect.Move(new_paint_offset);
PhysicalRect old_rect = box.PreviousPhysicalVisualOverflowRect();
old_rect.Move(adjusted_old_paint_offset);
bool should_create_containing_block_scope =
// TODO(crbug.com/1178618): Support multiple-fragments when switching to
// LayoutNGFragmentTraversal.
context.fragment_data == &box.FirstFragment() &&
box.IsLayoutBlockFlow() && box.ChildrenInline() && box.SlowFirstChild();
if (should_create_containing_block_scope) {
// For layout shift tracking of contained LayoutTexts.
context.containing_block_scope_ =
std::make_unique<LayoutShiftTracker::ContainingBlockScope>(
PhysicalSizeToBeNoop(box.PreviousSize()),
PhysicalSizeToBeNoop(box.Size()), old_rect, new_rect);
}
bool should_report_layout_shift = [&]() -> bool {
if (box.ShouldSkipNextLayoutShiftTracking()) {
box.GetMutableForPainting().SetShouldSkipNextLayoutShiftTracking(false);
return false;
}
// If the layout shift root has changed, LayoutShiftTracker can't use the
// current paint property tree to map the old rect.
if (tree_builder_context.current.layout_shift_root_changed)
return false;
if (new_rect.IsEmpty() || old_rect.IsEmpty())
return false;
// Track self-painting layers separately because their ancestors'
// PhysicalVisualOverflowRect may not cover them.
if (object.HasLayer() &&
To<LayoutBoxModelObject>(object).HasSelfPaintingLayer())
return true;
// Always track if the parent doesn't need to track (e.g. it has visibility:
// hidden), while this object needs (e.g. it has visibility: visible).
// This also includes non-anonymous child with an anonymous parent.
if (object.Parent()->ShouldSkipNextLayoutShiftTracking())
return true;
// Report if the parent is in a different transform space.
const auto* parent_context = context.ParentContext();
if (!parent_context || !parent_context->transform_ ||
parent_context->transform_ != tree_builder_context.current.transform)
return true;
// Report if this object has local movement (i.e. delta of paint offset is
// different from that of the parent).
return parent_context->fragment_data->PaintOffset() -
parent_context->old_paint_offset !=
new_paint_offset - context.old_paint_offset;
}();
if (should_report_layout_shift) {
layout_shift_tracker.NotifyBoxPrePaint(
box, property_tree_state, old_rect, new_rect, adjusted_old_paint_offset,
tree_builder_context.translation_2d_to_layout_shift_root_delta,
tree_builder_context.current.scroll_offset_to_layout_shift_root_delta,
new_paint_offset);
}
}
bool PaintInvalidator::InvalidatePaint(
const LayoutObject& object,
const NGPrePaintInfo* pre_paint_info,
const PaintPropertyTreeBuilderContext* tree_builder_context,
PaintInvalidatorContext& context) {
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("blink.invalidation"),
"PaintInvalidator::InvalidatePaint()", "object",
object.DebugName().Ascii());
if (object.IsSVGHiddenContainer() || object.IsLayoutTableCol())
context.subtree_flags |= PaintInvalidatorContext::kSubtreeNoInvalidation;
if (context.subtree_flags & PaintInvalidatorContext::kSubtreeNoInvalidation)
return false;
object.GetMutableForPainting().EnsureIsReadyForPaintInvalidation();
UpdatePaintingLayer(object, context, /* is_ng_painting */ !!pre_paint_info);
UpdateDirectlyCompositedContainer(object, context,
/* is_ng_painting */ !!pre_paint_info);
if (!object.ShouldCheckForPaintInvalidation() && !context.NeedsSubtreeWalk())
return false;
if (object.SubtreeShouldDoFullPaintInvalidation()) {
context.subtree_flags |=
PaintInvalidatorContext::kSubtreeFullInvalidation |
PaintInvalidatorContext::kSubtreeFullInvalidationForStackedContents;
}
if (object.SubtreeShouldCheckForPaintInvalidation()) {
context.subtree_flags |=
PaintInvalidatorContext::kSubtreeInvalidationChecking;
}
if (UNLIKELY(object.ContainsInlineWithOutlineAndContinuation()) &&
// Need this only if the subtree needs to check geometry change.
PrePaintTreeWalk::ObjectRequiresTreeBuilderContext(object)) {
// Force subtree invalidation checking to ensure invalidation of focus rings
// when continuation's geometry changes.
context.subtree_flags |=
PaintInvalidatorContext::kSubtreeInvalidationChecking;
}
if (pre_paint_info) {
FragmentData& fragment_data = pre_paint_info->fragment_data;
context.fragment_data = &fragment_data;
if (tree_builder_context) {
DCHECK_EQ(tree_builder_context->fragments.size(), 1u);
const auto& fragment_tree_builder_context =
tree_builder_context->fragments[0];
UpdateFromTreeBuilderContext(fragment_tree_builder_context, context);
UpdateLayoutShiftTracking(object, fragment_tree_builder_context, context);
} else {
context.old_paint_offset = fragment_data.PaintOffset();
}
object.InvalidatePaint(context);
} else {
unsigned tree_builder_index = 0;
for (auto* fragment_data = &object.GetMutableForPainting().FirstFragment();
fragment_data;
fragment_data = fragment_data->NextFragment(), tree_builder_index++) {
context.fragment_data = fragment_data;
DCHECK(!tree_builder_context ||
tree_builder_index < tree_builder_context->fragments.size());
if (tree_builder_context) {
const auto& fragment_tree_builder_context =
tree_builder_context->fragments[tree_builder_index];
UpdateFromTreeBuilderContext(fragment_tree_builder_context, context);
UpdateLayoutShiftTracking(object, fragment_tree_builder_context,
context);
object.GetFrameView()
->GetMobileFriendlinessChecker()
.NotifyInvalidatePaint(object);
} else {
context.old_paint_offset = fragment_data->PaintOffset();
}
object.InvalidatePaint(context);
}
}
auto reason = static_cast<const DisplayItemClient&>(object)
.GetPaintInvalidationReason();
if (object.ShouldDelayFullPaintInvalidation() &&
(!IsFullPaintInvalidationReason(reason) ||
// Delay invalidation if the client has never been painted.
reason == PaintInvalidationReason::kJustCreated))
pending_delayed_paint_invalidations_.push_back(&object);
if (AXObjectCache* cache = object.GetDocument().ExistingAXObjectCache())
cache->InvalidateBoundingBox(&object);
return reason != PaintInvalidationReason::kNone;
}
void PaintInvalidator::ProcessPendingDelayedPaintInvalidations() {
for (auto* target : pending_delayed_paint_invalidations_)
target->GetMutableForPainting().SetShouldDelayFullPaintInvalidation();
}
} // namespace blink