| // 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/box_painter.h" |
| |
| #include "base/optional.h" |
| #include "third_party/blink/renderer/core/display_lock/display_lock_context.h" |
| #include "third_party/blink/renderer/core/layout/background_bleed_avoidance.h" |
| #include "third_party/blink/renderer/core/layout/layout_box.h" |
| #include "third_party/blink/renderer/core/layout/layout_object.h" |
| #include "third_party/blink/renderer/core/layout/layout_theme.h" |
| #include "third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.h" |
| #include "third_party/blink/renderer/core/paint/background_image_geometry.h" |
| #include "third_party/blink/renderer/core/paint/box_decoration_data.h" |
| #include "third_party/blink/renderer/core/paint/box_model_object_painter.h" |
| #include "third_party/blink/renderer/core/paint/box_painter_base.h" |
| #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h" |
| #include "third_party/blink/renderer/core/paint/nine_piece_image_painter.h" |
| #include "third_party/blink/renderer/core/paint/object_painter.h" |
| #include "third_party/blink/renderer/core/paint/paint_info.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/core/paint/rounded_border_geometry.h" |
| #include "third_party/blink/renderer/core/paint/scoped_paint_state.h" |
| #include "third_party/blink/renderer/core/paint/scrollable_area_painter.h" |
| #include "third_party/blink/renderer/core/paint/svg_foreign_object_painter.h" |
| #include "third_party/blink/renderer/core/paint/theme_painter.h" |
| #include "third_party/blink/renderer/platform/geometry/layout_point.h" |
| #include "third_party/blink/renderer/platform/geometry/length_functions.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/display_item_cache_skipper.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/scoped_paint_chunk_properties.h" |
| |
| namespace blink { |
| |
| void BoxPainter::Paint(const PaintInfo& paint_info) { |
| // Default implementation. Just pass paint through to the children. |
| ScopedPaintState paint_state(layout_box_, paint_info); |
| PaintChildren(paint_state.GetPaintInfo()); |
| } |
| |
| void BoxPainter::PaintChildren(const PaintInfo& paint_info) { |
| if (paint_info.DescendantPaintingBlocked()) |
| return; |
| |
| PaintInfo child_info(paint_info); |
| for (LayoutObject* child = layout_box_.SlowFirstChild(); child; |
| child = child->NextSibling()) { |
| if (auto* foreign_object = DynamicTo<LayoutSVGForeignObject>(*child)) { |
| SVGForeignObjectPainter(*foreign_object).PaintLayer(paint_info); |
| } else { |
| child->Paint(child_info); |
| } |
| } |
| } |
| |
| void BoxPainter::PaintBoxDecorationBackground( |
| const PaintInfo& paint_info, |
| const PhysicalOffset& paint_offset) { |
| if (layout_box_.StyleRef().Visibility() != EVisibility::kVisible) |
| return; |
| |
| PhysicalRect paint_rect; |
| const DisplayItemClient* background_client = nullptr; |
| base::Optional<ScopedBoxContentsPaintState> contents_paint_state; |
| bool painting_scrolling_background = |
| BoxDecorationData::IsPaintingScrollingBackground(paint_info, layout_box_); |
| IntRect visual_rect; |
| if (painting_scrolling_background) { |
| // For the case where we are painting the background into the scrolling |
| // contents layer of a composited scroller we need to include the entire |
| // overflow rect. |
| paint_rect = layout_box_.PhysicalLayoutOverflowRect(); |
| contents_paint_state.emplace(paint_info, paint_offset, layout_box_); |
| paint_rect.Move(contents_paint_state->PaintOffset()); |
| |
| // The background painting code assumes that the borders are part of the |
| // paint_rect so we expand the paint_rect by the border size when painting |
| // the background into the scrolling contents layer. |
| paint_rect.Expand(layout_box_.BorderBoxOutsets()); |
| |
| background_client = &layout_box_.GetScrollableArea() |
| ->GetScrollingBackgroundDisplayItemClient(); |
| visual_rect = |
| layout_box_.GetScrollableArea()->ScrollingBackgroundVisualRect( |
| paint_offset); |
| } else { |
| paint_rect = layout_box_.PhysicalBorderBoxRect(); |
| paint_rect.Move(paint_offset); |
| background_client = &layout_box_; |
| visual_rect = VisualRect(paint_offset); |
| } |
| |
| // Paint the background if we're visible and this block has a box decoration |
| // (background, border, appearance, or box shadow). |
| const ComputedStyle& style = layout_box_.StyleRef(); |
| if (style.Visibility() == EVisibility::kVisible && |
| layout_box_.HasBoxDecorationBackground()) { |
| PaintBoxDecorationBackgroundWithRect( |
| contents_paint_state ? contents_paint_state->GetPaintInfo() |
| : paint_info, |
| visual_rect, paint_rect, *background_client); |
| } |
| |
| RecordHitTestData(paint_info, paint_rect, *background_client); |
| |
| bool needs_scroll_hit_test = true; |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // Pre-CompositeAfterPaint, there is no need to emit scroll hit test |
| // display items for composited scrollers because these display items are |
| // only used to create non-fast scrollable regions for non-composited |
| // scrollers. With CompositeAfterPaint, we always paint the scroll hit |
| // test display items but ignore the non-fast region if the scroll was |
| // composited in PaintArtifactCompositor::UpdateNonFastScrollableRegions. |
| if (layout_box_.HasLayer() && |
| layout_box_.Layer()->GetCompositedLayerMapping() && |
| layout_box_.Layer() |
| ->GetCompositedLayerMapping() |
| ->ScrollingContentsLayer()) { |
| needs_scroll_hit_test = false; |
| } |
| } |
| |
| // Record the scroll hit test after the non-scrolling background so |
| // background squashing is not affected. Hit test order would be equivalent |
| // if this were immediately before the non-scrolling background. |
| if (!painting_scrolling_background && needs_scroll_hit_test) |
| RecordScrollHitTestData(paint_info, *background_client); |
| } |
| |
| void BoxPainter::PaintBoxDecorationBackgroundWithRect( |
| const PaintInfo& paint_info, |
| const IntRect& visual_rect, |
| const PhysicalRect& paint_rect, |
| const DisplayItemClient& background_client) { |
| const ComputedStyle& style = layout_box_.StyleRef(); |
| |
| base::Optional<DisplayItemCacheSkipper> cache_skipper; |
| if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() && |
| BoxPainterBase::ShouldSkipPaintUnderInvalidationChecking(layout_box_)) |
| cache_skipper.emplace(paint_info.context); |
| |
| BoxDecorationData box_decoration_data(paint_info, layout_box_); |
| if (!box_decoration_data.ShouldPaint()) |
| return; |
| |
| if (DrawingRecorder::UseCachedDrawingIfPossible( |
| paint_info.context, background_client, |
| DisplayItem::kBoxDecorationBackground)) |
| return; |
| |
| DrawingRecorder recorder(paint_info.context, background_client, |
| DisplayItem::kBoxDecorationBackground, visual_rect); |
| GraphicsContextStateSaver state_saver(paint_info.context, false); |
| |
| bool needs_end_layer = false; |
| // FIXME: Should eventually give the theme control over whether the box |
| // shadow should paint, since controls could have custom shadows of their |
| // own. |
| if (box_decoration_data.ShouldPaintShadow()) { |
| BoxPainterBase::PaintNormalBoxShadow( |
| paint_info, paint_rect, style, PhysicalBoxSides(), |
| !box_decoration_data.ShouldPaintBackground()); |
| } |
| |
| if (BleedAvoidanceIsClipping( |
| box_decoration_data.GetBackgroundBleedAvoidance())) { |
| state_saver.Save(); |
| FloatRoundedRect border = |
| RoundedBorderGeometry::PixelSnappedRoundedBorder(style, paint_rect); |
| paint_info.context.ClipRoundedRect(border); |
| |
| if (box_decoration_data.GetBackgroundBleedAvoidance() == |
| kBackgroundBleedClipLayer) { |
| paint_info.context.BeginLayer(); |
| needs_end_layer = true; |
| } |
| } |
| |
| // If we have a native theme appearance, paint that before painting our |
| // background. The theme will tell us whether or not we should also paint the |
| // CSS background. |
| IntRect snapped_paint_rect(PixelSnappedIntRect(paint_rect)); |
| ThemePainter& theme_painter = LayoutTheme::GetTheme().Painter(); |
| bool theme_painted = |
| box_decoration_data.HasAppearance() && |
| !theme_painter.Paint(layout_box_, paint_info, snapped_paint_rect); |
| if (!theme_painted) { |
| if (box_decoration_data.ShouldPaintBackground()) { |
| PaintBackground(paint_info, paint_rect, |
| box_decoration_data.BackgroundColor(), |
| box_decoration_data.GetBackgroundBleedAvoidance()); |
| } |
| if (box_decoration_data.HasAppearance()) { |
| theme_painter.PaintDecorations(layout_box_.GetNode(), |
| layout_box_.GetDocument(), style, |
| paint_info, snapped_paint_rect); |
| } |
| } |
| |
| if (box_decoration_data.ShouldPaintShadow()) { |
| BoxPainterBase::PaintInsetBoxShadowWithBorderRect(paint_info, paint_rect, |
| style); |
| } |
| |
| // The theme will tell us whether or not we should also paint the CSS |
| // border. |
| if (box_decoration_data.ShouldPaintBorder()) { |
| if (!theme_painted) { |
| theme_painted = |
| box_decoration_data.HasAppearance() && |
| !theme_painter.PaintBorderOnly(layout_box_.GetNode(), style, |
| paint_info, snapped_paint_rect); |
| } |
| if (!theme_painted) { |
| BoxPainterBase::PaintBorder( |
| layout_box_, layout_box_.GetDocument(), layout_box_.GeneratingNode(), |
| paint_info, paint_rect, style, |
| box_decoration_data.GetBackgroundBleedAvoidance()); |
| } |
| } |
| |
| if (needs_end_layer) |
| paint_info.context.EndLayer(); |
| } |
| |
| void BoxPainter::PaintBackground(const PaintInfo& paint_info, |
| const PhysicalRect& paint_rect, |
| const Color& background_color, |
| BackgroundBleedAvoidance bleed_avoidance) { |
| if (layout_box_.BackgroundTransfersToView()) |
| return; |
| if (layout_box_.BackgroundIsKnownToBeObscured()) |
| return; |
| BackgroundImageGeometry geometry(layout_box_); |
| BoxModelObjectPainter box_model_painter(layout_box_); |
| box_model_painter.PaintFillLayers(paint_info, background_color, |
| layout_box_.StyleRef().BackgroundLayers(), |
| paint_rect, geometry, bleed_avoidance); |
| } |
| |
| void BoxPainter::PaintMask(const PaintInfo& paint_info, |
| const PhysicalOffset& paint_offset) { |
| DCHECK_EQ(PaintPhase::kMask, paint_info.phase); |
| |
| if (!layout_box_.HasMask() || |
| layout_box_.StyleRef().Visibility() != EVisibility::kVisible) |
| return; |
| |
| if (DrawingRecorder::UseCachedDrawingIfPossible( |
| paint_info.context, layout_box_, paint_info.phase)) |
| return; |
| |
| PhysicalRect paint_rect(paint_offset, layout_box_.Size()); |
| BoxDrawingRecorder recorder(paint_info.context, layout_box_, paint_info.phase, |
| paint_offset); |
| PaintMaskImages(paint_info, paint_rect); |
| } |
| |
| void BoxPainter::PaintMaskImages(const PaintInfo& paint_info, |
| const PhysicalRect& paint_rect) { |
| // For mask images legacy layout painting handles multi-line boxes by giving |
| // the full width of the element, not the current line box, thereby clipping |
| // the offending edges. |
| BackgroundImageGeometry geometry(layout_box_); |
| BoxModelObjectPainter painter(layout_box_); |
| painter.PaintMaskImages(paint_info, paint_rect, layout_box_, geometry, |
| PhysicalBoxSides()); |
| } |
| |
| void BoxPainter::RecordHitTestData(const PaintInfo& paint_info, |
| const PhysicalRect& paint_rect, |
| const DisplayItemClient& background_client) { |
| // Hit test data are only needed for compositing. This flag is used for for |
| // printing and drag images which do not need hit testing. |
| if (paint_info.GetGlobalPaintFlags() & kGlobalPaintFlattenCompositingLayers) |
| return; |
| |
| // If an object is not visible, it does not participate in hit testing. |
| if (layout_box_.StyleRef().Visibility() != EVisibility::kVisible) |
| return; |
| |
| if (!paint_info.FragmentToPaint(layout_box_)) |
| return; |
| |
| paint_info.context.GetPaintController().RecordHitTestData( |
| background_client, PixelSnappedIntRect(paint_rect), |
| layout_box_.EffectiveAllowedTouchAction(), |
| layout_box_.InsideBlockingWheelEventHandler()); |
| } |
| |
| void BoxPainter::RecordScrollHitTestData( |
| const PaintInfo& paint_info, |
| const DisplayItemClient& background_client) { |
| // Scroll hit test data are only needed for compositing. This flag is used for |
| // printing and drag images which do not need hit testing. |
| if (paint_info.GetGlobalPaintFlags() & kGlobalPaintFlattenCompositingLayers) |
| return; |
| |
| // If an object is not visible, it does not scroll. |
| if (layout_box_.StyleRef().Visibility() != EVisibility::kVisible) |
| return; |
| |
| if (!layout_box_.GetScrollableArea()) |
| return; |
| |
| const auto* fragment = paint_info.FragmentToPaint(layout_box_); |
| if (!fragment) |
| return; |
| |
| // If there is an associated scroll node, emit scroll hit test data. |
| const auto* properties = fragment->PaintProperties(); |
| if (properties && properties->Scroll()) { |
| DCHECK(properties->ScrollTranslation()); |
| // We record scroll hit test data in the local border box properties |
| // instead of the contents properties so that the scroll hit test is not |
| // clipped or scrolled. |
| auto& paint_controller = paint_info.context.GetPaintController(); |
| DCHECK_EQ(fragment->LocalBorderBoxProperties(), |
| paint_controller.CurrentPaintChunkProperties()); |
| paint_controller.RecordScrollHitTestData( |
| background_client, DisplayItem::kScrollHitTest, |
| properties->ScrollTranslation(), VisualRect(fragment->PaintOffset())); |
| } |
| |
| ScrollableAreaPainter(*layout_box_.GetScrollableArea()) |
| .RecordResizerScrollHitTestData(paint_info.context, |
| fragment->PaintOffset()); |
| } |
| |
| IntRect BoxPainter::VisualRect(const PhysicalOffset& paint_offset) { |
| DCHECK(!layout_box_.VisualRectRespectsVisibility() || |
| layout_box_.StyleRef().Visibility() == EVisibility::kVisible); |
| PhysicalRect rect = layout_box_.PhysicalSelfVisualOverflowRect(); |
| rect.Move(paint_offset); |
| return EnclosingIntRect(rect); |
| } |
| |
| } // namespace blink |