| // 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/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/legacy_layout_tree_walking.h" |
| #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h" |
| #include "third_party/blink/renderer/core/paint/clip_path_clipper.h" |
| #include "third_party/blink/renderer/core/paint/find_paint_offset_and_visual_rect_needing_update.h" |
| #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.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 { |
| |
| // If needed, exclude composited layer's subpixel accumulation to avoid full |
| // layer raster invalidations during animation with subpixels. |
| // See crbug.com/833083 for details. |
| bool PaintInvalidatorContext::ShouldExcludeCompositedLayerSubpixelAccumulation( |
| const LayoutObject& object) const { |
| // TODO(wangxianzhu): How to handle sub-pixel location animation for CAP? |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return false; |
| |
| // One of the following conditions happened in crbug.com/837226. |
| if (!paint_invalidation_container || |
| !paint_invalidation_container->FirstFragment() |
| .HasLocalBorderBoxProperties() || |
| !tree_builder_context_) |
| return false; |
| |
| if (!(paint_invalidation_container->Layer()->GetCompositingReasons() & |
| CompositingReason::kComboAllDirectReasons)) |
| return false; |
| |
| if (object != paint_invalidation_container && |
| &paint_invalidation_container->FirstFragment().PostScrollTranslation() != |
| tree_builder_context_->current.transform) { |
| // Subpixel accumulation doesn't propagate through non-translation |
| // transforms. Also skip all transforms, to avoid the runtime cost of |
| // verifying whether the transform is a translation. |
| return false; |
| } |
| |
| // Will exclude the subpixel accumulation so that the paint invalidator won't |
| // see changed visual rects during composited animation with subpixels, to |
| // avoid full layer invalidation. The subpixel accumulation will be added |
| // back in ChunkToLayerMapper::AdjustVisualRectBySubpixelOffset(). Should |
| // make sure the code is synced. |
| // TODO(wangxianzhu): Avoid exposing subpixel accumulation to platform code. |
| return true; |
| } |
| |
| IntRect PaintInvalidatorContext::MapLocalRectToVisualRect( |
| const LayoutObject& object, |
| const PhysicalRect& local_rect) const { |
| DCHECK(NeedsVisualRectUpdate(object)); |
| |
| if (local_rect.IsEmpty()) |
| return IntRect(); |
| |
| DCHECK(!object.IsSVGChild() || |
| // This function applies to SVG children derived from non-SVG layout |
| // objects, for carets, selections, etc. |
| object.IsBoxModelObject() || object.IsText()); |
| |
| // Unite visual rect with clip path bounding rect. |
| // It is because the clip path display items are owned by the layout object |
| // who has the clip path, and uses its visual rect as bounding rect too. |
| // Usually it is done at layout object level and included as a part of |
| // local visual overflow, but clip-path can be a reference to SVG, and we |
| // have to wait until pre-paint to ensure clean layout. |
| PhysicalRect rect = local_rect; |
| if (base::Optional<FloatRect> clip_path_bounding_box = |
| ClipPathClipper::LocalClipPathBoundingBox(object)) |
| rect.Unite(PhysicalRect(EnclosingIntRect(*clip_path_bounding_box))); |
| |
| rect.Move(fragment_data->PaintOffset()); |
| if (ShouldExcludeCompositedLayerSubpixelAccumulation(object)) |
| rect.Move(-paint_invalidation_container->Layer()->SubpixelAccumulation()); |
| // Use EnclosingIntRect to ensure the final visual rect will cover the rect |
| // in source coordinates no matter if the painting will snap to pixels. |
| return EnclosingIntRect(rect); |
| } |
| |
| IntRect PaintInvalidatorContext::MapLocalRectToVisualRectForSVGChild( |
| const LayoutObject& object, |
| const FloatRect& local_rect) const { |
| DCHECK(object.IsSVGChild()); |
| DCHECK(NeedsVisualRectUpdate(object)); |
| |
| if (local_rect.IsEmpty()) |
| return IntRect(); |
| |
| // Visual rects are in the space of their local transform node. For SVG, the |
| // input rect is in local SVG coordinates in which paint offset doesn't apply. |
| // We also don't need to adjust for clip path here because SVG the local |
| // visual rect has already been adjusted by clip path. |
| auto rect = local_rect; |
| if (ShouldExcludeCompositedLayerSubpixelAccumulation(object)) { |
| rect.Move(FloatSize( |
| -paint_invalidation_container->Layer()->SubpixelAccumulation())); |
| } |
| // Use EnclosingIntRect to ensure the final visual rect will cover the rect |
| // in source coordinates no matter if the painting will snap to pixels. |
| return EnclosingIntRect(rect); |
| } |
| |
| const PaintInvalidatorContext* |
| PaintInvalidatorContext::ParentContextAccessor::ParentContext() const { |
| return tree_walk_ ? &tree_walk_->ContextAt(parent_context_index_) |
| .paint_invalidator_context |
| : nullptr; |
| } |
| |
| IntRect PaintInvalidator::ComputeVisualRect( |
| const LayoutObject& object, |
| const PaintInvalidatorContext& context) { |
| if (object.IsSVGChild()) { |
| return context.MapLocalRectToVisualRectForSVGChild( |
| object, SVGLayoutSupport::LocalVisualRect(object)); |
| } |
| |
| return context.MapLocalRectToVisualRect(object, object.LocalVisualRect()); |
| } |
| |
| void PaintInvalidator::UpdatePaintingLayer(const LayoutObject& object, |
| PaintInvalidatorContext& context) { |
| if (object.HasLayer() && |
| ToLayoutBoxModelObject(object).HasSelfPaintingLayer()) { |
| context.painting_layer = ToLayoutBoxModelObject(object).Layer(); |
| } else if (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(); |
| |
| // Table collapsed borders are painted in PaintPhaseDescendantBlockBackgrounds |
| // on the table's layer. |
| if (object.IsTable() && |
| ToInterface<LayoutNGTableInterface>(object).HasCollapsedBorders()) |
| context.painting_layer->SetNeedsPaintPhaseDescendantBlockBackgrounds(); |
| |
| // The following flags are for descendants of the layer object only. |
| if (object == context.painting_layer->GetLayoutObject()) |
| return; |
| |
| if (object.IsTableSection()) { |
| const auto& section = ToInterface<LayoutNGTableSectionInterface>(object); |
| if (section.TableInterface()->HasColElements()) |
| context.painting_layer->SetNeedsPaintPhaseDescendantBlockBackgrounds(); |
| } |
| |
| if (object.StyleRef().HasOutline()) |
| context.painting_layer->SetNeedsPaintPhaseDescendantOutlines(); |
| |
| if (object.HasBoxDecorationBackground() |
| // We also paint overflow controls in background phase. |
| || (object.HasOverflowClip() && |
| ToLayoutBox(object).GetScrollableArea()->HasOverflowControls())) { |
| context.painting_layer->SetNeedsPaintPhaseDescendantBlockBackgrounds(); |
| } else { |
| // Hit testing rects for touch action paint in the background phase. |
| if (object.HasEffectiveAllowedTouchAction()) |
| context.painting_layer->SetNeedsPaintPhaseDescendantBlockBackgrounds(); |
| } |
| } |
| |
| void PaintInvalidator::UpdatePaintInvalidationContainer( |
| const LayoutObject& object, |
| PaintInvalidatorContext& context) { |
| if (object.IsPaintInvalidationContainer()) { |
| context.paint_invalidation_container = ToLayoutBoxModelObject(&object); |
| if (object.StyleRef().IsStackingContext() || object.IsSVGRoot()) |
| context.paint_invalidation_container_for_stacked_contents = |
| ToLayoutBoxModelObject(&object); |
| } else if (object.IsLayoutView()) { |
| // 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.paint_invalidation_container_for_stacked_contents = |
| context.paint_invalidation_container = |
| &object.ContainerForPaintInvalidation(); |
| } else if (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.paint_invalidation_container = |
| &object.ContainerForPaintInvalidation(); |
| } else if (object.StyleRef().IsStacked() && |
| // This is to exclude some objects (e.g. LayoutText) inheriting |
| // stacked style from parent but aren't actually stacked. |
| object.HasLayer() && |
| !ToLayoutBoxModelObject(object) |
| .Layer() |
| ->IsReplacedNormalFlowStacking() && |
| context.paint_invalidation_container != |
| context.paint_invalidation_container_for_stacked_contents) { |
| // The current object is stacked, so we should use |
| // m_paintInvalidationContainerForStackedContents as its paint invalidation |
| // container on which the current object is painted. |
| context.paint_invalidation_container = |
| context.paint_invalidation_container_for_stacked_contents; |
| if (context.subtree_flags & |
| PaintInvalidatorContext::kSubtreeFullInvalidationForStackedContents) { |
| context.subtree_flags |= |
| PaintInvalidatorContext::kSubtreeFullInvalidation; |
| } |
| } |
| |
| if (object == context.paint_invalidation_container) { |
| // When we hit a new paint invalidation 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.paint_invalidation_container_for_stacked_contents) { |
| // However, we need to keep kSubtreeVisualRectUpdate and |
| // kSubtreeFullInvalidationForStackedContents flags if the current |
| // object isn't the paint invalidation container of stacked contents. |
| context.subtree_flags &= |
| (PaintInvalidatorContext::kSubtreeVisualRectUpdate | |
| PaintInvalidatorContext::kSubtreeFullInvalidationForStackedContents); |
| } else { |
| context.subtree_flags = 0; |
| } |
| } |
| |
| DCHECK(context.paint_invalidation_container == |
| object.ContainerForPaintInvalidation()); |
| DCHECK(context.painting_layer == object.PaintingLayer()); |
| } |
| |
| void PaintInvalidator::UpdateVisualRect(const LayoutObject& object, |
| FragmentData& fragment_data, |
| PaintInvalidatorContext& context) { |
| if (!context.NeedsVisualRectUpdate(object)) |
| return; |
| |
| DCHECK(context.tree_builder_context_); |
| DCHECK(context.tree_builder_context_->current.paint_offset == |
| fragment_data.PaintOffset()); |
| |
| fragment_data.SetVisualRect(ComputeVisualRect(object, context)); |
| |
| object.GetFrameView()->GetLayoutShiftTracker().NotifyObjectPrePaint( |
| object, |
| PropertyTreeState(*context.tree_builder_context_->current.transform, |
| *context.tree_builder_context_->current.clip, |
| *context.tree_builder_context_->current_effect), |
| context.old_visual_rect, fragment_data.VisualRect(), |
| // Don't report a diff for a LayoutView. Any paint offset translation |
| // it has was inherited from the parent frame, and movements of a |
| // frame relative to its parent are tracked in the parent frame's |
| // LayoutShiftTracker, not the child frame's. |
| object.IsLayoutView() |
| ? FloatSize() |
| : context.tree_builder_context_->paint_offset_delta); |
| } |
| |
| void PaintInvalidator::UpdateEmptyVisualRectFlag( |
| const LayoutObject& object, |
| PaintInvalidatorContext& context) { |
| bool is_paint_invalidation_container = |
| object == context.paint_invalidation_container; |
| |
| // Content under transforms needs to invalidate, even if visual |
| // rects before and after update were the same. This is because |
| // we don't know whether this transform will end up composited in |
| // CAP, so such transforms are painted even if not visible |
| // due to ancestor clips. This does not apply in SPv1 mode when |
| // crossing paint invalidation container boundaries. |
| if (is_paint_invalidation_container) { |
| // Remove the flag when crossing paint invalidation container boundaries. |
| context.subtree_flags &= |
| ~PaintInvalidatorContext::kInvalidateEmptyVisualRect; |
| } else if (object.StyleRef().HasTransform()) { |
| context.subtree_flags |= |
| PaintInvalidatorContext::kInvalidateEmptyVisualRect; |
| } |
| } |
| |
| bool PaintInvalidator::InvalidatePaint( |
| const LayoutObject& object, |
| const PaintPropertyTreeBuilderContext* tree_builder_context, |
| PaintInvalidatorContext& context) { |
| TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("blink.invalidation"), |
| "PaintInvalidator::InvalidatePaint()", "object", |
| object.DebugName().Ascii()); |
| |
| if (object.IsSVGHiddenContainer()) |
| context.subtree_flags |= PaintInvalidatorContext::kSubtreeNoInvalidation; |
| |
| if (context.subtree_flags & PaintInvalidatorContext::kSubtreeNoInvalidation) |
| return false; |
| |
| object.GetMutableForPainting().EnsureIsReadyForPaintInvalidation(); |
| |
| UpdatePaintingLayer(object, context); |
| UpdatePaintInvalidationContainer(object, context); |
| UpdateEmptyVisualRectFlag(object, context); |
| |
| if (!object.ShouldCheckForPaintInvalidation() && !context.NeedsSubtreeWalk()) |
| return false; |
| |
| unsigned tree_builder_index = 0; |
| |
| for (auto* fragment_data = &object.GetMutableForPainting().FirstFragment(); |
| fragment_data; |
| fragment_data = fragment_data->NextFragment(), tree_builder_index++) { |
| context.old_visual_rect = fragment_data->VisualRect(); |
| context.fragment_data = fragment_data; |
| |
| DCHECK(!tree_builder_context || |
| tree_builder_index < tree_builder_context->fragments.size()); |
| |
| { |
| #if DCHECK_IS_ON() |
| context.tree_builder_context_actually_needed_ = |
| tree_builder_context && tree_builder_context->is_actually_needed; |
| FindObjectVisualRectNeedingUpdateScope finder(object, *fragment_data, |
| context); |
| #endif |
| if (tree_builder_context) { |
| context.tree_builder_context_ = |
| &tree_builder_context->fragments[tree_builder_index]; |
| context.old_paint_offset = |
| context.tree_builder_context_->old_paint_offset; |
| } else { |
| context.tree_builder_context_ = nullptr; |
| context.old_paint_offset = fragment_data->PaintOffset(); |
| } |
| |
| UpdateVisualRect(object, *fragment_data, context); |
| } |
| |
| 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 (object.SubtreeShouldDoFullPaintInvalidation()) { |
| context.subtree_flags |= |
| PaintInvalidatorContext::kSubtreeFullInvalidation | |
| PaintInvalidatorContext::kSubtreeFullInvalidationForStackedContents; |
| } |
| |
| if (object.SubtreeShouldCheckForPaintInvalidation()) { |
| context.subtree_flags |= |
| PaintInvalidatorContext::kSubtreeInvalidationChecking; |
| } |
| |
| if (context.subtree_flags && context.NeedsVisualRectUpdate(object)) { |
| // If any subtree flag is set, we also need to pass needsVisualRectUpdate |
| // requirement to the subtree. |
| // TODO(vmpstr): Investigate why this is true. Specifically, when crossing |
| // an isolation boundary, is it safe to clear this subtree requirement. |
| context.subtree_flags |= PaintInvalidatorContext::kSubtreeVisualRectUpdate; |
| } |
| |
| if (context.NeedsVisualRectUpdate(object) && |
| object.ContainsInlineWithOutlineAndContinuation()) { |
| // Force subtree visual rect update and invalidation checking to ensure |
| // invalidation of focus rings when continuation's geometry changes. |
| context.subtree_flags |= |
| PaintInvalidatorContext::kSubtreeVisualRectUpdate | |
| PaintInvalidatorContext::kSubtreeInvalidationChecking; |
| } |
| |
| return reason != PaintInvalidationReason::kNone; |
| } |
| |
| void PaintInvalidator::ProcessPendingDelayedPaintInvalidations() { |
| for (auto* target : pending_delayed_paint_invalidations_) |
| target->GetMutableForPainting().SetShouldDelayFullPaintInvalidation(); |
| } |
| |
| } // namespace blink |