| // Copyright 2014 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_layer_painter.h" |
| |
| #include "base/optional.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_video.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/paint/clip_path_clipper.h" |
| #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h" |
| #include "third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h" |
| #include "third_party/blink/renderer/core/paint/object_paint_properties.h" |
| #include "third_party/blink/renderer/core/paint/paint_info.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_paint_order_iterator.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/core/paint/paint_timing_detector.h" |
| #include "third_party/blink/renderer/core/paint/scrollable_area_painter.h" |
| #include "third_party/blink/renderer/platform/geometry/float_point_3d.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/scoped_display_item_fragment.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/scoped_paint_chunk_hint.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/subsequence_recorder.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| |
| namespace blink { |
| |
| void PaintLayerPainter::Paint(GraphicsContext& context, |
| const CullRect& cull_rect, |
| const GlobalPaintFlags global_paint_flags, |
| PaintLayerFlags paint_flags) { |
| PaintLayerPaintingInfo painting_info(&paint_layer_, cull_rect, |
| global_paint_flags, PhysicalOffset()); |
| if (!paint_layer_.PaintsIntoOwnOrGroupedBacking(global_paint_flags)) |
| Paint(context, painting_info, paint_flags); |
| } |
| |
| static ShouldRespectOverflowClipType ShouldRespectOverflowClip( |
| PaintLayerFlags paint_flags, |
| const LayoutObject& layout_object) { |
| return (paint_flags & kPaintLayerPaintingOverflowContents) |
| ? kIgnoreOverflowClip |
| : kRespectOverflowClip; |
| } |
| |
| bool PaintLayerPainter::PaintedOutputInvisible(const ComputedStyle& style) { |
| if (style.HasNonInitialBackdropFilter()) |
| return false; |
| |
| // Always paint when 'will-change: opacity' is present. Reduces jank for |
| // common animation implementation approaches, for example, an element that |
| // starts with opacity zero and later begins to animate. |
| if (style.HasWillChangeOpacityHint()) |
| return false; |
| |
| // 0.0004f < 1/2048. With 10-bit color channels (only available on the |
| // newest Macs; otherwise it's 8-bit), we see that an alpha of 1/2048 or |
| // less leads to a color output of less than 0.5 in all channels, hence |
| // not visible. |
| static const float kMinimumVisibleOpacity = 0.0004f; |
| if (style.Opacity() < kMinimumVisibleOpacity) |
| return true; |
| |
| return false; |
| } |
| |
| PaintResult PaintLayerPainter::Paint( |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& painting_info, |
| PaintLayerFlags paint_flags) { |
| if (paint_layer_.GetLayoutObject().GetFrameView()->ShouldThrottleRendering()) |
| return kFullyPainted; |
| |
| // Non self-painting layers without self-painting descendants don't need to be |
| // painted as their layoutObject() should properly paint itself. |
| if (!paint_layer_.IsSelfPaintingLayer() && |
| !paint_layer_.HasSelfPaintingLayerDescendant()) |
| return kFullyPainted; |
| |
| // If this layer is totally invisible then there is nothing to paint. |
| // In CompositeAfterPaint we simplify this optimization by painting even when |
| // effectively invisible but skipping the painted content during layerization |
| // in PaintArtifactCompositor. |
| if (paint_layer_.PaintsWithTransparency( |
| painting_info.GetGlobalPaintFlags())) { |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && |
| PaintedOutputInvisible(paint_layer_.GetLayoutObject().StyleRef())) |
| return kFullyPainted; |
| |
| paint_flags |= kPaintLayerHaveTransparency; |
| } |
| |
| // If the transform can't be inverted, then don't paint anything. |
| if (paint_layer_.PaintsWithTransform(painting_info.GetGlobalPaintFlags()) && |
| !paint_layer_.RenderableTransform(painting_info.GetGlobalPaintFlags()) |
| .IsInvertible()) { |
| return kFullyPainted; |
| } |
| |
| paint_flags |= kPaintLayerPaintingCompositingAllPhases; |
| return PaintLayerContents(context, painting_info, paint_flags); |
| } |
| |
| static bool ShouldCreateSubsequence( |
| const PaintLayer& paint_layer, |
| const GraphicsContext& context, |
| const PaintLayerPaintingInfo& painting_info) { |
| // Caching is not needed during printing or painting previews. |
| if (paint_layer.GetLayoutObject().GetDocument().IsPrintingOrPaintingPreview()) |
| return false; |
| |
| if (context.GetPaintController().IsSkippingCache()) |
| return false; |
| |
| if (!paint_layer.SupportsSubsequenceCaching()) |
| return false; |
| |
| // Don't create subsequence during special painting to avoid cache conflict |
| // with normal painting. |
| if (painting_info.GetGlobalPaintFlags() & |
| kGlobalPaintFlattenCompositingLayers) |
| return false; |
| |
| // Don't create subsequence for a composited layer because if it can be |
| // cached, we can skip the whole painting in GraphicsLayer::paint() with |
| // CachedDisplayItemList. This also avoids conflict of |
| // PaintLayer::previousXXX() when paintLayer is composited scrolling and is |
| // painted twice for GraphicsLayers of container and scrolling contents. |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && |
| (paint_layer.GetCompositingState() == kPaintsIntoOwnBacking)) |
| return false; |
| |
| return true; |
| } |
| |
| static bool ShouldRepaintSubsequence( |
| PaintLayer& paint_layer, |
| const PaintLayerPaintingInfo& painting_info) { |
| // Repaint subsequence if the layer is marked for needing repaint. |
| if (paint_layer.SelfOrDescendantNeedsRepaint()) |
| return true; |
| |
| // Repaint if previously the layer may be clipped by cull rect, and cull rect |
| // changes. |
| if (!RuntimeEnabledFeatures::CullRectUpdateEnabled() && |
| paint_layer.PreviousCullRect() != painting_info.cull_rect) { |
| if (paint_layer.PreviousPaintResult() == kMayBeClippedByCullRect) |
| return true; |
| // When PaintUnderInvalidationChecking is enabled, always repaint the |
| // subsequence when the paint rect changes because we will strictly match |
| // new and cached subsequences. Normally we can reuse the cached fully |
| // painted subsequence even if we would partially paint this time. |
| if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool IsUnclippedLayoutView(const PaintLayer& layer) { |
| // If MainFrameClipsContent is false which means that WebPreferences:: |
| // record_whole_document is true, we should not cull the scrolling contents |
| // of the main frame. |
| if (IsA<LayoutView>(layer.GetLayoutObject())) { |
| const auto* frame = layer.GetLayoutObject().GetFrame(); |
| if (frame && !frame->ClipsContent()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool PaintLayerPainter::ShouldUseInfiniteCullRect( |
| GlobalPaintFlags global_flags) { |
| bool is_printing = paint_layer_.GetLayoutObject().GetDocument().Printing(); |
| if (IsUnclippedLayoutView(paint_layer_) && !is_printing) |
| return true; |
| |
| // Cull rects and clips can't be propagated across a filter which moves |
| // pixels, since the input of the filter may be outside the cull rect / |
| // clips yet still result in painted output. |
| // TODO(wangxianzhu): With CullRectUpdate, we can let CullRect support |
| // mapping for pixel moving filters to avoid this infinite cull rect. |
| if (paint_layer_.HasFilterThatMovesPixels() && |
| // However during printing, we don't want filter outset to cross page |
| // boundaries. This also avoids performance issue because the PDF renderer |
| // is super slow for big filters. Otherwise all filtered contents would |
| // appear in the painted result of every page. |
| // TODO(crbug.com/1098995): For now we don't adjust cull rect for clips. |
| // When we do, we need to check if we are painting under a real clip. |
| // This won't be a problem when we use block fragments for printing. |
| !is_printing) |
| return true; |
| |
| // Cull rect mapping doesn't work under perspective in some cases. |
| // See http://crbug.com/887558 for details. |
| if (paint_layer_.GetLayoutObject().StyleRef().HasPerspective()) |
| return true; |
| |
| // We do not apply cull rect optimizations across transforms for two |
| // reasons: |
| // 1) Performance: We can optimize transform changes by not repainting. |
| // 2) Complexity: Difficulty updating clips when ancestor transforms |
| // change. |
| // For these reasons, we use an infinite dirty rect here. |
| // The reasons don't apply for CullRectUpdate. |
| if (!RuntimeEnabledFeatures::CullRectUpdateEnabled() && |
| paint_layer_.PaintsWithTransform(global_flags) && |
| // The reasons don't apply for printing though, because when we enter and |
| // leaving printing mode, full invalidations occur. |
| !is_printing) |
| return true; |
| |
| return false; |
| } |
| |
| void PaintLayerPainter::AdjustForPaintProperties( |
| const GraphicsContext& context, |
| PaintLayerPaintingInfo& painting_info, |
| PaintLayerFlags& paint_flags) { |
| const auto& first_fragment = paint_layer_.GetLayoutObject().FirstFragment(); |
| |
| bool should_use_infinite_cull_rect = |
| ShouldUseInfiniteCullRect(painting_info.GetGlobalPaintFlags()); |
| if (should_use_infinite_cull_rect) { |
| painting_info.cull_rect = CullRect::Infinite(); |
| // Avoid clipping during CollectFragments. |
| if (IsUnclippedLayoutView(paint_layer_)) |
| paint_flags |= kPaintLayerPaintingOverflowContents; |
| } |
| |
| if (painting_info.root_layer == &paint_layer_) |
| return; |
| |
| if (!should_use_infinite_cull_rect) { |
| // painting_info.cull_rect is currently in |painting_info.root_layer|'s |
| // pixel-snapped border box space. We need to adjust it into |
| // |paint_layer_|'s space. This handles the following cases: |
| // - The current layer has PaintOffsetTranslation; |
| // - The current layer's transform state escapes the root layers contents |
| // transform, e.g. a fixed-position layer; |
| // - Scroll offsets. |
| const auto& first_root_fragment = |
| painting_info.root_layer->GetLayoutObject().FirstFragment(); |
| const auto* source_transform = |
| &first_root_fragment.LocalBorderBoxProperties().Transform(); |
| const auto& destination_transform = |
| first_fragment.LocalBorderBoxProperties().Transform(); |
| if (source_transform == &destination_transform) |
| return; |
| |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| auto& cull_rect = painting_info.cull_rect; |
| // CullRect::ApplyTransforms() requires the cull rect in the source |
| // transform space. Convert cull_rect from the root layer's local space. |
| cull_rect.MoveBy(RoundedIntPoint(first_root_fragment.PaintOffset())); |
| base::Optional<CullRect> old_cull_rect; |
| if (!paint_layer_.SelfOrDescendantNeedsRepaint() && |
| !RuntimeEnabledFeatures::CullRectUpdateEnabled()) { |
| old_cull_rect = paint_layer_.PreviousCullRect(); |
| // Convert old_cull_rect into the layer's transform space. |
| old_cull_rect->MoveBy(RoundedIntPoint(first_fragment.PaintOffset())); |
| } |
| if (paint_flags & kPaintLayerPaintingOverflowContents) { |
| // Use PostScrollTranslation as the source transform to avoid clipping |
| // of the scrolling contents in CullRect::ApplyTransforms(). |
| source_transform = &first_root_fragment.PostScrollTranslation(); |
| // Map cull_rect into scrolling contents space (i.e. source_transform). |
| if (const auto* properties = first_root_fragment.PaintProperties()) { |
| if (const auto* scroll_translation = properties->ScrollTranslation()) |
| cull_rect.Move(-scroll_translation->Translation2D()); |
| } |
| } |
| cull_rect.ApplyTransforms(source_transform->Unalias(), |
| destination_transform.Unalias(), old_cull_rect); |
| // Convert cull_rect from the layer's transform space to the layer's local |
| // space. |
| cull_rect.MoveBy(-RoundedIntPoint(first_fragment.PaintOffset())); |
| } else if (!painting_info.cull_rect.IsInfinite()) { |
| auto rect = painting_info.cull_rect.Rect(); |
| first_root_fragment.MapRectToFragment(first_fragment, rect); |
| painting_info.cull_rect = CullRect(rect); |
| } |
| } |
| |
| // We reach here if the layer requires infinite cull rect or has different |
| // transform space from the current root layer. Use the current layer as |
| // the new root layer. |
| painting_info.root_layer = &paint_layer_; |
| // These flags no longer apply for the new root layer. |
| paint_flags &= ~kPaintLayerPaintingSkipRootBackground; |
| paint_flags &= ~kPaintLayerPaintingOverflowContents; |
| paint_flags &= ~kPaintLayerPaintingCompositingScrollingPhase; |
| |
| if (first_fragment.PaintProperties() && |
| first_fragment.PaintProperties()->PaintOffsetTranslation()) { |
| painting_info.sub_pixel_accumulation = first_fragment.PaintOffset(); |
| } |
| } |
| |
| static IntRect FirstFragmentVisualRect(const LayoutBoxModelObject& object) { |
| // We don't want to include overflowing contents. |
| PhysicalRect overflow_rect = |
| object.IsBox() ? To<LayoutBox>(object).PhysicalSelfVisualOverflowRect() |
| : object.PhysicalVisualOverflowRect(); |
| overflow_rect.Move(object.FirstFragment().PaintOffset()); |
| return EnclosingIntRect(overflow_rect); |
| } |
| |
| PaintResult PaintLayerPainter::PaintLayerContents( |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& painting_info_arg, |
| PaintLayerFlags paint_flags_arg) { |
| DCHECK(paint_layer_.IsSelfPaintingLayer() || |
| paint_layer_.HasSelfPaintingLayerDescendant()); |
| |
| const auto& object = paint_layer_.GetLayoutObject(); |
| PaintResult result = kFullyPainted; |
| if (object.GetFrameView()->ShouldThrottleRendering()) |
| return result; |
| |
| // A paint layer should always have LocalBorderBoxProperties when it's ready |
| // for paint. |
| if (!object.FirstFragment().HasLocalBorderBoxProperties()) { |
| // TODO(crbug.com/848056): This can happen e.g. when we paint a filter |
| // referencing a SVG foreign object through feImage, especially when there |
| // is circular references. Should find a better solution. |
| if (!RuntimeEnabledFeatures::CullRectUpdateEnabled()) |
| paint_layer_.SetPreviousCullRect(CullRect()); |
| return kMayBeClippedByCullRect; |
| } |
| |
| bool selection_drag_image_only = painting_info_arg.GetGlobalPaintFlags() & |
| kGlobalPaintSelectionDragImageOnly; |
| if (selection_drag_image_only && !object.IsSelected()) |
| return result; |
| |
| IgnorePaintTimingScope ignore_paint_timing; |
| if (object.StyleRef().Opacity() == 0.0f) { |
| IgnorePaintTimingScope::IncrementIgnoreDepth(); |
| } |
| // Explicitly compute opacity of documentElement, as it is special-cased in |
| // Largest Contentful Paint. |
| bool is_document_element_invisible = false; |
| if (const auto* document_element = object.GetDocument().documentElement()) { |
| if (document_element->GetLayoutObject() && |
| document_element->GetLayoutObject()->StyleRef().Opacity() == 0.0f) { |
| is_document_element_invisible = true; |
| } |
| } |
| IgnorePaintTimingScope::SetIsDocumentElementInvisible( |
| is_document_element_invisible); |
| |
| PaintLayerFlags paint_flags = paint_flags_arg; |
| PaintLayerPaintingInfo painting_info = painting_info_arg; |
| AdjustForPaintProperties(context, painting_info, paint_flags); |
| |
| bool is_self_painting_layer = paint_layer_.IsSelfPaintingLayer(); |
| bool is_painting_overlay_overflow_controls = |
| paint_flags & kPaintLayerPaintingOverlayOverflowControls; |
| bool is_painting_scrolling_content = |
| paint_flags & kPaintLayerPaintingCompositingScrollingPhase; |
| bool is_painting_composited_foreground = |
| paint_flags & kPaintLayerPaintingCompositingForegroundPhase; |
| bool is_painting_composited_background = |
| paint_flags & kPaintLayerPaintingCompositingBackgroundPhase; |
| bool is_painting_composited_decoration = |
| paint_flags & kPaintLayerPaintingCompositingDecorationPhase; |
| bool is_painting_overflow_contents = |
| paint_flags & kPaintLayerPaintingOverflowContents; |
| bool is_painting_mask = paint_flags & kPaintLayerPaintingCompositingMaskPhase; |
| |
| // Outline always needs to be painted even if we have no visible content. |
| // It is painted as part of the decoration phase which paints content that |
| // is not scrolled and should be above scrolled content. |
| bool should_paint_self_outline = |
| is_self_painting_layer && !is_painting_overlay_overflow_controls && |
| is_painting_composited_decoration && object.StyleRef().HasOutline(); |
| |
| PhysicalOffset subpixel_accumulation = |
| (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && |
| !(painting_info.GetGlobalPaintFlags() & |
| kGlobalPaintFlattenCompositingLayers) && |
| paint_layer_.GetCompositingState() == kPaintsIntoOwnBacking) |
| ? paint_layer_.SubpixelAccumulation() |
| : painting_info.sub_pixel_accumulation; |
| |
| ShouldRespectOverflowClipType respect_overflow_clip = |
| ShouldRespectOverflowClip(paint_flags, object); |
| |
| bool should_paint_content = |
| paint_layer_.HasVisibleContent() && |
| // Content under a LayoutSVGHiddenContainer is auxiliary resources for |
| // painting. Foreign content should never paint in this situation, as it |
| // is primary, not auxiliary. |
| !paint_layer_.IsUnderSVGHiddenContainer() && is_self_painting_layer && |
| !is_painting_overlay_overflow_controls; |
| |
| bool should_create_subsequence = |
| should_paint_content && |
| ShouldCreateSubsequence(paint_layer_, context, painting_info); |
| |
| base::Optional<SubsequenceRecorder> subsequence_recorder; |
| if (should_create_subsequence) { |
| if (!ShouldRepaintSubsequence(paint_layer_, painting_info) && |
| SubsequenceRecorder::UseCachedSubsequenceIfPossible(context, |
| paint_layer_)) { |
| return paint_layer_.PreviousPaintResult(); |
| } |
| DCHECK(paint_layer_.SupportsSubsequenceCaching()); |
| subsequence_recorder.emplace(context, paint_layer_); |
| } |
| |
| PhysicalOffset offset_from_root = |
| paint_layer_.GetLayoutObject().FirstFragment().PaintOffset(); |
| if (const PaintLayer* root = painting_info.root_layer) |
| offset_from_root -= root->GetLayoutObject().FirstFragment().PaintOffset(); |
| offset_from_root += subpixel_accumulation; |
| |
| IntRect visual_rect = FirstFragmentVisualRect(object); |
| if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) { |
| if (object.FirstFragment().NextFragment()) { |
| result = kMayBeClippedByCullRect; |
| } else if (!object.FirstFragment().GetCullRect().Rect().Contains( |
| visual_rect)) { |
| result = kMayBeClippedByCullRect; |
| } else if (const auto* box = DynamicTo<LayoutBox>(object)) { |
| PhysicalRect contents_visual_rect = |
| box->PhysicalContentsVisualOverflowRect(); |
| contents_visual_rect.Move(object.FirstFragment().PaintOffset()); |
| if (!PhysicalRect(object.FirstFragment().GetContentsCullRect().Rect()) |
| .Contains(contents_visual_rect)) { |
| result = kMayBeClippedByCullRect; |
| } |
| } |
| } else { |
| PhysicalRect bounds = paint_layer_.PhysicalBoundingBox(offset_from_root); |
| if (!PhysicalRect(painting_info.cull_rect.Rect()).Contains(bounds)) |
| result = kMayBeClippedByCullRect; |
| } |
| |
| PaintLayerPaintingInfo local_painting_info(painting_info); |
| local_painting_info.sub_pixel_accumulation = subpixel_accumulation; |
| |
| PaintLayerFragments layer_fragments; |
| |
| if (should_paint_content || should_paint_self_outline || |
| is_painting_overlay_overflow_controls) { |
| // Collect the fragments. This will compute the clip rectangles and paint |
| // offsets for each layer fragment. |
| paint_layer_.CollectFragments( |
| layer_fragments, local_painting_info.root_layer, |
| &local_painting_info.cull_rect, kIgnoreOverlayScrollbarSize, |
| respect_overflow_clip, &offset_from_root, |
| local_painting_info.sub_pixel_accumulation); |
| |
| // PaintLayer::CollectFragments depends on the paint dirty rect in |
| // complicated ways. For now, always assume a partially painted output |
| // for fragmented content. |
| if (layer_fragments.size() > 1) |
| result = kMayBeClippedByCullRect; |
| |
| if (should_paint_content) { |
| should_paint_content = AtLeastOneFragmentIntersectsDamageRect( |
| layer_fragments, local_painting_info, paint_flags, offset_from_root); |
| if (!should_paint_content) |
| result = kMayBeClippedByCullRect; |
| } |
| } |
| |
| bool is_painting_root_layer = (&paint_layer_) == painting_info.root_layer; |
| bool should_paint_background = |
| should_paint_content && !selection_drag_image_only && |
| (is_painting_composited_background || |
| (is_painting_root_layer && |
| !(paint_flags & kPaintLayerPaintingSkipRootBackground))); |
| bool should_paint_neg_z_order_list = |
| !is_painting_overlay_overflow_controls && |
| (is_painting_scrolling_content ? is_painting_overflow_contents |
| : is_painting_composited_background); |
| bool should_paint_own_contents = |
| is_painting_composited_foreground && should_paint_content; |
| bool should_paint_normal_flow_and_pos_z_order_lists = |
| is_painting_composited_foreground && |
| !is_painting_overlay_overflow_controls; |
| bool is_video = IsA<LayoutVideo>(object); |
| |
| base::Optional<ScopedPaintChunkHint> paint_chunk_hint; |
| if (should_paint_content) { |
| paint_chunk_hint.emplace(context.GetPaintController(), |
| object.FirstFragment().LocalBorderBoxProperties(), |
| paint_layer_, DisplayItem::kLayerChunk, |
| visual_rect); |
| } |
| |
| if (should_paint_background) { |
| PaintBackgroundForFragmentsWithPhase(PaintPhase::kSelfBlockBackgroundOnly, |
| layer_fragments, context, |
| local_painting_info, paint_flags); |
| } |
| |
| if (should_paint_neg_z_order_list) { |
| if (PaintChildren(kNegativeZOrderChildren, context, painting_info, |
| paint_flags) == kMayBeClippedByCullRect) |
| result = kMayBeClippedByCullRect; |
| } |
| |
| if (should_paint_own_contents) { |
| base::Optional<ScopedPaintChunkHint> paint_chunk_hint_foreground; |
| if (paint_chunk_hint && paint_chunk_hint->HasCreatedPaintChunk()) { |
| // Hint a foreground chunk if we have created any chunks, to give the |
| // paint chunk after the previous forced paint chunks a stable id. |
| paint_chunk_hint_foreground.emplace( |
| context.GetPaintController(), paint_layer_, |
| DisplayItem::kLayerChunkForeground, visual_rect); |
| } |
| if (selection_drag_image_only) { |
| PaintForegroundForFragmentsWithPhase(PaintPhase::kSelectionDragImage, |
| layer_fragments, context, |
| local_painting_info, paint_flags); |
| } else { |
| PaintForegroundForFragments(layer_fragments, context, local_painting_info, |
| paint_flags); |
| } |
| } |
| |
| if (!is_video && should_paint_self_outline) { |
| PaintBackgroundForFragmentsWithPhase(PaintPhase::kSelfOutlineOnly, |
| layer_fragments, context, |
| local_painting_info, paint_flags); |
| } |
| |
| if (should_paint_normal_flow_and_pos_z_order_lists) { |
| if (PaintChildren(kNormalFlowAndPositiveZOrderChildren, context, |
| painting_info, paint_flags) == kMayBeClippedByCullRect) |
| result = kMayBeClippedByCullRect; |
| } |
| |
| if (paint_layer_.GetScrollableArea() && |
| paint_layer_.GetScrollableArea() |
| ->ShouldOverflowControlsPaintAsOverlay()) { |
| if (is_painting_overlay_overflow_controls || |
| !paint_layer_.NeedsReorderOverlayOverflowControls()) { |
| PaintOverlayOverflowControlsForFragments( |
| layer_fragments, context, local_painting_info, paint_flags); |
| } |
| } |
| |
| if (is_video && should_paint_self_outline) { |
| // We paint outlines for video later so that they aren't obscured by the |
| // video controls. |
| PaintBackgroundForFragmentsWithPhase(PaintPhase::kSelfOutlineOnly, |
| layer_fragments, context, |
| local_painting_info, paint_flags); |
| } |
| |
| if (is_painting_mask && should_paint_content && !selection_drag_image_only) { |
| if (const auto* properties = object.FirstFragment().PaintProperties()) { |
| if (properties->Mask()) { |
| PaintBackgroundForFragmentsWithPhase(PaintPhase::kMask, layer_fragments, |
| context, local_painting_info, |
| paint_flags); |
| } |
| if (properties->ClipPathMask()) { |
| PhysicalOffset visual_offset_from_root = |
| paint_layer_.EnclosingPaginationLayer() |
| ? paint_layer_.VisualOffsetFromAncestor( |
| local_painting_info.root_layer, subpixel_accumulation) |
| : offset_from_root; |
| ClipPathClipper::PaintClipPathAsMaskImage(context, object, object, |
| visual_offset_from_root); |
| } |
| } |
| } |
| |
| paint_layer_.SetPreviousPaintResult(result); |
| if (!RuntimeEnabledFeatures::CullRectUpdateEnabled()) |
| paint_layer_.SetPreviousCullRect(local_painting_info.cull_rect); |
| return result; |
| } |
| |
| bool PaintLayerPainter::AtLeastOneFragmentIntersectsDamageRect( |
| PaintLayerFragments& fragments, |
| const PaintLayerPaintingInfo& local_painting_info, |
| PaintLayerFlags local_paint_flags, |
| const PhysicalOffset& offset_from_root) { |
| if (&paint_layer_ == local_painting_info.root_layer && |
| (local_paint_flags & kPaintLayerPaintingOverflowContents)) |
| return true; |
| |
| // Skip the optimization if the layer is fragmented to avoid complexity |
| // about overflows in fragments. LayoutObject painters will do cull rect |
| // optimization later. |
| if (paint_layer_.EnclosingPaginationLayer() || fragments.size() > 1) |
| return true; |
| |
| return paint_layer_.IntersectsDamageRect(fragments[0].layer_bounds, |
| fragments[0].background_rect.Rect(), |
| offset_from_root); |
| } |
| |
| template <typename Function> |
| static void ForAllFragments(GraphicsContext& context, |
| const PaintLayerFragments& fragments, |
| const Function& function) { |
| for (wtf_size_t i = 0; i < fragments.size(); ++i) { |
| base::Optional<ScopedDisplayItemFragment> scoped_display_item_fragment; |
| if (i) |
| scoped_display_item_fragment.emplace(context, i); |
| function(fragments[i]); |
| } |
| } |
| |
| PaintResult PaintLayerPainter::PaintChildren( |
| PaintLayerIteration children_to_visit, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& painting_info, |
| PaintLayerFlags paint_flags) { |
| PaintResult result = kFullyPainted; |
| if (!paint_layer_.HasSelfPaintingLayerDescendant()) |
| return result; |
| |
| if (paint_layer_.GetLayoutObject().ChildPaintBlockedByDisplayLock()) |
| return result; |
| |
| PaintLayerPaintOrderIterator iterator(paint_layer_, children_to_visit); |
| while (PaintLayer* child = iterator.Next()) { |
| // If this Layer should paint into its own backing or a grouped backing, |
| // that will be done via CompositedLayerMapping::PaintContents() and |
| // CompositedLayerMapping::DoPaintTask(). |
| if (child->PaintsIntoOwnOrGroupedBacking( |
| painting_info.GetGlobalPaintFlags())) |
| continue; |
| |
| if (child->IsReplacedNormalFlowStacking()) |
| continue; |
| |
| if (PaintLayerPainter(*child).Paint(context, painting_info, paint_flags) == |
| kMayBeClippedByCullRect) |
| result = kMayBeClippedByCullRect; |
| |
| if (const auto* layers_painting_overlay_overflow_controls_after = |
| iterator.LayersPaintingOverlayOverflowControlsAfter(child)) { |
| for (auto* reparent_overflow_controls_layer : |
| *layers_painting_overlay_overflow_controls_after) { |
| DCHECK(reparent_overflow_controls_layer |
| ->NeedsReorderOverlayOverflowControls()); |
| if (PaintLayerPainter(*reparent_overflow_controls_layer) |
| .Paint(context, painting_info, |
| kPaintLayerPaintingOverlayOverflowControls) == |
| kMayBeClippedByCullRect) |
| result = kMayBeClippedByCullRect; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| void PaintLayerPainter::PaintOverlayOverflowControlsForFragments( |
| const PaintLayerFragments& layer_fragments, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& painting_info, |
| PaintLayerFlags paint_flags) { |
| DCHECK( |
| paint_layer_.GetScrollableArea() && |
| paint_layer_.GetScrollableArea()->ShouldOverflowControlsPaintAsOverlay()); |
| |
| // We don't need to paint composited overflow controls. |
| if (paint_layer_.GetScrollableArea()->HasLayerForHorizontalScrollbar() || |
| paint_layer_.GetScrollableArea()->HasLayerForVerticalScrollbar() || |
| paint_layer_.GetScrollableArea()->HasLayerForScrollCorner()) |
| return; |
| |
| PaintBackgroundForFragmentsWithPhase(PaintPhase::kOverlayOverflowControls, |
| layer_fragments, context, painting_info, |
| paint_flags); |
| } |
| |
| void PaintLayerPainter::PaintFragmentWithPhase( |
| PaintPhase phase, |
| const PaintLayerFragment& fragment, |
| GraphicsContext& context, |
| const CullRect& cull_rect, |
| const PaintLayerPaintingInfo& painting_info, |
| PaintLayerFlags paint_flags) { |
| DCHECK(paint_layer_.IsSelfPaintingLayer()); |
| |
| auto chunk_properties = fragment.fragment_data->LocalBorderBoxProperties(); |
| if (phase == PaintPhase::kMask) { |
| const auto* properties = fragment.fragment_data->PaintProperties(); |
| DCHECK(properties); |
| DCHECK(properties->Mask()); |
| chunk_properties.SetEffect(*properties->Mask()); |
| } |
| ScopedPaintChunkProperties fragment_paint_chunk_properties( |
| context.GetPaintController(), chunk_properties, paint_layer_, |
| DisplayItem::PaintPhaseToDrawingType(phase)); |
| |
| PaintInfo paint_info( |
| context, cull_rect, phase, painting_info.GetGlobalPaintFlags(), |
| paint_flags, &painting_info.root_layer->GetLayoutObject(), |
| fragment.fragment_data ? fragment.fragment_data->LogicalTopInFlowThread() |
| : LayoutUnit()); |
| if (paint_layer_.GetLayoutObject().ChildPaintBlockedByDisplayLock()) |
| paint_info.SetDescendantPaintingBlocked(true); |
| |
| if (fragment.physical_fragment) |
| NGBoxFragmentPainter(*fragment.physical_fragment).Paint(paint_info); |
| else |
| paint_layer_.GetLayoutObject().Paint(paint_info); |
| } |
| |
| static CullRect LegacyCullRect(const PaintLayerFragment& fragment, |
| const ClipRect& clip_rect) { |
| DCHECK(!RuntimeEnabledFeatures::CullRectUpdateEnabled()); |
| PhysicalRect new_cull_rect(clip_rect.Rect()); |
| // Now |new_cull_rect| is in the pixel-snapped border box space of |
| // |fragment.root_fragment_data|. Adjust it to the containing transform node's |
| // space in which we will paint. |
| new_cull_rect.Move(PhysicalOffset( |
| RoundedIntPoint(fragment.root_fragment_data->PaintOffset()))); |
| return CullRect(PixelSnappedIntRect(new_cull_rect)); |
| } |
| |
| void PaintLayerPainter::PaintBackgroundForFragmentsWithPhase( |
| PaintPhase phase, |
| const PaintLayerFragments& layer_fragments, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& local_painting_info, |
| PaintLayerFlags paint_flags) { |
| ForAllFragments( |
| context, layer_fragments, [&](const PaintLayerFragment& fragment) { |
| CullRect cull_rect = |
| RuntimeEnabledFeatures::CullRectUpdateEnabled() |
| ? fragment.fragment_data->GetCullRect() |
| : LegacyCullRect(fragment, fragment.background_rect); |
| if (!cull_rect.Rect().IsEmpty()) { |
| PaintFragmentWithPhase(phase, fragment, context, cull_rect, |
| local_painting_info, paint_flags); |
| } |
| }); |
| } |
| |
| void PaintLayerPainter::PaintForegroundForFragments( |
| const PaintLayerFragments& layer_fragments, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& local_painting_info, |
| PaintLayerFlags paint_flags) { |
| PaintForegroundForFragmentsWithPhase( |
| PaintPhase::kDescendantBlockBackgroundsOnly, layer_fragments, context, |
| local_painting_info, paint_flags); |
| |
| if (paint_layer_.GetLayoutObject().GetDocument().InForcedColorsMode()) { |
| PaintForegroundForFragmentsWithPhase(PaintPhase::kForcedColorsModeBackplate, |
| layer_fragments, context, |
| local_painting_info, paint_flags); |
| } |
| |
| if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() || |
| paint_layer_.NeedsPaintPhaseFloat()) { |
| PaintForegroundForFragmentsWithPhase(PaintPhase::kFloat, layer_fragments, |
| context, local_painting_info, |
| paint_flags); |
| } |
| |
| PaintForegroundForFragmentsWithPhase(PaintPhase::kForeground, layer_fragments, |
| context, local_painting_info, |
| paint_flags); |
| |
| if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() || |
| paint_layer_.NeedsPaintPhaseDescendantOutlines()) { |
| PaintForegroundForFragmentsWithPhase(PaintPhase::kDescendantOutlinesOnly, |
| layer_fragments, context, |
| local_painting_info, paint_flags); |
| } |
| } |
| |
| void PaintLayerPainter::PaintForegroundForFragmentsWithPhase( |
| PaintPhase phase, |
| const PaintLayerFragments& layer_fragments, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& local_painting_info, |
| PaintLayerFlags paint_flags) { |
| ForAllFragments( |
| context, layer_fragments, [&](const PaintLayerFragment& fragment) { |
| CullRect cull_rect; |
| if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) { |
| // In CullRectUpdate, this function is the same as |
| // PaintBackgroundForFragmentsWithPhase(). The contents cull rect will |
| // be applied by ScopedBoxContentsPaintState. |
| cull_rect = fragment.fragment_data->GetCullRect(); |
| } else { |
| cull_rect = LegacyCullRect(fragment, fragment.foreground_rect); |
| } |
| if (!cull_rect.Rect().IsEmpty()) { |
| PaintFragmentWithPhase(phase, fragment, context, cull_rect, |
| local_painting_info, paint_flags); |
| } |
| }); |
| } |
| |
| void PaintLayerPainter::PaintOverlayOverflowControls( |
| GraphicsContext& context, |
| const CullRect& cull_rect, |
| const GlobalPaintFlags paint_flags) { |
| PaintLayerPaintingInfo painting_info(&paint_layer_, cull_rect, paint_flags, |
| PhysicalOffset()); |
| Paint(context, painting_info, kPaintLayerPaintingOverlayOverflowControls); |
| } |
| |
| } // namespace blink |