/*
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 * Copyright (C) 2000 Dirk Mueller (mueller@kde.org)
 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
 * Copyright (C) Research In Motion Limited 2011-2012. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "third_party/blink/renderer/core/layout/layout_replaced.h"

#include "base/memory/scoped_refptr.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/html/html_dimension.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/layout/fragmentation_utils.h"
#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h"
#include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.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/inline/inline_cursor.h"
#include "third_party/blink/renderer/core/layout/layout_block.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_image.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_object_inlines.h"
#include "third_party/blink/renderer/core/layout/layout_video.h"
#include "third_party/blink/renderer/core/layout/layout_view_transition_content.h"
#include "third_party/blink/renderer/core/layout/length_utils.h"
#include "third_party/blink/renderer/core/layout/natural_sizing_info.h"
#include "third_party/blink/renderer/core/layout/physical_box_fragment.h"
#include "third_party/blink/renderer/core/paint/border_shape_painter.h"
#include "third_party/blink/renderer/core/paint/border_shape_utils.h"
#include "third_party/blink/renderer/core/paint/contoured_border_geometry.h"
#include "third_party/blink/renderer/core/paint/outline_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/replaced_painter.h"
#include "third_party/blink/renderer/core/style/basic_shapes.h"
#include "third_party/blink/renderer/core/style/computed_style_base_constants.h"
#include "third_party/blink/renderer/platform/geometry/contoured_rect.h"
#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
#include "third_party/blink/renderer/platform/geometry/length_functions.h"
#include "third_party/blink/renderer/platform/geometry/physical_offset.h"
#include "third_party/blink/renderer/platform/geometry/physical_size.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size_f.h"

namespace blink {

const int LayoutReplaced::kDefaultWidth = 300;
const int LayoutReplaced::kDefaultHeight = 150;

LayoutReplaced::LayoutReplaced(Element* element) : LayoutBox(element) {}

LayoutReplaced::~LayoutReplaced() = default;

void LayoutReplaced::StyleDidChange(
    StyleDifference diff,
    const ComputedStyle* old_style,
    const ComputedStyle& new_style,
    const StyleChangeContext& style_change_context) {
  NOT_DESTROYED();
  LayoutBox::StyleDidChange(diff, old_style, new_style, style_change_context);

  // Replaced elements can have border-radius clips without clipping overflow;
  // the overflow clipping case is already covered in LayoutBox::StyleDidChange
  if (diff.border_radius_changed) {
    SetNeedsPaintPropertyUpdate();
  }

  const float old_zoom = old_style ? old_style->EffectiveZoom()
                                   : ComputedStyleInitialValues::InitialZoom();
  if (new_style.EffectiveZoom() != old_zoom) {
    NaturalSizeChanged();
  }
}

void LayoutReplaced::NaturalSizeChanged() {
  NOT_DESTROYED();
  SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
      layout_invalidation_reason::kSizeChanged);
}

namespace {

bool HitTestClippedOutByBorderShape(const LayoutBox& box,
                                    const HitTestLocation& hit_test_location,
                                    const PhysicalOffset& border_box_location) {
  PhysicalRect border_rect = box.PhysicalBorderBoxRect();
  border_rect.Move(border_box_location);
  if (box.ShouldApplyOverflowClipMargin()) {
    border_rect.Expand(box.BorderOutsetsForClipping());
  }
  Path hit_shape =
      ComputeBorderShapeOuterPath(box.StyleRef(), border_rect, &box);
  return !hit_test_location.Intersects(hit_shape);
}

}  // namespace

bool LayoutReplaced::HitTestClippedOutByBorder(
    const HitTestLocation& hit_test_location,
    const PhysicalOffset& border_box_location) const {
  NOT_DESTROYED();
  PhysicalRect border_rect = PhysicalBorderBoxRect();
  border_rect.Move(border_box_location);
  return !hit_test_location.Intersects(
      ContouredBorderGeometry::PixelSnappedContouredBorder(StyleRef(),
                                                           border_rect));
}

bool LayoutReplaced::NodeAtPoint(HitTestResult& result,
                                 const HitTestLocation& hit_test_location,
                                 const PhysicalOffset& accumulated_offset,
                                 HitTestPhase phase) {
  NOT_DESTROYED();
  if (!MayIntersect(result, hit_test_location, accumulated_offset)) {
    return false;
  }

  if (phase == HitTestPhase::kForeground && !HasSelfPaintingLayer() &&
      HitTestOverflowControl(result, hit_test_location, accumulated_offset)) {
    return true;
  }

  bool skip_children = (result.GetHitTestRequest().GetStopNode() == this) ||
                       ChildPaintBlockedByDisplayLock();
  if (!skip_children && ShouldClipOverflowAlongEitherAxis()) {
    // PaintLayer::HitTestFragmentsWithPhase() 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 (!HasSelfPaintingLayer()) {
      PhysicalRect clip_rect =
          OverflowClipRect(kExcludeOverlayScrollbarSizeForHitTesting);
      clip_rect.Move(accumulated_offset);
      if (!hit_test_location.Intersects(clip_rect)) {
        skip_children = true;
      }
    }
    if (!skip_children && StyleRef().HasBorderShape()) {
      skip_children = HitTestClippedOutByBorderShape(*this, hit_test_location,
                                                     accumulated_offset);
    } else if (!skip_children && StyleRef().HasBorderRadius()) {
      PhysicalRect bounds_rect(accumulated_offset, StitchedSize());
      skip_children = !hit_test_location.Intersects(
          ContouredBorderGeometry::PixelSnappedContouredInnerBorder(
              StyleRef(), bounds_rect));
    }
  }

  if (!skip_children &&
      HitTestChildren(result, hit_test_location, accumulated_offset, phase)) {
    return true;
  }

  if (!result.GetHitTestRequest().IsHitTestVisualOverflow()) {
    // Check if the location is outside any border-shape or border-radius.
    if (StyleRef().HasBorderShape()) {
      if (HitTestClippedOutByBorderShape(*this, hit_test_location,
                                         accumulated_offset)) {
        return false;
      }
    } else if (StyleRef().HasBorderRadius()) {
      if (HitTestClippedOutByBorder(hit_test_location, accumulated_offset)) {
        return false;
      }
    }
  }

  // Now hit test ourselves.
  if (IsInSelfHitTestingPhase(phase) &&
      VisibleToHitTestRequest(result.GetHitTestRequest())) {
    PhysicalRect bounds_rect;
    if (result.GetHitTestRequest().IsHitTestVisualOverflow()) [[unlikely]] {
      bounds_rect = VisualOverflowRectIncludingFilters();
    } else {
      bounds_rect = PhysicalBorderBoxRect();
    }
    bounds_rect.Move(accumulated_offset);
    if (hit_test_location.Intersects(bounds_rect)) {
      UpdateHitTestResult(result,
                          hit_test_location.Point() - accumulated_offset);
      if (result.AddNodeToListBasedTestResult(NodeForHitTest(),
                                              hit_test_location,
                                              bounds_rect) == kStopHitTesting) {
        return true;
      }
    }
  }

  return false;
}

bool LayoutReplaced::HitTestChildren(HitTestResult& result,
                                     const HitTestLocation& hit_test_location,
                                     const PhysicalOffset& accumulated_offset,
                                     HitTestPhase phase) const {
  NOT_DESTROYED();

  for (LayoutObject* child = SlowLastChild(); child;
       child = child->PreviousSibling()) {
    if (child->HasLayer() &&
        To<LayoutBoxModelObject>(child)->Layer()->IsSelfPaintingLayer()) {
      continue;
    }

    PhysicalOffset child_accumulated_offset = accumulated_offset;
    if (auto* box = DynamicTo<LayoutBox>(child)) {
      child_accumulated_offset += box->PhysicalLocation();
    }

    if (child->NodeAtPoint(result, hit_test_location, child_accumulated_offset,
                           phase)) {
      return true;
    }
  }

  return false;
}

void LayoutReplaced::Paint(const PaintInfo& paint_info) const {
  NOT_DESTROYED();
  ReplacedPainter(*this).Paint(paint_info);
}

PhysicalBoxStrut LayoutReplaced::ComputeVisualEffectOverflowOutsets() {
  NOT_DESTROYED();
  const ComputedStyle& style = StyleRef();
  DCHECK(style.HasVisualOverflowingEffect());

  PhysicalBoxStrut outsets = style.BoxDecorationOutsets();

  PhysicalRect border_rect(PhysicalOffset(), StitchedSize());
  std::optional<BorderShapeReferenceRects> border_shape_rects;

  if (style.HasBorderShape()) {
    border_shape_rects =
        ComputeBorderShapeReferenceRects(border_rect, style, *this);
    const PhysicalRect outer_reference_rect =
        border_shape_rects ? border_shape_rects->outer : border_rect;
    const PhysicalRect inner_reference_rect =
        border_shape_rects ? border_shape_rects->inner : border_rect;
    // VisualOutsets() returns the complete border-shape overflow: both the
    // border path's visual extent and the precise box-shadow extent.
    outsets.Unite(BorderShapePainter::VisualOutsets(
        style, border_rect, outer_reference_rect, inner_reference_rect));
  }

  if (style.HasOutline()) {
    OutlineInfo info;
    Vector<PhysicalRect> outline_rects =
        OutlineRects(&info, PhysicalOffset(),
                     style.OutlineRectsShouldIncludeBlockInkOverflow());
    PhysicalRect rect = UnionRect(outline_rects);
    PhysicalSize size = StitchedSize();
    bool outline_affected = rect.size != size;
    SetOutlineMayBeAffectedByDescendants(outline_affected);

    if (!style.HasBorderShape() || style.OutlineStyleIsAuto()) {
      rect.Inflate(
          LayoutUnit(OutlinePainter::OutlineOutsetExtent(style, info)));
      outsets.Unite(PhysicalBoxStrut(-rect.Y(), rect.Right() - size.width,
                                     rect.Bottom() - size.height, -rect.X()));
    }
  }

  return outsets;
}

void LayoutReplaced::AddVisualEffectOverflow() {
  NOT_DESTROYED();
  if (!StyleRef().HasVisualOverflowingEffect()) {
    return;
  }

  // Add in the final overflow with shadows, outsets and outline combined.
  PhysicalRect visual_effect_overflow = PhysicalBorderBoxRect();
  PhysicalBoxStrut outsets = ComputeVisualEffectOverflowOutsets();
  visual_effect_overflow.Expand(outsets);
  AddSelfVisualOverflow(visual_effect_overflow);
  UpdateHasSubpixelVisualEffectOutsets(outsets);
}

void LayoutReplaced::RecalcVisualOverflow() {
  NOT_DESTROYED();
  ClearVisualOverflow();
  LayoutObject::RecalcVisualOverflow();
  AddVisualEffectOverflow();

  // Replaced elements clip the content to the element's content-box by default.
  // But if the CSS overflow property is respected, the content may paint
  // outside the element's bounds as ink overflow (with overflow:visible for
  // example). So we add |ReplacedContentRect()|, which provides the element's
  // painting rectangle relative to it's bounding box in its visual overflow if
  // the overflow property is respected.
  // Note that |overflow_| is meant to track the maximum potential ink overflow.
  // The actual painted overflow (based on the values for overflow,
  // overflow-clip-margin and paint containment) is computed in
  // LayoutBox::VisualOverflowRect.
  if (RespectsCSSOverflow())
    AddContentsVisualOverflow(ReplacedContentRect());
}

std::optional<PhysicalRect> LayoutReplaced::ComputeObjectViewBoxRect(
    const PhysicalNaturalSizingInfo& sizing_info) const {
  NOT_DESTROYED();
  const BasicShape* object_view_box = StyleRef().ObjectViewBox();
  if (!object_view_box) [[likely]] {
    return std::nullopt;
  }

  if (!sizing_info.has_width || !sizing_info.has_height) {
    return std::nullopt;
  }

  if (!ShouldApplyObjectViewBox()) {
    return std::nullopt;
  }

  if (sizing_info.size.IsEmpty()) {
    return std::nullopt;
  }

  DCHECK_EQ(object_view_box->GetType(), BasicShape::kBasicShapeInsetType);

  const gfx::RectF bounding_box{gfx::SizeF(sizing_info.size)};
  const Path path =
      object_view_box->GetPath(bounding_box, /*zoom=*/1.f, /*path_scale=*/1.f);

  const PhysicalRect view_box_rect =
      PhysicalRect::EnclosingRect(path.BoundingRect());
  if (view_box_rect.IsEmpty())
    return std::nullopt;

  const PhysicalRect natural_rect(PhysicalOffset(), sizing_info.size);
  if (view_box_rect == natural_rect) {
    return std::nullopt;
  }

  return view_box_rect;
}

PhysicalRect LayoutReplaced::ComputeReplacedContentRect(
    const PhysicalRect& base_content_rect,
    const PhysicalNaturalSizingInfo& sizing_info) const {
  NOT_DESTROYED();
  // |intrinsic_size| provides the size of the embedded content rendered in the
  // replaced element. This is the reference size that object-view-box applies
  // to.
  // If present, object-view-box changes the notion of embedded content used for
  // painting the element and applying rest of the object* properties. The
  // following cases are possible:
  //
  // - object-view-box is a subset of the embedded content. For example,
  // [0,0 50x50] on an image with bounds 100x100.
  //
  // - object-view-box is a superset of the embedded content. For example,
  // [-10, -10, 120x120] on an image with bounds 100x100.
  //
  // - object-view-box intersects with the embedded content. For example,
  // [-10, -10, 50x50] on an image with bounds 100x100.
  //
  // - object-view-box has no intersection with the embedded content. For
  // example, [-50, -50, 50x50] on any image.
  //
  // The image is scaled (by object-fit) and positioned (by object-position)
  // assuming the embedded content to be provided by the box identified by
  // object-view-box.
  //
  // Regions outside the image bounds (but within object-view-box) paint
  // transparent pixels. Regions outside object-view-box (but within image
  // bounds) are scaled as defined by object-fit above and treated as ink
  // overflow.
  const auto view_box = ComputeObjectViewBoxRect(sizing_info);

  // If no view box override was applied, then we don't need to adjust the
  // view-box paint rect.
  if (!view_box) {
    return ComputeObjectFitAndPositionRect(base_content_rect, sizing_info);
  }

  // Compute the paint rect based on bounds provided by the view box.
  DCHECK(!view_box->IsEmpty());
  const auto view_box_paint_rect = ComputeObjectFitAndPositionRect(
      base_content_rect, PhysicalNaturalSizingInfo::MakeFixed(view_box->size));
  if (view_box_paint_rect.IsEmpty())
    return view_box_paint_rect;

  // Scale the original image bounds by the scale applied to the view box.
  const auto natural_size = sizing_info.size;
  auto scaled_width =
      natural_size.width.MulDiv(view_box_paint_rect.Width(), view_box->Width());
  auto scaled_height = natural_size.height.MulDiv(view_box_paint_rect.Height(),
                                                  view_box->Height());
  const PhysicalSize scaled_image_size(scaled_width, scaled_height);

  // Scale the offset from the image origin by the scale applied to the view
  // box.
  auto scaled_x_offset =
      view_box->X().MulDiv(view_box_paint_rect.Width(), view_box->Width());
  auto scaled_y_offset =
      view_box->Y().MulDiv(view_box_paint_rect.Height(), view_box->Height());
  const PhysicalOffset scaled_offset(scaled_x_offset, scaled_y_offset);

  return PhysicalRect(view_box_paint_rect.offset - scaled_offset,
                      scaled_image_size);
}

PhysicalRect LayoutReplaced::ComputeObjectFitAndPositionRect(
    const PhysicalRect& base_content_rect,
    const PhysicalNaturalSizingInfo& sizing_info) const {
  NOT_DESTROYED();
  EObjectFit object_fit = StyleRef().GetObjectFit();

  if (object_fit == EObjectFit::kFill &&
      StyleRef().ObjectPosition() ==
          ComputedStyleInitialValues::InitialObjectPosition()) {
    return base_content_rect;
  }

  const PhysicalSize intrinsic_size = sizing_info.size;
  const PhysicalSize aspect_ratio = sizing_info.aspect_ratio;

  if (intrinsic_size.IsEmpty() && aspect_ratio.IsEmpty()) {
    return base_content_rect;
  }

  PhysicalSize scaled_intrinsic_size(intrinsic_size);
  PhysicalSize object_size = base_content_rect.size;
  switch (object_fit) {
    case EObjectFit::kScaleDown:
      // Srcset images have an intrinsic size depending on their destination,
      // but with object-fit: scale-down they need to use the underlying image
      // src's size. So revert back to the original size in that case.
      if (auto* image = DynamicTo<LayoutImage>(this)) {
        scaled_intrinsic_size.Scale(1.0 / image->ImageDevicePixelRatio());
      }
      [[fallthrough]];
    case EObjectFit::kContain:
    case EObjectFit::kCover:
      if (!aspect_ratio.IsEmpty()) {
        object_size = object_size.FitToAspectRatio(
            aspect_ratio, object_fit == EObjectFit::kCover
                              ? kAspectRatioFitGrow
                              : kAspectRatioFitShrink);
      }
      if (object_fit != EObjectFit::kScaleDown ||
          object_size.width <= scaled_intrinsic_size.width) {
        break;
      }
      [[fallthrough]];
    case EObjectFit::kNone:
      object_size =
          intrinsic_size.IsEmpty()
              ? ConcreteObjectSize(sizing_info, base_content_rect.size)
              : scaled_intrinsic_size;
      break;
    case EObjectFit::kFill:
      break;
    default:
      NOTREACHED();
  }

  const PhysicalOffset object_position(
      MinimumValueForLength(StyleRef().ObjectPosition().X(),
                            base_content_rect.Width() - object_size.width),
      MinimumValueForLength(StyleRef().ObjectPosition().Y(),
                            base_content_rect.Height() - object_size.height));

  return {base_content_rect.offset + object_position, object_size};
}

PhysicalRect LayoutReplaced::ReplacedContentRect() const {
  NOT_DESTROYED();
  // This function should compute the result with old geometry even if a
  // BoxLayoutExtraInput exists.
  return ReplacedContentRectFrom(PhysicalContentBoxRect());
}

PhysicalRect LayoutReplaced::ReplacedContentRectFrom(
    const PhysicalRect& base_content_rect) const {
  NOT_DESTROYED();
  const PhysicalNaturalSizingInfo sizing_info = GetNaturalDimensions();
  return ComputeReplacedContentRect(base_content_rect, sizing_info);
}

PhysicalRect LayoutReplaced::PreSnappedRectForPersistentSizing(
    const PhysicalRect& rect) {
  return PhysicalRect(rect.offset, PhysicalSize(ToRoundedSize(rect.size)));
}

PhysicalNaturalSizingInfo LayoutReplaced::ComputeNaturalSizingInfo() const {
  NOT_DESTROYED();
  DCHECK(!ShouldApplySizeContainment());
  PhysicalNaturalSizingInfo sizing_info = GetNaturalDimensions();

  // Apply a 'object-view-box' (if present) to the provided natural dimensions.
  if (auto view_box = ComputeObjectViewBoxRect(sizing_info)) {
    sizing_info.size = view_box->size;
    if (!sizing_info.aspect_ratio.IsEmpty()) {
      sizing_info.aspect_ratio = sizing_info.size;
    }
  }
  return sizing_info;
}

static std::pair<LayoutUnit, LayoutUnit> SelectionTopAndBottom(
    const LayoutReplaced& layout_replaced,
    const LogicalRect& rect) {
  // TODO(layout-dev): This code is buggy if the replaced element is relative
  // positioned.

  // The fallback answer when we can't find the containing line box of
  // |layout_replaced|.
  const std::pair<LayoutUnit, LayoutUnit> fallback(rect.BlockStartOffset(),
                                                   rect.BlockEndOffset());

  if (layout_replaced.IsInline() &&
      layout_replaced.IsInLayoutNGInlineFormattingContext()) {
    // Step 1: Find the line box containing |layout_replaced|.
    InlineCursor line_box;
    line_box.MoveTo(layout_replaced);
    if (!line_box)
      return fallback;
    line_box.MoveToContainingLine();
    if (!line_box)
      return fallback;

    // Step 2: Return the logical top and bottom of the line box.
    // TODO(layout-dev): Use selection top & bottom instead of line's, or decide
    // if we still want to distinguish line and selection heights in NG.
    const ComputedStyle& line_style = line_box.Current().Style();
    const auto writing_direction = line_style.GetWritingDirection();
    const WritingModeConverter converter(writing_direction,
                                         line_box.ContainerFragment().Size());
    PhysicalRect physical_rect = line_box.CurrentRectInFirstContainerFragment();
    const LogicalRect logical_rect = converter.ToLogical(physical_rect);
    return {logical_rect.offset.block_offset, logical_rect.BlockEndOffset()};
  }

  return fallback;
}

PositionWithAffinity LayoutReplaced::PositionForPoint(
    const PhysicalOffset& point) const {
  NOT_DESTROYED();

  LogicalRect logical_rect = LogicalRectInContainer();
  auto [top, bottom] = SelectionTopAndBottom(*this, logical_rect);

  LogicalOffset logical_point =
      LocationContainer()->CreateWritingModeConverter().ToLogical(
          point + PhysicalLocation(), {});
  LayoutUnit block_direction_position = logical_point.block_offset;
  LayoutUnit line_direction_position = logical_point.inline_offset;

  if (block_direction_position < top)
    return PositionBeforeThis();  // coordinates are above

  if (block_direction_position >= bottom) {
    return PositionAfterThis();  // coordinates are below
  }

  if (GetNode()) {
    const bool is_at_left_side =
        line_direction_position <=
        logical_rect.offset.inline_offset + logical_rect.InlineSize() / 2;
    const bool is_at_start = is_at_left_side == IsLtr(ResolvedDirection());
    if (is_at_start)
      return PositionBeforeThis();
    return PositionAfterThis();
  }

  return LayoutBox::PositionForPoint(point);
}

gfx::Size LayoutReplaced::ComputeSpeculativeDecodeSize() const {
  NOT_DESTROYED();
  return ReplacedContentRect().PixelSnappedSize();
}

PhysicalRect LayoutReplaced::LocalSelectionVisualRect() const {
  NOT_DESTROYED();
  if (GetSelectionState() == SelectionState::kNone ||
      GetSelectionState() == SelectionState::kContain) {
    return PhysicalRect();
  }

  if (IsInline() && IsInLayoutNGInlineFormattingContext()) {
    PhysicalRect rect;
    InlineCursor cursor;
    cursor.MoveTo(*this);
    for (; cursor; cursor.MoveToNextForSameLayoutObject())
      rect.Unite(cursor.CurrentLocalSelectionRectForReplaced());
    return rect;
  }

  // We're a block-level replaced element.  Just return our own dimensions.
  return PhysicalRect(PhysicalOffset(), StitchedSize());
}

bool LayoutReplaced::RespectsCSSOverflow() const {
  NOT_DESTROYED();
  const Element* element = DynamicTo<Element>(GetNode());
  return element && element->IsReplacedElementRespectingCSSOverflow();
}

bool LayoutReplaced::ClipsToContentBox() const {
  NOT_DESTROYED();
  if (!RespectsCSSOverflow()) {
    // If an svg is clipped, it is guaranteed to be clipped to the element's
    // content box.
    if (IsSVGRoot())
      return GetOverflowClipAxes() == kOverflowClipBothAxis;
    return true;
  }

  // TODO(khushalsagar): There can be more cases where the content clips to
  // content box. For instance, when padding is 0 and the reference box is the
  // padding box.
  const auto& overflow_clip_margin = StyleRef().OverflowClipMargin();
  return GetOverflowClipAxes() == kOverflowClipBothAxis &&
         overflow_clip_margin &&
         overflow_clip_margin->GetReferenceBox() ==
             StyleOverflowClipMargin::ReferenceBox::kContentBox &&
         !overflow_clip_margin->GetMargin();
}

}  // namespace blink
