blob: cd5fa1569d9ac2b976d967172efd1724cee45452 [file] [log] [blame]
// 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/block_painter.h"
#include "base/optional.h"
#include "third_party/blink/renderer/core/editing/drag_caret.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/layout/api/line_layout_api_shim.h"
#include "third_party/blink/renderer/core/layout/api/line_layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_flexible_box.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/adjust_paint_offset_scope.h"
#include "third_party/blink/renderer/core/paint/block_flow_painter.h"
#include "third_party/blink/renderer/core/paint/box_clipper.h"
#include "third_party/blink/renderer/core/paint/box_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.h"
#include "third_party/blink/renderer/core/paint/scroll_recorder.h"
#include "third_party/blink/renderer/core/paint/scrollable_area_painter.h"
#include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
#include "third_party/blink/renderer/platform/graphics/paint/clip_recorder.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/paint/scroll_hit_test_display_item.h"
namespace blink {
DISABLE_CFI_PERF
void BlockPainter::Paint(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
AdjustPaintOffsetScope adjustment(layout_block_, paint_info, paint_offset);
auto adjusted_paint_offset = adjustment.AdjustedPaintOffset();
auto& local_paint_info = adjustment.MutablePaintInfo();
if (!IntersectsPaintRect(local_paint_info, adjusted_paint_offset))
return;
PaintPhase original_phase = local_paint_info.phase;
// There are some cases where not all clipped visual overflow is accounted
// for.
// FIXME: reduce the number of such cases.
ContentsClipBehavior contents_clip_behavior = kForceContentsClip;
if (layout_block_.ShouldClipOverflow() && !layout_block_.HasControlClip() &&
!layout_block_.ShouldPaintCarets())
contents_clip_behavior = kSkipContentsClipIfPossible;
if (original_phase == PaintPhase::kOutline) {
local_paint_info.phase = PaintPhase::kDescendantOutlinesOnly;
} else if (ShouldPaintSelfBlockBackground(original_phase)) {
local_paint_info.phase = PaintPhase::kSelfBlockBackgroundOnly;
layout_block_.PaintObject(local_paint_info, adjusted_paint_offset);
if (ShouldPaintDescendantBlockBackgrounds(original_phase))
local_paint_info.phase = PaintPhase::kDescendantBlockBackgroundsOnly;
}
if (original_phase != PaintPhase::kSelfBlockBackgroundOnly &&
original_phase != PaintPhase::kSelfOutlineOnly) {
base::Optional<BoxClipper> clipper;
// We have already applied clip in SVGForeignObjectClipper.
if (!layout_block_.IsSVGForeignObject() ||
RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) {
clipper.emplace(layout_block_, local_paint_info, adjusted_paint_offset,
contents_clip_behavior);
}
layout_block_.PaintObject(local_paint_info, adjusted_paint_offset);
}
if (ShouldPaintSelfOutline(original_phase)) {
local_paint_info.phase = PaintPhase::kSelfOutlineOnly;
layout_block_.PaintObject(local_paint_info, adjusted_paint_offset);
}
// Our scrollbar widgets paint exactly when we tell them to, so that they work
// properly with z-index. We paint after we painted the background/border, so
// that the scrollbars will sit above the background/border.
local_paint_info.phase = original_phase;
PaintOverflowControlsIfNeeded(local_paint_info, adjusted_paint_offset);
}
void BlockPainter::PaintOverflowControlsIfNeeded(
const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
if (layout_block_.HasOverflowClip() &&
layout_block_.Style()->Visibility() == EVisibility::kVisible &&
ShouldPaintSelfBlockBackground(paint_info.phase) &&
!paint_info.PaintRootBackgroundOnly()) {
base::Optional<ClipRecorder> clip_recorder;
if (!layout_block_.Layer()->IsSelfPaintingLayer()) {
LayoutRect clip_rect = layout_block_.BorderBoxRect();
clip_rect.MoveBy(paint_offset);
clip_recorder.emplace(paint_info.context, layout_block_,
DisplayItem::kClipScrollbarsToBoxBounds,
PixelSnappedIntRect(clip_rect));
}
ScrollableAreaPainter(*layout_block_.Layer()->GetScrollableArea())
.PaintOverflowControls(paint_info, RoundedIntPoint(paint_offset),
false /* painting_overlay_controls */);
}
}
void BlockPainter::PaintChildren(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
for (LayoutBox* child = layout_block_.FirstChildBox(); child;
child = child->NextSiblingBox())
PaintChild(*child, paint_info, paint_offset);
}
void BlockPainter::PaintChild(const LayoutBox& child,
const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
LayoutPoint child_point =
layout_block_.FlipForWritingModeForChildForPaint(&child, paint_offset);
if (!child.HasSelfPaintingLayer() && !child.IsFloating() &&
!child.IsColumnSpanAll())
child.Paint(paint_info, child_point);
}
void BlockPainter::PaintChildrenOfFlexibleBox(
const LayoutFlexibleBox& layout_flexible_box,
const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
for (const LayoutBox* child = layout_flexible_box.GetOrderIterator().First();
child; child = layout_flexible_box.GetOrderIterator().Next())
BlockPainter(layout_flexible_box)
.PaintAllChildPhasesAtomically(*child, paint_info, paint_offset);
}
void BlockPainter::PaintAllChildPhasesAtomically(
const LayoutBox& child,
const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
LayoutPoint child_point =
layout_block_.FlipForWritingModeForChildForPaint(&child, paint_offset);
if (!child.HasSelfPaintingLayer() && !child.IsFloating())
ObjectPainter(child).PaintAllPhasesAtomically(paint_info, child_point);
}
void BlockPainter::PaintInlineBox(const InlineBox& inline_box,
const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
if (paint_info.phase != PaintPhase::kForeground &&
paint_info.phase != PaintPhase::kSelection)
return;
// Text clips are painted only for the direct inline children of the object
// that has a text clip style on it, not block children.
DCHECK(paint_info.phase != PaintPhase::kTextClip);
LayoutPoint child_point = paint_offset;
if (inline_box.Parent()
->GetLineLayoutItem()
.Style()
->IsFlippedBlocksWritingMode()) {
// Faster than calling containingBlock().
child_point =
LineLayoutAPIShim::LayoutObjectFrom(inline_box.GetLineLayoutItem())
->ContainingBlock()
->FlipForWritingModeForChildForPaint(
ToLayoutBox(LineLayoutAPIShim::LayoutObjectFrom(
inline_box.GetLineLayoutItem())),
child_point);
}
ObjectPainter(
*LineLayoutAPIShim::ConstLayoutObjectFrom(inline_box.GetLineLayoutItem()))
.PaintAllPhasesAtomically(paint_info, child_point);
}
void BlockPainter::PaintScrollHitTestDisplayItem(const PaintInfo& paint_info) {
DCHECK(RuntimeEnabledFeatures::SlimmingPaintV2Enabled());
// Scroll hit test display items 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;
// The scroll hit test layer is in the unscrolled and unclipped space so the
// scroll hit test layer can be enlarged beyond the clip. This will let us fix
// crbug.com/753124 in the future where the scrolling element's border is hit
// test differently if composited.
const auto* fragment = paint_info.FragmentToPaint(layout_block_);
const auto* properties = fragment ? fragment->PaintProperties() : nullptr;
// Without RootLayerScrolling, the LayoutView will not create scroll paint
// properties and will rely on the LocalFrameView providing a scroll
// translation property.
if (!RuntimeEnabledFeatures::RootLayerScrollingEnabled() &&
layout_block_.IsLayoutView()) {
auto* view = layout_block_.GetFrame()->View();
if (view->ScrollTranslation() && view->ScrollTranslation()->ScrollNode()) {
// The scroll hit test is in the unscrolled unclipped space.
ScopedPaintChunkProperties scroll_hit_test_properties(
paint_info.context.GetPaintController(),
view->PreContentClipProperties(), layout_block_,
DisplayItem::kScrollHitTest);
ScrollHitTestDisplayItem::Record(paint_info.context, layout_block_,
DisplayItem::kScrollHitTest,
view->ScrollTranslation());
}
// The LayoutView should not create a scroll translation or scroll node,
// instead relying on the LocalFrameView's scroll translation and scroll.
DCHECK(!properties ||
(!properties->ScrollTranslation() && !properties->Scroll()));
return;
}
// If there is an associated scroll node, emit a scroll hit test display item.
if (properties && properties->Scroll()) {
DCHECK(properties->ScrollTranslation());
// The local border box properties are used instead of the contents
// properties so that the scroll hit test is not clipped or scrolled.
ScopedPaintChunkProperties scroll_hit_test_properties(
paint_info.context.GetPaintController(),
fragment->LocalBorderBoxProperties(), layout_block_,
DisplayItem::kScrollHitTest);
ScrollHitTestDisplayItem::Record(paint_info.context, layout_block_,
DisplayItem::kScrollHitTest,
properties->ScrollTranslation());
}
}
DISABLE_CFI_PERF
void BlockPainter::PaintObject(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
if (layout_block_.IsTruncated())
return;
const PaintPhase paint_phase = paint_info.phase;
if (ShouldPaintSelfBlockBackground(paint_phase)) {
if (layout_block_.Style()->Visibility() == EVisibility::kVisible &&
layout_block_.HasBoxDecorationBackground())
layout_block_.PaintBoxDecorationBackground(paint_info, paint_offset);
// Record the scroll hit test after the background so background squashing
// is not affected. Hit test order would be equivalent if this were
// immediately before the background.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
PaintScrollHitTestDisplayItem(paint_info);
// We're done. We don't bother painting any children.
if (paint_phase == PaintPhase::kSelfBlockBackgroundOnly)
return;
}
if (paint_info.PaintRootBackgroundOnly())
return;
if (paint_phase == PaintPhase::kMask &&
layout_block_.Style()->Visibility() == EVisibility::kVisible) {
layout_block_.PaintMask(paint_info, paint_offset);
return;
}
if (paint_phase == PaintPhase::kClippingMask &&
layout_block_.Style()->Visibility() == EVisibility::kVisible) {
// SPv175 always paints clipping mask in PaintLayerPainter.
DCHECK(!RuntimeEnabledFeatures::SlimmingPaintV175Enabled());
BoxPainter(layout_block_).PaintClippingMask(paint_info, paint_offset);
return;
}
if (paint_phase == PaintPhase::kForeground && paint_info.IsPrinting())
ObjectPainter(layout_block_)
.AddPDFURLRectIfNeeded(paint_info, paint_offset);
if (paint_phase != PaintPhase::kSelfOutlineOnly) {
base::Optional<ScopedPaintChunkProperties> scoped_scroll_property;
base::Optional<ScrollRecorder> scroll_recorder;
base::Optional<PaintInfo> scrolled_paint_info;
if (RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) {
if (const auto* fragment = paint_info.FragmentToPaint(layout_block_)) {
const auto* object_properties = fragment->PaintProperties();
auto* scroll_translation = object_properties
? object_properties->ScrollTranslation()
: nullptr;
if (scroll_translation) {
scoped_scroll_property.emplace(
paint_info.context.GetPaintController(), scroll_translation,
layout_block_, DisplayItem::PaintPhaseToScrollType(paint_phase));
scrolled_paint_info.emplace(paint_info);
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
scrolled_paint_info->UpdateCullRectForScrollingContents(
EnclosingIntRect(layout_block_.OverflowClipRect(paint_offset)),
scroll_translation->Matrix().ToAffineTransform());
} else {
scrolled_paint_info->UpdateCullRect(
scroll_translation->Matrix().ToAffineTransform());
}
}
}
} else if (layout_block_.HasOverflowClip()) {
IntSize scroll_offset = layout_block_.ScrolledContentOffset();
if (layout_block_.Layer()->ScrollsOverflow() || !scroll_offset.IsZero()) {
scroll_recorder.emplace(paint_info.context, layout_block_, paint_phase,
scroll_offset);
scrolled_paint_info.emplace(paint_info);
AffineTransform transform;
transform.Translate(-scroll_offset.Width(), -scroll_offset.Height());
scrolled_paint_info->UpdateCullRect(transform);
}
}
const PaintInfo& contents_paint_info =
scrolled_paint_info ? *scrolled_paint_info : paint_info;
if (layout_block_.IsLayoutBlockFlow()) {
BlockFlowPainter block_flow_painter(ToLayoutBlockFlow(layout_block_));
block_flow_painter.PaintContents(contents_paint_info, paint_offset);
if (paint_phase == PaintPhase::kFloat ||
paint_phase == PaintPhase::kSelection ||
paint_phase == PaintPhase::kTextClip)
block_flow_painter.PaintFloats(contents_paint_info, paint_offset);
} else {
PaintContents(contents_paint_info, paint_offset);
}
}
if (ShouldPaintSelfOutline(paint_phase))
ObjectPainter(layout_block_).PaintOutline(paint_info, paint_offset);
// If the caret's node's layout object's containing block is this block, and
// the paint action is PaintPhaseForeground, then paint the caret.
if (paint_phase == PaintPhase::kForeground &&
layout_block_.ShouldPaintCarets())
PaintCarets(paint_info, paint_offset);
}
void BlockPainter::PaintCarets(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
LocalFrame* frame = layout_block_.GetFrame();
if (layout_block_.ShouldPaintCursorCaret())
frame->Selection().PaintCaret(paint_info.context, paint_offset);
if (layout_block_.ShouldPaintDragCaret()) {
frame->GetPage()->GetDragCaret().PaintDragCaret(frame, paint_info.context,
paint_offset);
}
}
DISABLE_CFI_PERF
bool BlockPainter::IntersectsPaintRect(
const PaintInfo& paint_info,
const LayoutPoint& adjusted_paint_offset) const {
LayoutRect overflow_rect;
if (paint_info.IsPrinting() && layout_block_.IsAnonymousBlock() &&
layout_block_.ChildrenInline()) {
// For case <a href="..."><div>...</div></a>, when m_layoutBlock is the
// anonymous container of <a>, the anonymous container's visual overflow is
// empty, but we need to continue painting to output <a>'s PDF URL rect
// which covers the continuations, as if we included <a>'s PDF URL rect into
// m_layoutBlock's visual overflow.
Vector<LayoutRect> rects;
layout_block_.AddElementVisualOverflowRects(rects, LayoutPoint());
overflow_rect = UnionRect(rects);
}
overflow_rect.Unite(layout_block_.VisualOverflowRect());
bool uses_composited_scrolling = layout_block_.HasOverflowModel() &&
layout_block_.UsesCompositedScrolling();
if (uses_composited_scrolling) {
LayoutRect layout_overflow_rect = layout_block_.LayoutOverflowRect();
overflow_rect.Unite(layout_overflow_rect);
}
layout_block_.FlipForWritingMode(overflow_rect);
// Scrolling is applied in physical space, which is why it is after the flip
// above.
if (uses_composited_scrolling) {
overflow_rect.Move(-layout_block_.ScrolledContentOffset());
}
overflow_rect.MoveBy(adjusted_paint_offset);
return paint_info.GetCullRect().IntersectsCullRect(overflow_rect);
}
void BlockPainter::PaintContents(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) {
DCHECK(!layout_block_.ChildrenInline());
PaintInfo paint_info_for_descendants = paint_info.ForDescendants();
layout_block_.PaintChildren(paint_info_for_descendants, paint_offset);
}
} // namespace blink