| // 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 "core/paint/ng/ng_box_fragment_painter.h" |
| |
| #include "core/layout/BackgroundBleedAvoidance.h" |
| #include "core/layout/HitTestLocation.h" |
| #include "core/layout/HitTestResult.h" |
| #include "core/layout/ng/geometry/ng_border_edges.h" |
| #include "core/layout/ng/geometry/ng_box_strut.h" |
| #include "core/layout/ng/inline/ng_physical_line_box_fragment.h" |
| #include "core/layout/ng/inline/ng_physical_text_fragment.h" |
| #include "core/layout/ng/layout_ng_mixin.h" |
| #include "core/layout/ng/ng_physical_box_fragment.h" |
| #include "core/paint/AdjustPaintOffsetScope.h" |
| #include "core/paint/BackgroundImageGeometry.h" |
| #include "core/paint/BoxDecorationData.h" |
| #include "core/paint/ObjectPainter.h" |
| #include "core/paint/PaintInfo.h" |
| #include "core/paint/PaintLayer.h" |
| #include "core/paint/PaintPhase.h" |
| #include "core/paint/ScrollRecorder.h" |
| #include "core/paint/ScrollableAreaPainter.h" |
| #include "core/paint/ng/ng_box_clipper.h" |
| #include "core/paint/ng/ng_fragment_painter.h" |
| #include "core/paint/ng/ng_paint_fragment.h" |
| #include "core/paint/ng/ng_text_fragment_painter.h" |
| #include "platform/geometry/LayoutRectOutsets.h" |
| #include "platform/graphics/GraphicsContextStateSaver.h" |
| #include "platform/graphics/paint/ClipRecorder.h" |
| #include "platform/graphics/paint/DrawingRecorder.h" |
| #include "platform/scroll/ScrollTypes.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)); |
| } |
| } // anonymous namespace |
| |
| NGBoxFragmentPainter::NGBoxFragmentPainter(const NGPaintFragment& box) |
| : BoxPainterBase( |
| box, |
| &box.GetLayoutObject()->GetDocument(), |
| box.Style(), |
| box.GetLayoutObject()->GeneratingNode(), |
| BoxStrutToLayoutRectOutsets(box.PhysicalFragment().BorderWidths()), |
| LayoutRectOutsets()), |
| box_fragment_(box), |
| border_edges_( |
| NGBorderEdges::FromPhysical(box.PhysicalFragment().BorderEdges(), |
| box.Style().GetWritingMode())), |
| is_inline_(false) { |
| DCHECK(box.PhysicalFragment().IsBox()); |
| } |
| |
| void NGBoxFragmentPainter::Paint(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| const NGPhysicalFragment& fragment = box_fragment_.PhysicalFragment(); |
| const LayoutObject* layout_object = fragment.GetLayoutObject(); |
| DCHECK(layout_object && layout_object->IsBox()); |
| if (!fragment.IsPlacedByLayoutNG()) { |
| // |fragment.Offset()| is valid only when it is placed by LayoutNG parent. |
| // Use LayoutBox::Location() if not. crbug.com/788590 |
| AdjustPaintOffsetScope adjustment(ToLayoutBox(*layout_object), paint_info, |
| paint_offset); |
| PaintWithAdjustedOffset(adjustment.MutablePaintInfo(), |
| adjustment.AdjustedPaintOffset()); |
| return; |
| } |
| |
| AdjustPaintOffsetScope adjustment(box_fragment_, paint_info, paint_offset); |
| PaintWithAdjustedOffset(adjustment.MutablePaintInfo(), |
| adjustment.AdjustedPaintOffset()); |
| } |
| |
| void NGBoxFragmentPainter::PaintInlineBox( |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset, |
| const LayoutPoint& block_paint_offset) { |
| DCHECK(box_fragment_.GetLayoutObject() && |
| box_fragment_.GetLayoutObject()->IsLayoutInline()); |
| is_inline_ = true; |
| block_paint_offset_ = block_paint_offset; |
| PaintInfo info(paint_info); |
| PaintWithAdjustedOffset( |
| info, paint_offset + box_fragment_.Offset().ToLayoutPoint()); |
| } |
| |
| void NGBoxFragmentPainter::PaintWithAdjustedOffset( |
| PaintInfo& info, |
| const LayoutPoint& paint_offset) { |
| if (!IntersectsPaintRect(info, paint_offset)) |
| return; |
| |
| if (box_fragment_.PhysicalFragment().IsInlineBlock()) |
| return PaintInlineBlock(info, paint_offset); |
| |
| 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) { |
| NGBoxClipper box_clipper(box_fragment_, info); |
| PaintObject(info, paint_offset); |
| } |
| |
| if (ShouldPaintSelfOutline(original_phase)) { |
| info.phase = PaintPhase::kSelfOutlineOnly; |
| PaintObject(info, 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. |
| info.phase = original_phase; |
| PaintOverflowControlsIfNeeded(info, paint_offset); |
| } |
| |
| void NGBoxFragmentPainter::PaintObject(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| const PaintPhase paint_phase = paint_info.phase; |
| const ComputedStyle& style = box_fragment_.Style(); |
| bool is_visible = style.Visibility() == EVisibility::kVisible; |
| |
| if (ShouldPaintSelfBlockBackground(paint_phase)) { |
| // TODO(eae): style.HasBoxDecorationBackground isn't good enough, it needs |
| // to check the object as some objects may have box decoration background |
| // other than from their own style. |
| // PaintBoxDecorationBackground should be called here but is currently |
| // called during the foreground phase instead for all box types, not just |
| // for inline flow boxes. |
| // The paint phase and inline/block box distinction needs cleanup, but |
| // without this, borders on 'overflow: scroll' are clipped. |
| if (is_visible && style.HasBoxDecorationBackground() && !is_inline_) |
| 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 && is_visible) |
| return PaintMask(paint_info, paint_offset); |
| |
| if (paint_phase == PaintPhase::kClippingMask && is_visible) |
| return PaintClippingMask(paint_info, paint_offset); |
| |
| // TODO(eae): Add PDF URL painting for printing. |
| // if (paint_phase == PaintPhase::kForeground && paint_info.IsPrinting()) |
| // ObjectPainter(box_fragment_) |
| // .AddPDFURLRectIfNeeded(paint_info, paint_offset); |
| |
| if (paint_phase != PaintPhase::kSelfOutlineOnly) { |
| // TODO(layout-dev): Figure out where paint properties should live. |
| const auto& layout_object = *box_fragment_.GetLayoutObject(); |
| Optional<PaintInfo> scrolled_paint_info; |
| if (const auto* fragment = paint_info.FragmentToPaint(layout_object)) { |
| Optional<ScopedPaintChunkProperties> scoped_scroll_property; |
| Optional<ScrollRecorder> scroll_recorder; |
| DCHECK(RuntimeEnabledFeatures::SlimmingPaintV175Enabled()); |
| 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, |
| box_fragment_, DisplayItem::PaintPhaseToScrollType(paint_phase)); |
| scrolled_paint_info.emplace(paint_info); |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) { |
| scrolled_paint_info->UpdateCullRectForScrollingContents( |
| EnclosingIntRect(box_fragment_.OverflowClipRect( |
| paint_offset, kIgnorePlatformOverlayScrollbarSize)), |
| scroll_translation->Matrix().ToAffineTransform()); |
| } else { |
| scrolled_paint_info->UpdateCullRect( |
| scroll_translation->Matrix().ToAffineTransform()); |
| } |
| } |
| } |
| |
| const PaintInfo& contents_paint_info = |
| scrolled_paint_info ? *scrolled_paint_info : paint_info; |
| |
| // Paint our background, border and box-shadow. |
| // TODO(eae): We should only paint box-decorations during the foreground |
| // phase for inline boxes. Split this method into PaintBlock and PaintInline |
| // once we can tell the two types of fragments apart or we've eliminated the |
| // extra block wrapper fragments. |
| if (paint_info.phase == PaintPhase::kForeground && is_inline_) |
| PaintBoxDecorationBackground(paint_info, paint_offset); |
| |
| PaintContents(contents_paint_info, paint_offset); |
| |
| if (paint_phase == PaintPhase::kFloat || |
| paint_phase == PaintPhase::kSelection || |
| paint_phase == PaintPhase::kTextClip) |
| PaintFloats(contents_paint_info, paint_offset); |
| } |
| |
| if (ShouldPaintSelfOutline(paint_phase)) |
| NGFragmentPainter(box_fragment_).PaintOutline(paint_info, paint_offset); |
| |
| // TODO(layout-dev): Implement once we have selections in LayoutNG. |
| // 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 && |
| // box_fragment_.ShouldPaintCarets()) |
| // PaintCarets(paint_info, paint_offset); |
| } |
| |
| void NGBoxFragmentPainter::PaintInlineObject(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| DCHECK(!ShouldPaintSelfOutline(paint_info.phase) && |
| !ShouldPaintDescendantOutlines(paint_info.phase)); |
| |
| LayoutRect overflow_rect(box_fragment_.VisualOverflowRect()); |
| overflow_rect.MoveBy(paint_offset); |
| |
| if (!paint_info.GetCullRect().IntersectsCullRect(overflow_rect)) |
| return; |
| |
| if (paint_info.phase == PaintPhase::kMask) { |
| if (DrawingRecorder::UseCachedDrawingIfPossible( |
| paint_info.context, box_fragment_, |
| DisplayItem::PaintPhaseToDrawingType(paint_info.phase))) |
| return; |
| DrawingRecorder recorder( |
| paint_info.context, box_fragment_, |
| DisplayItem::PaintPhaseToDrawingType(paint_info.phase)); |
| PaintMask(paint_info, paint_offset); |
| return; |
| } |
| |
| // Paint our background, border and box-shadow. |
| if (paint_info.phase == PaintPhase::kForeground) |
| PaintBoxDecorationBackground(paint_info, paint_offset); |
| |
| // Paint our children. |
| PaintContents(paint_info, paint_offset); |
| } |
| |
| void NGBoxFragmentPainter::PaintContents(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| PaintInfo descendants_info = paint_info.ForDescendants(); |
| if (is_inline_) { |
| PaintInlineChildren(box_fragment_.Children(), descendants_info, |
| paint_offset); |
| return; |
| } |
| PaintChildren(box_fragment_.Children(), descendants_info, paint_offset); |
| } |
| |
| void NGBoxFragmentPainter::PaintFloats(const PaintInfo&, const LayoutPoint&) { |
| // TODO(eae): Implement once we have a way to distinguish float fragments. |
| } |
| |
| void NGBoxFragmentPainter::PaintMask(const PaintInfo&, const LayoutPoint&) { |
| // TODO(eae): Implement. |
| } |
| |
| void NGBoxFragmentPainter::PaintClippingMask(const PaintInfo&, |
| const LayoutPoint&) { |
| // TODO(eae): Implement. |
| } |
| |
| void NGBoxFragmentPainter::PaintBoxDecorationBackground( |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| LayoutRect paint_rect; |
| if (!IsPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer( |
| box_fragment_, paint_info)) { |
| // TODO(eae): We need better converters for ng geometry types. Long term we |
| // probably want to change the paint code to take NGPhysical* but that is a |
| // much bigger change. |
| NGPhysicalSize size = box_fragment_.Size(); |
| paint_rect = LayoutRect(LayoutPoint(), LayoutSize(size.width, size.height)); |
| } |
| |
| paint_rect.MoveBy(paint_offset); |
| PaintBoxDecorationBackgroundWithRect(paint_info, paint_offset, paint_rect); |
| } |
| |
| void NGBoxFragmentPainter::PaintBoxDecorationBackgroundWithRect( |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset, |
| const LayoutRect& paint_rect) { |
| bool painting_overflow_contents = |
| IsPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer( |
| box_fragment_, paint_info); |
| const ComputedStyle& style = box_fragment_.Style(); |
| |
| // TODO(layout-dev): Implement support for painting overflow contents. |
| const DisplayItemClient& display_item_client = box_fragment_; |
| if (DrawingRecorder::UseCachedDrawingIfPossible( |
| paint_info.context, display_item_client, |
| DisplayItem::kBoxDecorationBackground)) |
| return; |
| |
| DrawingRecorder recorder(paint_info.context, display_item_client, |
| DisplayItem::kBoxDecorationBackground); |
| BoxDecorationData box_decoration_data(box_fragment_.PhysicalFragment()); |
| GraphicsContextStateSaver state_saver(paint_info.context, false); |
| |
| if (!painting_overflow_contents) { |
| PaintNormalBoxShadow(paint_info, paint_rect, style); |
| |
| if (BleedAvoidanceIsClipping(box_decoration_data.bleed_avoidance)) { |
| state_saver.Save(); |
| FloatRoundedRect border = style.GetRoundedBorderFor( |
| paint_rect, border_edges_.line_left, border_edges_.line_right); |
| paint_info.context.ClipRoundedRect(border); |
| |
| if (box_decoration_data.bleed_avoidance == kBackgroundBleedClipLayer) |
| paint_info.context.BeginLayer(); |
| } |
| } |
| |
| // TODO(layout-dev): Support theme painting. |
| |
| // TODO(eae): Support SkipRootBackground painting. |
| bool should_paint_background = true; |
| if (should_paint_background) { |
| PaintBackground(paint_info, paint_rect, |
| box_decoration_data.background_color, |
| box_decoration_data.bleed_avoidance); |
| } |
| |
| if (!painting_overflow_contents) { |
| PaintInsetBoxShadowWithBorderRect(paint_info, paint_rect, style); |
| |
| if (box_decoration_data.has_border_decoration) { |
| Node* generating_node = box_fragment_.GetLayoutObject()->GeneratingNode(); |
| const Document& document = box_fragment_.GetLayoutObject()->GetDocument(); |
| PaintBorder(box_fragment_, document, generating_node, paint_info, |
| paint_rect, style, box_decoration_data.bleed_avoidance, |
| border_edges_.line_left, border_edges_.line_right); |
| } |
| } |
| |
| if (box_decoration_data.bleed_avoidance == kBackgroundBleedClipLayer) |
| paint_info.context.EndLayer(); |
| } |
| |
| static bool RequiresLegacyFallback(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 back. |
| if (fragment.IsBlockLayoutRoot()) |
| return true; |
| |
| // TODO(kojii): Review if this is still needed. |
| LayoutObject* layout_object = fragment.GetLayoutObject(); |
| return layout_object->IsLayoutReplaced(); |
| } |
| |
| void NGBoxFragmentPainter::PaintInlineChildBoxUsingLegacyFallback( |
| const NGPhysicalFragment& fragment, |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| LayoutObject* layout_object = fragment.GetLayoutObject(); |
| DCHECK(layout_object); |
| if (layout_object->IsLayoutNGMixin() && |
| ToLayoutBlockFlow(layout_object)->PaintFragment()) { |
| // This object will use NGBoxFragmentPainter. NGBoxFragmentPainter expects |
| // |paint_offset| relative to the parent, even when in inline context. |
| layout_object->Paint(paint_info, paint_offset); |
| return; |
| } |
| |
| // When in inline context, pre-NG painters expect |paint_offset| of their |
| // block container. |
| if (layout_object->IsAtomicInlineLevel()) { |
| // Pre-NG painters also expect callers to use |PaintAllPhasesAtomically()| |
| // for atomic inlines. |
| ObjectPainter(*layout_object) |
| .PaintAllPhasesAtomically(paint_info, block_paint_offset_); |
| return; |
| } |
| |
| layout_object->Paint(paint_info, block_paint_offset_); |
| } |
| |
| void NGBoxFragmentPainter::PaintAllPhasesAtomically( |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| // 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 PaintObject(paint_info, paint_offset); |
| |
| if (phase != PaintPhase::kForeground) |
| return; |
| |
| PaintInfo info(paint_info); |
| info.phase = PaintPhase::kBlockBackground; |
| PaintObject(info, paint_offset); |
| |
| info.phase = PaintPhase::kFloat; |
| PaintObject(info, paint_offset); |
| |
| info.phase = PaintPhase::kForeground; |
| PaintObject(info, paint_offset); |
| |
| info.phase = PaintPhase::kOutline; |
| PaintObject(info, paint_offset); |
| } |
| |
| void NGBoxFragmentPainter::PaintChildren( |
| const Vector<std::unique_ptr<NGPaintFragment>>& children, |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| for (const auto& child : children) { |
| const NGPhysicalFragment& fragment = child->PhysicalFragment(); |
| if (fragment.Type() == NGPhysicalFragment::kFragmentBox) { |
| if (child->HasSelfPaintingLayer()) |
| continue; |
| if (RequiresLegacyFallback(fragment)) |
| fragment.GetLayoutObject()->Paint(paint_info, paint_offset); |
| else |
| NGBoxFragmentPainter(*child).Paint(paint_info, paint_offset); |
| } else if (fragment.Type() == NGPhysicalFragment::kFragmentLineBox) { |
| PaintLineBox(*child, paint_info, paint_offset); |
| } |
| } |
| } |
| |
| void NGBoxFragmentPainter::PaintInlineChildren( |
| const Vector<std::unique_ptr<NGPaintFragment>>& children, |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| for (const auto& child : children) { |
| const NGPhysicalFragment& fragment = child->PhysicalFragment(); |
| if (fragment.Type() == NGPhysicalFragment::kFragmentText) { |
| PaintText(*child, paint_info, paint_offset); |
| } else if (fragment.Type() == NGPhysicalFragment::kFragmentBox) { |
| if (child->HasSelfPaintingLayer()) |
| continue; |
| if (RequiresLegacyFallback(fragment)) { |
| PaintInlineChildBoxUsingLegacyFallback(fragment, paint_info, |
| paint_offset); |
| } else { |
| NGBoxFragmentPainter(*child).PaintInlineBox(paint_info, paint_offset, |
| block_paint_offset_); |
| } |
| } |
| } |
| } |
| |
| void NGBoxFragmentPainter::PaintLineBox( |
| const NGPaintFragment& line_box_fragment, |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| // 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; |
| |
| // Line box fragments don't have LayoutObject and nothing to paint. Accumulate |
| // its offset and paint children. |
| block_paint_offset_ = paint_offset; |
| PaintInlineChildren( |
| line_box_fragment.Children(), paint_info, |
| paint_offset + line_box_fragment.Offset().ToLayoutPoint()); |
| } |
| |
| void NGBoxFragmentPainter::PaintInlineBlock(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); |
| |
| PaintAllPhasesAtomically(paint_info, paint_offset); |
| } |
| |
| void NGBoxFragmentPainter::PaintText(const NGPaintFragment& text_fragment, |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| if (DrawingRecorder::UseCachedDrawingIfPossible( |
| paint_info.context, text_fragment, |
| DisplayItem::PaintPhaseToDrawingType(paint_info.phase))) |
| return; |
| |
| DrawingRecorder recorder( |
| paint_info.context, text_fragment, |
| DisplayItem::PaintPhaseToDrawingType(paint_info.phase)); |
| |
| NGTextFragmentPainter text_painter(text_fragment); |
| text_painter.Paint(paint_info, paint_offset); |
| } |
| |
| bool NGBoxFragmentPainter:: |
| IsPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer( |
| const NGPaintFragment& fragment, |
| const PaintInfo& paint_info) { |
| // TODO(layout-dev): Implement once we have support for scrolling. |
| return false; |
| } |
| |
| // Clone of BlockPainter::PaintOverflowControlsIfNeeded |
| void NGBoxFragmentPainter::PaintOverflowControlsIfNeeded( |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) { |
| if (box_fragment_.HasOverflowClip() && |
| box_fragment_.Style().Visibility() == EVisibility::kVisible && |
| ShouldPaintSelfBlockBackground(paint_info.phase) && |
| !paint_info.PaintRootBackgroundOnly()) { |
| LayoutObject* layout_object = |
| box_fragment_.PhysicalFragment().GetLayoutObject(); |
| if (layout_object->IsLayoutBlock()) { |
| LayoutBlock* layout_block = ToLayoutBlock(layout_object); |
| 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 */); |
| } |
| } |
| } |
| |
| bool NGBoxFragmentPainter::IntersectsPaintRect( |
| const PaintInfo& paint_info, |
| const LayoutPoint& adjusted_paint_offset) const { |
| // TODO(layout-dev): Add support for scrolling, see |
| // BlockPainter::IntersectsPaintRect. |
| LayoutRect overflow_rect(box_fragment_.VisualOverflowRect()); |
| overflow_rect.MoveBy(adjusted_paint_offset); |
| return paint_info.GetCullRect().IntersectsCullRect(overflow_rect); |
| } |
| |
| void NGBoxFragmentPainter::PaintTextClipMask(GraphicsContext& context, |
| const IntRect& mask_rect, |
| const LayoutPoint& paint_offset) { |
| PaintInfo paint_info(context, mask_rect, PaintPhase::kTextClip, |
| kGlobalPaintNormalPhase, 0); |
| |
| // TODO(eae): Paint text child fragments. |
| } |
| |
| LayoutRect NGBoxFragmentPainter::AdjustForScrolledContent( |
| const PaintInfo&, |
| const BoxPainterBase::FillLayerInfo&, |
| const LayoutRect& rect) { |
| return rect; |
| } |
| |
| BoxPainterBase::FillLayerInfo NGBoxFragmentPainter::GetFillLayerInfo( |
| const Color& color, |
| const FillLayer& bg_layer, |
| BackgroundBleedAvoidance bleed_avoidance) const { |
| return BoxPainterBase::FillLayerInfo( |
| box_fragment_.GetLayoutObject()->GetDocument(), box_fragment_.Style(), |
| box_fragment_.HasOverflowClip(), color, bg_layer, bleed_avoidance, |
| border_edges_.line_left, border_edges_.line_right); |
| } |
| |
| void NGBoxFragmentPainter::PaintBackground( |
| const PaintInfo& paint_info, |
| const LayoutRect& paint_rect, |
| const Color& background_color, |
| BackgroundBleedAvoidance bleed_avoidance) { |
| // 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); |
| } |
| |
| bool NGBoxFragmentPainter::NodeAtPoint( |
| HitTestResult& result, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset, |
| HitTestAction action) { |
| // TODO(eae): Switch to using NG geometry types. |
| LayoutSize offset(box_fragment_.Offset().left, box_fragment_.Offset().top); |
| LayoutPoint adjusted_location = accumulated_offset + offset; |
| LayoutSize size(box_fragment_.Size().width, box_fragment_.Size().height); |
| const ComputedStyle& style = box_fragment_.Style(); |
| |
| bool hit_test_self = action == kHitTestForeground; |
| |
| // TODO(layout-dev): Add support for hit testing overflow controls once we |
| // overflow has been implemented. |
| // if (hit_test_self && HasOverflowClip() && |
| // HitTestOverflowControl(result, location_in_container, adjusted_location)) |
| // return true; |
| |
| bool skip_children = false; |
| if (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() && |
| !location_in_container.Intersects(box_fragment_.OverflowClipRect( |
| adjusted_location, kExcludeOverlayScrollbarSizeForHitTesting))) { |
| skip_children = true; |
| } |
| if (!skip_children && style.HasBorderRadius()) { |
| LayoutRect bounds_rect(adjusted_location, size); |
| skip_children = !location_in_container.Intersects( |
| style.GetRoundedInnerBorderFor(bounds_rect)); |
| } |
| } |
| |
| if (!skip_children && |
| HitTestChildren(result, box_fragment_.Children(), location_in_container, |
| adjusted_location, action)) { |
| return true; |
| } |
| |
| // TODO(eae): Implement once we support clipping in LayoutNG. |
| // if (style.HasBorderRadius() && |
| // HitTestClippedOutByBorder(location_in_container, adjusted_location)) |
| // return false; |
| |
| // Now hit test ourselves. |
| if (hit_test_self && VisibleToHitTestRequest(result.GetHitTestRequest())) { |
| LayoutRect bounds_rect(adjusted_location, size); |
| if (location_in_container.Intersects(bounds_rect)) { |
| Node* node = box_fragment_.GetNode(); |
| if (!result.InnerNode() && node) { |
| LayoutPoint point = |
| location_in_container.Point() - ToLayoutSize(adjusted_location); |
| result.SetNodeAndPosition(node, point); |
| } |
| if (result.AddNodeToListBasedTestResult(node, location_in_container, |
| bounds_rect) == kStopHitTesting) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| bool NGBoxFragmentPainter::VisibleToHitTestRequest( |
| const HitTestRequest& request) const { |
| return box_fragment_.Style().Visibility() == EVisibility::kVisible && |
| (request.IgnorePointerEventsNone() || |
| box_fragment_.Style().PointerEvents() != EPointerEvents::kNone) && |
| !(box_fragment_.GetNode() && box_fragment_.GetNode()->IsInert()); |
| } |
| |
| bool NGBoxFragmentPainter::HitTestTextFragment( |
| HitTestResult& result, |
| const NGPhysicalFragment& text_fragment, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset) { |
| LayoutSize offset(text_fragment.Offset().left, text_fragment.Offset().top); |
| LayoutPoint adjusted_location = accumulated_offset + offset; |
| LayoutSize size(text_fragment.Size().width, text_fragment.Size().height); |
| LayoutRect border_rect(adjusted_location, size); |
| const ComputedStyle& style = text_fragment.Style(); |
| |
| if (style.HasBorderRadius()) { |
| FloatRoundedRect border = style.GetRoundedBorderFor( |
| border_rect, |
| text_fragment.BorderEdges() & NGBorderEdges::Physical::kLeft, |
| text_fragment.BorderEdges() & NGBorderEdges::Physical::kRight); |
| if (!location_in_container.Intersects(border)) |
| return false; |
| } |
| |
| // TODO(layout-dev): Clip to line-top/bottom. |
| LayoutRect rect = LayoutRect(PixelSnappedIntRect(border_rect)); |
| if (VisibleToHitTestRequest(result.GetHitTestRequest()) && |
| location_in_container.Intersects(rect)) { |
| Node* node = text_fragment.GetNode(); |
| if (!result.InnerNode() && node) { |
| LayoutPoint point = |
| location_in_container.Point() - ToLayoutSize(accumulated_offset); |
| result.SetNodeAndPosition(node, point); |
| } |
| |
| if (result.AddNodeToListBasedTestResult(node, location_in_container, |
| rect) == kStopHitTesting) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool NGBoxFragmentPainter::HitTestChildren( |
| HitTestResult& result, |
| const Vector<std::unique_ptr<NGPaintFragment>>& children, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset, |
| HitTestAction action) { |
| for (auto iter = children.rbegin(); iter != children.rend(); iter++) { |
| const std::unique_ptr<NGPaintFragment>& child = *iter; |
| |
| // TODO(layout-dev): Handle self painting layers. |
| const NGPhysicalFragment& fragment = child->PhysicalFragment(); |
| bool stop_hit_testing = false; |
| if (fragment.Type() == NGPhysicalFragment::kFragmentBox) { |
| if (RequiresLegacyFallback(fragment)) { |
| stop_hit_testing = fragment.GetLayoutObject()->NodeAtPoint( |
| result, location_in_container, accumulated_offset, action); |
| } else { |
| stop_hit_testing = NGBoxFragmentPainter(*child).NodeAtPoint( |
| result, location_in_container, accumulated_offset, action); |
| } |
| |
| } else if (fragment.Type() == NGPhysicalFragment::kFragmentLineBox) { |
| stop_hit_testing = |
| HitTestChildren(result, child->Children(), location_in_container, |
| accumulated_offset, action); |
| |
| } else if (fragment.Type() == NGPhysicalFragment::kFragmentText) { |
| // TODO(eae): Should this hit test on the text itself or the containing |
| // node? |
| stop_hit_testing = HitTestTextFragment( |
| result, fragment, location_in_container, accumulated_offset); |
| } |
| if (stop_hit_testing) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| } // namespace blink |