blob: 26e03a779d02362c6bdcf49623417da8b44c2422 [file] [log] [blame]
// Copyright 2017 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/ng/ng_box_fragment_painter.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/frame/local_frame.h"
#include "third_party/blink/renderer/core/layout/background_bleed_avoidance.h"
#include "third_party/blink/renderer/core/layout/hit_test_location.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_table_cell.h"
#include "third_party/blink/renderer/core/layout/ng/geometry/ng_border_edges.h"
#include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/page/page.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/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/ng/ng_fieldset_painter.h"
#include "third_party/blink/renderer/core/paint/ng/ng_fragment_painter.h"
#include "third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h"
#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
#include "third_party/blink/renderer/core/paint/ng/ng_text_fragment_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/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/paint/paint_phase.h"
#include "third_party/blink/renderer/core/paint/paint_timing_detector.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/theme_painter.h"
#include "third_party/blink/renderer/core/scroll/scroll_types.h"
#include "third_party/blink/renderer/platform/geometry/layout_rect_outsets.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/hit_test_display_item.h"
#include "third_party/blink/renderer/platform/graphics/paint/scroll_hit_test_display_item.h"
namespace blink {
namespace {
LayoutRectOutsets BoxStrutToLayoutRectOutsets(
const NGPixelSnappedPhysicalBoxStrut& box_strut) {
return LayoutRectOutsets(
LayoutUnit(box_strut.top), LayoutUnit(box_strut.right),
LayoutUnit(box_strut.bottom), LayoutUnit(box_strut.left));
}
inline bool IsVisibleToPaint(const NGPhysicalFragment& fragment,
const ComputedStyle& style) {
return !fragment.IsHiddenForPaint() &&
style.Visibility() == EVisibility::kVisible;
}
bool FragmentVisibleToHitTestRequest(const NGPaintFragment& paint_fragment,
const HitTestRequest& request) {
const NGPhysicalFragment& fragment = paint_fragment.PhysicalFragment();
const ComputedStyle& style = fragment.Style();
return IsVisibleToPaint(fragment, style) &&
(request.IgnorePointerEventsNone() ||
style.PointerEvents() != EPointerEvents::kNone);
}
// Hit tests inline ancestor elements of |fragment| who do not have their own
// box fragments.
// @param physical_offset Physical offset of |fragment| in the paint layer.
bool HitTestCulledInlineAncestors(HitTestResult& result,
const NGPaintFragment& fragment,
const NGPaintFragment* previous_sibling,
const HitTestLocation& hit_test_location,
const PhysicalOffset& physical_offset) {
DCHECK(fragment.Parent());
DCHECK(fragment.PhysicalFragment().IsInline());
const NGPaintFragment& parent = *fragment.Parent();
// To be passed as |accumulated_offset| to LayoutInline::HitTestCulledInline,
// where it equals the physical offset of the containing block in paint layer.
const PhysicalOffset fallback_accumulated_offset =
physical_offset - fragment.InlineOffsetToContainerBox();
const LayoutObject* limit_layout_object =
parent.PhysicalFragment().IsLineBox() ? parent.Parent()->GetLayoutObject()
: parent.GetLayoutObject();
const LayoutObject* current_layout_object = fragment.GetLayoutObject();
for (LayoutObject* culled_parent = current_layout_object->Parent();
culled_parent && culled_parent != limit_layout_object;
culled_parent = culled_parent->Parent()) {
// |culled_parent| is a culled inline element to be hit tested, since it's
// "between" |fragment| and |fragment->Parent()| but doesn't have its own
// box fragment.
// To ensure the correct hit test ordering, |culled_parent| must be hit
// tested only once after all of its descendants are hit tested:
// - Shortcut: when |current_layout_object| is the only child (of
// |culled_parent|), since it's just hit tested, we can safely hit test its
// parent;
// - General case: we hit test |culled_parent| only when it is not an
// ancestor of |previous_sibling|; otherwise, |previous_sibling| has to be
// hit tested first.
// TODO(crbug.com/849331): It's wrong for bidi inline fragmentation. Fix it.
const bool has_sibling = current_layout_object->PreviousSibling() ||
current_layout_object->NextSibling();
if (has_sibling && previous_sibling &&
previous_sibling->GetLayoutObject()->IsDescendantOf(culled_parent))
break;
if (culled_parent->IsLayoutInline() &&
ToLayoutInline(culled_parent)
->HitTestCulledInline(result, hit_test_location,
fallback_accumulated_offset, &parent))
return true;
current_layout_object = culled_parent;
}
return false;
}
// Returns if this fragment may not be laid out by LayoutNG.
//
// This function is for an optimization to skip a few virtual
// calls. When this is |false|, we know |LayoutObject::Paint()| calls
// |NGBoxFragmentPainter|, and that we can instantiate a child
// |NGBoxFragmentPainter| directly. All code should work without this.
//
// TODO(kojii): This may become more complicated when we use
// |NGBoxFragmentPainter| for all fragments, and we still want this
// oprimization.
bool FragmentRequiresLegacyFallback(const NGPhysicalFragment& fragment) {
// Fallback to LayoutObject if this is a root of NG block layout.
// If this box is for this painter, LayoutNGBlockFlow will call this back.
// Otherwise it calls legacy painters.
return fragment.IsBlockFormattingContextRoot();
}
// Recursively build up backplates behind inline text boxes, each split at the
// paragraph level. Store the results in paragraph_backplates.
void BuildBackplate(const NGPaintFragment* line,
const PhysicalOffset& paint_offset,
PhysicalRect* current_backplate,
int* consecutive_line_breaks,
Vector<PhysicalRect>* paragraph_backplates) {
DCHECK(current_backplate && consecutive_line_breaks && paragraph_backplates);
// The number of consecutive forced breaks that split the backplate by
// paragraph.
static constexpr int kMaxConsecutiveLineBreaks = 2;
// Build up and paint backplates of all child inline text boxes. We are not
// able to simply use the linebox rect to compute the backplate because the
// backplate should only be painted for inline text and not for atomic
// inlines.
for (const NGPaintFragment* child : line->Children()) {
const NGPhysicalFragment& child_fragment = child->PhysicalFragment();
if (child_fragment.IsHiddenForPaint() || child_fragment.IsFloating())
continue;
if (auto* text_fragment =
DynamicTo<NGPhysicalTextFragment>(child_fragment)) {
if (text_fragment->IsLineBreak()) {
(*consecutive_line_breaks)++;
continue;
}
if (*consecutive_line_breaks >= kMaxConsecutiveLineBreaks) {
// This is a paragraph point.
paragraph_backplates->push_back(*current_backplate);
*current_backplate = PhysicalRect();
}
*consecutive_line_breaks = 0;
PhysicalRect box_rect(child->InlineOffsetToContainerBox() + paint_offset,
child->Size());
current_backplate->Unite(box_rect);
}
if (child_fragment.Type() == NGPhysicalFragment::kFragmentBox) {
// If a fragment box was reached, continue to recursively build
// up the backplate.
BuildBackplate(child, paint_offset, current_backplate,
consecutive_line_breaks, paragraph_backplates);
}
}
}
// Returns a vector of backplates that surround the paragraphs of text within
// line_boxes.
Vector<PhysicalRect> BuildBackplate(const NGPaintFragment::ChildList line_boxes,
const PhysicalOffset& paint_offset) {
Vector<PhysicalRect> paragraph_backplates;
PhysicalRect current_backplate;
int consecutive_line_breaks = 0;
for (const NGPaintFragment* line : line_boxes) {
// Recursively build up and paint backplates for line boxes containing text.
BuildBackplate(line, paint_offset, &current_backplate,
&consecutive_line_breaks, &paragraph_backplates);
}
if (!current_backplate.IsEmpty())
paragraph_backplates.push_back(current_backplate);
return paragraph_backplates;
}
} // anonymous namespace
const NGBorderEdges& NGBoxFragmentPainter::BorderEdges() const {
if (border_edges_.has_value())
return *border_edges_;
const NGPhysicalBoxFragment& fragment = PhysicalFragment();
border_edges_ = NGBorderEdges::FromPhysical(
fragment.BorderEdges(), fragment.Style().GetWritingMode());
return *border_edges_;
}
void NGBoxFragmentPainter::Paint(const PaintInfo& paint_info) {
if (PhysicalFragment().IsAtomicInline() &&
!box_fragment_.HasSelfPaintingLayer())
PaintAtomicInline(paint_info);
else
PaintInternal(paint_info);
}
void NGBoxFragmentPainter::PaintInternal(const PaintInfo& paint_info) {
ScopedPaintState paint_state(box_fragment_, paint_info);
if (!ShouldPaint(paint_state))
return;
PaintInfo& info = paint_state.MutablePaintInfo();
PhysicalOffset paint_offset = paint_state.PaintOffset();
PaintPhase original_phase = info.phase;
if (original_phase == PaintPhase::kOutline) {
info.phase = PaintPhase::kDescendantOutlinesOnly;
} else if (ShouldPaintSelfBlockBackground(original_phase)) {
info.phase = PaintPhase::kSelfBlockBackgroundOnly;
PaintObject(info, paint_offset);
if (ShouldPaintDescendantBlockBackgrounds(original_phase))
info.phase = PaintPhase::kDescendantBlockBackgroundsOnly;
}
if (original_phase != PaintPhase::kSelfBlockBackgroundOnly &&
original_phase != PaintPhase::kSelfOutlineOnly) {
if ((original_phase == PaintPhase::kForeground ||
original_phase == PaintPhase::kFloat ||
original_phase == PaintPhase::kForcedColorsModeBackplate ||
original_phase == PaintPhase::kDescendantOutlinesOnly) &&
box_fragment_.GetLayoutObject()->IsBox()) {
ScopedBoxContentsPaintState contents_paint_state(
paint_state, ToLayoutBox(*box_fragment_.GetLayoutObject()));
PaintObject(contents_paint_state.GetPaintInfo(),
contents_paint_state.PaintOffset());
} else {
PaintObject(info, paint_offset);
}
}
if (ShouldPaintSelfOutline(original_phase)) {
info.phase = PaintPhase::kSelfOutlineOnly;
PaintObject(info, paint_offset);
}
// We paint scrollbars after we painted other things, so that the scrollbars
// will sit above them.
info.phase = original_phase;
if (box_fragment_.HasOverflowClip()) {
ScrollableAreaPainter(*PhysicalFragment().Layer()->GetScrollableArea())
.PaintOverflowControls(info, RoundedIntPoint(paint_offset));
}
}
void NGBoxFragmentPainter::RecordScrollHitTestData(
const PaintInfo& paint_info,
const DisplayItemClient& background_client) {
DCHECK(RuntimeEnabledFeatures::PaintNonFastScrollableRegionsEnabled());
// 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;
// If an object is not visible, it does not scroll.
if (!IsVisibleToPaint(PhysicalFragment(), box_fragment_.Style()))
return;
// Only create scroll hit test data for objects that scroll.
const auto* layer = PhysicalFragment().Layer();
if (!layer || !layer->GetScrollableArea() ||
!layer->GetScrollableArea()->ScrollsOverflow()) {
return;
}
// TODO(pdr): Break dependency on LayoutObject functionality.
const LayoutObject& layout_object = *box_fragment_.GetLayoutObject();
const auto* fragment = paint_info.FragmentToPaint(layout_object);
const auto* properties = fragment ? fragment->PaintProperties() : nullptr;
// 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(), background_client,
DisplayItem::kScrollHitTest);
ScrollHitTestDisplayItem::Record(
paint_info.context, background_client, DisplayItem::kScrollHitTest,
properties->ScrollTranslation(), fragment->VisualRect());
}
}
void NGBoxFragmentPainter::RecordHitTestDataForLine(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset,
const NGPaintFragment& line) {
PhysicalRect border_box = line.PhysicalFragment().LocalRect();
border_box.offset += paint_offset;
HitTestDisplayItem::Record(
paint_info.context, line,
HitTestRect(border_box.ToLayoutRect(),
PhysicalFragment().EffectiveAllowedTouchAction()));
}
void NGBoxFragmentPainter::PaintObject(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset,
bool suppress_box_decoration_background) {
const PaintPhase paint_phase = paint_info.phase;
const NGPhysicalBoxFragment& physical_box_fragment = PhysicalFragment();
const ComputedStyle& style = box_fragment_.Style();
bool is_visible = IsVisibleToPaint(physical_box_fragment, style);
if (!is_visible)
suppress_box_decoration_background = true;
if (ShouldPaintSelfBlockBackground(paint_phase)) {
PaintBoxDecorationBackground(paint_info, paint_offset,
suppress_box_decoration_background);
// We're done. We don't bother painting any children.
if (paint_phase == PaintPhase::kSelfBlockBackgroundOnly)
return;
}
if (paint_phase == PaintPhase::kMask && is_visible)
return PaintMask(paint_info, paint_offset);
if (paint_phase == PaintPhase::kForeground && paint_info.IsPrinting()) {
NGFragmentPainter(box_fragment_, paint_fragment_)
.AddPDFURLRectIfNeeded(paint_info, paint_offset);
}
if (paint_phase != PaintPhase::kSelfOutlineOnly &&
!physical_box_fragment.Children().empty() &&
!paint_info.DescendantPaintingBlocked()) {
if (physical_box_fragment.ChildrenInline()) {
DCHECK(paint_fragment_ || PhysicalFragment().HasItems());
if (paint_phase != PaintPhase::kFloat) {
if (physical_box_fragment.IsBlockFlow()) {
PaintBlockFlowContents(paint_info, paint_offset);
} else if (ShouldPaintDescendantOutlines(paint_info.phase)) {
// TODO(kojii): |PaintInlineChildrenOutlines()| should do the work
// instead. Legacy does so, and is more efficient. But NG outline
// logic currently depends on |PaintInlineChildren()|.
PaintInlineChildren(paint_fragment_->Children(),
paint_info.ForDescendants(), paint_offset);
} else {
PaintInlineChildren(paint_fragment_->Children(), paint_info,
paint_offset);
}
}
if (paint_phase == PaintPhase::kFloat ||
paint_phase == PaintPhase::kSelection ||
paint_phase == PaintPhase::kTextClip) {
if (physical_box_fragment.HasFloatingDescendants())
PaintFloats(paint_info);
}
} else {
if (paint_phase != PaintPhase::kFloat) {
PaintBlockChildren(paint_info);
}
if (paint_phase == PaintPhase::kFloat ||
paint_phase == PaintPhase::kSelection ||
paint_phase == PaintPhase::kTextClip) {
PaintFloats(paint_info);
}
}
}
if (ShouldPaintSelfOutline(paint_phase)) {
NGFragmentPainter(box_fragment_, paint_fragment_)
.PaintOutline(paint_info, paint_offset);
}
// If the caret's node's fragment's containing block is this block, and
// the paint action is PaintPhaseForeground, then paint the caret.
if (paint_phase == PaintPhase::kForeground &&
physical_box_fragment.ShouldPaintCarets())
PaintCarets(paint_info, paint_offset);
}
void NGBoxFragmentPainter::PaintCarets(const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
const NGPhysicalBoxFragment& fragment = PhysicalFragment();
LocalFrame* frame = fragment.GetLayoutObject()->GetFrame();
if (fragment.ShouldPaintCursorCaret())
frame->Selection().PaintCaret(paint_info.context, paint_offset);
if (fragment.ShouldPaintDragCaret()) {
frame->GetPage()->GetDragCaret().PaintDragCaret(frame, paint_info.context,
paint_offset);
}
}
void NGBoxFragmentPainter::PaintBlockFlowContents(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
const NGPhysicalBoxFragment& fragment = PhysicalFragment();
const LayoutObject* layout_object = fragment.GetLayoutObject();
DCHECK(fragment.ChildrenInline());
DCHECK(paint_fragment_ || items_);
if (items_) {
PaintInlineItems(paint_info.ForDescendants(), paint_offset);
return;
}
// Check if there were contents to be painted and return early if none.
// The union of |ContentsInkOverflow()| and |LocalRect()| covers the rect to
// check, in both cases of:
// 1. Painting non-scrolling contents.
// 2. Painting scrolling contents.
// For 1, check with |ContentsInkOverflow()|, except when there is no
// overflow, in which case check with |LocalRect()|. For 2, check with
// |LayoutOverflow()|, but this can be approximiated with
// |ContentsInkOverflow()|.
PhysicalRect content_ink_rect = fragment.LocalRect();
content_ink_rect.Unite(paint_fragment_->ContentsInkOverflow());
content_ink_rect.offset += PhysicalOffset(paint_offset);
if (!paint_info.GetCullRect().Intersects(content_ink_rect.ToLayoutRect()))
return;
if (paint_info.phase == PaintPhase::kMask) {
PaintMask(paint_info, paint_offset);
return;
}
DCHECK(layout_object->IsLayoutBlockFlow());
const auto& layout_block = To<LayoutBlock>(*layout_object);
DCHECK(layout_block.ChildrenInline());
PaintLineBoxChildren(paint_fragment_->Children(), paint_info.ForDescendants(),
paint_offset);
}
void NGBoxFragmentPainter::PaintBlockChildren(const PaintInfo& paint_info) {
DCHECK(!box_fragment_.ChildrenInline());
DCHECK(!box_fragment_.GetLayoutObject()->ChildrenInline());
PaintInfo paint_info_for_descendants = paint_info.ForDescendants();
for (const NGLink& child : box_fragment_.Children()) {
const NGPhysicalFragment& child_fragment = *child;
if (child_fragment.HasSelfPaintingLayer() || child_fragment.IsFloating())
continue;
if (child_fragment.Type() == NGPhysicalFragment::kFragmentBox) {
// TODO(kojii): We could skip going through |LayoutObject| when we know
// children are always laid out by NG. See
// |FragmentRequiresLegacyFallback|.
child_fragment.GetLayoutObject()->Paint(paint_info_for_descendants);
} else {
DCHECK_EQ(child_fragment.Type(),
NGPhysicalFragment::kFragmentRenderedLegend);
}
}
}
void NGBoxFragmentPainter::PaintInlineFloatingChildren(
NGPaintFragment::ChildList children,
const PaintInfo& paint_info) {
for (const NGPaintFragment* child : children) {
const NGPhysicalFragment& child_fragment = child->PhysicalFragment();
if (child_fragment.HasSelfPaintingLayer())
continue;
if (child_fragment.IsFloating()) {
// TODO(kojii): The float is outside of the inline formatting context and
// that it maybe another NG inline formatting context, NG block layout, or
// legacy. NGBoxFragmentPainter can handle only the first case. In order
// to cover more tests for other two cases, we always fallback to legacy,
// which will forward back to NGBoxFragmentPainter if the float is for
// NGBoxFragmentPainter. We can shortcut this for the first case when
// we're more stable.
ObjectPainter(*child_fragment.GetLayoutObject())
.PaintAllPhasesAtomically(paint_info);
continue;
}
if (const auto* child_container =
DynamicTo<NGPhysicalContainerFragment>(&child_fragment)) {
if (child_container->HasFloatingDescendants())
PaintInlineFloatingChildren(child->Children(), paint_info);
}
}
}
void NGBoxFragmentPainter::PaintBlockFloatingChildren(
const NGPhysicalContainerFragment& container,
const PaintInfo& paint_info) {
for (const NGLink& child : container.Children()) {
const NGPhysicalFragment& child_fragment = *child;
if (child_fragment.HasSelfPaintingLayer())
continue;
if (child_fragment.IsFloating()) {
// TODO(kojii): The float is outside of the inline formatting context and
// that it maybe another NG inline formatting context, NG block layout, or
// legacy. NGBoxFragmentPainter can handle only the first case. In order
// to cover more tests for other two cases, we always fallback to legacy,
// which will forward back to NGBoxFragmentPainter if the float is for
// NGBoxFragmentPainter. We can shortcut this for the first case when
// we're more stable.
ObjectPainter(*child_fragment.GetLayoutObject())
.PaintAllPhasesAtomically(paint_info);
continue;
}
if (const auto* child_container =
DynamicTo<NGPhysicalContainerFragment>(&child_fragment)) {
PaintBlockFloatingChildren(*child_container, paint_info);
}
}
}
void NGBoxFragmentPainter::PaintFloats(const PaintInfo& paint_info) {
DCHECK(PhysicalFragment().HasFloatingDescendants() ||
!PhysicalFragment().ChildrenInline());
PaintInfo float_paint_info(paint_info);
if (paint_info.phase == PaintPhase::kFloat)
float_paint_info.phase = PaintPhase::kForeground;
if (paint_fragment_) {
PaintInlineFloatingChildren(paint_fragment_->Children(), float_paint_info);
return;
}
PaintBlockFloatingChildren(PhysicalFragment(), float_paint_info);
}
void NGBoxFragmentPainter::PaintMask(const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
DCHECK_EQ(PaintPhase::kMask, paint_info.phase);
const NGPhysicalBoxFragment& physical_box_fragment = PhysicalFragment();
const ComputedStyle& style = physical_box_fragment.Style();
if (!style.HasMask() || !IsVisibleToPaint(physical_box_fragment, style))
return;
if (DrawingRecorder::UseCachedDrawingIfPossible(
paint_info.context, GetDisplayItemClient(), paint_info.phase))
return;
// TODO(eae): Switch to LayoutNG version of BackgroundImageGeometry.
BackgroundImageGeometry geometry(*static_cast<const LayoutBoxModelObject*>(
box_fragment_.GetLayoutObject()));
DrawingRecorder recorder(paint_info.context, GetDisplayItemClient(),
paint_info.phase);
PhysicalRect paint_rect(paint_offset, box_fragment_.Size());
const NGBorderEdges& border_edges = BorderEdges();
PaintMaskImages(paint_info, paint_rect, *box_fragment_.GetLayoutObject(),
geometry, border_edges.line_left, border_edges.line_right);
}
// TODO(kojii): This logic is kept in sync with BoxPainter. Not much efforts to
// eliminate LayoutObject dependency were done yet.
void NGBoxFragmentPainter::PaintBoxDecorationBackground(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset,
bool suppress_box_decoration_background) {
// TODO(mstensho): Break dependency on LayoutObject functionality.
const LayoutObject& layout_object = *box_fragment_.GetLayoutObject();
PhysicalRect paint_rect;
const DisplayItemClient* background_client = nullptr;
base::Optional<ScopedBoxContentsPaintState> contents_paint_state;
bool painting_scrolling_background =
IsPaintingScrollingBackground(paint_info);
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.
const LayoutBox& layout_box = ToLayoutBox(layout_object);
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
// paintRect so we expand the paintRect 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();
} else {
paint_rect.offset = paint_offset;
paint_rect.size = box_fragment_.Size();
background_client = &GetDisplayItemClient();
}
if (!suppress_box_decoration_background) {
// The fieldset painter is not skipped when there is no background because
// the legend needs to paint.
if (PhysicalFragment().IsFieldsetContainer()) {
NGFieldsetPainter(box_fragment_)
.PaintBoxDecorationBackground(paint_info, paint_offset);
} else if (box_fragment_.Style().HasBoxDecorationBackground()) {
PaintBoxDecorationBackgroundWithRect(
contents_paint_state ? contents_paint_state->GetPaintInfo()
: paint_info,
paint_rect, *background_client);
}
}
if (NGFragmentPainter::ShouldRecordHitTestData(paint_info,
PhysicalFragment())) {
HitTestDisplayItem::Record(
paint_info.context, *background_client,
HitTestRect(paint_rect.ToLayoutRect(),
PhysicalFragment().EffectiveAllowedTouchAction()));
}
if (RuntimeEnabledFeatures::PaintNonFastScrollableRegionsEnabled()) {
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.
const auto* layer = PhysicalFragment().Layer();
if (layer && layer->GetCompositedLayerMapping() &&
layer->GetCompositedLayerMapping()->HasScrollingLayer()) {
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);
}
}
// TODO(kojii): This logic is kept in sync with BoxPainter. Not much efforts to
// eliminate LayoutObject dependency were done yet.
bool NGBoxFragmentPainter::BackgroundIsKnownToBeOpaque(
const PaintInfo& paint_info) {
const LayoutBox& layout_box = ToLayoutBox(*box_fragment_.GetLayoutObject());
// If the box has multiple fragments, its VisualRect is the bounding box of
// all fragments' visual rects, which is likely to cover areas that are not
// covered by painted background.
if (layout_box.FirstFragment().NextFragment())
return false;
PhysicalRect bounds = IsPaintingScrollingBackground(paint_info)
? layout_box.PhysicalLayoutOverflowRect()
: layout_box.PhysicalSelfVisualOverflowRect();
return layout_box.BackgroundIsKnownToBeOpaqueInRect(bounds);
}
// TODO(kojii): This logic is kept in sync with BoxPainter. Not much efforts to
// eliminate LayoutObject dependency were done yet.
void NGBoxFragmentPainter::PaintBoxDecorationBackgroundWithRect(
const PaintInfo& paint_info,
const PhysicalRect& paint_rect,
const DisplayItemClient& background_client) {
const LayoutObject& layout_object = *box_fragment_.GetLayoutObject();
const LayoutBox& layout_box = ToLayoutBox(layout_object);
const ComputedStyle& style = box_fragment_.Style();
base::Optional<DisplayItemCacheSkipper> cache_skipper;
// Disable cache in under-invalidation checking mode for MediaSliderPart
// because we always paint using the latest data (buffered ranges, current
// time and duration) which may be different from the cached data, and for
// delayed-invalidation object because it may change before it's actually
// invalidated. Note that we still report harmless under-invalidation of
// non-delayed-invalidation animated background, which should be ignored.
if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
(style.EffectiveAppearance() == kMediaSliderPart ||
layout_box.ShouldDelayFullPaintInvalidation())) {
cache_skipper.emplace(paint_info.context);
}
BoxDecorationData box_decoration_data(paint_info, PhysicalFragment());
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);
GraphicsContextStateSaver state_saver(paint_info.context, false);
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() &&
paint_rect.EdgesOnPixelBoundaries() &&
BackgroundIsKnownToBeOpaque(paint_info))
recorder.SetKnownToBeOpaque();
const NGBorderEdges& border_edges = BorderEdges();
if (box_decoration_data.ShouldPaintShadow()) {
PaintNormalBoxShadow(paint_info, paint_rect, style, border_edges.line_left,
border_edges.line_right,
!box_decoration_data.ShouldPaintBackground());
}
bool needs_end_layer = false;
if (!box_decoration_data.IsPaintingScrollingBackground()) {
if (box_fragment_.HasSelfPaintingLayer() && layout_box.IsTableCell() &&
ToInterface<LayoutNGTableCellInterface>(layout_box)
.TableInterface()
->ShouldCollapseBorders()) {
// We have to clip here because the background would paint on top of the
// collapsed table borders otherwise, since this is a self-painting layer.
PhysicalRect clip_rect = paint_rect;
clip_rect.Expand(layout_box.BorderInsets());
state_saver.Save();
paint_info.context.Clip(PixelSnappedIntRect(clip_rect));
} else if (BleedAvoidanceIsClipping(
box_decoration_data.GetBackgroundBleedAvoidance())) {
state_saver.Save();
FloatRoundedRect border = style.GetRoundedBorderFor(
paint_rect.ToLayoutRect(), border_edges.line_left,
border_edges.line_right);
paint_info.context.ClipRoundedRect(border);
if (box_decoration_data.GetBackgroundBleedAvoidance() ==
kBackgroundBleedClipLayer) {
paint_info.context.BeginLayer();
needs_end_layer = true;
}
}
}
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()) {
PaintInsetBoxShadowWithBorderRect(paint_info, paint_rect, style,
border_edges.line_left,
border_edges.line_right);
}
// 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() &&
!LayoutTheme::GetTheme().Painter().PaintBorderOnly(
layout_box.GetNode(), style, paint_info, snapped_paint_rect);
}
if (!theme_painted) {
Node* generating_node = layout_object.GeneratingNode();
const Document& document = layout_object.GetDocument();
PaintBorder(*box_fragment_.GetLayoutObject(), document, generating_node,
paint_info, paint_rect, style,
box_decoration_data.GetBackgroundBleedAvoidance(),
border_edges.line_left, border_edges.line_right);
}
}
if (needs_end_layer)
paint_info.context.EndLayer();
}
// TODO(kojii): This logic is kept in sync with BoxPainter. Not much efforts to
// eliminate LayoutObject dependency were done yet.
void NGBoxFragmentPainter::PaintBackground(
const PaintInfo& paint_info,
const PhysicalRect& paint_rect,
const Color& background_color,
BackgroundBleedAvoidance bleed_avoidance) {
const LayoutObject& layout_object = *box_fragment_.GetLayoutObject();
const LayoutBox& layout_box = ToLayoutBox(layout_object);
if (layout_box.BackgroundTransfersToView())
return;
if (layout_box.BackgroundIsKnownToBeObscured())
return;
// TODO(eae): Switch to LayoutNG version of BackgroundImageGeometry.
BackgroundImageGeometry geometry(*static_cast<const LayoutBoxModelObject*>(
box_fragment_.GetLayoutObject()));
PaintFillLayers(paint_info, background_color,
box_fragment_.Style().BackgroundLayers(), paint_rect,
geometry, bleed_avoidance);
}
void NGBoxFragmentPainter::PaintInlineChildBoxUsingLegacyFallback(
const NGPhysicalFragment& fragment,
const PaintInfo& paint_info) {
const LayoutObject* child_layout_object = fragment.GetLayoutObject();
DCHECK(child_layout_object);
if (child_layout_object->PaintFragment()) {
// This object will use NGBoxFragmentPainter.
child_layout_object->Paint(paint_info);
return;
}
if (child_layout_object->IsAtomicInlineLevel()) {
// Pre-NG painters also expect callers to use |PaintAllPhasesAtomically()|
// for atomic inlines.
ObjectPainter(*child_layout_object).PaintAllPhasesAtomically(paint_info);
return;
}
child_layout_object->Paint(paint_info);
}
void NGBoxFragmentPainter::PaintAllPhasesAtomically(
const PaintInfo& paint_info) {
// Self-painting AtomicInlines should go to normal paint logic.
DCHECK(!(PhysicalFragment().IsAtomicInline() &&
box_fragment_.HasSelfPaintingLayer()));
// Pass PaintPhaseSelection and PaintPhaseTextClip is handled by the regular
// foreground paint implementation. We don't need complete painting for these
// phases.
PaintPhase phase = paint_info.phase;
if (phase == PaintPhase::kSelection || phase == PaintPhase::kTextClip)
return PaintInternal(paint_info);
if (phase != PaintPhase::kForeground)
return;
PaintInfo local_paint_info(paint_info);
local_paint_info.phase = PaintPhase::kBlockBackground;
PaintInternal(local_paint_info);
local_paint_info.phase = PaintPhase::kForcedColorsModeBackplate;
PaintInternal(local_paint_info);
local_paint_info.phase = PaintPhase::kFloat;
PaintInternal(local_paint_info);
local_paint_info.phase = PaintPhase::kForeground;
PaintInternal(local_paint_info);
local_paint_info.phase = PaintPhase::kOutline;
PaintInternal(local_paint_info);
}
void NGBoxFragmentPainter::PaintInlineItems(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
DCHECK(items_);
ScopedPaintTimingDetectorBlockPaintHook
scoped_paint_timing_detector_block_paint_hook;
// TODO(kojii): Copy more from |PaintLineBoxChildren|.
NGInlineCursor cursor(*items_);
while (cursor) {
const NGFragmentItem* item = cursor.CurrentItem();
DCHECK(item);
switch (item->Type()) {
case NGFragmentItem::kText:
PaintTextItem(cursor, paint_info, paint_offset);
break;
case NGFragmentItem::kGeneratedText:
// TODO(kojii): Implement.
break;
case NGFragmentItem::kLine:
// TODO(kojii): Implement.
break;
case NGFragmentItem::kBox:
if (PaintBoxItem(*item, paint_info, paint_offset) == kSkipChildren) {
cursor.MoveToNextSkippingChildren();
continue;
}
break;
}
cursor.MoveToNext();
}
}
void NGBoxFragmentPainter::PaintLineBoxChildren(
NGPaintFragment::ChildList line_boxes,
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
// Only paint during the foreground/selection phases.
if (paint_info.phase != PaintPhase::kForeground &&
paint_info.phase != PaintPhase::kForcedColorsModeBackplate &&
paint_info.phase != PaintPhase::kSelection &&
paint_info.phase != PaintPhase::kTextClip &&
paint_info.phase != PaintPhase::kMask &&
paint_info.phase != PaintPhase::kDescendantOutlinesOnly &&
paint_info.phase != PaintPhase::kOutline)
return;
// The only way an inline could paint like this is if it has a layer.
const auto* layout_object = box_fragment_.GetLayoutObject();
DCHECK(layout_object->IsLayoutBlock() ||
(layout_object->IsLayoutInline() && layout_object->HasLayer()));
// if (paint_info.phase == PaintPhase::kForeground && paint_info.IsPrinting())
// AddPDFURLRectsForInlineChildrenRecursively(layout_object, paint_info,
// paint_offset);
// If we have no lines then we have no work to do.
if (line_boxes.IsEmpty())
return;
ScopedPaintTimingDetectorBlockPaintHook
scoped_paint_timing_detector_block_paint_hook;
const auto& layout_block = To<LayoutBlock>(*layout_object);
if (RuntimeEnabledFeatures::FirstContentfulPaintPlusPlusEnabled() ||
RuntimeEnabledFeatures::ElementTimingEnabled(
&layout_block.GetDocument())) {
if (paint_info.phase == PaintPhase::kForeground) {
scoped_paint_timing_detector_block_paint_hook.EmplaceIfNeeded(
layout_block, paint_info.context.GetPaintController()
.CurrentPaintChunkProperties());
}
}
if (paint_info.phase == PaintPhase::kForcedColorsModeBackplate &&
layout_block.GetDocument().InForcedColorsMode()) {
PaintBackplate(line_boxes, paint_info, paint_offset);
return;
}
const bool is_horizontal = box_fragment_.Style().IsHorizontalWritingMode();
for (const NGPaintFragment* line : line_boxes) {
const NGPhysicalFragment& child_fragment = line->PhysicalFragment();
DCHECK(!child_fragment.IsOutOfFlowPositioned());
if (child_fragment.IsFloating())
continue;
// Check if CullRect intersects with this child, only in block direction
// because soft-wrap and <br> needs to paint outside of InkOverflow() in
// inline direction.
const PhysicalOffset child_offset = paint_offset + line->Offset();
PhysicalRect child_rect = line->InkOverflow();
if (is_horizontal) {
LayoutUnit y = child_rect.offset.top + child_offset.top;
if (!paint_info.GetCullRect().IntersectsVerticalRange(
y, y + child_rect.size.height))
continue;
} else {
LayoutUnit x = child_rect.offset.left + child_offset.left;
if (!paint_info.GetCullRect().IntersectsHorizontalRange(
x, x + child_rect.size.width))
continue;
}
if (child_fragment.IsListMarker()) {
PaintAtomicInlineChild(*line, paint_info);
continue;
}
DCHECK(child_fragment.IsLineBox());
if (paint_info.phase == PaintPhase::kForeground) {
if (NGFragmentPainter::ShouldRecordHitTestData(paint_info,
PhysicalFragment())) {
RecordHitTestDataForLine(paint_info, child_offset, *line);
}
// Line boxes don't paint anything, except when its ::first-line style has
// a background.
if (UNLIKELY(NGLineBoxFragmentPainter::NeedsPaint(child_fragment))) {
DCHECK(paint_fragment_);
NGLineBoxFragmentPainter line_box_painter(*line, *paint_fragment_);
line_box_painter.PaintBackgroundBorderShadow(paint_info, child_offset);
}
}
PaintInlineChildren(line->Children(), paint_info, child_offset);
}
}
void NGBoxFragmentPainter::PaintBackplate(NGPaintFragment::ChildList line_boxes,
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
if (paint_info.phase != PaintPhase::kForcedColorsModeBackplate)
return;
// Only paint backplates behind text when forced-color-adjust is auto.
const ComputedStyle& style = line_boxes.front().Style();
if (style.ForcedColorAdjust() == EForcedColorAdjust::kNone)
return;
if (DrawingRecorder::UseCachedDrawingIfPossible(
paint_info.context, GetDisplayItemClient(),
DisplayItem::kForcedColorsModeBackplate))
return;
DrawingRecorder recorder(paint_info.context, GetDisplayItemClient(),
DisplayItem::kForcedColorsModeBackplate);
Color backplate_color = style.ForcedBackplateColor();
const auto& backplates = BuildBackplate(line_boxes, paint_offset);
for (const auto backplate : backplates)
paint_info.context.FillRect(FloatRect(backplate), backplate_color);
}
void NGBoxFragmentPainter::PaintInlineChildren(
NGPaintFragment::ChildList inline_children,
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
// TODO(kojii): Move kOutline painting into a |PaintInlineChildrenOutlines()|
// method instead as it would be more efficient. Would require repeating some
// of the code below though.
// This DCHECK can then match to |InlineFlowBoxPainter::Paint|.
DCHECK_NE(paint_info.phase, PaintPhase::kDescendantOutlinesOnly);
for (const NGPaintFragment* child : inline_children) {
const NGPhysicalFragment& child_fragment = child->PhysicalFragment();
if (child_fragment.IsHiddenForPaint())
continue;
if (child_fragment.IsFloating())
continue;
// Skip if this child does not intersect with CullRect.
if (!paint_info.IntersectsCullRect(child->InkOverflow(),
paint_offset + child->Offset()) &&
// Don't skip empty size text in order to paint selection for <br>.
!(child_fragment.IsText() && child_fragment.Size().IsEmpty()))
continue;
if (child_fragment.Type() == NGPhysicalFragment::kFragmentText) {
DCHECK(!child_fragment.HasSelfPaintingLayer() ||
To<NGPhysicalTextFragment>(child_fragment).IsEllipsis());
PaintTextChild(*child, paint_info, paint_offset);
} else if (child_fragment.Type() == NGPhysicalFragment::kFragmentBox) {
if (child_fragment.HasSelfPaintingLayer())
continue;
if (child_fragment.IsAtomicInline())
PaintAtomicInlineChild(*child, paint_info);
else
NGInlineBoxFragmentPainter(*child).Paint(paint_info, paint_offset);
} else {
NOTREACHED();
}
}
}
void NGBoxFragmentPainter::PaintAtomicInlineChild(const NGPaintFragment& child,
const PaintInfo& paint_info) {
// Inline children should be painted by PaintInlineChild.
DCHECK(child.PhysicalFragment().IsAtomicInline());
const NGPhysicalFragment& fragment = child.PhysicalFragment();
if (child.HasSelfPaintingLayer())
return;
if (fragment.Type() == NGPhysicalFragment::kFragmentBox &&
FragmentRequiresLegacyFallback(fragment)) {
PaintInlineChildBoxUsingLegacyFallback(fragment, paint_info);
} else {
NGBoxFragmentPainter(child).PaintAllPhasesAtomically(paint_info);
}
}
void NGBoxFragmentPainter::PaintTextChild(const NGPaintFragment& paint_fragment,
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
// Inline blocks should be painted by PaintAtomicInlineChild.
DCHECK(!paint_fragment.PhysicalFragment().IsAtomicInline());
// Only paint during the foreground/selection phases.
if (paint_info.phase != PaintPhase::kForeground &&
paint_info.phase != PaintPhase::kSelection &&
paint_info.phase != PaintPhase::kTextClip &&
paint_info.phase != PaintPhase::kMask)
return;
NGTextPainterCursor cursor(paint_fragment);
NGTextFragmentPainter<NGTextPainterCursor> text_painter(cursor);
text_painter.Paint(paint_info, paint_offset);
}
void NGBoxFragmentPainter::PaintTextItem(const NGInlineCursor& cursor,
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
DCHECK_EQ(cursor.CurrentItem()->Type(), NGFragmentItem::kText);
DCHECK(items_);
// Only paint during the foreground/selection phases.
if (paint_info.phase != PaintPhase::kForeground &&
paint_info.phase != PaintPhase::kSelection &&
paint_info.phase != PaintPhase::kTextClip &&
paint_info.phase != PaintPhase::kMask)
return;
NGTextFragmentPainter<NGInlineCursor> text_painter(cursor);
text_painter.Paint(paint_info, paint_offset);
}
NGBoxFragmentPainter::MoveTo NGBoxFragmentPainter::PaintBoxItem(
const NGFragmentItem& item,
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
DCHECK_EQ(item.Type(), NGFragmentItem::kBox);
DCHECK(items_);
// Nothing to paint if this is a culled inline box. Proceed to its
// descendants.
const NGPhysicalBoxFragment* child_fragment = item.BoxFragment();
if (!child_fragment)
return kDontSkipChildren;
if (child_fragment->HasSelfPaintingLayer() ||
child_fragment->IsHiddenForPaint() || child_fragment->IsFloating())
return kSkipChildren;
// TODO(kojii): Check CullRect.
if (child_fragment->IsAtomicInline() || child_fragment->IsListMarker()) {
if (FragmentRequiresLegacyFallback(*child_fragment)) {
PaintInlineChildBoxUsingLegacyFallback(*child_fragment, paint_info);
return kDontSkipChildren;
}
NGBoxFragmentPainter(*child_fragment).PaintAllPhasesAtomically(paint_info);
return kDontSkipChildren;
}
// TODO(kojii): Implement.
// NGInlineBoxFragmentPainter(*child).Paint(paint_info, paint_offset);
return kDontSkipChildren;
}
void NGBoxFragmentPainter::PaintAtomicInline(const PaintInfo& paint_info) {
DCHECK(PhysicalFragment().IsAtomicInline());
// Self-painting AtomicInlines should go to normal paint logic.
DCHECK(!box_fragment_.HasSelfPaintingLayer());
// Text clips are painted only for the direct inline children of the object
// that has a text clip style on it, not block children.
if (paint_info.phase == PaintPhase::kTextClip)
return;
PaintAllPhasesAtomically(paint_info);
}
bool NGBoxFragmentPainter::IsPaintingScrollingBackground(
const PaintInfo& paint_info) {
// TODO(layout-dev): Change paint_info.PaintContainer to accept fragments
// once LayoutNG supports scrolling containers.
return paint_info.PaintFlags() & kPaintLayerPaintingOverflowContents &&
!(paint_info.PaintFlags() &
kPaintLayerPaintingCompositingBackgroundPhase) &&
box_fragment_.GetLayoutObject() == paint_info.PaintContainer();
}
bool NGBoxFragmentPainter::ShouldPaint(
const ScopedPaintState& paint_state) const {
// TODO(layout-dev): Add support for scrolling, see BlockPainter::ShouldPaint.
if (paint_fragment_) {
return paint_state.LocalRectIntersectsCullRect(
paint_fragment_->InkOverflow());
}
const NGPhysicalBoxFragment& fragment = PhysicalFragment();
if (!fragment.IsInlineBox()) {
return paint_state.LocalRectIntersectsCullRect(
ToLayoutBox(fragment.GetLayoutObject())
->PhysicalSelfVisualOverflowRect());
}
NOTREACHED();
return false;
}
void NGBoxFragmentPainter::PaintTextClipMask(GraphicsContext& context,
const IntRect& mask_rect,
const PhysicalOffset& paint_offset,
bool object_has_multiple_boxes) {
PaintInfo paint_info(context, mask_rect, PaintPhase::kTextClip,
kGlobalPaintNormalPhase, 0);
if (object_has_multiple_boxes) {
DCHECK(paint_fragment_);
PhysicalOffset local_offset = paint_fragment_->Offset();
DCHECK(paint_fragment_);
NGInlineBoxFragmentPainter inline_box_painter(*paint_fragment_);
if (box_fragment_.Style().BoxDecorationBreak() ==
EBoxDecorationBreak::kSlice) {
LayoutUnit offset_on_line;
LayoutUnit total_width;
inline_box_painter.ComputeFragmentOffsetOnLine(
box_fragment_.Style().Direction(), &offset_on_line, &total_width);
LayoutSize line_offset(offset_on_line, LayoutUnit());
local_offset -=
PhysicalOffset(box_fragment_.Style().IsHorizontalWritingMode()
? line_offset
: line_offset.TransposedSize());
}
inline_box_painter.Paint(paint_info, paint_offset - local_offset);
} else {
PaintObject(paint_info, paint_offset);
}
}
PhysicalRect NGBoxFragmentPainter::AdjustRectForScrolledContent(
const PaintInfo& paint_info,
const BoxPainterBase::FillLayerInfo& info,
const PhysicalRect& rect) {
PhysicalRect scrolled_paint_rect = rect;
GraphicsContext& context = paint_info.context;
const NGPhysicalBoxFragment& physical = PhysicalFragment();
// Clip to the overflow area.
if (info.is_clipped_with_local_scrolling &&
!IsPaintingScrollingBackground(paint_info)) {
context.Clip(FloatRect(physical.OverflowClipRect(rect.offset)));
// Adjust the paint rect to reflect a scrolled content box with borders at
// the ends.
PhysicalOffset offset(physical.ScrolledContentOffset());
scrolled_paint_rect.Move(-offset);
LayoutRectOutsets borders = AdjustedBorderOutsets(info);
scrolled_paint_rect.size =
physical.ScrollSize() + PhysicalSize(borders.Size());
}
return scrolled_paint_rect;
}
LayoutRectOutsets NGBoxFragmentPainter::ComputeBorders() const {
return BoxStrutToLayoutRectOutsets(PhysicalFragment().BorderWidths());
}
LayoutRectOutsets NGBoxFragmentPainter::ComputePadding() const {
return BoxStrutToLayoutRectOutsets(PhysicalFragment().PixelSnappedPadding());
}
BoxPainterBase::FillLayerInfo NGBoxFragmentPainter::GetFillLayerInfo(
const Color& color,
const FillLayer& bg_layer,
BackgroundBleedAvoidance bleed_avoidance) const {
const NGBorderEdges& border_edges = BorderEdges();
const NGPhysicalBoxFragment& fragment = PhysicalFragment();
return BoxPainterBase::FillLayerInfo(
fragment.GetLayoutObject()->GetDocument(), fragment.Style(),
fragment.HasOverflowClip(), color, bg_layer, bleed_avoidance,
border_edges.line_left, border_edges.line_right,
fragment.GetLayoutObject()->IsInline());
}
bool NGBoxFragmentPainter::IsInSelfHitTestingPhase(HitTestAction action) const {
// TODO(layout-dev): We should set an IsContainingBlock flag on
// NGPhysicalBoxFragment, instead of routing back to LayoutObject.
if (const auto* box = ToLayoutBoxOrNull(PhysicalFragment().GetLayoutObject()))
return box->IsInSelfHitTestingPhase(action);
return action == kHitTestForeground;
}
bool NGBoxFragmentPainter::NodeAtPoint(HitTestResult& result,
const HitTestLocation& hit_test_location,
const PhysicalOffset& physical_offset,
HitTestAction action) {
const NGPhysicalBoxFragment& fragment = PhysicalFragment();
const PhysicalSize& size = box_fragment_.Size();
const ComputedStyle& style = box_fragment_.Style();
bool hit_test_self = IsInSelfHitTestingPhase(action);
// TODO(layout-dev): Add support for hit testing overflow controls once we
// overflow has been implemented.
// if (hit_test_self && HasOverflowClip() &&
// HitTestOverflowControl(result, hit_test_location, physical_offset))
// return true;
bool skip_children = result.GetHitTestRequest().GetStopNode() ==
PhysicalFragment().GetLayoutObject();
if (!skip_children && box_fragment_.ShouldClipOverflow()) {
// PaintLayer::HitTestContentsForFragments checked the fragments'
// foreground rect for intersection if a layer is self painting,
// so only do the overflow clip check here for non-self-painting layers.
if (!box_fragment_.HasSelfPaintingLayer() &&
!hit_test_location.Intersects(PhysicalFragment().OverflowClipRect(
physical_offset, kExcludeOverlayScrollbarSizeForHitTesting))) {
skip_children = true;
}
if (!skip_children && style.HasBorderRadius()) {
PhysicalRect bounds_rect(physical_offset, size);
skip_children = !hit_test_location.Intersects(
style.GetRoundedInnerBorderFor(bounds_rect.ToLayoutRect()));
}
}
DCHECK(paint_fragment_);
if (!skip_children) {
PhysicalOffset scrolled_offset = physical_offset;
if (box_fragment_.HasOverflowClip()) {
scrolled_offset -=
PhysicalOffset(PhysicalFragment().ScrolledContentOffset());
}
if (HitTestChildren(result, paint_fragment_->Children(), hit_test_location,
scrolled_offset, action)) {
return true;
}
}
if (style.HasBorderRadius() &&
HitTestClippedOutByBorder(hit_test_location, physical_offset))
return false;
// Now hit test ourselves.
if (hit_test_self && VisibleToHitTestRequest(result.GetHitTestRequest())) {
PhysicalRect bounds_rect(physical_offset, size);
if (UNLIKELY(result.GetHitTestRequest().GetType() &
HitTestRequest::kHitTestVisualOverflow)) {
bounds_rect = paint_fragment_->SelfInkOverflow();
bounds_rect.Move(physical_offset);
}
// TODO(kojii): Don't have good explanation why only inline box needs to
// snap, but matches to legacy and fixes crbug.com/976606.
if (fragment.IsInlineBox())
bounds_rect = PhysicalRect(PixelSnappedIntRect(bounds_rect));
if (hit_test_location.Intersects(bounds_rect)) {
Node* node = paint_fragment_->NodeForHitTest();
if (!result.InnerNode() && node) {
PhysicalOffset point = hit_test_location.Point() - physical_offset;
result.SetNodeAndPosition(node, point);
}
if (result.AddNodeToListBasedTestResult(node, hit_test_location,
bounds_rect) == kStopHitTesting) {
return true;
}
}
}
return false;
}
bool NGBoxFragmentPainter::VisibleToHitTestRequest(
const HitTestRequest& request) const {
DCHECK(paint_fragment_);
return FragmentVisibleToHitTestRequest(*paint_fragment_, request);
}
bool NGBoxFragmentPainter::HitTestTextFragment(
HitTestResult& result,
const NGPaintFragment& text_paint_fragment,
const HitTestLocation& hit_test_location,
const PhysicalOffset& physical_offset,
HitTestAction action) {
if (action != kHitTestForeground)
return false;
const auto& text_fragment =
To<NGPhysicalTextFragment>(text_paint_fragment.PhysicalFragment());
PhysicalSize size(text_fragment.Size().width, text_fragment.Size().height);
PhysicalRect border_rect(physical_offset, size);
// TODO(layout-dev): Clip to line-top/bottom.
PhysicalRect rect(PixelSnappedIntRect(border_rect));
if (UNLIKELY(result.GetHitTestRequest().GetType() &
HitTestRequest::kHitTestVisualOverflow)) {
rect = text_fragment.SelfInkOverflow();
rect.Move(border_rect.offset);
}
if (FragmentVisibleToHitTestRequest(text_paint_fragment,
result.GetHitTestRequest()) &&
hit_test_location.Intersects(rect)) {
Node* node = text_paint_fragment.NodeForHitTest();
if (!result.InnerNode() && node) {
PhysicalOffset point = hit_test_location.Point() - physical_offset +
text_paint_fragment.InlineOffsetToContainerBox();
result.SetNodeAndPosition(node, point);
}
if (result.AddNodeToListBasedTestResult(node, hit_test_location, rect) ==
kStopHitTesting) {
return true;
}
}
return false;
}
// Replicates logic in legacy InlineFlowBox::NodeAtPoint().
bool NGBoxFragmentPainter::HitTestLineBoxFragment(
HitTestResult& result,
const NGPaintFragment& fragment,
const HitTestLocation& hit_test_location,
const PhysicalOffset& physical_offset,
HitTestAction action) {
if (HitTestChildren(result, fragment.Children(), hit_test_location,
physical_offset, action))
return true;
if (action != kHitTestForeground)
return false;
if (!VisibleToHitTestRequest(result.GetHitTestRequest()))
return false;
const PhysicalOffset overflow_location =
fragment.SelfInkOverflow().offset + physical_offset;
if (HitTestClippedOutByBorder(hit_test_location, overflow_location))
return false;
const PhysicalSize size = fragment.Size();
const PhysicalRect bounds_rect(physical_offset, size);
const ComputedStyle& containing_box_style = box_fragment_.Style();
if (containing_box_style.HasBorderRadius() &&
!hit_test_location.Intersects(containing_box_style.GetRoundedBorderFor(
bounds_rect.ToLayoutRect()))) {
return false;
}
// Now hit test ourselves.
if (!hit_test_location.Intersects(bounds_rect))
return false;
// Floats will be hit-tested in |kHitTestFloat| phase, but
// |LayoutObject::HitTestAllPhases| does not try it if |kHitTestForeground|
// succeeds. Pretend the location is not in this linebox if it hits floating
// descendants. TODO(kojii): Computing this is redundant, consider
// restructuring. Changing the caller logic isn't easy because currently
// floats are in the bounds of line boxes only in NG.
const auto& line = To<NGPhysicalLineBoxFragment>(fragment.PhysicalFragment());
if (line.HasFloatingDescendants()) {
DCHECK_NE(action, kHitTestFloat);
if (HitTestChildren(result, fragment.Children(), hit_test_location,
physical_offset, kHitTestFloat)) {
return false;
}
}
Node* node = fragment.NodeForHitTest();
if (!result.InnerNode() && node) {
const PhysicalOffset point = hit_test_location.Point() - physical_offset +
fragment.InlineOffsetToContainerBox();
result.SetNodeAndPosition(node, point);
}
return result.AddNodeToListBasedTestResult(node, hit_test_location,
bounds_rect) == kStopHitTesting;
}
bool NGBoxFragmentPainter::HitTestChildBoxFragment(
HitTestResult& result,
const NGPaintFragment& paint_fragment,
const HitTestLocation& hit_test_location,
const PhysicalOffset& physical_offset,
HitTestAction action) {
const NGPhysicalFragment& fragment = paint_fragment.PhysicalFragment();
// Note: Floats should only be hit tested in the |kHitTestFloat| phase, so we
// shouldn't enter a float when |action| doesn't match. However, as floats may
// scatter around in the entire inline formatting context, we should always
// enter non-floating inline child boxes to search for floats in the
// |kHitTestFloat| phase, unless the child box forms another context.
if (fragment.IsFloating() && action != kHitTestFloat)
return false;
if (!FragmentRequiresLegacyFallback(fragment)) {
// TODO(layout-dev): Implement HitTestAllPhases in NG after we stop
// falling back to legacy for child atomic inlines and floats.
DCHECK(!fragment.IsAtomicInline());
DCHECK(!fragment.IsFloating());
return NGBoxFragmentPainter(paint_fragment)
.NodeAtPoint(result, hit_test_location, physical_offset, action);
}
if (fragment.IsInline() && action != kHitTestForeground)
return false;
LayoutBox* const layout_box = ToLayoutBox(fragment.GetMutableLayoutObject());
// https://www.w3.org/TR/CSS22/zindex.html#painting-order
// Hit test all phases of inline blocks, inline tables, replaced elements and
// non-positioned floats as if they created their own stacking contexts.
const bool should_hit_test_all_phases =
fragment.IsAtomicInline() || fragment.IsFloating();
return should_hit_test_all_phases
? layout_box->HitTestAllPhases(result, hit_test_location,
physical_offset)
: layout_box->NodeAtPoint(result, hit_test_location,
physical_offset, action);
}
bool NGBoxFragmentPainter::HitTestChildren(
HitTestResult& result,
NGPaintFragment::ChildList children,
const HitTestLocation& hit_test_location,
const PhysicalOffset& accumulated_offset,
HitTestAction action) {
Vector<NGPaintFragment*, 16> child_vector;
children.ToList(&child_vector);
for (unsigned i = child_vector.size(); i;) {
const NGPaintFragment* child = child_vector[--i];
const PhysicalOffset offset = child->Offset();
if (child->HasSelfPaintingLayer())
continue;
const NGPhysicalFragment& fragment = child->PhysicalFragment();
const PhysicalOffset child_physical_offset = accumulated_offset + offset;
bool stop_hit_testing = false;
if (fragment.Type() == NGPhysicalFragment::kFragmentBox) {
stop_hit_testing = HitTestChildBoxFragment(
result, *child, hit_test_location, child_physical_offset, action);
} else if (fragment.Type() == NGPhysicalFragment::kFragmentLineBox) {
stop_hit_testing = HitTestLineBoxFragment(
result, *child, hit_test_location, child_physical_offset, action);
} else if (fragment.Type() == NGPhysicalFragment::kFragmentText) {
stop_hit_testing = HitTestTextFragment(result, *child, hit_test_location,
child_physical_offset, action);
}
if (stop_hit_testing)
return true;
if (!fragment.IsInline() || action != kHitTestForeground)
continue;
// Hit test culled inline boxes between |fragment| and its parent fragment.
const NGPaintFragment* previous_sibling = i ? child_vector[i - 1] : nullptr;
if (HitTestCulledInlineAncestors(result, *child, previous_sibling,
hit_test_location, child_physical_offset))
return true;
}
return false;
}
bool NGBoxFragmentPainter::HitTestClippedOutByBorder(
const HitTestLocation& hit_test_location,
const PhysicalOffset& border_box_location) const {
const ComputedStyle& style = box_fragment_.Style();
PhysicalRect rect(PhysicalOffset(), PhysicalFragment().Size());
rect.Move(border_box_location);
const NGBorderEdges& border_edges = BorderEdges();
return !hit_test_location.Intersects(style.GetRoundedBorderFor(
rect.ToLayoutRect(), border_edges.line_left, border_edges.line_right));
}
} // namespace blink